add: user lock on reservations

This commit is contained in:
2025-12-14 20:29:02 +01:00
parent 23e19932d2
commit 2b74c11884
4 changed files with 95 additions and 11 deletions

View File

@@ -7,12 +7,25 @@
itemId: string; itemId: string;
isReserved: boolean; isReserved: boolean;
reserverName?: string | null; 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 showReserveForm = $state(false);
let name = $state(''); 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);
</script> </script>
{#if isReserved} {#if isReserved}
@@ -23,12 +36,52 @@
by {reserverName} by {reserverName}
{/if} {/if}
</div> </div>
<form method="POST" action="?/unreserve" use:enhance> {#if canCancel()}
<input type="hidden" name="itemId" value={itemId} /> {#if showCancelConfirmation}
<Button type="submit" variant="outline" size="sm"> <div class="flex flex-col gap-2 items-end">
Cancel Reservation <p class="text-sm text-muted-foreground">
</Button> Cancel this reservation?
</form> </p>
<div class="flex gap-2">
<form method="POST" action="?/unreserve" use:enhance={() => {
return async ({ update }) => {
showCancelConfirmation = false;
await update();
};
}}>
<input type="hidden" name="itemId" value={itemId} />
<Button type="submit" variant="destructive" size="sm">
Yes, Cancel
</Button>
</form>
<Button
type="button"
variant="outline"
size="sm"
onclick={() => (showCancelConfirmation = false)}
>
No, Keep It
</Button>
</div>
</div>
{:else if isAnonymousReservation}
<Button
type="button"
variant="outline"
size="sm"
onclick={() => (showCancelConfirmation = true)}
>
Cancel Reservation
</Button>
{:else}
<form method="POST" action="?/unreserve" use:enhance>
<input type="hidden" name="itemId" value={itemId} />
<Button type="submit" variant="outline" size="sm">
Cancel Reservation
</Button>
</form>
{/if}
{/if}
</div> </div>
{:else if showReserveForm} {:else if showReserveForm}
<form <form

View File

@@ -117,6 +117,7 @@ export const reservations = pgTable('reservations', {
itemId: text('item_id') itemId: text('item_id')
.notNull() .notNull()
.references(() => items.id, { onDelete: 'cascade' }), .references(() => items.id, { onDelete: 'cascade' }),
userId: text('user_id').references(() => users.id, { onDelete: 'set null' }),
reserverName: text('reserver_name'), reserverName: text('reserver_name'),
createdAt: timestamp('created_at').defaultNow().notNull() createdAt: timestamp('created_at').defaultNow().notNull()
}); });
@@ -125,6 +126,10 @@ export const reservationsRelations = relations(reservations, ({ one }) => ({
item: one(items, { item: one(items, {
fields: [reservations.itemId], fields: [reservations.itemId],
references: [items.id] 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 }) => ({ export const usersRelations = relations(users, ({ many }) => ({
wishlists: many(wishlists), wishlists: many(wishlists),
savedWishlists: many(savedWishlists) savedWishlists: many(savedWishlists),
reservations: many(reservations)
})); }));
export type User = typeof users.$inferSelect; export type User = typeof users.$inferSelect;

View File

@@ -43,12 +43,13 @@ export const load: PageServerLoad = async ({ params, locals }) => {
isSaved, isSaved,
isClaimed, isClaimed,
savedWishlistId, savedWishlistId,
isAuthenticated: !!session?.user isAuthenticated: !!session?.user,
currentUserId: session?.user?.id || null
}; };
}; };
export const actions: Actions = { export const actions: Actions = {
reserve: async ({ request }) => { reserve: async ({ request, locals }) => {
const formData = await request.formData(); const formData = await request.formData();
const itemId = formData.get('itemId') as string; const itemId = formData.get('itemId') as string;
const reserverName = formData.get('reserverName') 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' }; return { success: false, error: 'Item ID is required' };
} }
const session = await locals.auth();
const existingReservation = await db.query.reservations.findFirst({ const existingReservation = await db.query.reservations.findFirst({
where: eq(reservations.itemId, itemId) where: eq(reservations.itemId, itemId)
}); });
@@ -68,6 +71,7 @@ export const actions: Actions = {
await db.transaction(async (tx) => { await db.transaction(async (tx) => {
await tx.insert(reservations).values({ await tx.insert(reservations).values({
itemId, itemId,
userId: session?.user?.id || null,
reserverName: reserverName?.trim() || null reserverName: reserverName?.trim() || null
}); });
@@ -80,7 +84,7 @@ export const actions: Actions = {
return { success: true }; return { success: true };
}, },
unreserve: async ({ request }) => { unreserve: async ({ request, locals }) => {
const formData = await request.formData(); const formData = await request.formData();
const itemId = formData.get('itemId') as string; const itemId = formData.get('itemId') as string;
@@ -88,6 +92,25 @@ export const actions: Actions = {
return { success: false, error: 'Item ID is required' }; 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 db.transaction(async (tx) => {
await tx.delete(reservations).where(eq(reservations.itemId, itemId)); await tx.delete(reservations).where(eq(reservations.itemId, itemId));

View File

@@ -115,6 +115,8 @@
itemId={item.id} itemId={item.id}
isReserved={item.isReserved} isReserved={item.isReserved}
reserverName={item.reservations?.[0]?.reserverName} reserverName={item.reservations?.[0]?.reserverName}
reservationUserId={item.reservations?.[0]?.userId}
currentUserId={data.currentUserId}
/> />
</WishlistItem> </WishlistItem>
{/each} {/each}