import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Pressable } from '@tamagui/react-native-web-lite'; import { RefreshCcw, Users, User } from 'lucide-react'; import toast from 'react-hot-toast'; import { MobileShell, HeaderActionButton } from './components/MobileShell'; import { MobileCard, CTAButton, PillBadge, SkeletonCard } from './components/Primitives'; import { MobileField, MobileInput, MobileSelect, MobileTextArea } from './components/FormControls'; import { useEventContext } from '../context/EventContext'; import { GuestNotificationSummary, SendGuestNotificationPayload, getEvents, listGuestNotifications, sendGuestNotification, TenantEvent, } from '../api'; import { isAuthError } from '../auth/tokens'; import { getApiErrorMessage } from '../lib/apiError'; import { adminPath } from '../constants'; import { formatGuestMessageDate } from './guestMessages'; import { useBackNavigation } from './hooks/useBackNavigation'; import { useAdminTheme } from './theme'; type FormState = { title: string; message: string; audience: 'all' | 'guest'; guest_identifier: string; cta_label: string; cta_url: string; expires_in_minutes: string; priority: string; }; export default function MobileEventGuestNotificationsPage() { const { slug: slugParam } = useParams<{ slug?: string }>(); const navigate = useNavigate(); const { t, i18n } = useTranslation('management'); const { textStrong, text, muted, border, danger } = useAdminTheme(); const { activeEvent, selectEvent } = useEventContext(); const slug = slugParam ?? activeEvent?.slug ?? null; const [history, setHistory] = React.useState([]); const [loading, setLoading] = React.useState(true); const [sending, setSending] = React.useState(false); const [error, setError] = React.useState(null); const [fallbackAttempted, setFallbackAttempted] = React.useState(false); const formRef = React.useRef(null); const back = useBackNavigation(slug ? adminPath(`/mobile/events/${slug}`) : adminPath('/mobile/events')); const [form, setForm] = React.useState({ title: '', message: '', audience: 'all', guest_identifier: '', cta_label: '', cta_url: '', expires_in_minutes: '', priority: '1', }); React.useEffect(() => { if (slugParam && activeEvent?.slug !== slugParam) { selectEvent(slugParam); } }, [slugParam, activeEvent?.slug, selectEvent]); const loadHistory = React.useCallback(async () => { if (!slug) { if (!fallbackAttempted) { setFallbackAttempted(true); try { const events = await getEvents({ force: true }); const first = events[0] as TenantEvent | undefined; if (first?.slug) { selectEvent(first.slug); navigate(adminPath(`/mobile/events/${first.slug}/guest-notifications`), { replace: true }); } } catch { // ignore } } setLoading(false); setError(t('events.errors.missingSlug', 'No event selected.')); return; } setLoading(true); setError(null); try { const notifications = await listGuestNotifications(slug); setHistory(notifications); } catch (err) { if (!isAuthError(err)) { const message = getApiErrorMessage(err, t('guestMessages.errorLoad', 'Messages could not be loaded.')); setError(message); toast.error(message); } } finally { setLoading(false); } }, [slug, t, fallbackAttempted, selectEvent, navigate]); React.useEffect(() => { void loadHistory(); }, [loadHistory]); const canSend = form.title.trim().length > 0 && form.message.trim().length > 0 && (form.audience === 'all' || form.guest_identifier.trim().length > 0); async function handleSend() { if (!slug || sending) return; if (!canSend) { const message = t('guestMessages.form.validation', 'Add a title, message, and target guest when needed.'); setError(message); toast.error(message); return; } const ctaLabel = form.cta_label.trim(); const ctaUrl = form.cta_url.trim(); if ((ctaLabel && !ctaUrl) || (!ctaLabel && ctaUrl)) { const message = t('guestMessages.form.ctaError', 'CTA label and link are required together.'); setError(message); toast.error(message); return; } const payload: SendGuestNotificationPayload = { title: form.title.trim(), message: form.message.trim(), audience: form.audience, }; if (form.audience === 'guest' && form.guest_identifier.trim()) { payload.guest_identifier = form.guest_identifier.trim(); } if (ctaLabel && ctaUrl) { payload.cta = { label: ctaLabel, url: ctaUrl }; } if (form.expires_in_minutes.trim()) { payload.expires_in_minutes = Number(form.expires_in_minutes); } if (form.priority.trim()) { payload.priority = Number(form.priority); } setSending(true); setError(null); try { const created = await sendGuestNotification(slug, payload); setHistory((prev) => [created, ...prev]); setForm((prev) => ({ ...prev, title: '', message: '', guest_identifier: '', cta_label: '', cta_url: '', expires_in_minutes: '', })); toast.success(t('guestMessages.sendSuccess', 'Notification sent to guests.')); } catch (err) { if (!isAuthError(err)) { const message = getApiErrorMessage(err, t('guestMessages.errorSend', 'Message could not be sent.')); setError(message); toast.error(message); } } finally { setSending(false); } } const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE'; const mutedText = muted; return ( loadHistory()} ariaLabel={t('common.refresh', 'Refresh')}> } > {error ? ( {error} ) : null} {t('guestMessages.composeTitle', 'Send a message')} setForm((prev) => ({ ...prev, title: e.target.value }))} placeholder={t('guestMessages.form.titlePlaceholder', 'Gallery reminder, upload nudge, ...')} /> setForm((prev) => ({ ...prev, message: e.target.value }))} placeholder={t('guestMessages.form.messagePlaceholder', 'Write a short note for your guests.')} /> setForm((prev) => ({ ...prev, audience: e.target.value as FormState['audience'] }))} > {form.audience === 'guest' ? ( setForm((prev) => ({ ...prev, guest_identifier: e.target.value }))} placeholder={t('guestMessages.form.guestPlaceholder', 'e.g., Alex or device token')} /> ) : null} setForm((prev) => ({ ...prev, cta_label: e.target.value }))} placeholder={t('guestMessages.form.ctaLabel', 'Button label')} /> setForm((prev) => ({ ...prev, cta_url: e.target.value }))} placeholder={t('guestMessages.form.ctaUrl', 'https://your-link.com')} /> {t('guestMessages.form.ctaHint', 'Both fields are required to add a button.')} setForm((prev) => ({ ...prev, expires_in_minutes: e.target.value }))} placeholder={t('guestMessages.form.expiresPlaceholder', '60')} /> setForm((prev) => ({ ...prev, priority: e.target.value }))} > {[0, 1, 2, 3, 4, 5].map((value) => ( ))} handleSend()} tone="primary" fullWidth /> {!canSend ? ( {t('guestMessages.form.validation', 'Add a title and message. Target guests need an identifier.')} ) : null} {t('guestMessages.historyTitle', 'Recent messages')} loadHistory()}> {loading ? ( {Array.from({ length: 3 }).map((_, idx) => ( ))} ) : history.length === 0 ? ( {t('guestMessages.emptyTitle', 'Send your first guest message')} {t('guestMessages.emptyBody', 'Share a quick reminder or highlight to keep guests engaged.')} formRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })} /> ) : ( {history.map((item) => ( {item.title || t('guestMessages.history.untitled', 'Untitled')} {t(`guestMessages.status.${item.status}`, item.status)} {item.audience_scope === 'guest' ? t('guestMessages.audience.guest', 'Specific guest') : t('guestMessages.audience.all', 'All guests')} {item.body ?? t('guestMessages.history.noBody', 'No body provided.')} {t(`guestMessages.type.${item.type}`, item.type)} {item.target_identifier ? ( {item.target_identifier} ) : ( {t('guestMessages.audience.all', 'All guests')} )} {formatGuestMessageDate(item.created_at, locale)} ))} )} ); }