2
0
forked from jmug/cactoide

feat: add event guest management

This commit is contained in:
Levente Orban
2025-09-02 11:16:44 +02:00
parent 1d523c4b88
commit b5df1479dd
2 changed files with 96 additions and 19 deletions

View File

@@ -69,6 +69,7 @@ export const actions: Actions = {
const formData = await request.formData();
const name = formData.get('newAttendeeName') as string;
const numberOfGuests = parseInt(formData.get('numberOfGuests') as string) || 0;
const userId = cookies.get('cactoideUserId');
if (!name?.trim() || !userId) {
@@ -82,27 +83,48 @@ export const actions: Actions = {
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)
if (eventData.type === 'limited' && eventData.attendeeLimit) {
const currentRSVPs = await database.select().from(rsvps).where(eq(rsvps.eventId, eventId));
if (currentRSVPs.length >= eventData.attendeeLimit) {
return fail(400, { error: 'Event is full' });
if (totalAttendees > eventData.attendeeLimit) {
return fail(400, {
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
const existingRSVPs = await database.select().from(rsvps).where(eq(rsvps.eventId, eventId));
if (existingRSVPs.some((rsvp) => rsvp.name.toLowerCase() === name.toLowerCase())) {
if (currentRSVPs.some((rsvp) => rsvp.name.toLowerCase() === name.toLowerCase())) {
return fail(400, { error: 'Name already exists for this event' });
}
// Add RSVP to database
await database.insert(rsvps).values({
// Prepare RSVPs to insert
const rsvpsToInsert = [
{
eventId: eventId,
name: name.trim(),
userId: userId,
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' };
} catch (err) {

View File

@@ -14,6 +14,8 @@
let isAddingRSVP = false;
let error = '';
let success = '';
let addGuests = false;
let numberOfGuests = 1;
// Use server-side data
$: event = data.event;
@@ -22,7 +24,7 @@
// Handle form errors from server
$: if (form?.error) {
error = form.error;
error = String(form.error);
success = '';
}
@@ -31,6 +33,8 @@
success = 'RSVP added successfully!';
error = '';
newAttendeeName = '';
addGuests = false;
numberOfGuests = 1;
}
const eventId = $page.params.id;
@@ -179,7 +183,7 @@
return async ({ result, update }) => {
isAddingRSVP = false;
if (result.type === 'failure') {
error = result.data?.error || 'Failed to add RSVP';
error = String(result.data?.error || 'Failed to add RSVP');
}
update();
};
@@ -203,9 +207,48 @@
/>
</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
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"
>
{#if isAddingRSVP}
@@ -215,6 +258,8 @@
></div>
Adding...
</div>
{:else if addGuests && numberOfGuests > 0}
Join Event + {numberOfGuests} Guest{numberOfGuests > 1 ? 's' : ''}
{:else}
Join Event
{/if}
@@ -243,12 +288,22 @@
>
<div class="flex items-center space-x-3">
<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()}
</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">
{(() => {
const date = new Date(attendee.created_at);
@@ -271,7 +326,7 @@
clearMessages();
return async ({ result, update }) => {
if (result.type === 'failure') {
error = result.data?.error || 'Failed to remove RSVP';
error = String(result.data?.error || 'Failed to remove RSVP');
}
update();
};
@@ -300,7 +355,7 @@
</div>
{/if}
</div>
succcess: {success}
<!-- Action Buttons -->
<div class="max-w-2xl">
<button