import { createFileRoute } from '@tanstack/react-router' import { useEffect, useRef, useState } from 'react' import { m } from '@/i18n/messages' import { useLocale, getLocale } from '@/i18n/config' import { asI18n } from '@pikku/react' import { useQueryClient } from '@tanstack/react-query' import { Alert, Box, Button, Checkbox, Container, Divider, Group, Loader, NumberInput, Paper, Radio, SimpleGrid, Stack, Text, Textarea, TextInput, Title, } from '@pikku/mantine/core' import { usePikkuMutation, usePikkuQuery } from '@project/functions-sdk/pikku/api.gen' import { PageTitle } from '../components/AppLayout' import { fmtDate } from '../lib/dates' type FormState = { name: string billingAddress: string contactPhone: string website: string paymentMethod: 'cash' | 'transfer' expectedPersons: string arrivalTime: string departureTime: string dailyPlan: string onlineAd: boolean notes: string acceptTerms: boolean acceptPrivacy: boolean } /** * Public landing for the passwordless booking-form magic link. Consumes the * token (which issues a client session cookie), then renders the binding form. * The token IS the auth — this route is intentionally not behind RequireAuth. */ function BookingFormLanding() { const { token } = Route.useParams() useLocale() const qc = useQueryClient() const [bookingId, setBookingId] = useState(null) // Outcome is tracked in component state (not the mutation observer) so it // survives StrictMode's mount→unmount→mount and the consumedRef guard below. const [phase, setPhase] = useState<'pending' | 'ready' | 'error'>('pending') const consumedRef = useRef(false) const consume = usePikkuMutation('consumeBookingFormLink', { onSuccess: async (data) => { await qc.invalidateQueries({ queryKey: ['me'] }) setBookingId(data.bookingId) setPhase('ready') }, onError: () => setPhase('error'), }) useEffect(() => { if (consumedRef.current) return consumedRef.current = true consume.mutate({ token }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [token]) if (phase === 'error') { return ( {m.common__pages__booking_form__link_invalid__body()} ) } if (!bookingId) { return ( {m.common__states__loading()} ) } return } function BookingFormInner({ bookingId }: { bookingId: string }) { useLocale() const [form, setForm] = useState(null) const [success, setSuccess] = useState(false) const { data, isLoading, error } = usePikkuQuery('getBookingFormData', { bookingId }) const submit = usePikkuMutation('submitBookingForm', { onSuccess: () => setSuccess(true), }) // Seed editable state once the prefill data arrives. useEffect(() => { if (data && !form) { setForm({ name: data.client.name ?? '', billingAddress: data.client.billingAddress ?? '', contactPhone: data.client.contactPhone ?? '', website: data.client.website ?? '', paymentMethod: (data.paymentMethod as 'cash' | 'transfer') ?? 'cash', expectedPersons: data.expectedPersons != null ? String(data.expectedPersons) : '', arrivalTime: data.arrivalTime ?? '', departureTime: data.departureTime ?? '', dailyPlan: data.dailyPlan ?? '', onlineAd: data.onlineAd, notes: data.notes ?? '', acceptTerms: false, acceptPrivacy: false, }) } }, [data, form]) if (isLoading || !form) { if (error) { return ( {m.common__states__error()} ) } return ( ) } const set = (key: K, value: FormState[K]) => setForm((f) => (f ? { ...f, [key]: value } : f)) if (success) { return ( {m.common__pages__booking_form__success__body()} ) } const canSubmit = form.name.trim() && form.billingAddress.trim() && form.acceptTerms && form.acceptPrivacy const submitForm = () => { submit.mutate({ bookingId, name: form.name.trim(), billingAddress: form.billingAddress.trim(), contactPhone: form.contactPhone || undefined, website: form.website || undefined, paymentMethod: form.paymentMethod, expectedPersons: form.expectedPersons ? Number(form.expectedPersons) : undefined, arrivalTime: form.arrivalTime || undefined, departureTime: form.departureTime || undefined, dailyPlan: form.dailyPlan || undefined, onlineAd: form.onlineAd, notes: form.notes || undefined, acceptTerms: form.acceptTerms, acceptPrivacy: form.acceptPrivacy, }) } return ( {m.common__pages__booking_form__heading()} {asI18n( `${data!.eventName}${data!.startDate ? ` · ${fmtDate(new Date(data!.startDate), getLocale())}` : ''}${data!.endDate ? ` → ${fmtDate(new Date(data!.endDate), getLocale())}` : ''}`, )}
set('name', e.currentTarget.value)} />