From e9afbeb028c1d483f455c231dd0636798688f42f Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Mon, 12 Jan 2026 10:31:05 +0100 Subject: [PATCH] Unify admin home with event overview --- resources/js/admin/mobile/DashboardPage.tsx | 399 +++++++++++------- resources/js/admin/mobile/EventDetailPage.tsx | 343 --------------- resources/js/admin/mobile/prefetch.ts | 1 - resources/js/admin/router.tsx | 3 +- 4 files changed, 254 insertions(+), 492 deletions(-) delete mode 100644 resources/js/admin/mobile/EventDetailPage.tsx diff --git a/resources/js/admin/mobile/DashboardPage.tsx b/resources/js/admin/mobile/DashboardPage.tsx index 24ffd19..7771e9f 100644 --- a/resources/js/admin/mobile/DashboardPage.tsx +++ b/resources/js/admin/mobile/DashboardPage.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; -import { Bell, CheckCircle2, Download, Image as ImageIcon, ListTodo, MessageCircle, QrCode, Settings, ShieldCheck, Smartphone, Users, Sparkles, TrendingUp } from 'lucide-react'; +import { Bell, CalendarDays, Camera, CheckCircle2, ChevronDown, Download, Image as ImageIcon, Layout, ListTodo, MapPin, Megaphone, MessageCircle, Pencil, QrCode, Settings, ShieldCheck, Smartphone, Sparkles, TrendingUp, Tv, Users } 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 { MobileShell, renderEventLocation } from './components/MobileShell'; +import { MobileShell } from './components/MobileShell'; import { MobileCard, CTAButton, KpiTile, ActionTile, PillBadge, SkeletonCard } from './components/Primitives'; import { MobileSheet } from './components/Sheet'; import { adminPath, ADMIN_WELCOME_BASE_PATH } from '../constants'; @@ -21,6 +21,7 @@ import { collectPackageFeatures, formatPackageLimit, getPackageFeatureLabel, get import { trackOnboarding } from '../api'; import { useAuth } from '../auth/context'; import { ADMIN_ACTION_COLORS, ADMIN_MOTION, useAdminTheme } from './theme'; +import { isPastEvent } from './eventDate'; type DeviceSetupProps = { installPrompt: ReturnType; @@ -32,6 +33,7 @@ type DeviceSetupProps = { export default function MobileDashboardPage() { const navigate = useNavigate(); const location = useLocation(); + const { slug: slugParam } = useParams<{ slug?: string }>(); const { t, i18n } = useTranslation('management'); const { events, activeEvent, hasEvents, hasMultipleEvents, isLoading, selectEvent } = useEventContext(); const { status } = useAuth(); @@ -42,11 +44,12 @@ export default function MobileDashboardPage() { const [tourStep, setTourStep] = React.useState(0); const [summaryOpen, setSummaryOpen] = React.useState(false); const [summarySeenOverride, setSummarySeenOverride] = React.useState(null); + const [eventSwitcherOpen, setEventSwitcherOpen] = React.useState(false); const onboardingTrackedRef = React.useRef(false); const installPrompt = useInstallPrompt(); const pushState = useAdminPushSubscription(); const devicePermissions = useDevicePermissions(); - const { textStrong, muted, border, surface, accentSoft, primary } = useAdminTheme(); + const { textStrong, muted, accentSoft, primary } = useAdminTheme(); const text = textStrong; const accentText = primary; @@ -84,6 +87,14 @@ export default function MobileDashboardPage() { const tourTargetSlug = activeEvent?.slug ?? effectiveEvents[0]?.slug ?? null; const tourStepKeys = React.useMemo(() => resolveTourStepKeys(effectiveHasEvents), [effectiveHasEvents]); + React.useEffect(() => { + if (!slugParam || slugParam === activeEvent?.slug) { + return; + } + + selectEvent(slugParam); + }, [activeEvent?.slug, selectEvent, slugParam]); + React.useEffect(() => { if (status !== 'authenticated' || onboardingTrackedRef.current) { return; @@ -424,7 +435,7 @@ export default function MobileDashboardPage() { onOpen={() => setSummaryOpen(true)} /> ) : null} - + {tourSheet} {packageSummarySheet} @@ -434,8 +445,7 @@ export default function MobileDashboardPage() { return ( {showPackageSummaryBanner ? ( setSummaryOpen(true)} /> ) : null} - navigate(adminPath('/mobile/settings'))} - /> - activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}/photos`))} - onManageTasks={() => activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}/tasks`))} - onShowQr={() => activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}/qr`))} - /> - - activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}/members`))} - onPrint={() => activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}/qr`))} - onInvites={() => activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}/members`))} - onSettings={() => activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}`))} - onAnalytics={() => activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}/analytics`))} + locale={locale} + canSwitch={effectiveMultiple} + onSwitch={() => setEventSwitcherOpen(true)} + onEdit={() => activeEvent?.slug && navigate(adminPath(`/mobile/events/${activeEvent.slug}/edit`))} + /> + navigate(path)} /> - + navigate(adminPath('/mobile/settings'))} + /> {tourSheet} {packageSummarySheet} + setEventSwitcherOpen(false)} + events={effectiveEvents} + locale={locale} + /> ); } @@ -976,8 +988,20 @@ function OnboardingEmptyState({ installPrompt, pushState, devicePermissions, onO ); } -function EventPickerList({ events, locale, text, muted, border }: { events: TenantEvent[]; locale: string; text: string; muted: string; border: string }) { +function EventPickerList({ + events, + locale, + onPick, + navigateOnSelect = true, +}: { + events: TenantEvent[]; + locale: string; + onPick?: (event: TenantEvent) => void; + navigateOnSelect?: boolean; +}) { const { t } = useTranslation('management'); + const { textStrong, muted, border } = useAdminTheme(); + const text = textStrong; const { selectEvent } = useEventContext(); const navigate = useNavigate(); const [localEvents, setLocalEvents] = React.useState(events); @@ -1008,7 +1032,8 @@ function EventPickerList({ events, locale, text, muted, border }: { events: Tena key={event.slug} onPress={() => { selectEvent(event.slug ?? null); - if (event.slug) { + onPick?.(event); + if (navigateOnSelect && event.slug) { navigate(adminPath(`/mobile/events/${event.slug}`)); } }} @@ -1036,140 +1061,232 @@ function EventPickerList({ events, locale, text, muted, border }: { events: Tena ); } -function FeaturedActions({ - tasksEnabled, - onReviewPhotos, - onManageTasks, - onShowQr, +function EventSwitcherSheet({ + open, + onClose, + events, + locale, }: { - tasksEnabled: boolean; - onReviewPhotos: () => void; - onManageTasks: () => void; - onShowQr: () => void; + open: boolean; + onClose: () => void; + events: TenantEvent[]; + locale: string; }) { const { t } = useTranslation('management'); - const { textStrong, muted, subtle } = useAdminTheme(); - const text = textStrong; - const cards = [ - { - key: 'photos', - label: t('mobileDashboard.photosLabel', 'Review photos'), - desc: t('mobileDashboard.photosDesc', 'Moderate uploads and highlights'), - icon: ImageIcon, - color: ADMIN_ACTION_COLORS.images, - action: onReviewPhotos, - }, - { - key: 'tasks', - label: t('mobileDashboard.tasksLabel', 'Manage tasks & challenges'), - desc: tasksEnabled - ? t('mobileDashboard.tasksDesc', 'Assign and track progress') - : t('mobileDashboard.tasksDisabledDesc', 'Guests do not see tasks (task mode off)'), - icon: ListTodo, - color: ADMIN_ACTION_COLORS.tasks, - action: onManageTasks, - }, - { - key: 'qr', - label: t('mobileDashboard.qrLabel', 'Show / share QR code'), - desc: t('mobileDashboard.qrDesc', 'Posters, cards, and links'), - icon: QrCode, - color: ADMIN_ACTION_COLORS.qr, - action: onShowQr, - }, - ]; return ( - - {cards.map((card) => ( - - - - - - - - - {card.label} - - - {card.desc} - - - - ˃ - - - - - ))} - + + + ); } -function SecondaryGrid({ +function resolveLocation(event: TenantEvent | null, t: (key: string, fallback: string) => string): string { + if (!event) return t('events.detail.locationPlaceholder', '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 t('events.detail.locationPlaceholder', 'Location'); +} + +function EventHeaderCard({ event, - onGuests, - onPrint, - onInvites, - onSettings, - onAnalytics, + locale, + canSwitch, + onSwitch, + onEdit, }: { event: TenantEvent | null; - onGuests: () => void; - onPrint: () => void; - onInvites: () => void; - onSettings: () => void; - onAnalytics: () => void; + locale: string; + canSwitch: boolean; + onSwitch: () => void; + onEdit: () => void; }) { const { t } = useTranslation('management'); const { textStrong, muted, border, surface, accentSoft, primary } = useAdminTheme(); - const text = textStrong; + + if (!event) { + return null; + } + + const dateLabel = formatEventDate(event.event_date, locale) ?? t('events.detail.dateTbd', 'Date tbd'); + const locationLabel = resolveLocation(event, t); + + return ( + + + {canSwitch ? ( + + + + {resolveEventDisplayName(event)} + + + + + ) : ( + + {resolveEventDisplayName(event)} + + )} + + {event.status === 'published' + ? t('events.status.published', 'Live') + : t('events.status.draft', 'Draft')} + + + + + + + {dateLabel} + + + + {locationLabel} + + + + + + + + ); +} + +function EventManagementGrid({ + event, + tasksEnabled, + onNavigate, +}: { + event: TenantEvent | null; + tasksEnabled: boolean; + onNavigate: (path: string) => void; +}) { + const { t } = useTranslation('management'); + const { textStrong } = useAdminTheme(); + const slug = event?.slug ?? null; const brandingAllowed = isBrandingAllowed(event ?? null); + + if (!event) { + return null; + } const tiles = [ + { + icon: Pencil, + label: t('mobileDashboard.shortcutSettings', 'Event settings'), + color: ADMIN_ACTION_COLORS.settings, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/edit`)) : undefined, + disabled: !slug, + }, + { + icon: Sparkles, + label: tasksEnabled + ? t('events.quick.tasks', 'Tasks & Checklists') + : `${t('events.quick.tasks', 'Tasks & Checklists')} (${t('common:states.disabled', 'Disabled')})`, + color: ADMIN_ACTION_COLORS.tasks, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/tasks`)) : undefined, + disabled: !tasksEnabled || !slug, + }, + { + icon: QrCode, + label: t('events.quick.qr', 'QR Code Layouts'), + color: ADMIN_ACTION_COLORS.qr, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/qr`)) : undefined, + disabled: !slug, + }, + { + icon: ImageIcon, + label: t('events.quick.images', 'Image Management'), + color: ADMIN_ACTION_COLORS.images, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/photos`)) : undefined, + disabled: !slug, + }, + { + icon: Tv, + label: t('events.quick.liveShow', 'Live Show queue'), + color: ADMIN_ACTION_COLORS.images, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/live-show`)) : undefined, + disabled: !slug, + }, + { + icon: Settings, + label: t('events.quick.liveShowSettings', 'Live Show settings'), + color: ADMIN_ACTION_COLORS.images, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/live-show/settings`)) : undefined, + disabled: !slug, + }, { icon: Users, - label: t('mobileDashboard.shortcutGuests', 'Guest management'), + label: t('events.quick.guests', 'Guest Management'), color: ADMIN_ACTION_COLORS.guests, - action: onGuests, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/members`)) : undefined, + disabled: !slug, + }, + { + icon: Megaphone, + label: t('events.quick.guestMessages', 'Guest messages'), + color: ADMIN_ACTION_COLORS.guestMessages, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/guest-notifications`)) : undefined, + disabled: !slug, + }, + { + icon: Layout, + label: t('events.quick.branding', 'Branding & Theme'), + color: ADMIN_ACTION_COLORS.branding, + onPress: slug && brandingAllowed ? () => onNavigate(adminPath(`/mobile/events/${slug}/branding`)) : undefined, + disabled: !brandingAllowed || !slug, + }, + { + icon: Camera, + label: t('events.quick.photobooth', 'Photobooth'), + color: ADMIN_ACTION_COLORS.photobooth, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/photobooth`)) : undefined, + disabled: !slug, }, { icon: TrendingUp, label: t('mobileDashboard.shortcutAnalytics', 'Analytics'), color: ADMIN_ACTION_COLORS.analytics, - action: onAnalytics, - }, - { - icon: QrCode, - label: t('mobileDashboard.shortcutPrints', 'Print & poster downloads'), - color: ADMIN_ACTION_COLORS.qr, - action: onPrint, - }, - { - icon: Sparkles, - label: t('mobileDashboard.shortcutInvites', 'Team / helper invites'), - color: ADMIN_ACTION_COLORS.invites, - action: onInvites, - }, - { - icon: Settings, - label: t('mobileDashboard.shortcutSettings', 'Event settings'), - color: ADMIN_ACTION_COLORS.settings, - action: onSettings, - }, - { - icon: Sparkles, - label: t('mobileDashboard.shortcutBranding', 'Branding & moderation'), - color: ADMIN_ACTION_COLORS.branding, - action: brandingAllowed ? onSettings : undefined, - disabled: !brandingAllowed, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/analytics`)) : undefined, + disabled: !slug, }, ]; + if (event && isPastEvent(event.event_date)) { + tiles.push({ + icon: Sparkles, + label: t('events.quick.recap', 'Recap & Archive'), + color: ADMIN_ACTION_COLORS.recap, + onPress: slug ? () => onNavigate(adminPath(`/mobile/events/${slug}/recap`)) : undefined, + disabled: !slug, + }); + } + return ( - - - {t('mobileDashboard.shortcutsTitle', 'Shortcuts')} + + + {t('events.detail.managementTitle', 'Event management')} {tiles.map((tile, index) => ( @@ -1178,22 +1295,12 @@ function SecondaryGrid({ icon={tile.icon} label={tile.label} color={tile.color} - onPress={tile.action} + onPress={tile.onPress} disabled={tile.disabled} delayMs={index * ADMIN_MOTION.tileStaggerMs} /> ))} - {event ? ( - - - {resolveEventDisplayName(event)} - - - {renderEventLocation(event)} - - - ) : null} ); } diff --git a/resources/js/admin/mobile/EventDetailPage.tsx b/resources/js/admin/mobile/EventDetailPage.tsx deleted file mode 100644 index 0aa0dc4..0000000 --- a/resources/js/admin/mobile/EventDetailPage.tsx +++ /dev/null @@ -1,343 +0,0 @@ -import React from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; -import { CalendarDays, MapPin, Settings, Users, Camera, Sparkles, QrCode, Image, Shield, Layout, RefreshCcw, Pencil, Megaphone, Tv } 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 { MobileShell, HeaderActionButton } from './components/MobileShell'; -import { MobileCard, PillBadge, KpiTile, ActionTile } from './components/Primitives'; -import { TenantEvent, EventStats, EventToolkit, getEvent, getEventStats, getEventToolkit, getEvents } from '../api'; -import { adminPath, ADMIN_EVENT_BRANDING_PATH, ADMIN_EVENT_GUEST_NOTIFICATIONS_PATH, ADMIN_EVENT_INVITES_PATH, ADMIN_EVENT_LIVE_SHOW_PATH, ADMIN_EVENT_LIVE_SHOW_SETTINGS_PATH, ADMIN_EVENT_MEMBERS_PATH, ADMIN_EVENT_PHOTOS_PATH, ADMIN_EVENT_TASKS_PATH } from '../constants'; -import { isAuthError } from '../auth/tokens'; -import { getApiErrorMessage } from '../lib/apiError'; -import { MobileSheet } from './components/Sheet'; -import { useEventContext } from '../context/EventContext'; -import { formatEventDate, isBrandingAllowed, resolveEngagementMode, resolveEventDisplayName } from '../lib/events'; -import { isPastEvent } from './eventDate'; -import { useBackNavigation } from './hooks/useBackNavigation'; -import { ADMIN_ACTION_COLORS, ADMIN_MOTION, useAdminTheme } from './theme'; - -export default function MobileEventDetailPage() { - const { slug: slugParam } = useParams<{ slug?: string }>(); - const slug = slugParam ?? null; - const navigate = useNavigate(); - const { t } = useTranslation('management'); - - const [event, setEvent] = React.useState(null); - const [stats, setStats] = React.useState(null); - const [toolkit, setToolkit] = React.useState(null); - const [loading, setLoading] = React.useState(true); - const [error, setError] = React.useState(null); - const { events, activeEvent, selectEvent } = useEventContext(); - const [showEventPicker, setShowEventPicker] = React.useState(false); - const back = useBackNavigation(adminPath('/mobile/events')); - const { textStrong, text, muted, danger, accentSoft } = useAdminTheme(); - - React.useEffect(() => { - if (!slug) return; - selectEvent(slug); - }, [slug, selectEvent]); - - React.useEffect(() => { - if (!slug) return; - (async () => { - setLoading(true); - try { - const [eventData, statsData, toolkitData] = await Promise.all([getEvent(slug), getEventStats(slug), getEventToolkit(slug)]); - setEvent(eventData); - setStats(statsData); - setToolkit(toolkitData); - setError(null); - } catch (err) { - if (!isAuthError(err)) { - try { - const list = await getEvents({ force: true }); - const fallback = list.find((ev: TenantEvent) => ev.slug === slug) ?? null; - if (fallback) { - setEvent(fallback); - setError(null); - } else { - setError(getApiErrorMessage(err, t('events.errors.loadFailed', 'Event konnte nicht geladen werden.'))); - } - } catch (fallbackErr) { - setError(getApiErrorMessage(fallbackErr, t('events.errors.loadFailed', 'Event konnte nicht geladen werden.'))); - } - } - } finally { - setLoading(false); - } - })(); - }, [slug, t]); - - const tasksEnabled = resolveEngagementMode(event ?? activeEvent ?? null) !== 'photo_only'; - const brandingAllowed = isBrandingAllowed(event ?? activeEvent ?? null); - - const kpis = [ - { - label: t('events.detail.kpi.guests', 'Guests Registered'), - value: toolkit?.invites?.summary.total ?? event?.active_invites_count ?? '—', - icon: Users, - }, - { - label: t('events.detail.kpi.photos', 'Images Uploaded'), - value: stats?.uploads_total ?? event?.photo_count ?? '—', - icon: Camera, - }, - ]; - - if (tasksEnabled) { - kpis.unshift({ - label: t('events.detail.kpi.tasks', 'Active Tasks'), - value: event?.tasks_count ?? toolkit?.tasks?.summary?.total ?? '—', - icon: Sparkles, - }); - } - - return ( - - navigate(adminPath('/mobile/settings'))} ariaLabel={t('mobileSettings.title', 'Settings')}> - - - navigate(0)} ariaLabel={t('common.refresh', 'Refresh')}> - - - - } - > - {error ? ( - - - {error} - - - ) : null} - - - - {event ? renderName(event.name, t) : t('events.placeholders.untitled', 'Unbenanntes Event')} - - - - - {formatDate(event?.event_date, t)} - - - - {resolveLocation(event, t)} - - - - {event?.status === 'published' ? t('events.status.published', 'Live') : t('events.status.draft', 'Draft')} - - slug && navigate(adminPath(`/mobile/events/${slug}/edit`))} - style={{ - position: 'absolute', - right: 16, - top: 16, - width: 44, - height: 44, - borderRadius: 22, - backgroundColor: accentSoft, - alignItems: 'center', - justifyContent: 'center', - boxShadow: '0 6px 16px rgba(0,0,0,0.12)', - }} - > - - - - - - {loading ? ( - - {Array.from({ length: 3 }).map((_, idx) => ( - - ))} - - ) : ( - - {kpis.map((kpi) => ( - - ))} - - )} - - - setShowEventPicker(false)} - title={t('events.detail.pickEvent', 'Event wählen')} - footer={null} - bottomOffsetPx={120} - > - - {events.length === 0 ? ( - - {t('events.list.empty.description', 'Starte jetzt mit deinem ersten Event.')} - - ) : ( - events.map((ev) => ( - { - selectEvent(ev.slug ?? null); - setShowEventPicker(false); - navigate(adminPath(`/mobile/events/${ev.slug}`)); - }} - > - - - - {renderName(ev.name, t)} - - - - - {formatDate(ev.event_date, t)} - - - - - {ev.slug === activeEvent?.slug ? t('events.detail.active', 'Aktiv') : t('events.actions.open', 'Öffnen')} - - - - )) - )} - - - - - - {t('events.detail.managementTitle', 'Event Management')} - - - navigate(adminPath(`/mobile/events/${slug ?? ''}/tasks`))} - delayMs={0} - /> - navigate(adminPath(`/mobile/events/${slug ?? ''}/qr`))} - delayMs={ADMIN_MOTION.tileStaggerMs} - /> - navigate(adminPath(`/mobile/events/${slug ?? ''}/photos`))} - delayMs={ADMIN_MOTION.tileStaggerMs * 2} - /> - slug && navigate(ADMIN_EVENT_LIVE_SHOW_PATH(slug))} - disabled={!slug} - delayMs={ADMIN_MOTION.tileStaggerMs * 3} - /> - slug && navigate(ADMIN_EVENT_LIVE_SHOW_SETTINGS_PATH(slug))} - disabled={!slug} - delayMs={ADMIN_MOTION.tileStaggerMs * 4} - /> - navigate(adminPath(`/mobile/events/${slug ?? ''}/members`))} - delayMs={ADMIN_MOTION.tileStaggerMs * 5} - /> - slug && navigate(ADMIN_EVENT_GUEST_NOTIFICATIONS_PATH(slug))} - disabled={!slug} - delayMs={ADMIN_MOTION.tileStaggerMs * 6} - /> - navigate(adminPath(`/mobile/events/${slug ?? ''}/branding`)) : undefined - } - disabled={!brandingAllowed} - delayMs={ADMIN_MOTION.tileStaggerMs * 7} - /> - navigate(adminPath(`/mobile/events/${slug ?? ''}/photobooth`))} - delayMs={ADMIN_MOTION.tileStaggerMs * 8} - /> - {isPastEvent(event?.event_date) ? ( - navigate(adminPath(`/mobile/events/${slug ?? ''}/recap`))} - delayMs={ADMIN_MOTION.tileStaggerMs * 9} - /> - ) : null} - - - - ); -} - -function renderName(name: TenantEvent['name'], t: (key: string, fallback: string) => string): string { - const fallback = t('events.placeholders.untitled', 'Untitled event'); - if (typeof name === 'string' && name.trim()) return name; - if (name && typeof name === 'object') { - return name.de ?? name.en ?? Object.values(name)[0] ?? fallback; - } - return fallback; -} - -function formatDate(iso: string | null | undefined, t: (key: string, fallback: string) => string): string { - if (!iso) return t('events.detail.dateTbd', 'Date tbd'); - const date = new Date(iso); - if (Number.isNaN(date.getTime())) return t('events.detail.dateTbd', 'Date tbd'); - return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); -} - -function resolveLocation(event: TenantEvent | null, t: (key: string, fallback: string) => string): string { - if (!event) return t('events.detail.locationPlaceholder', '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 t('events.detail.locationPlaceholder', 'Location'); -} diff --git a/resources/js/admin/mobile/prefetch.ts b/resources/js/admin/mobile/prefetch.ts index 2177b22..91594d0 100644 --- a/resources/js/admin/mobile/prefetch.ts +++ b/resources/js/admin/mobile/prefetch.ts @@ -12,7 +12,6 @@ export function prefetchMobileRoutes() { schedule(() => { void import('./DashboardPage'); void import('./EventsPage'); - void import('./EventDetailPage'); void import('./EventPhotosPage'); void import('./EventTasksPage'); void import('./NotificationsPage'); diff --git a/resources/js/admin/router.tsx b/resources/js/admin/router.tsx index dda541a..eaf53d4 100644 --- a/resources/js/admin/router.tsx +++ b/resources/js/admin/router.tsx @@ -20,7 +20,6 @@ const AuthCallbackPage = React.lazy(() => import('./mobile/AuthCallbackPage')); const LoginStartPage = React.lazy(() => import('./mobile/LoginStartPage')); const LogoutPage = React.lazy(() => import('./mobile/LogoutPage')); const MobileEventsPage = React.lazy(() => import('./mobile/EventsPage')); -const MobileEventDetailPage = React.lazy(() => import('./mobile/EventDetailPage')); const MobileEventPhotoboothPage = React.lazy(() => import('./mobile/EventPhotoboothPage')); const MobileBrandingPage = React.lazy(() => import('./mobile/BrandingPage')); const MobileEventFormPage = React.lazy(() => import('./mobile/EventFormPage')); @@ -195,7 +194,7 @@ export const router = createBrowserRouter([ { path: 'events/:slug/guest-notifications', element: `${ADMIN_EVENTS_PATH}/${slug}/guest-notifications`} /> }, { path: 'events/:slug/toolkit', element: `${ADMIN_EVENTS_PATH}/${slug}`} /> }, { path: 'mobile/events', element: }, - { path: 'mobile/events/:slug', element: }, + { path: 'mobile/events/:slug', element: }, { path: 'mobile/events/:slug/branding', element: }, { path: 'mobile/events/new', element: }, { path: 'mobile/events/:slug/edit', element: },