add: color selection on dashboard

This commit is contained in:
rasmusq
2025-12-19 19:33:43 +01:00
parent 22f9f8f0c9
commit b80ef2cfea
10 changed files with 128 additions and 14 deletions

View File

@@ -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)}
<div class="flex gap-2 flex-wrap">

View File

@@ -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));
</script>
<Card style={cardStyle} class="h-full flex flex-col relative overflow-hidden">
<ThemeCard themeName={theme} color={color} />
<ThemeCard themeName={finalTheme} color={finalColor} />
<CardHeader class="flex-shrink-0 relative z-10">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 sm:gap-2">
<CardTitle class="text-lg flex items-center gap-2 flex-1 min-w-0">

View File

@@ -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 @@
}
</script>
<Card>
<CardHeader>
<Card style={cardStyle} class="relative overflow-hidden">
<ThemeCard themeName={fallbackTheme} color={fallbackColor} />
<CardHeader class="relative z-10">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex-1 min-w-0">
<CardTitle>{title}</CardTitle>
@@ -63,7 +72,7 @@
</div>
{/if}
</CardHeader>
<CardContent>
<CardContent class="relative z-10">
{#if items && items.length > 0}
<div
bind:this={scrollContainer}

View File

@@ -21,6 +21,8 @@
emptyActionHref,
showCreateButton = false,
hideIfEmpty = false,
fallbackColor = null,
fallbackTheme = null,
actions
}: {
title: string;
@@ -32,6 +34,8 @@
emptyActionHref?: string;
showCreateButton?: boolean;
hideIfEmpty?: boolean;
fallbackColor?: string | null;
fallbackTheme?: string | null;
actions: Snippet<[WishlistItem, boolean]>; // item, unlocked
} = $props();
@@ -126,6 +130,8 @@
{emptyDescription}
{emptyActionLabel}
{emptyActionHref}
{fallbackColor}
{fallbackTheme}
>
{#snippet headerAction()}
<div class="flex flex-col sm:flex-row gap-2">
@@ -150,6 +156,8 @@
itemCount={wishlist.items?.length || 0}
color={wishlist.color}
theme={wishlist.theme}
fallbackColor={fallbackColor}
fallbackTheme={fallbackTheme}
>
{@render actions(item, unlocked)}
</WishlistCard>

View File

@@ -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
});
}
}
</script>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
@@ -52,6 +79,7 @@
{/if}
</div>
<div class="flex items-center gap-1 sm:gap-2 flex-shrink-0">
<ColorPicker bind:color={localColor} onchange={handleColorChange} />
<ThemePicker value={dashboardTheme} onValueChange={handleThemeChange} />
<LanguageToggle />
<ThemeToggle />

View File

@@ -7,8 +7,8 @@
themeName,
color
}: {
themeName?: string;
color?: string;
themeName?: string | null;
color?: string | null;
} = $props();
const theme = $derived(getTheme(themeName));