From b80ef2cfea65397c00b90f838da4ac99bbaa177e Mon Sep 17 00:00:00 2001 From: rasmusq Date: Fri, 19 Dec 2025 19:33:43 +0100 Subject: [PATCH 1/3] add: color selection on dashboard --- drizzle/schema.ts | 1 + .../dashboard/LocalWishlistsSection.svelte | 8 +++- .../components/dashboard/WishlistCard.svelte | 10 ++++- .../components/dashboard/WishlistGrid.svelte | 15 +++++-- .../dashboard/WishlistSection.svelte | 8 ++++ .../components/layout/DashboardHeader.svelte | 34 +++++++++++++-- src/lib/components/themes/ThemeCard.svelte | 4 +- src/lib/server/schema.ts | 3 +- src/routes/dashboard/+page.server.ts | 16 +++++++ src/routes/dashboard/+page.svelte | 43 ++++++++++++++++++- 10 files changed, 128 insertions(+), 14 deletions(-) diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 2114757..ebf2d4b 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -77,6 +77,7 @@ export const user = pgTable("user", { password: text(), username: text(), dashboardTheme: text("dashboard_theme").default('none'), + dashboardColor: text("dashboard_color"), }, (table) => [ unique("user_email_unique").on(table.email), unique("user_username_unique").on(table.username), diff --git a/src/lib/components/dashboard/LocalWishlistsSection.svelte b/src/lib/components/dashboard/LocalWishlistsSection.svelte index 6eb21a3..023443d 100644 --- a/src/lib/components/dashboard/LocalWishlistsSection.svelte +++ b/src/lib/components/dashboard/LocalWishlistsSection.svelte @@ -7,9 +7,13 @@ import { onMount } from 'svelte'; let { - isAuthenticated = false + isAuthenticated = false, + fallbackColor = null, + fallbackTheme = null }: { isAuthenticated?: boolean; + fallbackColor?: string | null; + fallbackTheme?: string | null; } = $props(); const t = $derived(languageStore.t); @@ -114,6 +118,8 @@ emptyActionLabel={t.dashboard.createLocalWishlist || "Create local wishlist"} emptyActionHref="/" showCreateButton={true} + fallbackColor={fallbackColor} + fallbackTheme={fallbackTheme} > {#snippet actions(wishlist, unlocked)}
diff --git a/src/lib/components/dashboard/WishlistCard.svelte b/src/lib/components/dashboard/WishlistCard.svelte index 3d94650..0569cec 100644 --- a/src/lib/components/dashboard/WishlistCard.svelte +++ b/src/lib/components/dashboard/WishlistCard.svelte @@ -11,6 +11,8 @@ itemCount, color = null, theme = null, + fallbackColor = null, + fallbackTheme = null, children }: { title: string; @@ -18,14 +20,18 @@ itemCount: number; color?: string | null; theme?: string | null; + fallbackColor?: string | null; + fallbackTheme?: string | null; children?: Snippet; } = $props(); - const cardStyle = $derived(getCardStyle(color)); + const finalColor = $derived(color || fallbackColor); + const finalTheme = $derived(theme || fallbackTheme); + const cardStyle = $derived(getCardStyle(color, fallbackColor)); - +
diff --git a/src/lib/components/dashboard/WishlistGrid.svelte b/src/lib/components/dashboard/WishlistGrid.svelte index 9645670..20401ad 100644 --- a/src/lib/components/dashboard/WishlistGrid.svelte +++ b/src/lib/components/dashboard/WishlistGrid.svelte @@ -4,6 +4,8 @@ import EmptyState from '$lib/components/layout/EmptyState.svelte'; import type { Snippet } from 'svelte'; import { flip } from 'svelte/animate'; + import { getCardStyle } from '$lib/utils/colors'; + import ThemeCard from '$lib/components/themes/ThemeCard.svelte'; let { title, @@ -13,6 +15,8 @@ emptyDescription, emptyActionLabel, emptyActionHref, + fallbackColor = null, + fallbackTheme = null, headerAction, searchBar, children @@ -24,11 +28,15 @@ emptyDescription?: string; emptyActionLabel?: string; emptyActionHref?: string; + fallbackColor?: string | null; + fallbackTheme?: string | null; headerAction?: Snippet; searchBar?: Snippet; children: Snippet<[any]>; } = $props(); + const cardStyle = $derived(getCardStyle(fallbackColor, null)); + let scrollContainer: HTMLElement | null = null; function handleWheel(event: WheelEvent) { @@ -44,8 +52,9 @@ } - - + + +
{title} @@ -63,7 +72,7 @@
{/if} - + {#if items && items.length > 0}
; // item, unlocked } = $props(); @@ -126,6 +130,8 @@ {emptyDescription} {emptyActionLabel} {emptyActionHref} + {fallbackColor} + {fallbackTheme} > {#snippet headerAction()}
@@ -150,6 +156,8 @@ itemCount={wishlist.items?.length || 0} color={wishlist.color} theme={wishlist.theme} + fallbackColor={fallbackColor} + fallbackTheme={fallbackTheme} > {@render actions(item, unlocked)} diff --git a/src/lib/components/layout/DashboardHeader.svelte b/src/lib/components/layout/DashboardHeader.svelte index 5bb5a9d..5852c7d 100644 --- a/src/lib/components/layout/DashboardHeader.svelte +++ b/src/lib/components/layout/DashboardHeader.svelte @@ -3,6 +3,7 @@ import { ThemeToggle } from '$lib/components/ui/theme-toggle'; import { LanguageToggle } from '$lib/components/ui/language-toggle'; import ThemePicker from '$lib/components/ui/theme-picker.svelte'; + import ColorPicker from '$lib/components/ui/ColorPicker.svelte'; import { signOut } from '@auth/sveltekit/client'; import { languageStore } from '$lib/stores/language.svelte'; import { enhance } from '$app/forms'; @@ -11,25 +12,27 @@ userName, userEmail, dashboardTheme = 'none', + dashboardColor = null, isAuthenticated = false, - onThemeUpdate + onThemeUpdate, + onColorUpdate }: { userName?: string | null; userEmail?: string | null; dashboardTheme?: string; + dashboardColor?: string | null; isAuthenticated?: boolean; onThemeUpdate?: (theme: string | null) => void; + onColorUpdate?: (color: string | null) => void; } = $props(); const t = $derived(languageStore.t); async function handleThemeChange(theme: string) { - // Update theme immediately for instant visual feedback if (onThemeUpdate) { onThemeUpdate(theme); } - // Only submit to database for authenticated users if (isAuthenticated) { const formData = new FormData(); formData.append('theme', theme); @@ -40,6 +43,30 @@ }); } } + + let localColor = $state(dashboardColor); + + $effect(() => { + localColor = dashboardColor; + }); + + async function handleColorChange() { + if (onColorUpdate) { + onColorUpdate(localColor); + } + + if (isAuthenticated) { + const formData = new FormData(); + if (localColor) { + formData.append('color', localColor); + } + + await fetch('?/updateDashboardColor', { + method: 'POST', + body: formData + }); + } + }
@@ -52,6 +79,7 @@ {/if}
+ diff --git a/src/lib/components/themes/ThemeCard.svelte b/src/lib/components/themes/ThemeCard.svelte index 70fe3cc..535c490 100644 --- a/src/lib/components/themes/ThemeCard.svelte +++ b/src/lib/components/themes/ThemeCard.svelte @@ -7,8 +7,8 @@ themeName, color }: { - themeName?: string; - color?: string; + themeName?: string | null; + color?: string | null; } = $props(); const theme = $derived(getTheme(themeName)); diff --git a/src/lib/server/schema.ts b/src/lib/server/schema.ts index f5adc97..d5a796c 100644 --- a/src/lib/server/schema.ts +++ b/src/lib/server/schema.ts @@ -13,7 +13,8 @@ export const users = pgTable('user', { image: text('image'), password: text('password'), username: text('username').unique(), - dashboardTheme: text('dashboard_theme').default('none') + dashboardTheme: text('dashboard_theme').default('none'), + dashboardColor: text('dashboard_color') }); export const accounts = pgTable( diff --git a/src/routes/dashboard/+page.server.ts b/src/routes/dashboard/+page.server.ts index ff67458..cc81900 100644 --- a/src/routes/dashboard/+page.server.ts +++ b/src/routes/dashboard/+page.server.ts @@ -174,6 +174,22 @@ export const actions: Actions = { .set({ dashboardTheme: theme }) .where(eq(users.id, session.user.id)); + return { success: true }; + }, + + updateDashboardColor: async ({ request, locals }) => { + const session = await locals.auth(); + if (!session?.user?.id) { + throw redirect(303, '/signin'); + } + + const formData = await request.formData(); + const color = formData.get('color') as string | null; + + await db.update(users) + .set({ dashboardColor: color }) + .where(eq(users.id, session.user.id)); + return { success: true }; } }; diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index e29f516..93a51ef 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -24,7 +24,21 @@ } } + // For anonymous users, get color from localStorage + function getInitialColor() { + if (data.isAuthenticated) { + return data.user?.dashboardColor || null; + } else { + // Anonymous user - get from localStorage + if (typeof window !== 'undefined') { + return localStorage.getItem('dashboardColor') || null; + } + return null; + } + } + let currentTheme = $state(getInitialTheme()); + let currentColor = $state(getInitialColor()); // Save to localStorage when theme changes for anonymous users function handleThemeUpdate(theme: string | null) { @@ -35,6 +49,19 @@ } } + // Save to localStorage when color changes for anonymous users + function handleColorUpdate(color: string | null) { + currentColor = color; + + if (!data.isAuthenticated && typeof window !== 'undefined') { + if (color) { + localStorage.setItem('dashboardColor', color); + } else { + localStorage.removeItem('dashboardColor'); + } + } + } + const t = $derived(languageStore.t); // Only owned wishlists for "My Wishlists" @@ -58,17 +85,23 @@ }); - + - + {#if data.isAuthenticated} @@ -80,6 +113,8 @@ emptyActionLabel={t.dashboard.emptyWishlistsAction} emptyActionHref="/" showCreateButton={true} + fallbackColor={currentColor} + fallbackTheme={currentTheme} > {#snippet actions(wishlist, unlocked)}
@@ -135,6 +170,8 @@ emptyMessage={t.dashboard.emptyClaimedWishlists} emptyDescription={t.dashboard.emptyClaimedWishlistsDescription} hideIfEmpty={true} + fallbackColor={currentColor} + fallbackTheme={currentTheme} > {#snippet actions(wishlist, unlocked)}
@@ -189,6 +226,8 @@ items={savedWishlists()} emptyMessage={t.dashboard.emptySavedWishlists} emptyDescription={t.dashboard.emptySavedWishlistsDescription} + fallbackColor={currentColor} + fallbackTheme={currentTheme} > {#snippet actions(saved, unlocked)}
From 19493b4cd3b57dad58bb2e0b1d1971f25fcb9073 Mon Sep 17 00:00:00 2001 From: rasmusq Date: Fri, 19 Dec 2025 20:09:49 +0100 Subject: [PATCH 2/3] remove: svg from dashboard lists --- src/lib/components/dashboard/WishlistGrid.svelte | 2 +- src/lib/components/themes/ThemeCard.svelte | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/components/dashboard/WishlistGrid.svelte b/src/lib/components/dashboard/WishlistGrid.svelte index 20401ad..877f6e4 100644 --- a/src/lib/components/dashboard/WishlistGrid.svelte +++ b/src/lib/components/dashboard/WishlistGrid.svelte @@ -53,7 +53,7 @@ - +
diff --git a/src/lib/components/themes/ThemeCard.svelte b/src/lib/components/themes/ThemeCard.svelte index 535c490..ab5a8de 100644 --- a/src/lib/components/themes/ThemeCard.svelte +++ b/src/lib/components/themes/ThemeCard.svelte @@ -5,10 +5,12 @@ let { themeName, - color + color, + showPattern = true }: { themeName?: string | null; color?: string | null; + showPattern?: boolean; } = $props(); const theme = $derived(getTheme(themeName)); @@ -18,6 +20,6 @@ }); -{#if theme.pattern !== 'none'} +{#if showPattern && theme.pattern !== 'none'} {/if} From ac81b8175c6951da290bc0dc976547ed0fe3f3bd Mon Sep 17 00:00:00 2001 From: rasmusq Date: Fri, 19 Dec 2025 20:20:11 +0100 Subject: [PATCH 3/3] fix: missing color on empty lists in edit and wishlist pages --- .../wishlist/EditableItemsList.svelte | 8 ++++++-- src/routes/wishlist/[token]/+page.svelte | 19 +++++++------------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/lib/components/wishlist/EditableItemsList.svelte b/src/lib/components/wishlist/EditableItemsList.svelte index 2b19a98..6be081c 100644 --- a/src/lib/components/wishlist/EditableItemsList.svelte +++ b/src/lib/components/wishlist/EditableItemsList.svelte @@ -7,6 +7,8 @@ import { enhance } from "$app/forms"; import { flip } from "svelte/animate"; import { languageStore } from '$lib/stores/language.svelte'; + import ThemeCard from "$lib/components/themes/ThemeCard.svelte"; + import { getCardStyle } from "$lib/utils/colors"; let { items = $bindable([]), @@ -25,6 +27,7 @@ } = $props(); const t = $derived(languageStore.t); + const cardStyle = $derived(getCardStyle(wishlistColor));
@@ -68,8 +71,9 @@ {/each}
{:else} - - + + + diff --git a/src/routes/wishlist/[token]/+page.svelte b/src/routes/wishlist/[token]/+page.svelte index f94c3a0..08fbb52 100644 --- a/src/routes/wishlist/[token]/+page.svelte +++ b/src/routes/wishlist/[token]/+page.svelte @@ -7,8 +7,6 @@ CardTitle, } from "$lib/components/ui/card"; import { Button } from "$lib/components/ui/button"; - import { Input } from "$lib/components/ui/input"; - import { Label } from "$lib/components/ui/label"; import type { PageData } from "./$types"; import WishlistItem from "$lib/components/wishlist/WishlistItem.svelte"; import ReservationButton from "$lib/components/wishlist/ReservationButton.svelte"; @@ -19,6 +17,7 @@ import { getCardStyle } from "$lib/utils/colors"; import { languageStore } from '$lib/stores/language.svelte'; import SearchBar from "$lib/components/ui/SearchBar.svelte"; + import ThemeCard from "$lib/components/themes/ThemeCard.svelte"; let { data }: { data: PageData } = $props(); @@ -41,7 +40,6 @@ showDashboardLink={true} /> -
@@ -55,7 +53,6 @@
{#if data.isAuthenticated} {#if data.isClaimed} -