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..6eb21a3 100644 --- a/src/lib/components/dashboard/LocalWishlistsSection.svelte +++ b/src/lib/components/dashboard/LocalWishlistsSection.svelte @@ -15,43 +15,106 @@ 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(() => { + 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 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)}
- +
- + {#if canCancel()} + {#if showCancelConfirmation} +
+

+ Cancel this reservation? +

+
+
{ + return async ({ update }) => { + showCancelConfirmation = false; + await update(); + }; + }}> + + +
+ +
+
+ {:else if isAnonymousReservation} + + {:else} +
+ + +
+ {/if} + {/if}
{:else if showReserveForm}
Promise; onDescriptionUpdate: (description: string | null) => Promise; onColorUpdate: (color: string | null) => void; onEndDateUpdate: (endDate: string | null) => void; + onThemeUpdate: (theme: string | null) => void; } = $props(); const t = $derived(languageStore.t); @@ -29,6 +32,7 @@ let wishlistTitle = $state(wishlist.title); let wishlistDescription = $state(wishlist.description || ""); let wishlistColor = $state(wishlist.color); + let wishlistTheme = $state(wishlist.theme || 'none'); let wishlistEndDate = $state( wishlist.endDate ? new Date(wishlist.endDate).toISOString().split("T")[0] @@ -102,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} @@ -112,7 +116,16 @@ {/if}
-
+
+ { + wishlistTheme = theme; + 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}
items.id, { onDelete: 'cascade' }), + userId: text('user_id').references(() => users.id, { onDelete: 'set null' }), reserverName: text('reserver_name'), createdAt: timestamp('created_at').defaultNow().notNull() }); @@ -123,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] }) })); @@ -152,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/lib/stores/theme.svelte.ts b/src/lib/stores/theme.svelte.ts index 85cf553..f5d14ef 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'); @@ -11,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(); } @@ -35,6 +34,14 @@ 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'; + } + 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 new file mode 100644 index 0000000..f938f41 --- /dev/null +++ b/src/lib/utils/themes.ts @@ -0,0 +1,41 @@ +import { themeStore } from '$lib/stores/theme.svelte'; + +export type ThemePattern = 'waves' | 'geometric' | 'dots' | 'none'; + +export interface Theme { + name: string; + pattern: ThemePattern; +} + +export const AVAILABLE_THEMES: Record = { + none: { + name: 'None', + pattern: 'none' + }, + waves: { + name: 'Waves', + pattern: 'waves' + }, + geometric: { + name: 'Geometric', + pattern: 'geometric' + }, + dots: { + name: '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]) { + return AVAILABLE_THEMES[DEFAULT_THEME]; + } + return AVAILABLE_THEMES[themeName]; +} + +export function getPatternColor(customColor?: string): string { + return customColor || (themeStore.getResolvedTheme() === 'dark' ? '#FFFFFF' : '#000000'); +} 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/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.server.ts b/src/routes/dashboard/+page.server.ts index 0e2bb3c..ff67458 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) => { @@ -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 @@ -149,6 +154,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..e29f516 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,8 +58,14 @@ }); - - + + 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 81199df..1c639c3 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} - + {/each} diff --git a/src/routes/wishlist/[token]/edit/+page.server.ts b/src/routes/wishlist/[token]/edit/+page.server.ts index 6e2bd07..6bbad6c 100644 --- a/src/routes/wishlist/[token]/edit/+page.server.ts +++ b/src/routes/wishlist/[token]/edit/+page.server.ts @@ -203,6 +203,7 @@ export const actions: Actions = { const title = formData.get('title'); const description = formData.get('description'); const endDate = formData.get('endDate'); + const theme = formData.get('theme'); const wishlist = await db.query.wishlists.findFirst({ where: eq(wishlists.ownerToken, params.token) @@ -237,6 +238,10 @@ export const actions: Actions = { updates.endDate = endDateStr ? new Date(endDateStr) : null; } + if (theme !== null) { + updates.theme = theme?.toString().trim() || 'none'; + } + await db.update(wishlists) .set(updates) .where(eq(wishlists.id, wishlist.id)); diff --git a/src/routes/wishlist/[token]/edit/+page.svelte b/src/routes/wishlist/[token]/edit/+page.svelte index 578740f..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); + } - + 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/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/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 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