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;
isReserved: boolean;
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 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>
{#if isReserved}
@@ -23,12 +36,52 @@
by {reserverName}
{/if}
</div>
{#if canCancel()}
{#if showCancelConfirmation}
<div class="flex flex-col gap-2 items-end">
<p class="text-sm text-muted-foreground">
Cancel this reservation?
</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>
{:else if showReserveForm}
<form

View File

@@ -117,6 +117,7 @@ export const reservations = pgTable('reservations', {
itemId: text('item_id')
.notNull()
.references(() => items.id, { onDelete: 'cascade' }),
userId: text('user_id').references(() => users.id, { onDelete: 'set null' }),
reserverName: text('reserver_name'),
createdAt: timestamp('created_at').defaultNow().notNull()
});
@@ -125,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]
})
}));
@@ -154,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;

View File

@@ -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));

View File

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