import React from 'react'; import { useNavigate } from 'react-router-dom'; import { ChevronDown, ChevronLeft, Bell, QrCode } from 'lucide-react'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Pressable } from '@tamagui/react-native-web-lite'; import { useTranslation } from 'react-i18next'; import { useEventContext } from '../../context/EventContext'; import { BottomNav, NavKey } from './BottomNav'; import { useMobileNav } from '../hooks/useMobileNav'; import { adminPath } from '../../constants'; import { MobileSheet } from './Sheet'; import { MobileCard, PillBadge } from './Primitives'; import { useAlertsBadge } from '../hooks/useAlertsBadge'; import { formatEventDate, resolveEventDisplayName } from '../../lib/events'; import { TenantEvent, getEvents } from '../../api'; type MobileShellProps = { title?: string; subtitle?: string; children: React.ReactNode; activeTab: NavKey; onBack?: () => void; headerActions?: React.ReactNode; }; export function MobileShell({ title, subtitle, children, activeTab, onBack, headerActions }: MobileShellProps) { const { events, activeEvent, hasMultipleEvents, hasEvents, selectEvent } = useEventContext(); const { go } = useMobileNav(activeEvent?.slug); const navigate = useNavigate(); const { t, i18n } = useTranslation('mobile'); const { count: alertCount } = useAlertsBadge(); const [pickerOpen, setPickerOpen] = React.useState(false); const [fallbackEvents, setFallbackEvents] = React.useState([]); const [loadingEvents, setLoadingEvents] = React.useState(false); const [attemptedFetch, setAttemptedFetch] = React.useState(false); const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE'; const effectiveEvents = events.length ? events : fallbackEvents; const effectiveHasMultiple = hasMultipleEvents || effectiveEvents.length > 1; const effectiveHasEvents = hasEvents || effectiveEvents.length > 0; const effectiveActive = activeEvent ?? (effectiveEvents.length === 1 ? effectiveEvents[0] : null); React.useEffect(() => { if (events.length || loadingEvents || attemptedFetch) { return; } setAttemptedFetch(true); setLoadingEvents(true); getEvents({ force: true }) .then((list) => { setFallbackEvents(list ?? []); if (!activeEvent && list?.length === 1) { selectEvent(list[0]?.slug ?? null); } }) .catch(() => setFallbackEvents([])) .finally(() => setLoadingEvents(false)); }, [events.length, loadingEvents, attemptedFetch, activeEvent, selectEvent]); React.useEffect(() => { if (!pickerOpen) return; if (effectiveEvents.length) return; setLoadingEvents(true); getEvents({ force: true }) .then((list) => setFallbackEvents(list ?? [])) .catch(() => setFallbackEvents([])) .finally(() => setLoadingEvents(false)); }, [pickerOpen, effectiveEvents.length]); const eventTitle = title ?? (effectiveActive ? resolveEventDisplayName(effectiveActive) : t('header.appName', 'Event Admin')); const subtitleText = subtitle ?? (effectiveActive?.event_date ? formatEventDate(effectiveActive.event_date, locale) ?? '' : effectiveHasEvents ? t('header.selectEvent', 'Select an event to continue') : t('header.empty', 'Create your first event to get started')); const showEventSwitcher = effectiveHasMultiple; const showQr = Boolean(effectiveActive?.slug); return ( {onBack ? ( ) : null} setPickerOpen(true)} style={{ alignItems: 'flex-start' }} > {eventTitle} {subtitleText ? ( {subtitleText} ) : null} {showEventSwitcher ? : null} navigate(adminPath('/mobile/alerts'))}> {alertCount > 0 ? ( {alertCount > 9 ? '9+' : alertCount} ) : null} {showQr ? ( navigate(adminPath(`/mobile/events/${effectiveActive?.slug}/qr`))}> {t('header.quickQr', 'Quick QR')} ) : null} {headerActions ?? null} {children} setPickerOpen(false)} title={t('header.eventSwitcher', 'Choose an event')} footer={null} bottomOffsetPx={110} > {effectiveEvents.length === 0 ? ( {t('header.noEventsTitle', 'Create your first event')} {t('header.noEventsBody', 'Start an event to access tasks, uploads, QR posters and more.')} navigate(adminPath('/mobile/events/new'))}> {t('header.createEvent', 'Create event')} ) : ( effectiveEvents.map((event) => ( { const targetSlug = event.slug ?? null; selectEvent(targetSlug); setPickerOpen(false); if (targetSlug) { navigate(adminPath(`/mobile/events/${targetSlug}`)); } }} > {resolveEventDisplayName(event)} {formatEventDate(event.event_date, locale) ?? t('header.noDate', 'Date tbd')} {event.slug === activeEvent?.slug ? t('header.active', 'Active') : (event.status ?? '—')} )) )} {activeEvent ? ( { selectEvent(null); setPickerOpen(false); }} > {t('header.clearSelection', 'Clear selection')} ) : null} ); } export function renderEventLocation(event?: TenantEvent | null): string { if (!event) return 'Location'; const settings = (event.settings ?? {}) as Record; const candidate = (settings.location as string | undefined) ?? (settings.address as string | undefined) ?? (settings.city as string | undefined); if (candidate && candidate.trim()) { return candidate; } return 'Location'; }