From 7c6ff9458f77355cd8bbf7ddbdfc4fff50dbb717 Mon Sep 17 00:00:00 2001 From: rasmusq Date: Fri, 28 Nov 2025 00:26:43 +0100 Subject: [PATCH 1/7] wip: not loading themes until reload, missing in dashboard, bad alignment and scaling --- .../components/dashboard/WishlistCard.svelte | 10 ++- .../dashboard/WishlistSection.svelte | 1 + .../components/layout/DashboardHeader.svelte | 33 ++++++++- .../components/layout/PageContainer.svelte | 18 ++++- .../components/themes/ThemeBackground.svelte | 37 ++++++++++ src/lib/components/themes/ThemeCard.svelte | 23 +++++++ .../themes/svgs/BottomPattern.svelte | 38 +++++++++++ .../components/themes/svgs/CardPattern.svelte | 38 +++++++++++ .../components/themes/svgs/TopPattern.svelte | 38 +++++++++++ src/lib/components/ui/theme-picker.svelte | 68 +++++++++++++++++++ .../wishlist/EditableItemsList.svelte | 6 +- .../components/wishlist/WishlistHeader.svelte | 17 ++++- .../components/wishlist/WishlistItem.svelte | 8 ++- src/lib/server/schema.ts | 4 +- src/lib/utils/themes.ts | 51 ++++++++++++++ src/lib/utils/wishlistUpdates.ts | 4 ++ src/routes/dashboard/+page.server.ts | 22 +++++- src/routes/dashboard/+page.svelte | 8 ++- src/routes/wishlist/[token]/+page.svelte | 4 +- .../wishlist/[token]/edit/+page.server.ts | 5 ++ src/routes/wishlist/[token]/edit/+page.svelte | 4 +- 21 files changed, 417 insertions(+), 20 deletions(-) create mode 100644 src/lib/components/themes/ThemeBackground.svelte create mode 100644 src/lib/components/themes/ThemeCard.svelte create mode 100644 src/lib/components/themes/svgs/BottomPattern.svelte create mode 100644 src/lib/components/themes/svgs/CardPattern.svelte create mode 100644 src/lib/components/themes/svgs/TopPattern.svelte create mode 100644 src/lib/components/ui/theme-picker.svelte create mode 100644 src/lib/utils/themes.ts diff --git a/src/lib/components/dashboard/WishlistCard.svelte b/src/lib/components/dashboard/WishlistCard.svelte index b74e0e5..3d94650 100644 --- a/src/lib/components/dashboard/WishlistCard.svelte +++ b/src/lib/components/dashboard/WishlistCard.svelte @@ -3,26 +3,30 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '$lib/components/ui/card'; import type { Snippet } from 'svelte'; import { getCardStyle } from '$lib/utils/colors'; + import ThemeCard from '$lib/components/themes/ThemeCard.svelte'; let { title, description, itemCount, color = null, + theme = null, children }: { title: string; description?: string | null; itemCount: number; color?: string | null; + theme?: string | null; children?: Snippet; } = $props(); const cardStyle = $derived(getCardStyle(color)); - - + + +
{title} @@ -35,7 +39,7 @@ {description} {/if} - + {#if children}
{@render children()} diff --git a/src/lib/components/dashboard/WishlistSection.svelte b/src/lib/components/dashboard/WishlistSection.svelte index 396d3d4..53c664e 100644 --- a/src/lib/components/dashboard/WishlistSection.svelte +++ b/src/lib/components/dashboard/WishlistSection.svelte @@ -149,6 +149,7 @@ description={getWishlistDescription(item)} itemCount={wishlist.items?.length || 0} color={wishlist.color} + theme={wishlist.theme} > {@render actions(item, unlocked)} diff --git a/src/lib/components/layout/DashboardHeader.svelte b/src/lib/components/layout/DashboardHeader.svelte index 83b8943..7d06297 100644 --- a/src/lib/components/layout/DashboardHeader.svelte +++ b/src/lib/components/layout/DashboardHeader.svelte @@ -2,13 +2,41 @@ import { Button } from '$lib/components/ui/button'; 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 { signOut } from '@auth/sveltekit/client'; import { languageStore } from '$lib/stores/language.svelte'; + import { enhance } from '$app/forms'; - let { userName, userEmail }: { userName?: string | null; userEmail?: string | null } = $props(); + let { + userName, + userEmail, + dashboardTheme = 'none' + }: { + userName?: string | null; + userEmail?: string | null; + dashboardTheme?: string; + } = $props(); const t = $derived(languageStore.t); const isAuthenticated = $derived(!!userName || !!userEmail); + + let currentTheme = $state(dashboardTheme); + + async function handleThemeChange(theme: string) { + currentTheme = theme; + + // Submit form to update theme + const formData = new FormData(); + formData.append('theme', theme); + + await fetch('?/updateDashboardTheme', { + method: 'POST', + body: formData + }); + + // Reload to apply new theme + window.location.reload(); + }
@@ -21,6 +49,9 @@ {/if}
+ {#if isAuthenticated} + + {/if} {#if isAuthenticated} diff --git a/src/lib/components/layout/PageContainer.svelte b/src/lib/components/layout/PageContainer.svelte index 844e8d0..135bd8c 100644 --- a/src/lib/components/layout/PageContainer.svelte +++ b/src/lib/components/layout/PageContainer.svelte @@ -1,11 +1,23 @@ -
-
+
+ +
{@render children()}
diff --git a/src/lib/components/themes/ThemeBackground.svelte b/src/lib/components/themes/ThemeBackground.svelte new file mode 100644 index 0000000..f983a58 --- /dev/null +++ b/src/lib/components/themes/ThemeBackground.svelte @@ -0,0 +1,37 @@ + + +{#if theme.pattern !== 'none'} + {#if showTop} + + {/if} + {#if showBottom} + + {/if} +{/if} diff --git a/src/lib/components/themes/ThemeCard.svelte b/src/lib/components/themes/ThemeCard.svelte new file mode 100644 index 0000000..0a3c685 --- /dev/null +++ b/src/lib/components/themes/ThemeCard.svelte @@ -0,0 +1,23 @@ + + +{#if theme.pattern !== 'none'} + +{/if} diff --git a/src/lib/components/themes/svgs/BottomPattern.svelte b/src/lib/components/themes/svgs/BottomPattern.svelte new file mode 100644 index 0000000..1e696d7 --- /dev/null +++ b/src/lib/components/themes/svgs/BottomPattern.svelte @@ -0,0 +1,38 @@ + + +
+ {#if pattern === 'waves'} + + + + {:else if pattern === 'geometric'} + + + + {:else if pattern === 'dots'} + + + + + + + + + {/if} +
diff --git a/src/lib/components/themes/svgs/CardPattern.svelte b/src/lib/components/themes/svgs/CardPattern.svelte new file mode 100644 index 0000000..6c00a8c --- /dev/null +++ b/src/lib/components/themes/svgs/CardPattern.svelte @@ -0,0 +1,38 @@ + + +
+ {#if pattern === 'waves'} + + + + {:else if pattern === 'geometric'} + + + + {:else if pattern === 'dots'} + + + + + + + + + {/if} +
diff --git a/src/lib/components/themes/svgs/TopPattern.svelte b/src/lib/components/themes/svgs/TopPattern.svelte new file mode 100644 index 0000000..3aa0247 --- /dev/null +++ b/src/lib/components/themes/svgs/TopPattern.svelte @@ -0,0 +1,38 @@ + + +
+ {#if pattern === 'waves'} + + + + {:else if pattern === 'geometric'} + + + + {:else if pattern === 'dots'} + + + + + + + + + {/if} +
diff --git a/src/lib/components/ui/theme-picker.svelte b/src/lib/components/ui/theme-picker.svelte new file mode 100644 index 0000000..4f93363 --- /dev/null +++ b/src/lib/components/ui/theme-picker.svelte @@ -0,0 +1,68 @@ + + +
+ + + {#if showMenu} +
+
+ {#each Object.entries(AVAILABLE_THEMES) as [key, theme]} + + {/each} +
+
+ {/if} +
diff --git a/src/lib/components/wishlist/EditableItemsList.svelte b/src/lib/components/wishlist/EditableItemsList.svelte index 1ef8374..dbdc6fc 100644 --- a/src/lib/components/wishlist/EditableItemsList.svelte +++ b/src/lib/components/wishlist/EditableItemsList.svelte @@ -12,12 +12,14 @@ items = $bindable([]), rearranging, onStartEditing, - onReorder + onReorder, + theme = null }: { items: Item[]; rearranging: boolean; onStartEditing: (item: Item) => void; onReorder: (items: Item[]) => Promise; + theme?: string | null; } = $props(); const t = $derived(languageStore.t); @@ -28,7 +30,7 @@
{#each items as item (item.id)}
- +
-
+
+ { + wishlistTheme = theme; + await onThemeUpdate(theme); + // Force reactivity by updating the wishlist object + wishlist.theme = theme; + }} + /> onColorUpdate(wishlistColor)} diff --git a/src/lib/components/wishlist/WishlistItem.svelte b/src/lib/components/wishlist/WishlistItem.svelte index ed22299..b4e0461 100644 --- a/src/lib/components/wishlist/WishlistItem.svelte +++ b/src/lib/components/wishlist/WishlistItem.svelte @@ -5,12 +5,14 @@ import { getCardStyle } from '$lib/utils/colors'; import { Button } from "$lib/components/ui/button"; import { languageStore } from '$lib/stores/language.svelte'; + import ThemeCard from '$lib/components/themes/ThemeCard.svelte'; interface Props { item: Item; showImage?: boolean; children?: any; showDragHandle?: boolean; + theme?: string | null; } let { @@ -18,6 +20,7 @@ showImage = true, children, showDragHandle = false, + theme = null }: Props = $props(); const t = $derived(languageStore.t); @@ -51,8 +54,9 @@ const cardStyle = $derived(getCardStyle(item.color)); - - + + +
{#if showDragHandle}
= { + none: { + name: 'None', + pattern: 'none', + opacity: 0 + }, + waves: { + name: 'Waves', + pattern: 'waves', + opacity: 0.1 + }, + geometric: { + name: 'Geometric', + pattern: 'geometric', + opacity: 0.1 + }, + dots: { + name: 'Dots', + pattern: 'dots', + opacity: 0.15 + } +}; + +export const DEFAULT_THEME = 'none'; + +export function getTheme(themeName?: string | null): Theme { + if (!themeName || !AVAILABLE_THEMES[themeName]) { + return AVAILABLE_THEMES[DEFAULT_THEME]; + } + return AVAILABLE_THEMES[themeName]; +} + +/** + * Get color from a CSS color string or class + * For now, we'll use currentColor which inherits from the parent + */ +export function getThemeColor(color?: string | null): string { + return color || 'currentColor'; +} diff --git a/src/lib/utils/wishlistUpdates.ts b/src/lib/utils/wishlistUpdates.ts index bd0f9d2..184ffd9 100644 --- a/src/lib/utils/wishlistUpdates.ts +++ b/src/lib/utils/wishlistUpdates.ts @@ -38,6 +38,10 @@ export async function updateEndDate(endDate: string | null): Promise { return updateWishlist({ endDate: endDate || "" }); } +export async function updateTheme(theme: string | null): Promise { + return updateWishlist({ theme: theme || "none" }); +} + export async function reorderItems(items: Array<{ id: string; order: number }>): Promise { const response = await fetch("?/reorderItems", { method: "POST", diff --git a/src/routes/dashboard/+page.server.ts b/src/routes/dashboard/+page.server.ts index 0e2bb3c..358e14e 100644 --- a/src/routes/dashboard/+page.server.ts +++ b/src/routes/dashboard/+page.server.ts @@ -1,7 +1,7 @@ import { redirect } from '@sveltejs/kit'; import type { PageServerLoad, Actions } from './$types'; import { db } from '$lib/server/db'; -import { wishlists, savedWishlists } from '$lib/server/schema'; +import { wishlists, savedWishlists, users } from '$lib/server/schema'; import { eq, and } from 'drizzle-orm'; export const load: PageServerLoad = async (event) => { @@ -149,6 +149,26 @@ export const actions: Actions = { eq(wishlists.userId, session.user.id) )); + return { success: true }; + }, + + updateDashboardTheme: async ({ request, locals }) => { + const session = await locals.auth(); + if (!session?.user?.id) { + throw redirect(303, '/signin'); + } + + const formData = await request.formData(); + const theme = formData.get('theme') as string; + + if (!theme) { + return { success: false, error: 'Theme is required' }; + } + + await db.update(users) + .set({ dashboardTheme: theme }) + .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 4ecbc3c..3eba68c 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -34,8 +34,12 @@ }); - - + + diff --git a/src/routes/wishlist/[token]/+page.svelte b/src/routes/wishlist/[token]/+page.svelte index 81199df..da74e6a 100644 --- a/src/routes/wishlist/[token]/+page.svelte +++ b/src/routes/wishlist/[token]/+page.svelte @@ -35,7 +35,7 @@ ); - + {#if filteredItems.length > 0} {#each filteredItems as item} - + - + -- 2.49.1 From eb7ccdf7a20a1b8d399e12c9a7d902b4f73144d9 Mon Sep 17 00:00:00 2001 From: rasmusq Date: Fri, 28 Nov 2025 12:45:20 +0100 Subject: [PATCH 2/7] add: themes for wishlists and dashboards --- drizzle/relations.ts | 58 ++++++++ drizzle/schema.ts | 137 ++++++++++++++++++ .../dashboard/LocalWishlistsSection.svelte | 27 ++-- .../components/layout/DashboardHeader.svelte | 37 ++--- src/routes/dashboard/+page.server.ts | 7 +- src/routes/dashboard/+page.svelte | 30 +++- src/routes/wishlist/[token]/edit/+page.svelte | 12 +- 7 files changed, 275 insertions(+), 33 deletions(-) create mode 100644 drizzle/relations.ts create mode 100644 drizzle/schema.ts diff --git a/drizzle/relations.ts b/drizzle/relations.ts new file mode 100644 index 0000000..2bea2d5 --- /dev/null +++ b/drizzle/relations.ts @@ -0,0 +1,58 @@ +import { relations } from "drizzle-orm/relations"; +import { wishlists, items, user, savedWishlists, reservations, session, account } from "./schema"; + +export const itemsRelations = relations(items, ({one, many}) => ({ + wishlist: one(wishlists, { + fields: [items.wishlistId], + references: [wishlists.id] + }), + reservations: many(reservations), +})); + +export const wishlistsRelations = relations(wishlists, ({one, many}) => ({ + items: many(items), + user: one(user, { + fields: [wishlists.userId], + references: [user.id] + }), + savedWishlists: many(savedWishlists), +})); + +export const userRelations = relations(user, ({many}) => ({ + wishlists: many(wishlists), + savedWishlists: many(savedWishlists), + sessions: many(session), + accounts: many(account), +})); + +export const savedWishlistsRelations = relations(savedWishlists, ({one}) => ({ + user: one(user, { + fields: [savedWishlists.userId], + references: [user.id] + }), + wishlist: one(wishlists, { + fields: [savedWishlists.wishlistId], + references: [wishlists.id] + }), +})); + +export const reservationsRelations = relations(reservations, ({one}) => ({ + item: one(items, { + fields: [reservations.itemId], + references: [items.id] + }), +})); + +export const sessionRelations = relations(session, ({one}) => ({ + user: one(user, { + fields: [session.userId], + references: [user.id] + }), +})); + +export const accountRelations = relations(account, ({one}) => ({ + user: one(user, { + fields: [account.userId], + references: [user.id] + }), +})); \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts new file mode 100644 index 0000000..2114757 --- /dev/null +++ b/drizzle/schema.ts @@ -0,0 +1,137 @@ +import { pgTable, foreignKey, text, numeric, boolean, timestamp, unique, primaryKey } from "drizzle-orm/pg-core" +import { sql } from "drizzle-orm" + + + +export const items = pgTable("items", { + id: text().primaryKey().notNull(), + wishlistId: text("wishlist_id").notNull(), + title: text().notNull(), + description: text(), + link: text(), + imageUrl: text("image_url"), + price: numeric({ precision: 10, scale: 2 }), + currency: text().default('DKK'), + color: text(), + order: numeric().default('0').notNull(), + isReserved: boolean("is_reserved").default(false).notNull(), + createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(), +}, (table) => [ + foreignKey({ + columns: [table.wishlistId], + foreignColumns: [wishlists.id], + name: "items_wishlist_id_wishlists_id_fk" + }).onDelete("cascade"), +]); + +export const wishlists = pgTable("wishlists", { + id: text().primaryKey().notNull(), + userId: text("user_id"), + title: text().notNull(), + description: text(), + ownerToken: text("owner_token").notNull(), + publicToken: text("public_token").notNull(), + isFavorite: boolean("is_favorite").default(false).notNull(), + color: text(), + endDate: timestamp("end_date", { mode: 'string' }), + createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(), + theme: text().default('none'), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "wishlists_user_id_user_id_fk" + }).onDelete("set null"), + unique("wishlists_owner_token_unique").on(table.ownerToken), + unique("wishlists_public_token_unique").on(table.publicToken), +]); + +export const savedWishlists = pgTable("saved_wishlists", { + id: text().primaryKey().notNull(), + userId: text("user_id").notNull(), + wishlistId: text("wishlist_id").notNull(), + isFavorite: boolean("is_favorite").default(false).notNull(), + createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(), + ownerToken: text("owner_token"), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "saved_wishlists_user_id_user_id_fk" + }).onDelete("cascade"), + foreignKey({ + columns: [table.wishlistId], + foreignColumns: [wishlists.id], + name: "saved_wishlists_wishlist_id_wishlists_id_fk" + }).onDelete("cascade"), +]); + +export const user = pgTable("user", { + id: text().primaryKey().notNull(), + name: text(), + email: text(), + emailVerified: timestamp({ mode: 'string' }), + image: text(), + password: text(), + username: text(), + dashboardTheme: text("dashboard_theme").default('none'), +}, (table) => [ + unique("user_email_unique").on(table.email), + unique("user_username_unique").on(table.username), +]); + +export const reservations = pgTable("reservations", { + id: text().primaryKey().notNull(), + itemId: text("item_id").notNull(), + reserverName: text("reserver_name"), + createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(), +}, (table) => [ + foreignKey({ + columns: [table.itemId], + foreignColumns: [items.id], + name: "reservations_item_id_items_id_fk" + }).onDelete("cascade"), +]); + +export const session = pgTable("session", { + sessionToken: text().primaryKey().notNull(), + userId: text().notNull(), + expires: timestamp({ mode: 'string' }).notNull(), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "session_userId_user_id_fk" + }).onDelete("cascade"), +]); + +export const verificationToken = pgTable("verificationToken", { + identifier: text().notNull(), + token: text().notNull(), + expires: timestamp({ mode: 'string' }).notNull(), +}, (table) => [ + primaryKey({ columns: [table.identifier, table.token], name: "verificationToken_identifier_token_pk"}), +]); + +export const account = pgTable("account", { + userId: text().notNull(), + type: text().notNull(), + provider: text().notNull(), + providerAccountId: text().notNull(), + refreshToken: text("refresh_token"), + accessToken: text("access_token"), + expiresAt: numeric("expires_at"), + tokenType: text("token_type"), + scope: text(), + idToken: text("id_token"), + sessionState: text("session_state"), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "account_userId_user_id_fk" + }).onDelete("cascade"), + primaryKey({ columns: [table.provider, table.providerAccountId], name: "account_provider_providerAccountId_pk"}), +]); diff --git a/src/lib/components/dashboard/LocalWishlistsSection.svelte b/src/lib/components/dashboard/LocalWishlistsSection.svelte index 1328a39..ba96c01 100644 --- a/src/lib/components/dashboard/LocalWishlistsSection.svelte +++ b/src/lib/components/dashboard/LocalWishlistsSection.svelte @@ -43,15 +43,25 @@ items: [] // We don't have item data in localStorage })); }); + + // Description depends on authentication status + const sectionDescription = $derived(() => { + if (isAuthenticated) { + return t.dashboard.localWishlistsAuthDescription || "Wishlists stored in your browser that haven't been claimed yet."; + } + return t.dashboard.localWishlistsDescription || "Wishlists stored in your browser. Sign in to save them permanently."; + }); -{#if localWishlists.length > 0} - + {#snippet actions(wishlist, unlocked)}
- {#if isAuthenticated} - - {/if} + {#if isAuthenticated} diff --git a/src/routes/dashboard/+page.server.ts b/src/routes/dashboard/+page.server.ts index 358e14e..ff67458 100644 --- a/src/routes/dashboard/+page.server.ts +++ b/src/routes/dashboard/+page.server.ts @@ -17,6 +17,11 @@ export const load: PageServerLoad = async (event) => { }; } + // Fetch user with theme + const user = await db.query.users.findFirst({ + where: eq(users.id, session.user.id) + }); + const userWishlists = await db.query.wishlists.findMany({ where: eq(wishlists.userId, session.user.id), with: { @@ -57,7 +62,7 @@ export const load: PageServerLoad = async (event) => { })); return { - user: session.user, + user: user, wishlists: userWishlists, savedWishlists: savedWithAccess, isAuthenticated: true diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index 3eba68c..c02fc1d 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -11,6 +11,30 @@ let { data }: { data: PageData } = $props(); + // For anonymous users, get theme from localStorage + function getInitialTheme() { + if (data.isAuthenticated) { + return data.user?.dashboardTheme || 'none'; + } else { + // Anonymous user - get from localStorage + if (typeof window !== 'undefined') { + return localStorage.getItem('dashboardTheme') || 'none'; + } + return 'none'; + } + } + + let currentTheme = $state(getInitialTheme()); + + // Save to localStorage when theme changes for anonymous users + function handleThemeUpdate(theme: string | null) { + currentTheme = theme || 'none'; + + if (!data.isAuthenticated && typeof window !== 'undefined') { + localStorage.setItem('dashboardTheme', currentTheme); + } + } + const t = $derived(languageStore.t); // Only owned wishlists for "My Wishlists" @@ -34,11 +58,13 @@ }); - + diff --git a/src/routes/wishlist/[token]/edit/+page.svelte b/src/routes/wishlist/[token]/edit/+page.svelte index 2f09791..dbc2d39 100644 --- a/src/routes/wishlist/[token]/edit/+page.svelte +++ b/src/routes/wishlist/[token]/edit/+page.svelte @@ -22,6 +22,7 @@ let addFormElement = $state(null); let editFormElement = $state(null); let searchQuery = $state(""); + let currentTheme = $state(data.wishlist.theme || 'none'); let items = $state([]); @@ -105,9 +106,14 @@ items = newItems; await handleReorder(newItems); } + + async function handleThemeUpdate(theme: string | null) { + currentTheme = theme || 'none'; + await wishlistUpdates.updateTheme(theme); + } - + -- 2.49.1 From d165e5992ace62daebbbb73bb80eeaf36f83ac28 Mon Sep 17 00:00:00 2001 From: rasmusq Date: Fri, 28 Nov 2025 13:03:13 +0100 Subject: [PATCH 3/7] fix: missing theme on local wishlists in dashboard page --- .../dashboard/LocalWishlistsSection.svelte | 85 +++++++++++++++---- src/lib/utils/themes.ts | 9 +- src/routes/api/wishlist/[token]/+server.ts | 38 +++++++++ src/routes/dashboard/+page.svelte | 2 +- 4 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 src/routes/api/wishlist/[token]/+server.ts diff --git a/src/lib/components/dashboard/LocalWishlistsSection.svelte b/src/lib/components/dashboard/LocalWishlistsSection.svelte index ba96c01..6eb21a3 100644 --- a/src/lib/components/dashboard/LocalWishlistsSection.svelte +++ b/src/lib/components/dashboard/LocalWishlistsSection.svelte @@ -15,34 +15,87 @@ const t = $derived(languageStore.t); let localWishlists = $state([]); + let enrichedWishlists = $state([]); - // Load local wishlists on mount (client-side only) - onMount(() => { + // Load local wishlists on mount and fetch their data from server + onMount(async () => { localWishlists = getLocalWishlists(); + + // Fetch full wishlist data for each local wishlist + const promises = localWishlists.map(async (local) => { + try { + const response = await fetch(`/api/wishlist/${local.ownerToken}`); + if (response.ok) { + const data = await response.json(); + return { + ...data, + isFavorite: local.isFavorite || false + }; + } + } catch (error) { + console.error('Failed to fetch wishlist data:', error); + } + // Fallback to local data if fetch fails + return { + id: local.ownerToken, + title: local.title, + ownerToken: local.ownerToken, + publicToken: local.publicToken, + createdAt: local.createdAt, + isFavorite: local.isFavorite || false, + items: [], + theme: null, + color: null + }; + }); + + enrichedWishlists = await Promise.all(promises); }); - function handleForget(ownerToken: string) { + async function refreshEnrichedWishlists() { + const promises = localWishlists.map(async (local) => { + try { + const response = await fetch(`/api/wishlist/${local.ownerToken}`); + if (response.ok) { + const data = await response.json(); + return { + ...data, + isFavorite: local.isFavorite || false + }; + } + } catch (error) { + console.error('Failed to fetch wishlist data:', error); + } + return { + id: local.ownerToken, + title: local.title, + ownerToken: local.ownerToken, + publicToken: local.publicToken, + createdAt: local.createdAt, + isFavorite: local.isFavorite || false, + items: [], + theme: null, + color: null + }; + }); + + enrichedWishlists = await Promise.all(promises); + } + + async function handleForget(ownerToken: string) { forgetLocalWishlist(ownerToken); localWishlists = getLocalWishlists(); + await refreshEnrichedWishlists(); } - function handleToggleFavorite(ownerToken: string) { + async function handleToggleFavorite(ownerToken: string) { toggleLocalFavorite(ownerToken); localWishlists = getLocalWishlists(); + await refreshEnrichedWishlists(); } - // Transform LocalWishlist to match the format expected by WishlistSection - const transformedWishlists = $derived(() => { - return localWishlists.map(w => ({ - id: w.ownerToken, - title: w.title, - ownerToken: w.ownerToken, - publicToken: w.publicToken, - createdAt: w.createdAt, - isFavorite: w.isFavorite || false, - items: [] // We don't have item data in localStorage - })); - }); + // Use enriched wishlists which have full data including theme and color + const transformedWishlists = $derived(() => enrichedWishlists); // Description depends on authentication status const sectionDescription = $derived(() => { diff --git a/src/lib/utils/themes.ts b/src/lib/utils/themes.ts index 63e49e3..c8c2ed6 100644 --- a/src/lib/utils/themes.ts +++ b/src/lib/utils/themes.ts @@ -44,8 +44,13 @@ export function getTheme(themeName?: string | null): Theme { /** * Get color from a CSS color string or class - * For now, we'll use currentColor which inherits from the parent + * Returns the provided color or a default primary color */ export function getThemeColor(color?: string | null): string { - return color || 'currentColor'; + // Use the provided color, or default to a visible color + if (color && color !== 'null' && color !== '') { + return color; + } + // Default to a blue color (can't use CSS variables in SVG fill) + return '#3b82f6'; } diff --git a/src/routes/api/wishlist/[token]/+server.ts b/src/routes/api/wishlist/[token]/+server.ts new file mode 100644 index 0000000..f84afd7 --- /dev/null +++ b/src/routes/api/wishlist/[token]/+server.ts @@ -0,0 +1,38 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { db } from '$lib/server/db'; +import { wishlists } from '$lib/server/schema'; +import { eq, or } from 'drizzle-orm'; + +export const GET: RequestHandler = async ({ params }) => { + const { token } = params; + + // Find wishlist by either ownerToken or publicToken + const wishlist = await db.query.wishlists.findFirst({ + where: or( + eq(wishlists.ownerToken, token), + eq(wishlists.publicToken, token) + ), + with: { + items: { + orderBy: (items, { asc }) => [asc(items.order)] + } + } + }); + + if (!wishlist) { + return json({ error: 'Wishlist not found' }, { status: 404 }); + } + + // Return only the necessary fields + return json({ + id: wishlist.id, + title: wishlist.title, + ownerToken: wishlist.ownerToken, + publicToken: wishlist.publicToken, + createdAt: wishlist.createdAt, + theme: wishlist.theme, + color: wishlist.color, + items: wishlist.items || [] + }); +}; diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index c02fc1d..e29f516 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -58,7 +58,7 @@ }); - + Date: Sun, 30 Nov 2025 01:30:43 +0100 Subject: [PATCH 4/7] wip: making themes more dynamic and easy to add --- .../components/themes/ThemeBackground.svelte | 19 ++--- src/lib/components/themes/ThemeCard.svelte | 12 ++-- .../components/themes/svgs/CardPattern.svelte | 38 ++-------- .../components/themes/svgs/TopPattern.svelte | 69 ++++++++++--------- .../components/wishlist/WishlistHeader.svelte | 6 +- src/lib/stores/theme.svelte.ts | 7 ++ src/lib/utils/themes.ts | 18 ----- static/themes/dots/item.svg | 1 + static/themes/geometric/bgbottom.svg | 1 + static/themes/geometric/bgtop.svg | 1 + static/themes/geometric/item.svg | 1 + static/themes/waves/item.svg | 1 + 12 files changed, 76 insertions(+), 98 deletions(-) create mode 100644 static/themes/dots/item.svg create mode 100644 static/themes/geometric/bgbottom.svg create mode 100644 static/themes/geometric/bgtop.svg create mode 100644 static/themes/geometric/item.svg create mode 100644 static/themes/waves/item.svg diff --git a/src/lib/components/themes/ThemeBackground.svelte b/src/lib/components/themes/ThemeBackground.svelte index f983a58..4353fea 100644 --- a/src/lib/components/themes/ThemeBackground.svelte +++ b/src/lib/components/themes/ThemeBackground.svelte @@ -1,37 +1,38 @@ {#if theme.pattern !== 'none'} {#if showTop} {/if} {#if showBottom} {/if} {/if} diff --git a/src/lib/components/themes/ThemeCard.svelte b/src/lib/components/themes/ThemeCard.svelte index 0a3c685..ca09aa3 100644 --- a/src/lib/components/themes/ThemeCard.svelte +++ b/src/lib/components/themes/ThemeCard.svelte @@ -1,23 +1,23 @@ {#if theme.pattern !== 'none'} {/if} diff --git a/src/lib/components/themes/svgs/CardPattern.svelte b/src/lib/components/themes/svgs/CardPattern.svelte index 6c00a8c..1ee075e 100644 --- a/src/lib/components/themes/svgs/CardPattern.svelte +++ b/src/lib/components/themes/svgs/CardPattern.svelte @@ -1,38 +1,14 @@ -
- {#if pattern === 'waves'} - - - - {:else if pattern === 'geometric'} - - - - {:else if pattern === 'dots'} - - - - - - - - - {/if} +
+ card svg background pattern
diff --git a/src/lib/components/themes/svgs/TopPattern.svelte b/src/lib/components/themes/svgs/TopPattern.svelte index 3aa0247..543702b 100644 --- a/src/lib/components/themes/svgs/TopPattern.svelte +++ b/src/lib/components/themes/svgs/TopPattern.svelte @@ -1,38 +1,45 @@ -
- {#if pattern === 'waves'} - - - - {:else if pattern === 'geometric'} - - - - {:else if pattern === 'dots'} - - - - - - - - - {/if} +
+ {@html svgContent}
diff --git a/src/lib/components/wishlist/WishlistHeader.svelte b/src/lib/components/wishlist/WishlistHeader.svelte index f0f0c89..6381bde 100644 --- a/src/lib/components/wishlist/WishlistHeader.svelte +++ b/src/lib/components/wishlist/WishlistHeader.svelte @@ -106,7 +106,7 @@ editingTitle = true; } }} - class="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-input hover:bg-accent transition-colors" + class="shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-input hover:bg-accent transition-colors" aria-label={editingTitle ? "Save title" : "Edit title"} > {#if editingTitle} @@ -116,12 +116,12 @@ {/if}
-
+
{ wishlistTheme = theme; - await onThemeUpdate(theme); + onThemeUpdate(theme); // Force reactivity by updating the wishlist object wishlist.theme = theme; }} diff --git a/src/lib/stores/theme.svelte.ts b/src/lib/stores/theme.svelte.ts index 85cf553..9a6da49 100644 --- a/src/lib/stores/theme.svelte.ts +++ b/src/lib/stores/theme.svelte.ts @@ -1,6 +1,7 @@ import { browser } from '$app/environment'; type Theme = 'light' | 'dark' | 'system'; +type ResolvedTheme = 'light' | 'dark'; class ThemeStore { current = $state('system'); @@ -35,6 +36,12 @@ class ThemeStore { } } + getResolvedTheme(): ResolvedTheme { + const isDark = this.current === 'dark' || + (this.current === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); + return isDark ? 'dark' : 'light'; + } + toggle() { // Cycle through: light -> dark -> system -> light if (this.current === 'light') { diff --git a/src/lib/utils/themes.ts b/src/lib/utils/themes.ts index c8c2ed6..49af6ca 100644 --- a/src/lib/utils/themes.ts +++ b/src/lib/utils/themes.ts @@ -7,29 +7,24 @@ export type ThemePattern = 'waves' | 'geometric' | 'dots' | 'none'; export interface Theme { name: string; pattern: ThemePattern; - opacity: number; } export const AVAILABLE_THEMES: Record = { none: { name: 'None', pattern: 'none', - opacity: 0 }, waves: { name: 'Waves', pattern: 'waves', - opacity: 0.1 }, geometric: { name: 'Geometric', pattern: 'geometric', - opacity: 0.1 }, dots: { name: 'Dots', pattern: 'dots', - opacity: 0.15 } }; @@ -41,16 +36,3 @@ export function getTheme(themeName?: string | null): Theme { } return AVAILABLE_THEMES[themeName]; } - -/** - * Get color from a CSS color string or class - * Returns the provided color or a default primary color - */ -export function getThemeColor(color?: string | null): string { - // Use the provided color, or default to a visible color - if (color && color !== 'null' && color !== '') { - return color; - } - // Default to a blue color (can't use CSS variables in SVG fill) - return '#3b82f6'; -} diff --git a/static/themes/dots/item.svg b/static/themes/dots/item.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/dots/item.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/themes/geometric/bgbottom.svg b/static/themes/geometric/bgbottom.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/geometric/bgbottom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/themes/geometric/bgtop.svg b/static/themes/geometric/bgtop.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/geometric/bgtop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/themes/geometric/item.svg b/static/themes/geometric/item.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/geometric/item.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/themes/waves/item.svg b/static/themes/waves/item.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/waves/item.svg @@ -0,0 +1 @@ + \ No newline at end of file -- 2.49.1 From 23e19932d2d75e15954118e01e76b9909c5af5f9 Mon Sep 17 00:00:00 2001 From: rasmusq Date: Sun, 14 Dec 2025 20:06:36 +0100 Subject: [PATCH 5/7] add: dynamic themes and streamlined theme creation system --- .../components/themes/ThemeBackground.svelte | 19 ++---- src/lib/components/themes/ThemeCard.svelte | 10 +--- .../themes/svgs/BottomPattern.svelte | 54 ++++++++--------- .../components/themes/svgs/CardPattern.svelte | 31 ++++++++-- .../components/themes/svgs/TopPattern.svelte | 59 ++++++++----------- src/lib/stores/theme.svelte.ts | 4 +- src/lib/utils/themes.ts | 17 +++--- static/themes/dots/bgbottom.svg | 1 + static/themes/dots/bgtop.svg | 1 + static/themes/waves/bgbottom.svg | 1 + static/themes/waves/bgtop.svg | 1 + 11 files changed, 96 insertions(+), 102 deletions(-) create mode 100644 static/themes/dots/bgbottom.svg create mode 100644 static/themes/dots/bgtop.svg create mode 100644 static/themes/waves/bgbottom.svg create mode 100644 static/themes/waves/bgtop.svg diff --git a/src/lib/components/themes/ThemeBackground.svelte b/src/lib/components/themes/ThemeBackground.svelte index 4353fea..6a626e4 100644 --- a/src/lib/components/themes/ThemeBackground.svelte +++ b/src/lib/components/themes/ThemeBackground.svelte @@ -1,15 +1,13 @@ {#if theme.pattern !== 'none'} {#if showTop} - + {/if} {#if showBottom} - + {/if} {/if} diff --git a/src/lib/components/themes/ThemeCard.svelte b/src/lib/components/themes/ThemeCard.svelte index ca09aa3..bb0164c 100644 --- a/src/lib/components/themes/ThemeCard.svelte +++ b/src/lib/components/themes/ThemeCard.svelte @@ -1,8 +1,6 @@ {#if theme.pattern !== 'none'} - + {/if} diff --git a/src/lib/components/themes/svgs/BottomPattern.svelte b/src/lib/components/themes/svgs/BottomPattern.svelte index 1e696d7..3459dd3 100644 --- a/src/lib/components/themes/svgs/BottomPattern.svelte +++ b/src/lib/components/themes/svgs/BottomPattern.svelte @@ -1,38 +1,32 @@ -
- {#if pattern === 'waves'} - - - - {:else if pattern === 'geometric'} - - - - {:else if pattern === 'dots'} - - - - - - - - - {/if} -
+{#if pattern !== 'none'} +
+{/if} diff --git a/src/lib/components/themes/svgs/CardPattern.svelte b/src/lib/components/themes/svgs/CardPattern.svelte index 1ee075e..52dc17c 100644 --- a/src/lib/components/themes/svgs/CardPattern.svelte +++ b/src/lib/components/themes/svgs/CardPattern.svelte @@ -1,14 +1,33 @@ -
- card svg background pattern -
+{#if pattern !== 'none'} +
+{/if} diff --git a/src/lib/components/themes/svgs/TopPattern.svelte b/src/lib/components/themes/svgs/TopPattern.svelte index 543702b..87524a2 100644 --- a/src/lib/components/themes/svgs/TopPattern.svelte +++ b/src/lib/components/themes/svgs/TopPattern.svelte @@ -1,45 +1,32 @@ -
- {@html svgContent} -
+{#if pattern !== 'none'} +
+{/if} diff --git a/src/lib/stores/theme.svelte.ts b/src/lib/stores/theme.svelte.ts index 9a6da49..f5d14ef 100644 --- a/src/lib/stores/theme.svelte.ts +++ b/src/lib/stores/theme.svelte.ts @@ -12,10 +12,8 @@ class ThemeStore { this.current = stored || 'system'; this.applyTheme(); - // Listen for system theme changes const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); mediaQuery.addEventListener('change', () => { - // Re-apply theme if in system mode if (this.current === 'system') { this.applyTheme(); } @@ -37,6 +35,8 @@ class ThemeStore { } getResolvedTheme(): ResolvedTheme { + if (!browser) return 'light'; + const isDark = this.current === 'dark' || (this.current === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); return isDark ? 'dark' : 'light'; diff --git a/src/lib/utils/themes.ts b/src/lib/utils/themes.ts index 49af6ca..f938f41 100644 --- a/src/lib/utils/themes.ts +++ b/src/lib/utils/themes.ts @@ -1,6 +1,4 @@ -/** - * Theme configuration for SVG overlays - */ +import { themeStore } from '$lib/stores/theme.svelte'; export type ThemePattern = 'waves' | 'geometric' | 'dots' | 'none'; @@ -12,23 +10,24 @@ export interface Theme { export const AVAILABLE_THEMES: Record = { none: { name: 'None', - pattern: 'none', + pattern: 'none' }, waves: { name: 'Waves', - pattern: 'waves', + pattern: 'waves' }, geometric: { name: 'Geometric', - pattern: 'geometric', + pattern: 'geometric' }, dots: { name: 'Dots', - pattern: 'dots', + pattern: 'dots' } }; export const DEFAULT_THEME = 'none'; +export const PATTERN_OPACITY = 0.1; export function getTheme(themeName?: string | null): Theme { if (!themeName || !AVAILABLE_THEMES[themeName]) { @@ -36,3 +35,7 @@ export function getTheme(themeName?: string | null): Theme { } return AVAILABLE_THEMES[themeName]; } + +export function getPatternColor(customColor?: string): string { + return customColor || (themeStore.getResolvedTheme() === 'dark' ? '#FFFFFF' : '#000000'); +} diff --git a/static/themes/dots/bgbottom.svg b/static/themes/dots/bgbottom.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/dots/bgbottom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/themes/dots/bgtop.svg b/static/themes/dots/bgtop.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/dots/bgtop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/themes/waves/bgbottom.svg b/static/themes/waves/bgbottom.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/waves/bgbottom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/themes/waves/bgtop.svg b/static/themes/waves/bgtop.svg new file mode 100644 index 0000000..455daf8 --- /dev/null +++ b/static/themes/waves/bgtop.svg @@ -0,0 +1 @@ + \ No newline at end of file -- 2.49.1 From 2b74c118849d2a2fba65048ceba6f378ffa8cf21 Mon Sep 17 00:00:00 2001 From: rasmusq Date: Sun, 14 Dec 2025 20:29:02 +0100 Subject: [PATCH 6/7] add: user lock on reservations --- .../wishlist/ReservationButton.svelte | 67 +++++++++++++++++-- src/lib/server/schema.ts | 8 ++- src/routes/wishlist/[token]/+page.server.ts | 29 +++++++- src/routes/wishlist/[token]/+page.svelte | 2 + 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/lib/components/wishlist/ReservationButton.svelte b/src/lib/components/wishlist/ReservationButton.svelte index 2e4cca5..6742fe6 100644 --- a/src/lib/components/wishlist/ReservationButton.svelte +++ b/src/lib/components/wishlist/ReservationButton.svelte @@ -7,12 +7,25 @@ itemId: string; isReserved: boolean; reserverName?: string | null; + reservationUserId?: string | null; + currentUserId?: string | null; } - let { itemId, isReserved, reserverName }: Props = $props(); + let { itemId, isReserved, reserverName, reservationUserId, currentUserId }: Props = $props(); let showReserveForm = $state(false); let name = $state(''); + let showCancelConfirmation = $state(false); + + const canCancel = $derived(() => { + if (!isReserved) return false; + if (reservationUserId) { + return currentUserId === reservationUserId; + } + return true; + }); + + const isAnonymousReservation = $derived(!reservationUserId); {#if isReserved} @@ -23,12 +36,52 @@ by {reserverName} {/if}
-
- - -
+ {#if canCancel()} + {#if showCancelConfirmation} +
+

+ Cancel this reservation? +

+
+
{ + return async ({ update }) => { + showCancelConfirmation = false; + await update(); + }; + }}> + + +
+ +
+
+ {:else if isAnonymousReservation} + + {:else} +
+ + +
+ {/if} + {/if}
{:else if showReserveForm}
items.id, { onDelete: 'cascade' }), + userId: text('user_id').references(() => users.id, { onDelete: 'set null' }), reserverName: text('reserver_name'), createdAt: timestamp('created_at').defaultNow().notNull() }); @@ -125,6 +126,10 @@ export const reservationsRelations = relations(reservations, ({ one }) => ({ item: one(items, { fields: [reservations.itemId], references: [items.id] + }), + user: one(users, { + fields: [reservations.userId], + references: [users.id] }) })); @@ -154,7 +159,8 @@ export const savedWishlistsRelations = relations(savedWishlists, ({ one }) => ({ export const usersRelations = relations(users, ({ many }) => ({ wishlists: many(wishlists), - savedWishlists: many(savedWishlists) + savedWishlists: many(savedWishlists), + reservations: many(reservations) })); export type User = typeof users.$inferSelect; diff --git a/src/routes/wishlist/[token]/+page.server.ts b/src/routes/wishlist/[token]/+page.server.ts index ae726a6..0c77ca2 100644 --- a/src/routes/wishlist/[token]/+page.server.ts +++ b/src/routes/wishlist/[token]/+page.server.ts @@ -43,12 +43,13 @@ export const load: PageServerLoad = async ({ params, locals }) => { isSaved, isClaimed, savedWishlistId, - isAuthenticated: !!session?.user + isAuthenticated: !!session?.user, + currentUserId: session?.user?.id || null }; }; export const actions: Actions = { - reserve: async ({ request }) => { + reserve: async ({ request, locals }) => { const formData = await request.formData(); const itemId = formData.get('itemId') as string; const reserverName = formData.get('reserverName') as string; @@ -57,6 +58,8 @@ export const actions: Actions = { return { success: false, error: 'Item ID is required' }; } + const session = await locals.auth(); + const existingReservation = await db.query.reservations.findFirst({ where: eq(reservations.itemId, itemId) }); @@ -68,6 +71,7 @@ export const actions: Actions = { await db.transaction(async (tx) => { await tx.insert(reservations).values({ itemId, + userId: session?.user?.id || null, reserverName: reserverName?.trim() || null }); @@ -80,7 +84,7 @@ export const actions: Actions = { return { success: true }; }, - unreserve: async ({ request }) => { + unreserve: async ({ request, locals }) => { const formData = await request.formData(); const itemId = formData.get('itemId') as string; @@ -88,6 +92,25 @@ export const actions: Actions = { return { success: false, error: 'Item ID is required' }; } + const session = await locals.auth(); + + const reservation = await db.query.reservations.findFirst({ + where: eq(reservations.itemId, itemId) + }); + + if (!reservation) { + return { success: false, error: 'Reservation not found' }; + } + + if (reservation.userId) { + if (!session?.user?.id || session.user.id !== reservation.userId) { + return { + success: false, + error: 'You can only cancel your own reservations' + }; + } + } + await db.transaction(async (tx) => { await tx.delete(reservations).where(eq(reservations.itemId, itemId)); diff --git a/src/routes/wishlist/[token]/+page.svelte b/src/routes/wishlist/[token]/+page.svelte index da74e6a..1c639c3 100644 --- a/src/routes/wishlist/[token]/+page.svelte +++ b/src/routes/wishlist/[token]/+page.svelte @@ -115,6 +115,8 @@ itemId={item.id} isReserved={item.isReserved} reserverName={item.reservations?.[0]?.reserverName} + reservationUserId={item.reservations?.[0]?.userId} + currentUserId={data.currentUserId} /> {/each} -- 2.49.1 From 8af491d82390e9a52f0566b8582d68cee7bfd8db Mon Sep 17 00:00:00 2001 From: rasmusq Date: Sun, 14 Dec 2025 20:31:10 +0100 Subject: [PATCH 7/7] fix: alignment on wish reservation cancellation dialog --- src/lib/components/wishlist/ReservationButton.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/wishlist/ReservationButton.svelte b/src/lib/components/wishlist/ReservationButton.svelte index 6742fe6..3dc8c98 100644 --- a/src/lib/components/wishlist/ReservationButton.svelte +++ b/src/lib/components/wishlist/ReservationButton.svelte @@ -29,7 +29,7 @@ {#if isReserved} -
+
✓ Reserved {#if reserverName} @@ -38,7 +38,7 @@
{#if canCancel()} {#if showCancelConfirmation} -
+

Cancel this reservation?

-- 2.49.1