add: multiple ownership of wishlists

This commit is contained in:
2025-11-25 17:33:10 +01:00
parent 0144e8df1a
commit 615f2dbb8d
3 changed files with 191 additions and 27 deletions

View File

@@ -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}
>
<div class="flex gap-2 flex-wrap">
<form method="POST" action="?/toggleFavorite" use:enhance={() => {
return async ({ update }) => {
await update({ reset: false });
};
}}>
<input type="hidden" name="wishlistId" value={wishlist.id} />
<input type="hidden" name="isFavorite" value={wishlist.isFavorite} />
<Button type="submit" size="sm" variant="outline">
<Star class={wishlist.isFavorite ? "fill-yellow-500 text-yellow-500" : ""} />
</Button>
</form>
{#if wishlist.isClaimed}
<!-- For claimed wishlists, use saved favorite toggle -->
<form method="POST" action="?/toggleSavedFavorite" use:enhance={() => {
return async ({ update }) => {
await update({ reset: false });
};
}}>
<input type="hidden" name="savedWishlistId" value={wishlist.savedId} />
<input type="hidden" name="isFavorite" value={wishlist.isFavorite} />
<Button type="submit" size="sm" variant="outline">
<Star class={wishlist.isFavorite ? "fill-yellow-500 text-yellow-500" : ""} />
</Button>
</form>
{:else}
<!-- For owned wishlists, use regular favorite toggle -->
<form method="POST" action="?/toggleFavorite" use:enhance={() => {
return async ({ update }) => {
await update({ reset: false });
};
}}>
<input type="hidden" name="wishlistId" value={wishlist.id} />
<input type="hidden" name="isFavorite" value={wishlist.isFavorite} />
<Button type="submit" size="sm" variant="outline">
<Star class={wishlist.isFavorite ? "fill-yellow-500 text-yellow-500" : ""} />
</Button>
</form>
{/if}
<Button
size="sm"
onclick={() => (window.location.href = `/wishlist/${wishlist.ownerToken}/edit`)}
@@ -135,6 +169,19 @@
>
{t.dashboard.copyLink}
</Button>
{#if wishlist.isClaimed}
<!-- Add unclaim button for claimed wishlists -->
<form method="POST" action="?/unsaveWishlist" use:enhance={() => {
return async ({ update }) => {
await update({ reset: false });
};
}}>
<input type="hidden" name="savedWishlistId" value={wishlist.savedId} />
<Button type="submit" size="sm" variant="destructive">
{t.dashboard.unsave}
</Button>
</form>
{/if}
</div>
</WishlistCard>
{/snippet}

View File

@@ -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' };
}
};

View File

@@ -206,6 +206,44 @@
ownerUrl="/wishlist/{data.wishlist.ownerToken}/edit"
/>
{#if data.isAuthenticated}
<div class="mb-6">
{#if data.isOwner}
<Button
disabled
variant="outline"
class="w-full md:w-auto opacity-60 cursor-not-allowed"
>
You Own This Wishlist
</Button>
<p class="text-sm text-muted-foreground mt-2">
This wishlist is already in your dashboard as the owner.
</p>
{:else}
<form
method="POST"
action={data.hasClaimed ? "?/unclaimWishlist" : "?/claimWishlist"}
use:enhance
>
<Button
type="submit"
variant={data.hasClaimed ? "outline" : "default"}
class="w-full md:w-auto"
>
{data.hasClaimed ? "Unclaim Wishlist" : "Claim Wishlist"}
</Button>
</form>
<p class="text-sm text-muted-foreground mt-2">
{#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}
</p>
{/if}
</div>
{/if}
<WishlistActionButtons
bind:rearranging={rearranging}
onToggleAddForm={handleToggleAddForm}