Add guest support for events and RSVP system
All checks were successful
Build And Push Image / docker (push) Successful in 3m52s
All checks were successful
Build And Push Image / docker (push) Successful in 3m52s
- Add guest settings to event creation with configurable max guests per person - Implement guest selection in RSVP form when guests are permitted - Update API endpoints to handle guest count in RSVP requests - Extend event and RSVP types to support guest-related fields
This commit is contained in:
@@ -143,8 +143,33 @@
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Payment Settings -->
|
||||
<!-- Guest Settings -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-switch
|
||||
v-model="allowGuests"
|
||||
label="Allow Guests"
|
||||
color="primary"
|
||||
inset
|
||||
hint="Members can bring additional guests"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Max Guests Per Person (shown when guests allowed) -->
|
||||
<v-col v-if="allowGuests" cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="maxGuestsPerPerson"
|
||||
label="Max Guests Per Person"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
:rules="allowGuests ? [v => v && parseInt(v) > 0 || 'Must allow at least 1 guest'] : []"
|
||||
hint="Maximum additional guests each member can bring"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Payment Settings -->
|
||||
<v-col cols="12" :md="allowGuests ? 6 : 6">
|
||||
<v-switch
|
||||
v-model="isPaidEvent"
|
||||
label="Paid Event"
|
||||
@@ -313,9 +338,15 @@ const eventData = reactive<EventCreateRequest>({
|
||||
cost_non_members: '',
|
||||
member_pricing_enabled: 'true',
|
||||
visibility: 'public',
|
||||
status: 'active'
|
||||
status: 'active',
|
||||
guests_permitted: 'false',
|
||||
max_guests_permitted: '0'
|
||||
});
|
||||
|
||||
// Guest settings
|
||||
const allowGuests = ref(false);
|
||||
const maxGuestsPerPerson = ref(1);
|
||||
|
||||
// Computed
|
||||
const show = computed({
|
||||
get: () => props.modelValue,
|
||||
@@ -364,6 +395,35 @@ watch(memberPricingEnabled, (newValue) => {
|
||||
eventData.member_pricing_enabled = newValue ? 'true' : 'false';
|
||||
});
|
||||
|
||||
watch(allowGuests, (newValue) => {
|
||||
eventData.guests_permitted = newValue ? 'true' : 'false';
|
||||
if (!newValue) {
|
||||
eventData.max_guests_permitted = '0';
|
||||
maxGuestsPerPerson.value = 1;
|
||||
}
|
||||
});
|
||||
|
||||
watch(maxGuestsPerPerson, (newValue) => {
|
||||
if (allowGuests.value) {
|
||||
eventData.max_guests_permitted = newValue.toString();
|
||||
}
|
||||
});
|
||||
|
||||
// Fix date picker binding - ensure proper syncing
|
||||
watch(startDateModel, (newDate) => {
|
||||
if (newDate instanceof Date) {
|
||||
eventData.start_datetime = newDate.toISOString();
|
||||
console.log('[CreateEventDialog] Start date updated:', eventData.start_datetime);
|
||||
}
|
||||
});
|
||||
|
||||
watch(endDateModel, (newDate) => {
|
||||
if (newDate instanceof Date) {
|
||||
eventData.end_datetime = newDate.toISOString();
|
||||
console.log('[CreateEventDialog] End date updated:', eventData.end_datetime);
|
||||
}
|
||||
});
|
||||
|
||||
watch(isRecurring, (newValue) => {
|
||||
eventData.is_recurring = newValue ? 'true' : 'false';
|
||||
if (newValue) {
|
||||
@@ -440,6 +500,8 @@ const resetForm = () => {
|
||||
eventData.cost_members = '';
|
||||
eventData.cost_non_members = '';
|
||||
eventData.member_pricing_enabled = 'true';
|
||||
eventData.guests_permitted = 'false';
|
||||
eventData.max_guests_permitted = '0';
|
||||
eventData.visibility = 'public';
|
||||
eventData.status = 'active';
|
||||
eventData.is_recurring = 'false';
|
||||
@@ -449,10 +511,13 @@ const resetForm = () => {
|
||||
startDateModel.value = null;
|
||||
endDateModel.value = null;
|
||||
|
||||
// Reset UI state
|
||||
isPaidEvent.value = false;
|
||||
memberPricingEnabled.value = true;
|
||||
isRecurring.value = false;
|
||||
recurrenceFrequency.value = 'weekly';
|
||||
allowGuests.value = false;
|
||||
maxGuestsPerPerson.value = 1;
|
||||
|
||||
form.value?.resetValidation();
|
||||
};
|
||||
|
||||
@@ -192,6 +192,28 @@
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-0">
|
||||
<v-form v-model="rsvpValid">
|
||||
<!-- Guest Selection (if event allows guests) -->
|
||||
<div v-if="allowsGuests" class="mb-4">
|
||||
<v-card variant="tonal" class="mb-3">
|
||||
<v-card-text class="py-3">
|
||||
<div class="d-flex align-center mb-2">
|
||||
<v-icon class="me-2">mdi-account-group</v-icon>
|
||||
<span class="font-weight-medium">Bring Guests</span>
|
||||
</div>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||
This event allows up to {{ maxGuestsAllowed }} additional guests per person.
|
||||
</p>
|
||||
<v-select
|
||||
v-model="selectedGuests"
|
||||
:items="guestOptions"
|
||||
label="Number of Additional Guests"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<v-textarea
|
||||
v-model="rsvpNotes"
|
||||
label="Notes (optional)"
|
||||
@@ -200,31 +222,18 @@
|
||||
class="mb-3"
|
||||
/>
|
||||
|
||||
<div class="d-flex justify-space-between gap-4">
|
||||
<v-btn
|
||||
@click="submitRSVP('confirmed')"
|
||||
color="success"
|
||||
:loading="rsvpLoading"
|
||||
:disabled="isEventFull && !isWaitlistAvailable"
|
||||
size="large"
|
||||
class="flex-grow-1"
|
||||
>
|
||||
<v-icon start>mdi-check</v-icon>
|
||||
{{ isEventFull ? 'Join Waitlist' : 'Confirm Attendance' }}
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
@click="submitRSVP('declined')"
|
||||
color="error"
|
||||
variant="outlined"
|
||||
:loading="rsvpLoading"
|
||||
size="large"
|
||||
class="flex-grow-1"
|
||||
>
|
||||
<v-icon start>mdi-close</v-icon>
|
||||
Decline
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
@click="submitRSVP('confirmed')"
|
||||
color="primary"
|
||||
:loading="rsvpLoading"
|
||||
:disabled="isEventFull && !isWaitlistAvailable"
|
||||
size="large"
|
||||
block
|
||||
class="mb-2"
|
||||
>
|
||||
<v-icon start>mdi-check</v-icon>
|
||||
{{ isEventFull ? 'Join Waitlist' : 'RSVP' }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -286,6 +295,7 @@ const { rsvpToEvent } = useEvents();
|
||||
const rsvpValid = ref(false);
|
||||
const rsvpLoading = ref(false);
|
||||
const rsvpNotes = ref('');
|
||||
const selectedGuests = ref(0);
|
||||
|
||||
// Computed properties
|
||||
const show = computed({
|
||||
@@ -440,6 +450,28 @@ const paymentInfo = computed(() => ({
|
||||
recipient: 'MonacoUSA Association' // This should come from config
|
||||
}));
|
||||
|
||||
// Guest functionality
|
||||
const allowsGuests = computed(() => {
|
||||
return props.event?.guests_permitted === 'true';
|
||||
});
|
||||
|
||||
const maxGuestsAllowed = computed(() => {
|
||||
if (!allowsGuests.value) return 0;
|
||||
return parseInt(props.event?.max_guests_permitted || '0');
|
||||
});
|
||||
|
||||
const guestOptions = computed(() => {
|
||||
const max = maxGuestsAllowed.value;
|
||||
const options = [];
|
||||
for (let i = 0; i <= max; i++) {
|
||||
options.push({
|
||||
title: i === 0 ? 'No additional guests' : `${i} guest${i > 1 ? 's' : ''}`,
|
||||
value: i
|
||||
});
|
||||
}
|
||||
return options;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const close = () => {
|
||||
show.value = false;
|
||||
@@ -452,21 +484,27 @@ const submitRSVP = async (status: 'confirmed' | 'declined') => {
|
||||
rsvpLoading.value = true;
|
||||
|
||||
try {
|
||||
// Extract database ID - props.event is a raw Event object, not FullCalendar object
|
||||
// Database ID is stored in 'Id' field (capital I) from NocoDB
|
||||
const databaseId = (props.event as any).Id || (props.event as any).extendedProps?.database_id || props.event.id;
|
||||
console.log('[EventDetailsDialog] Using database ID for RSVP:', databaseId);
|
||||
console.log('[EventDetailsDialog] Event object keys:', Object.keys(props.event));
|
||||
console.log('[EventDetailsDialog] Event Id field:', (props.event as any).Id);
|
||||
// Use event_id field for consistent RSVP relationships
|
||||
// This ensures RSVPs are linked properly to events using the business identifier
|
||||
const eventId = props.event.event_id ||
|
||||
(props.event as any).extendedProps?.event_id ||
|
||||
(props.event as any).Id || // Fallback to database ID if event_id not available
|
||||
props.event.id;
|
||||
|
||||
if (!databaseId) {
|
||||
throw new Error('Unable to determine database ID for event');
|
||||
console.log('[EventDetailsDialog] Using event identifier for RSVP:', eventId);
|
||||
console.log('[EventDetailsDialog] Event object keys:', Object.keys(props.event));
|
||||
console.log('[EventDetailsDialog] Event event_id field:', props.event.event_id);
|
||||
console.log('[EventDetailsDialog] Event database Id field:', (props.event as any).Id);
|
||||
|
||||
if (!eventId) {
|
||||
throw new Error('Unable to determine event identifier');
|
||||
}
|
||||
|
||||
await rsvpToEvent(databaseId, {
|
||||
await rsvpToEvent(eventId, {
|
||||
member_id: '', // This will be filled by the composable
|
||||
rsvp_status: status,
|
||||
rsvp_notes: rsvpNotes.value
|
||||
rsvp_notes: rsvpNotes.value,
|
||||
extra_guests: selectedGuests.value.toString()
|
||||
});
|
||||
|
||||
emit('rsvp-updated', props.event);
|
||||
|
||||
Reference in New Issue
Block a user