feat: Reinitialize frontend with SvelteKit and TypeScript

- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
This commit is contained in:
2026-02-17 16:19:59 -05:00
parent 54df6018f5
commit de2d83092e
28274 changed files with 3816354 additions and 90 deletions

View File

@@ -0,0 +1,42 @@
import { s as store_get, e as escape_html, u as unsubscribe_stores, a as slot } from "../../chunks/root.js";
import { u as user } from "../../chunks/auth.js";
import "@sveltejs/kit/internal";
import "../../chunks/exports.js";
import "../../chunks/utils.js";
import "clsx";
import "@sveltejs/kit/internal/server";
import "../../chunks/state.svelte.js";
function Navigation($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
$$renderer2.push(`<nav class="navbar bg-base-100 shadow-lg"><div class="flex-1"><a href="/" class="btn btn-ghost normal-case text-xl">Headroom</a></div> <div class="flex-none gap-2">`);
if (store_get($$store_subs ??= {}, "$user", user)) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="dropdown dropdown-end"><label tabindex="0" class="btn btn-ghost btn-circle avatar"><div class="w-10 rounded-full bg-primary"><span class="text-xl">${escape_html(store_get($$store_subs ??= {}, "$user", user).email?.charAt(0).toUpperCase())}</span></div></label> <ul tabindex="0" class="mt-3 p-2 shadow menu menu-compact dropdown-content bg-base-100 rounded-box w-52"><li><a href="/dashboard" class="justify-between">Dashboard</a></li> `);
if (store_get($$store_subs ??= {}, "$user", user).role === "superuser" || store_get($$store_subs ??= {}, "$user", user).role === "manager") {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<li><a href="/team-members">Team Members</a></li> <li><a href="/projects">Projects</a></li>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <li><a href="/reports">Reports</a></li> <div class="divider"></div> <li><button class="text-error">Logout</button></li></ul></div>`);
} else {
$$renderer2.push("<!--[!-->");
$$renderer2.push(`<a href="/login" class="btn btn-primary btn-sm">Login</a>`);
}
$$renderer2.push(`<!--]--></div></nav>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function _layout($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
$$renderer2.push(`<div class="min-h-screen bg-base-200">`);
Navigation($$renderer2);
$$renderer2.push(`<!----> <main class="container mx-auto px-4 py-6"><!--[-->`);
slot($$renderer2, $$props, "default", {});
$$renderer2.push(`<!--]--></main></div>`);
});
}
export {
_layout as default
};

View File

@@ -0,0 +1,16 @@
import "clsx";
import "@sveltejs/kit/internal";
import "../../chunks/exports.js";
import "../../chunks/utils.js";
import "@sveltejs/kit/internal/server";
import "../../chunks/root.js";
import "../../chunks/state.svelte.js";
import "../../chunks/auth.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
$$renderer2.push(`<div class="flex items-center justify-center min-h-screen"><div class="loading loading-spinner loading-lg text-primary"></div></div>`);
});
}
export {
_page as default
};

View File

@@ -0,0 +1,14 @@
import "clsx";
import "@sveltejs/kit/internal";
import "../../../chunks/exports.js";
import "../../../chunks/utils.js";
import "@sveltejs/kit/internal/server";
import "../../../chunks/root.js";
import "../../../chunks/state.svelte.js";
import "../../../chunks/auth.js";
const load = async () => {
return { authenticated: true };
};
export {
load
};

View File

@@ -0,0 +1,31 @@
import { h as head, s as store_get, e as escape_html, u as unsubscribe_stores } from "../../../chunks/root.js";
import { u as user } from "../../../chunks/auth.js";
import "@sveltejs/kit/internal";
import "../../../chunks/exports.js";
import "../../../chunks/utils.js";
import "clsx";
import "@sveltejs/kit/internal/server";
import "../../../chunks/state.svelte.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
head("x1i5gj", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>Dashboard - Headroom</title>`);
});
});
$$renderer2.push(`<div class="max-w-4xl mx-auto"><div class="card bg-base-100 shadow-xl"><div class="card-body"><h1 class="card-title text-3xl mb-4">Welcome to Headroom! 👋</h1> `);
if (store_get($$store_subs ??= {}, "$user", user)) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="alert alert-info mb-4"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <span>Logged in as ${escape_html(store_get($$store_subs ??= {}, "$user", user).email)} (${escape_html(store_get($$store_subs ??= {}, "$user", user).role)})</span></div>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <p class="text-lg mb-4">You have successfully authenticated. This is a protected dashboard page
that requires a valid JWT token to access.</p> <div class="divider"></div> <h2 class="text-xl font-bold mb-2">Authentication Features Implemented:</h2> <ul class="list-disc list-inside space-y-2 mb-6"><li>✅ JWT Token Authentication</li> <li>✅ Token Auto-refresh on 401</li> <li>✅ Protected Route Guards</li> <li>✅ Form Validation with Zod</li> <li>✅ Role-based Access Control</li> <li>✅ Redis Token Storage</li></ul> <div class="card-actions"><button class="btn btn-error">Logout</button></div></div></div></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};

View File

@@ -0,0 +1,113 @@
import { f as fallback, e as escape_html, b as attr_class, c as attr, d as bind_props, h as head, s as store_get, u as unsubscribe_stores } from "../../../chunks/root.js";
import "@sveltejs/kit/internal";
import "../../../chunks/exports.js";
import "../../../chunks/utils.js";
import "clsx";
import "@sveltejs/kit/internal/server";
import "../../../chunks/state.svelte.js";
import { z } from "zod";
import "ts-deepmerge";
import "@sveltejs/kit";
import "memoize-weak";
import Type from "typebox";
import "zod-v3-to-json-schema";
import { a as auth } from "../../../chunks/auth.js";
var FetchStatus;
(function(FetchStatus2) {
FetchStatus2[FetchStatus2["Idle"] = 0] = "Idle";
FetchStatus2[FetchStatus2["Submitting"] = 1] = "Submitting";
FetchStatus2[FetchStatus2["Delayed"] = 2] = "Delayed";
FetchStatus2[FetchStatus2["Timeout"] = 3] = "Timeout";
})(FetchStatus || (FetchStatus = {}));
let LEGACY_MODE = false;
try {
if (SUPERFORMS_LEGACY)
LEGACY_MODE = true;
} catch {
}
let STORYBOOK_MODE = false;
try {
if (globalThis.STORIES)
STORYBOOK_MODE = true;
} catch {
}
let legacyMode = false;
try {
if (SUPERFORMS_LEGACY)
legacyMode = true;
} catch {
}
class TDate extends Type.Base {
Check(value) {
return value instanceof globalThis.Date;
}
Errors(value) {
return this.Check(value) ? [] : [{ message: "must be Date" }];
}
Create() {
return new globalThis.Date(0);
}
}
function LoginForm($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let isLoading = fallback($$props["isLoading"], false);
let errorMessage = fallback($$props["errorMessage"], null);
z.object({
email: z.string().min(1, "Email is required").email("Invalid email format"),
password: z.string().min(1, "Password is required")
});
let formData = { email: "", password: "" };
let errors = {};
$$renderer2.push(`<form class="space-y-4">`);
if (errorMessage) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="alert alert-error" role="alert"><svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <span>${escape_html(errorMessage)}</span></div>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <div class="form-control"><label class="label" for="email"><span class="label-text">Email</span></label> <input type="email" id="email"${attr_class("input input-bordered w-full", void 0, { "input-error": errors.email })} placeholder="admin@example.com"${attr("value", formData.email)}${attr("disabled", isLoading, true)}${attr("aria-invalid", errors.email ? "true" : "false")}${attr("aria-describedby", errors.email ? "email-error" : void 0)}/> `);
if (errors.email) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<span id="email-error" class="label-text-alt text-error">${escape_html(errors.email)}</span>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div> <div class="form-control"><label class="label" for="password"><span class="label-text">Password</span></label> <input type="password" id="password"${attr_class("input input-bordered w-full", void 0, { "input-error": errors.password })} placeholder="••••••••"${attr("value", formData.password)}${attr("disabled", isLoading, true)}${attr("aria-invalid", errors.password ? "true" : "false")}${attr("aria-describedby", errors.password ? "password-error" : void 0)}/> `);
if (errors.password) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<span id="password-error" class="label-text-alt text-error">${escape_html(errors.password)}</span>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div> <button type="submit" class="btn btn-primary w-full"${attr("disabled", isLoading, true)}>`);
if (isLoading) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<span class="loading loading-spinner loading-sm"></span> Logging in...`);
} else {
$$renderer2.push("<!--[!-->");
$$renderer2.push(`Login`);
}
$$renderer2.push(`<!--]--></button></form>`);
bind_props($$props, { isLoading, errorMessage });
});
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
head("1x05zx6", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>Login - Headroom</title>`);
});
});
$$renderer2.push(`<div class="min-h-screen flex items-center justify-center bg-base-200"><div class="card w-full max-w-md bg-base-100 shadow-xl"><div class="card-body"><h1 class="card-title text-2xl text-center justify-center mb-6">Welcome to Headroom</h1> <p class="text-center text-base-content/70 mb-6">Sign in to access your dashboard</p> `);
LoginForm($$renderer2, {
isLoading: store_get($$store_subs ??= {}, "$auth", auth).isLoading,
errorMessage: store_get($$store_subs ??= {}, "$auth", auth).error
});
$$renderer2.push(`<!----> <div class="divider"></div> <div class="text-center text-sm text-base-content/60"><p>Demo credentials:</p> <p class="font-mono mt-1">admin@example.com / password</p></div></div></div></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};

View File

@@ -0,0 +1,4 @@
const ssr = false;
export {
ssr
};