'use client'; import { useRouter } from 'next/navigation'; import { useSearchParams } from 'next/navigation'; import React from 'react'; import { toast } from 'sonner'; import zod from 'zod/v4'; import Calendar from '@/components/calendar'; import LabeledInput from '@/components/custom-ui/labeled-input'; import Logo from '@/components/misc/logo'; import { ToastInner } from '@/components/misc/toast-inner'; import { UserSearchInput } from '@/components/misc/user-search'; import TimePicker from '@/components/time-picker'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { PublicUserSchema } from '@/app/api/user/validation'; import { useGetApiEventEventID, usePatchApiEventEventID, usePostApiEvent, } from '@/generated/api/event/event'; import { useGetApiUserMe } from '@/generated/api/user/user'; import ParticipantListEntry from '@/components/custom-ui/participant-list-entry'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; type User = zod.output; interface EventFormProps { type: 'create' | 'edit'; eventId?: string; } const EventForm: React.FC = (props) => { // Runtime validation if (props.type === 'edit' && !props.eventId) { throw new Error( 'Error [event-form]: eventId must be provided when type is "edit".', ); } const searchParams = useSearchParams(); const startFromUrl = searchParams.get('start'); const endFromUrl = searchParams.get('end'); const { mutate: createEvent, status, isSuccess, error } = usePostApiEvent(); const { data, isLoading, error: fetchError } = useGetApiUserMe(); const { data: eventData } = useGetApiEventEventID(props.eventId!, { query: { enabled: props.type === 'edit' }, }); const patchEvent = usePatchApiEventEventID(); const router = useRouter(); // Extract event fields for form defaults const event = eventData?.data?.event; // State for date and time fields const [startDate, setStartDate] = React.useState(undefined); const [startTime, setStartTime] = React.useState(''); const [endDate, setEndDate] = React.useState(undefined); const [endTime, setEndTime] = React.useState(''); // State for participants const [selectedParticipants, setSelectedParticipants] = React.useState< User[] >([]); // State for form fields const [title, setTitle] = React.useState(''); const [location, setLocation] = React.useState(''); const [description, setDescription] = React.useState(''); const [calendarOpen, setCalendarOpen] = React.useState(false); // Update state when event data loads React.useEffect(() => { if (props.type === 'edit' && event) { setTitle(event.title || ''); // Parse start_time and end_time if (event.start_time) { const start = new Date(event.start_time); setStartDate(start); setStartTime(start.toTimeString().slice(0, 5)); // "HH:mm" } if (event.end_time) { const end = new Date(event.end_time); setEndDate(end); setEndTime(end.toTimeString().slice(0, 5)); // "HH:mm" } setLocation(event.location || ''); setDescription(event.description || ''); setSelectedParticipants(event.participants?.map((u) => u.user) || []); } else if (props.type === 'create' && startFromUrl && endFromUrl) { // If creating a new event with URL params, set title and dates setTitle(''); const start = new Date(startFromUrl); setStartDate(start); setStartTime(start.toTimeString().slice(0, 5)); // "HH:mm" const end = new Date(endFromUrl); setEndDate(end); setEndTime(end.toTimeString().slice(0, 5)); // "HH:mm" } }, [event, props.type, startFromUrl, endFromUrl]); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); const formData = new FormData(e.currentTarget); function combine(date?: Date, time?: string) { if (!date || !time) return undefined; const [hours, minutes] = time.split(':'); const d = new Date(date); d.setHours(Number(hours), Number(minutes), 0, 0); return d; } const start = combine(startDate, startTime); const end = combine(endDate, endTime); //validate form data if (!formData.get('eventName')) { alert('Event name is required.'); return; } if (!start || !end) { alert('Please provide both start and end date/time.'); return; } else if (start >= end) { alert('End time must be after start time.'); return; } const data = { title: formData.get('eventName') as string, description: formData.get('eventDescription') as string, start_time: start.toISOString(), end_time: end.toISOString(), location: formData.get('eventLocation') as string, created_at: formData.get('createdAt') as string, updated_at: formData.get('updatedAt') as string, organiser: formData.get('organiser') as string, participants: selectedParticipants.map((u) => u.id), }; if (props.type === 'edit' && props.eventId) { await patchEvent.mutateAsync({ eventID: props.eventId, data: { title: data.title, description: data.description, start_time: data.start_time, end_time: data.end_time, location: data.location, participants: data.participants, }, }); console.log('Updating event'); } else { console.log('Creating event'); createEvent({ data }); } toast.custom((t) => ( router.push(`/events/${event?.id}`)} variant='success' buttonText='show' /> )); router.back(); } // Calculate values for organiser, created, and updated const organiserValue = isLoading ? 'Loading...' : data?.data.user?.name || 'Unknown User'; // Use DB values for created_at/updated_at in edit mode const createdAtValue = props.type === 'edit' && event?.created_at ? event.created_at : new Date().toISOString(); const updatedAtValue = props.type === 'edit' && event?.updated_at ? event.updated_at : new Date().toISOString(); // Format date for display const createdAtDisplay = new Date(createdAtValue).toLocaleDateString(); const updatedAtDisplay = new Date(updatedAtValue).toLocaleDateString(); if (props.type === 'edit' && isLoading) return
Loading...
; if (props.type === 'edit' && fetchError) return
Error loading event.
; return ( <>
setTitle(e.target.value)} />
setLocation(e.target.value)} />

{updatedAtDisplay}

setDescription(e.target.value)} >
{ setSelectedParticipants((current) => current.find((u) => u.id === user.id) ? current : [...current, user], ); }} removeUserAction={(user) => { setSelectedParticipants((current) => current.filter((u) => u.id !== user.id), ); }} />
{selectedParticipants.map((user) => ( ))}
{isSuccess &&

Event created!

} {error &&

Error: {error.message}

}
Calendar Calendar for selected participants u.id)} additionalEvents={[ { id: 'temp-event', title: title || 'New Event', start: startDate ? new Date(startDate) : new Date(), end: endDate ? new Date(endDate) : new Date(), type: 'event', userId: 'create-event', colorOverride: '#ff9800', }, ]} height='600px' />
); }; export default EventForm;