add: user lock on reservations
This commit is contained in:
@@ -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>
|
||||
<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 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user