From 85f8671c722af72194ac45e6340e8c95f8c9eb28 Mon Sep 17 00:00:00 2001
From: rasmusq
Date: Thu, 27 Nov 2025 21:35:28 +0100
Subject: [PATCH] add: local wishlists stored in local storage for anonymous
users
---
.../dashboard/LocalWishlistsSection.svelte | 93 ++++++++++++++++
.../components/layout/DashboardHeader.svelte | 13 ++-
.../wishlist/ClaimWishlistSection.svelte | 18 ++-
src/lib/utils/localWishlists.ts | 103 ++++++++++++++++++
src/routes/+page.svelte | 20 +++-
src/routes/api/wishlists/+server.ts | 8 +-
src/routes/dashboard/+page.server.ts | 11 +-
src/routes/dashboard/+page.svelte | 10 +-
src/routes/wishlist/[token]/edit/+page.svelte | 1 +
9 files changed, 264 insertions(+), 13 deletions(-)
create mode 100644 src/lib/components/dashboard/LocalWishlistsSection.svelte
create mode 100644 src/lib/utils/localWishlists.ts
diff --git a/src/lib/components/dashboard/LocalWishlistsSection.svelte b/src/lib/components/dashboard/LocalWishlistsSection.svelte
new file mode 100644
index 0000000..1328a39
--- /dev/null
+++ b/src/lib/components/dashboard/LocalWishlistsSection.svelte
@@ -0,0 +1,93 @@
+
+
+{#if localWishlists.length > 0}
+
+ {#snippet actions(wishlist, unlocked)}
+
+
+
+
+ {#if unlocked}
+
+ {/if}
+
+ {/snippet}
+
+{/if}
diff --git a/src/lib/components/layout/DashboardHeader.svelte b/src/lib/components/layout/DashboardHeader.svelte
index afbf9b2..83b8943 100644
--- a/src/lib/components/layout/DashboardHeader.svelte
+++ b/src/lib/components/layout/DashboardHeader.svelte
@@ -8,16 +8,25 @@
let { userName, userEmail }: { userName?: string | null; userEmail?: string | null } = $props();
const t = $derived(languageStore.t);
+ const isAuthenticated = $derived(!!userName || !!userEmail);
{t.nav.dashboard}
-
{t.dashboard.welcomeBack}, {userName || userEmail}
+ {#if isAuthenticated}
+
{t.dashboard.welcomeBack}, {userName || userEmail}
+ {:else}
+
{t.dashboard.anonymousDashboard || "Your local wishlists"}
+ {/if}
-
+ {#if isAuthenticated}
+
+ {:else}
+
+ {/if}
diff --git a/src/lib/components/wishlist/ClaimWishlistSection.svelte b/src/lib/components/wishlist/ClaimWishlistSection.svelte
index 00f8746..4916b39 100644
--- a/src/lib/components/wishlist/ClaimWishlistSection.svelte
+++ b/src/lib/components/wishlist/ClaimWishlistSection.svelte
@@ -2,18 +2,24 @@
import { Button } from '$lib/components/ui/button';
import { enhance } from '$app/forms';
import { languageStore } from '$lib/stores/language.svelte';
+ import { isLocalWishlist } from '$lib/utils/localWishlists';
let {
isAuthenticated,
isOwner,
- hasClaimed
+ hasClaimed,
+ ownerToken
}: {
isAuthenticated: boolean;
isOwner: boolean;
hasClaimed: boolean;
+ ownerToken: string;
} = $props();
const t = $derived(languageStore.t);
+
+ // Check if this wishlist is in localStorage
+ const isLocal = $derived(isLocalWishlist(ownerToken));
{#if isAuthenticated}
@@ -33,7 +39,11 @@
{/if}
diff --git a/src/lib/utils/localWishlists.ts b/src/lib/utils/localWishlists.ts
new file mode 100644
index 0000000..72f0ea9
--- /dev/null
+++ b/src/lib/utils/localWishlists.ts
@@ -0,0 +1,103 @@
+/**
+ * Utility functions for managing anonymous user's wishlists in localStorage
+ */
+
+const LOCAL_WISHLISTS_KEY = 'local_wishlists';
+
+export interface LocalWishlist {
+ ownerToken: string;
+ publicToken: string;
+ title: string;
+ createdAt: string;
+ isFavorite?: boolean;
+}
+
+/**
+ * Get all local wishlists from localStorage
+ */
+export function getLocalWishlists(): LocalWishlist[] {
+ if (typeof window === 'undefined') return [];
+
+ try {
+ const stored = localStorage.getItem(LOCAL_WISHLISTS_KEY);
+ return stored ? JSON.parse(stored) : [];
+ } catch (error) {
+ console.error('Failed to parse local wishlists:', error);
+ return [];
+ }
+}
+
+/**
+ * Add a wishlist to localStorage
+ */
+export function addLocalWishlist(wishlist: LocalWishlist): void {
+ if (typeof window === 'undefined') return;
+
+ try {
+ const wishlists = getLocalWishlists();
+
+ // Check if already exists
+ const exists = wishlists.some(w => w.ownerToken === wishlist.ownerToken);
+ if (exists) return;
+
+ wishlists.push(wishlist);
+ localStorage.setItem(LOCAL_WISHLISTS_KEY, JSON.stringify(wishlists));
+ } catch (error) {
+ console.error('Failed to add local wishlist:', error);
+ }
+}
+
+/**
+ * Remove a wishlist from localStorage (forget it)
+ */
+export function forgetLocalWishlist(ownerToken: string): void {
+ if (typeof window === 'undefined') return;
+
+ try {
+ const wishlists = getLocalWishlists();
+ const filtered = wishlists.filter(w => w.ownerToken !== ownerToken);
+ localStorage.setItem(LOCAL_WISHLISTS_KEY, JSON.stringify(filtered));
+ } catch (error) {
+ console.error('Failed to forget local wishlist:', error);
+ }
+}
+
+/**
+ * Clear all local wishlists (e.g., when user claims all wishlists)
+ */
+export function clearLocalWishlists(): void {
+ if (typeof window === 'undefined') return;
+
+ try {
+ localStorage.removeItem(LOCAL_WISHLISTS_KEY);
+ } catch (error) {
+ console.error('Failed to clear local wishlists:', error);
+ }
+}
+
+/**
+ * Check if a wishlist is in local storage
+ */
+export function isLocalWishlist(ownerToken: string): boolean {
+ const wishlists = getLocalWishlists();
+ return wishlists.some(w => w.ownerToken === ownerToken);
+}
+
+/**
+ * Toggle favorite status for a local wishlist
+ */
+export function toggleLocalFavorite(ownerToken: string): void {
+ if (typeof window === 'undefined') return;
+
+ try {
+ const wishlists = getLocalWishlists();
+ const updated = wishlists.map(w =>
+ w.ownerToken === ownerToken
+ ? { ...w, isFavorite: !w.isFavorite }
+ : w
+ );
+ localStorage.setItem(LOCAL_WISHLISTS_KEY, JSON.stringify(updated));
+ } catch (error) {
+ console.error('Failed to toggle local wishlist favorite:', error);
+ }
+}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index f13d5b2..0a64cf0 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -10,6 +10,7 @@
import ColorPicker from '$lib/components/ui/ColorPicker.svelte';
import type { PageData } from './$types';
import { languageStore } from '$lib/stores/language.svelte';
+ import { addLocalWishlist } from '$lib/utils/localWishlists';
let { data }: { data: PageData } = $props();
@@ -32,7 +33,19 @@
});
if (response.ok) {
- const { ownerToken } = await response.json();
+ const result = await response.json();
+ const { ownerToken, publicToken, title: wishlistTitle, createdAt } = result;
+
+ // If user is not authenticated, save to localStorage
+ if (!data.session?.user) {
+ addLocalWishlist({
+ ownerToken,
+ publicToken,
+ title: wishlistTitle,
+ createdAt
+ });
+ }
+
goto(`/wishlist/${ownerToken}/edit`);
}
} catch (error) {
@@ -56,9 +69,8 @@
- {#if data.session?.user}
-
- {:else}
+
+ {#if !data.session?.user}
{/if}
diff --git a/src/routes/api/wishlists/+server.ts b/src/routes/api/wishlists/+server.ts
index 5b7432f..e43bd16 100644
--- a/src/routes/api/wishlists/+server.ts
+++ b/src/routes/api/wishlists/+server.ts
@@ -29,5 +29,11 @@ export const POST: RequestHandler = async ({ request, locals }) => {
})
.returning();
- return json({ ownerToken, publicToken, id: wishlist.id });
+ return json({
+ ownerToken,
+ publicToken,
+ id: wishlist.id,
+ title: wishlist.title,
+ createdAt: wishlist.createdAt
+ });
};
diff --git a/src/routes/dashboard/+page.server.ts b/src/routes/dashboard/+page.server.ts
index 20d03b9..0e2bb3c 100644
--- a/src/routes/dashboard/+page.server.ts
+++ b/src/routes/dashboard/+page.server.ts
@@ -7,8 +7,14 @@ import { eq, and } from 'drizzle-orm';
export const load: PageServerLoad = async (event) => {
const session = await event.locals.auth();
+ // Allow anonymous users to access dashboard for local wishlists
if (!session?.user?.id) {
- throw redirect(303, '/signin');
+ return {
+ user: null,
+ wishlists: [],
+ savedWishlists: [],
+ isAuthenticated: false
+ };
}
const userWishlists = await db.query.wishlists.findMany({
@@ -53,7 +59,8 @@ export const load: PageServerLoad = async (event) => {
return {
user: session.user,
wishlists: userWishlists,
- savedWishlists: savedWithAccess
+ savedWishlists: savedWithAccess,
+ isAuthenticated: true
};
};
diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte
index 3764612..4ecbc3c 100644
--- a/src/routes/dashboard/+page.svelte
+++ b/src/routes/dashboard/+page.svelte
@@ -4,6 +4,7 @@
import PageContainer from '$lib/components/layout/PageContainer.svelte';
import DashboardHeader from '$lib/components/layout/DashboardHeader.svelte';
import WishlistSection from '$lib/components/dashboard/WishlistSection.svelte';
+ import LocalWishlistsSection from '$lib/components/dashboard/LocalWishlistsSection.svelte';
import { enhance } from '$app/forms';
import { Star } from 'lucide-svelte';
import { languageStore } from '$lib/stores/language.svelte';
@@ -36,8 +37,12 @@
-
-
+
+
+ {#if data.isAuthenticated}
+
+
{/snippet}
+ {/if}
diff --git a/src/routes/wishlist/[token]/edit/+page.svelte b/src/routes/wishlist/[token]/edit/+page.svelte
index 586bfd6..578740f 100644
--- a/src/routes/wishlist/[token]/edit/+page.svelte
+++ b/src/routes/wishlist/[token]/edit/+page.svelte
@@ -130,6 +130,7 @@
isAuthenticated={data.isAuthenticated}
isOwner={data.isOwner}
hasClaimed={data.hasClaimed}
+ ownerToken={data.wishlist.ownerToken}
/>