diff --git a/prisma/seed.ts b/prisma/seed.ts
index 057df37..149de36 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -29,10 +29,10 @@ async function main() {
// --- Admin User ---
const adminPassword = await bcrypt.hash('admin123', 12);
const admin = await prisma.user.upsert({
- where: { email: 'admin@localhost' },
+ where: { email: 'admin@launcher.local' },
update: {},
create: {
- email: 'admin@localhost',
+ email: 'admin@launcher.local',
password: adminPassword,
displayName: 'Administrator',
role: 'admin',
@@ -44,10 +44,10 @@ async function main() {
// --- Regular User ---
const userPassword = await bcrypt.hash('user123', 12);
const regularUser = await prisma.user.upsert({
- where: { email: 'user@localhost' },
+ where: { email: 'user@launcher.local' },
update: {},
create: {
- email: 'user@localhost',
+ email: 'user@launcher.local',
password: userPassword,
displayName: 'Demo User',
role: 'user',
diff --git a/src/lib/components/board/BoardCard.svelte b/src/lib/components/board/BoardCard.svelte
index 87999c4..b1e17f8 100644
--- a/src/lib/components/board/BoardCard.svelte
+++ b/src/lib/components/board/BoardCard.svelte
@@ -1,4 +1,6 @@
+
+{#if iconComponent}
+
+{/if}
diff --git a/src/lib/stores/search.svelte.ts b/src/lib/stores/search.svelte.ts
index e80d410..69e6c29 100644
--- a/src/lib/stores/search.svelte.ts
+++ b/src/lib/stores/search.svelte.ts
@@ -31,7 +31,10 @@ class SearchStore {
window.addEventListener('keydown', handleKeyDown);
}
+ }
+ /** Must be called from within a component to set up reactive search effect */
+ initEffects() {
$effect(() => {
const q = this.query;
if (q.length < 2) {
diff --git a/src/lib/stores/theme.svelte.ts b/src/lib/stores/theme.svelte.ts
index 3c06a6c..48e6540 100644
--- a/src/lib/stores/theme.svelte.ts
+++ b/src/lib/stores/theme.svelte.ts
@@ -56,7 +56,10 @@ class ThemeStore {
this.#systemPreference = e.matches ? 'dark' : 'light';
});
}
+ }
+ /** Must be called from within a component to set up persistence and DOM effects */
+ initEffects() {
$effect(() => {
if (typeof window === 'undefined') return;
localStorage.setItem(THEME_STORAGE_KEY, this.mode);
diff --git a/src/lib/stores/ui.svelte.ts b/src/lib/stores/ui.svelte.ts
index 77178d1..d8a6c23 100644
--- a/src/lib/stores/ui.svelte.ts
+++ b/src/lib/stores/ui.svelte.ts
@@ -40,7 +40,10 @@ class UiStore {
window.addEventListener('resize', handleResize);
}
+ }
+ /** Must be called from within a component to set up persistence effects */
+ initEffects() {
$effect(() => {
if (typeof window === 'undefined') return;
localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(this.sidebarCollapsed));
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 044b59d..61cadd3 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -5,9 +5,17 @@
import MainLayout from '$lib/components/layout/MainLayout.svelte';
import { page } from '$app/stores';
import { fade } from 'svelte/transition';
+ import { theme } from '$lib/stores/theme.svelte';
+ import { ui } from '$lib/stores/ui.svelte';
+ import { search } from '$lib/stores/search.svelte';
let { data, children }: { data: LayoutData; children: Snippet } = $props();
+ // Initialize store effects within component context
+ theme.initEffects();
+ ui.initEffects();
+ search.initEffects();
+
// Pages that should NOT have the main layout (login, register)
const noLayoutPaths = ['/login', '/register'];
const showLayout = $derived(!noLayoutPaths.includes($page.url.pathname));
diff --git a/src/routes/boards/new/+page.server.ts b/src/routes/boards/new/+page.server.ts
new file mode 100644
index 0000000..c9b03e3
--- /dev/null
+++ b/src/routes/boards/new/+page.server.ts
@@ -0,0 +1,41 @@
+import type { PageServerLoad, Actions } from './$types.js';
+import { redirect, fail } from '@sveltejs/kit';
+import { superValidate, message } from 'sveltekit-superforms';
+import { zod } from '$lib/utils/zod-adapter.js';
+import { createBoardSchema } from '$lib/utils/validators.js';
+import * as boardService from '$lib/server/services/boardService.js';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ if (!locals.user || locals.user.role !== 'admin') {
+ throw redirect(302, '/boards');
+ }
+
+ const form = await superValidate(zod(createBoardSchema));
+ return { form };
+};
+
+export const actions: Actions = {
+ default: async ({ request, locals }) => {
+ if (!locals.user || locals.user.role !== 'admin') {
+ return fail(403, { error: 'Forbidden' });
+ }
+
+ const form = await superValidate(request, zod(createBoardSchema));
+ if (!form.valid) {
+ return fail(400, { form });
+ }
+
+ try {
+ const board = await boardService.createBoard({
+ ...form.data,
+ createdById: locals.user.id
+ });
+ throw redirect(302, `/boards/${board.id}`);
+ } catch (err) {
+ if (err && typeof err === 'object' && 'status' in err && err.status === 302) {
+ throw err;
+ }
+ return message(form, 'Failed to create board', { status: 500 });
+ }
+ }
+};
diff --git a/src/routes/boards/new/+page.svelte b/src/routes/boards/new/+page.svelte
new file mode 100644
index 0000000..57d7616
--- /dev/null
+++ b/src/routes/boards/new/+page.svelte
@@ -0,0 +1,88 @@
+
+
+
+ New Board — Web App Launcher
+
+
+
diff --git a/vite.config.ts b/vite.config.ts
index 6d65e6a..e922b4f 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -4,6 +4,10 @@ import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
+ server: {
+ port: 5181,
+ host: '0.0.0.0'
+ },
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
environment: 'node',