From 615f2dbb8d4f6a828c7c9910953bfb9ecf058319 Mon Sep 17 00:00:00 2001 From: rasmusq Date: Tue, 25 Nov 2025 17:33:10 +0100 Subject: [PATCH] add: multiple ownership of wishlists --- src/routes/dashboard/+page.svelte | 95 ++++++++++++++----- .../wishlist/[token]/edit/+page.server.ts | 85 ++++++++++++++++- src/routes/wishlist/[token]/edit/+page.svelte | 38 ++++++++ 3 files changed, 191 insertions(+), 27 deletions(-) diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index 69d67a7..be4572c 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -13,8 +13,23 @@ const t = $derived(languageStore.t); + // Combine owned and claimed wishlists for "My Wishlists" + const allMyWishlists = $derived(() => { + const owned = data.wishlists || []; + // Only include claimed wishlists (those with ownerToken, meaning they were claimed via edit link) + const claimed = (data.savedWishlists || []) + .filter(saved => saved.wishlist?.ownerToken) // Has edit access + .map(saved => ({ + ...saved.wishlist, + isFavorite: saved.isFavorite, + isClaimed: true, + savedId: saved.id + })); + return [...owned, ...claimed]; + }); + const sortedWishlists = $derived( - [...data.wishlists].sort((a, b) => { + [...allMyWishlists()].sort((a, b) => { if (a.isFavorite && !b.isFavorite) return -1; if (!a.isFavorite && b.isFavorite) return 1; @@ -32,23 +47,26 @@ }) ); + // Saved wishlists are those WITHOUT ownerToken (saved from public view only) const sortedSavedWishlists = $derived( - [...data.savedWishlists].sort((a, b) => { - if (a.isFavorite && !b.isFavorite) return -1; - if (!a.isFavorite && b.isFavorite) return 1; + [...(data.savedWishlists || [])] + .filter(saved => !saved.wishlist?.ownerToken) // No edit access + .sort((a, b) => { + if (a.isFavorite && !b.isFavorite) return -1; + if (!a.isFavorite && b.isFavorite) return 1; - const aHasEndDate = !!a.wishlist?.endDate; - const bHasEndDate = !!b.wishlist?.endDate; + const aHasEndDate = !!a.wishlist?.endDate; + const bHasEndDate = !!b.wishlist?.endDate; - if (aHasEndDate && !bHasEndDate) return -1; - if (!aHasEndDate && bHasEndDate) return 1; + if (aHasEndDate && !bHasEndDate) return -1; + if (!aHasEndDate && bHasEndDate) return 1; - if (aHasEndDate && bHasEndDate) { - return new Date(a.wishlist.endDate!).getTime() - new Date(b.wishlist.endDate!).getTime(); - } + if (aHasEndDate && bHasEndDate) { + return new Date(a.wishlist.endDate!).getTime() - new Date(b.wishlist.endDate!).getTime(); + } - return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); - }) + return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + }) ); function formatEndDate(date: Date | string | null): string | null { @@ -107,17 +125,33 @@ color={wishlist.color} >
-
{ - return async ({ update }) => { - await update({ reset: false }); - }; - }}> - - - -
+ {#if wishlist.isClaimed} + +
{ + return async ({ update }) => { + await update({ reset: false }); + }; + }}> + + + +
+ {:else} + +
{ + return async ({ update }) => { + await update({ reset: false }); + }; + }}> + + + +
+ {/if} + {#if wishlist.isClaimed} + +
{ + return async ({ update }) => { + await update({ reset: false }); + }; + }}> + + +
+ {/if}
{/snippet} diff --git a/src/routes/wishlist/[token]/edit/+page.server.ts b/src/routes/wishlist/[token]/edit/+page.server.ts index 299c845..31a8bda 100644 --- a/src/routes/wishlist/[token]/edit/+page.server.ts +++ b/src/routes/wishlist/[token]/edit/+page.server.ts @@ -1,8 +1,8 @@ import { error } from '@sveltejs/kit'; import type { PageServerLoad, Actions } from './$types'; import { db } from '$lib/server/db'; -import { wishlists, items } from '$lib/server/schema'; -import { eq } from 'drizzle-orm'; +import { wishlists, items, savedWishlists } from '$lib/server/schema'; +import { eq, and } from 'drizzle-orm'; export const load: PageServerLoad = async ({ params, locals }) => { const wishlist = await db.query.wishlists.findFirst({ @@ -19,11 +19,29 @@ export const load: PageServerLoad = async ({ params, locals }) => { } const session = await locals.auth(); + let hasClaimed = false; + let isOwner = false; + + if (session?.user?.id) { + // Check if user is the owner + isOwner = wishlist.userId === session.user.id; + + // Check if user has claimed this wishlist + const savedWishlist = await db.query.savedWishlists.findFirst({ + where: and( + eq(savedWishlists.userId, session.user.id), + eq(savedWishlists.wishlistId, wishlist.id) + ) + }); + hasClaimed = !!savedWishlist; + } return { wishlist, publicUrl: `/wishlist/${wishlist.publicToken}`, - isAuthenticated: !!session?.user + isAuthenticated: !!session?.user, + hasClaimed, + isOwner }; }; @@ -224,5 +242,66 @@ export const actions: Actions = { .where(eq(wishlists.id, wishlist.id)); return { success: true }; + }, + + claimWishlist: async ({ params, locals }) => { + const session = await locals.auth(); + + if (!session?.user?.id) { + throw error(401, 'You must be signed in to claim a wishlist'); + } + + const wishlist = await db.query.wishlists.findFirst({ + where: eq(wishlists.ownerToken, params.token) + }); + + if (!wishlist) { + throw error(404, 'Wishlist not found'); + } + + // Check if already claimed + const existing = await db.query.savedWishlists.findFirst({ + where: and( + eq(savedWishlists.userId, session.user.id), + eq(savedWishlists.wishlistId, wishlist.id) + ) + }); + + if (existing) { + return { success: true, message: 'Already claimed' }; + } + + await db.insert(savedWishlists).values({ + userId: session.user.id, + wishlistId: wishlist.id, + isFavorite: false + }); + + return { success: true, message: 'Wishlist claimed successfully' }; + }, + + unclaimWishlist: async ({ params, locals }) => { + const session = await locals.auth(); + + if (!session?.user?.id) { + throw error(401, 'You must be signed in'); + } + + const wishlist = await db.query.wishlists.findFirst({ + where: eq(wishlists.ownerToken, params.token) + }); + + if (!wishlist) { + throw error(404, 'Wishlist not found'); + } + + await db.delete(savedWishlists).where( + and( + eq(savedWishlists.userId, session.user.id), + eq(savedWishlists.wishlistId, wishlist.id) + ) + ); + + return { success: true, message: 'Wishlist unclaimed' }; } }; diff --git a/src/routes/wishlist/[token]/edit/+page.svelte b/src/routes/wishlist/[token]/edit/+page.svelte index 69d1476..1e50c16 100644 --- a/src/routes/wishlist/[token]/edit/+page.svelte +++ b/src/routes/wishlist/[token]/edit/+page.svelte @@ -206,6 +206,44 @@ ownerUrl="/wishlist/{data.wishlist.ownerToken}/edit" /> + {#if data.isAuthenticated} +
+ {#if data.isOwner} + +

+ This wishlist is already in your dashboard as the owner. +

+ {:else} +
+ +
+

+ {#if data.hasClaimed} + You have claimed this wishlist. It will appear in your dashboard. + {:else} + Claim this wishlist to add it to your dashboard for easy access. + {/if} +

+ {/if} +
+ {/if} +