forked from jmug/cactoide
feat: add event guest management
This commit is contained in:
@@ -69,6 +69,7 @@ export const actions: Actions = {
|
|||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
|
|
||||||
const name = formData.get('newAttendeeName') as string;
|
const name = formData.get('newAttendeeName') as string;
|
||||||
|
const numberOfGuests = parseInt(formData.get('numberOfGuests') as string) || 0;
|
||||||
const userId = cookies.get('cactoideUserId');
|
const userId = cookies.get('cactoideUserId');
|
||||||
|
|
||||||
if (!name?.trim() || !userId) {
|
if (!name?.trim() || !userId) {
|
||||||
@@ -82,27 +83,48 @@ export const actions: Actions = {
|
|||||||
return fail(404, { error: 'Event not found' });
|
return fail(404, { error: 'Event not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get current RSVPs
|
||||||
|
const currentRSVPs = await database.select().from(rsvps).where(eq(rsvps.eventId, eventId));
|
||||||
|
|
||||||
|
// Calculate total attendees (including guests)
|
||||||
|
const totalAttendees = currentRSVPs.length + numberOfGuests;
|
||||||
|
|
||||||
// Check if event is full (for limited type events)
|
// Check if event is full (for limited type events)
|
||||||
if (eventData.type === 'limited' && eventData.attendeeLimit) {
|
if (eventData.type === 'limited' && eventData.attendeeLimit) {
|
||||||
const currentRSVPs = await database.select().from(rsvps).where(eq(rsvps.eventId, eventId));
|
if (totalAttendees > eventData.attendeeLimit) {
|
||||||
if (currentRSVPs.length >= eventData.attendeeLimit) {
|
return fail(400, {
|
||||||
return fail(400, { error: 'Event is full' });
|
error: `Event capacity exceeded. You're trying to add ${numberOfGuests + 1} attendees (including yourself), but only ${eventData.attendeeLimit - currentRSVPs.length} spots remain.`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if name is already in the list
|
// Check if name is already in the list
|
||||||
const existingRSVPs = await database.select().from(rsvps).where(eq(rsvps.eventId, eventId));
|
if (currentRSVPs.some((rsvp) => rsvp.name.toLowerCase() === name.toLowerCase())) {
|
||||||
if (existingRSVPs.some((rsvp) => rsvp.name.toLowerCase() === name.toLowerCase())) {
|
|
||||||
return fail(400, { error: 'Name already exists for this event' });
|
return fail(400, { error: 'Name already exists for this event' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add RSVP to database
|
// Prepare RSVPs to insert
|
||||||
await database.insert(rsvps).values({
|
const rsvpsToInsert = [
|
||||||
|
{
|
||||||
eventId: eventId,
|
eventId: eventId,
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
userId: userId,
|
userId: userId,
|
||||||
createdAt: new Date()
|
createdAt: new Date()
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add guest entries
|
||||||
|
for (let i = 1; i <= numberOfGuests; i++) {
|
||||||
|
rsvpsToInsert.push({
|
||||||
|
eventId: eventId,
|
||||||
|
name: `${name.trim()}'s Guest #${i}`,
|
||||||
|
userId: userId,
|
||||||
|
createdAt: new Date()
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert all RSVPs
|
||||||
|
await database.insert(rsvps).values(rsvpsToInsert);
|
||||||
|
|
||||||
return { success: true, type: 'add' };
|
return { success: true, type: 'add' };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
let isAddingRSVP = false;
|
let isAddingRSVP = false;
|
||||||
let error = '';
|
let error = '';
|
||||||
let success = '';
|
let success = '';
|
||||||
|
let addGuests = false;
|
||||||
|
let numberOfGuests = 1;
|
||||||
|
|
||||||
// Use server-side data
|
// Use server-side data
|
||||||
$: event = data.event;
|
$: event = data.event;
|
||||||
@@ -22,7 +24,7 @@
|
|||||||
|
|
||||||
// Handle form errors from server
|
// Handle form errors from server
|
||||||
$: if (form?.error) {
|
$: if (form?.error) {
|
||||||
error = form.error;
|
error = String(form.error);
|
||||||
success = '';
|
success = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +33,8 @@
|
|||||||
success = 'RSVP added successfully!';
|
success = 'RSVP added successfully!';
|
||||||
error = '';
|
error = '';
|
||||||
newAttendeeName = '';
|
newAttendeeName = '';
|
||||||
|
addGuests = false;
|
||||||
|
numberOfGuests = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventId = $page.params.id;
|
const eventId = $page.params.id;
|
||||||
@@ -179,7 +183,7 @@
|
|||||||
return async ({ result, update }) => {
|
return async ({ result, update }) => {
|
||||||
isAddingRSVP = false;
|
isAddingRSVP = false;
|
||||||
if (result.type === 'failure') {
|
if (result.type === 'failure') {
|
||||||
error = result.data?.error || 'Failed to add RSVP';
|
error = String(result.data?.error || 'Failed to add RSVP');
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
};
|
};
|
||||||
@@ -203,9 +207,48 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Guests Toggle -->
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<input
|
||||||
|
id="addGuests"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={addGuests}
|
||||||
|
class="h-4 w-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500"
|
||||||
|
/>
|
||||||
|
<label for="addGuests" class="text-sm font-medium text-white">
|
||||||
|
Add guest users
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Number of Guests Input -->
|
||||||
|
{#if addGuests}
|
||||||
|
<div>
|
||||||
|
<label for="numberOfGuests" class="mb-2 block text-sm font-semibold">
|
||||||
|
Number of Guests <span class="text-red-400">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="numberOfGuests"
|
||||||
|
name="numberOfGuests"
|
||||||
|
type="number"
|
||||||
|
bind:value={numberOfGuests}
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
class="border-dark-300 w-full rounded-sm border-2 px-4 py-3 text-slate-900 shadow-sm"
|
||||||
|
placeholder="Enter number of guests"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="mt-1 text-xs text-slate-400">
|
||||||
|
Guests will be added as "{newAttendeeName || 'Your Name'}'s Guest #1", "{newAttendeeName ||
|
||||||
|
'Your Name'}'s Guest #2", etc.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isAddingRSVP || !newAttendeeName.trim()}
|
disabled={isAddingRSVP ||
|
||||||
|
!newAttendeeName.trim() ||
|
||||||
|
(addGuests && numberOfGuests < 1)}
|
||||||
class=" hover:bg-violet-400/70' w-full rounded-sm border-2 border-violet-500 bg-violet-400/20 px-4 py-3 py-4 font-bold font-medium font-semibold text-white shadow-lg transition-all duration-200 hover:scale-105"
|
class=" hover:bg-violet-400/70' w-full rounded-sm border-2 border-violet-500 bg-violet-400/20 px-4 py-3 py-4 font-bold font-medium font-semibold text-white shadow-lg transition-all duration-200 hover:scale-105"
|
||||||
>
|
>
|
||||||
{#if isAddingRSVP}
|
{#if isAddingRSVP}
|
||||||
@@ -215,6 +258,8 @@
|
|||||||
></div>
|
></div>
|
||||||
Adding...
|
Adding...
|
||||||
</div>
|
</div>
|
||||||
|
{:else if addGuests && numberOfGuests > 0}
|
||||||
|
Join Event + {numberOfGuests} Guest{numberOfGuests > 1 ? 's' : ''}
|
||||||
{:else}
|
{:else}
|
||||||
Join Event
|
Join Event
|
||||||
{/if}
|
{/if}
|
||||||
@@ -243,12 +288,22 @@
|
|||||||
>
|
>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<div
|
<div
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold"
|
class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold {attendee.name.includes(
|
||||||
|
"'s Guest"
|
||||||
|
)
|
||||||
|
? 'text-white-400 bg-violet-500/40'
|
||||||
|
: 'bg-violet-500/20 text-violet-400'}"
|
||||||
>
|
>
|
||||||
{attendee.name.charAt(0).toUpperCase()}
|
{attendee.name.charAt(0).toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-white">{attendee.name}</p>
|
<p
|
||||||
|
class="font-medium text-white {attendee.name.includes("'s Guest")
|
||||||
|
? 'text-amber-300'
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
|
{attendee.name}
|
||||||
|
</p>
|
||||||
<p class="text-xs text-violet-400">
|
<p class="text-xs text-violet-400">
|
||||||
{(() => {
|
{(() => {
|
||||||
const date = new Date(attendee.created_at);
|
const date = new Date(attendee.created_at);
|
||||||
@@ -271,7 +326,7 @@
|
|||||||
clearMessages();
|
clearMessages();
|
||||||
return async ({ result, update }) => {
|
return async ({ result, update }) => {
|
||||||
if (result.type === 'failure') {
|
if (result.type === 'failure') {
|
||||||
error = result.data?.error || 'Failed to remove RSVP';
|
error = String(result.data?.error || 'Failed to remove RSVP');
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
};
|
};
|
||||||
@@ -300,7 +355,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
succcess: {success}
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user