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