237 lines
8.1 KiB
TypeScript
237 lines
8.1 KiB
TypeScript
import React from 'react';
|
|
import { YStack } from '@tamagui/stacks';
|
|
import { Trophy, UploadCloud, Sparkles, Cast, Share2, Compass, Image, Camera, Settings, Home } from 'lucide-react';
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
import TopBar from './TopBar';
|
|
import BottomDock from './BottomDock';
|
|
import FloatingActionButton from './FloatingActionButton';
|
|
import FabActionSheet from './FabActionSheet';
|
|
import CompassHub, { type CompassAction } from './CompassHub';
|
|
import AmbientBackground from './AmbientBackground';
|
|
import NotificationSheet from './NotificationSheet';
|
|
import SettingsSheet from './SettingsSheet';
|
|
import GuestAnalyticsNudge from './GuestAnalyticsNudge';
|
|
import { useEventData } from '../context/EventDataContext';
|
|
import { buildEventPath } from '../lib/routes';
|
|
import { useOptionalNotificationCenter } from '@/guest/context/NotificationCenterContext';
|
|
import { useTranslation } from '@/guest/i18n/useTranslation';
|
|
import { useAppearance } from '@/hooks/use-appearance';
|
|
|
|
type AppShellProps = {
|
|
children: React.ReactNode;
|
|
};
|
|
|
|
export default function AppShell({ children }: AppShellProps) {
|
|
const [sheetOpen, setSheetOpen] = React.useState(false);
|
|
const [compassOpen, setCompassOpen] = React.useState(false);
|
|
const [notificationsOpen, setNotificationsOpen] = React.useState(false);
|
|
const [settingsOpen, setSettingsOpen] = React.useState(false);
|
|
const { tasksEnabled, event, token } = useEventData();
|
|
const notificationCenter = useOptionalNotificationCenter();
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const { t } = useTranslation();
|
|
const { resolved } = useAppearance();
|
|
const isDark = resolved === 'dark';
|
|
const actionIconColor = isDark ? '#F8FAFF' : '#0F172A';
|
|
const matomoEnabled = typeof window !== 'undefined' && Boolean((window as any).__MATOMO_GUEST__?.enabled);
|
|
const showFab = !/\/photo\/\d+/.test(location.pathname);
|
|
|
|
const goTo = (path: string) => () => {
|
|
setSheetOpen(false);
|
|
setCompassOpen(false);
|
|
setNotificationsOpen(false);
|
|
setSettingsOpen(false);
|
|
navigate(buildEventPath(token, path));
|
|
};
|
|
|
|
const openSheet = () => {
|
|
setCompassOpen(false);
|
|
setNotificationsOpen(false);
|
|
setSettingsOpen(false);
|
|
setSheetOpen(true);
|
|
};
|
|
|
|
const openCompass = () => {
|
|
setSheetOpen(false);
|
|
setNotificationsOpen(false);
|
|
setSettingsOpen(false);
|
|
setCompassOpen(true);
|
|
};
|
|
|
|
const actions = [
|
|
{
|
|
key: 'upload',
|
|
label: t('appShell.actions.upload.label', 'Upload / Take photo'),
|
|
description: t('appShell.actions.upload.description', 'Add a moment from your device or camera.'),
|
|
icon: <UploadCloud size={18} color={actionIconColor} />,
|
|
onPress: goTo('/upload'),
|
|
},
|
|
{
|
|
key: 'compass',
|
|
label: t('appShell.actions.compass.label', 'Compass hub'),
|
|
description: t('appShell.actions.compass.description', 'Quick jump to key areas.'),
|
|
icon: <Compass size={18} color={actionIconColor} />,
|
|
onPress: () => {
|
|
setSheetOpen(false);
|
|
openCompass();
|
|
},
|
|
},
|
|
tasksEnabled
|
|
? {
|
|
key: 'task',
|
|
label: t('appShell.actions.task.label', 'Start a task'),
|
|
description: t('appShell.actions.task.description', 'Pick a challenge and capture it now.'),
|
|
icon: <Sparkles size={18} color={actionIconColor} />,
|
|
onPress: goTo('/tasks'),
|
|
}
|
|
: null,
|
|
{
|
|
key: 'live',
|
|
label: t('appShell.actions.live.label', 'Live show'),
|
|
description: t('appShell.actions.live.description', 'See the real-time highlight stream.'),
|
|
icon: <Cast size={18} color={actionIconColor} />,
|
|
onPress: () => {
|
|
setSheetOpen(false);
|
|
setCompassOpen(false);
|
|
setNotificationsOpen(false);
|
|
setSettingsOpen(false);
|
|
if (token) {
|
|
navigate(`/show/${encodeURIComponent(token)}`);
|
|
}
|
|
},
|
|
},
|
|
{
|
|
key: 'slideshow',
|
|
label: t('appShell.actions.slideshow.label', 'Slideshow'),
|
|
description: t('appShell.actions.slideshow.description', 'Lean back and watch the gallery roll.'),
|
|
icon: <Image size={18} color={actionIconColor} />,
|
|
onPress: goTo('/slideshow'),
|
|
},
|
|
{
|
|
key: 'share',
|
|
label: t('appShell.actions.share.label', 'Share invite'),
|
|
description: t('appShell.actions.share.description', 'Send the event link or QR code.'),
|
|
icon: <Share2 size={18} color={actionIconColor} />,
|
|
onPress: goTo('/share'),
|
|
},
|
|
tasksEnabled
|
|
? {
|
|
key: 'achievements',
|
|
label: t('appShell.actions.achievements.label', 'Achievements'),
|
|
description: t('appShell.actions.achievements.description', 'Track your photo streaks.'),
|
|
icon: <Trophy size={18} color={actionIconColor} />,
|
|
onPress: goTo('/achievements'),
|
|
}
|
|
: null,
|
|
].filter(Boolean) as Array<{
|
|
key: string;
|
|
label: string;
|
|
description: string;
|
|
icon: React.ReactNode;
|
|
onPress?: () => void;
|
|
}>;
|
|
|
|
const compassQuadrants: [CompassAction, CompassAction, CompassAction, CompassAction] = [
|
|
{
|
|
key: 'home',
|
|
label: t('navigation.home', 'Home'),
|
|
icon: <Home size={18} color={actionIconColor} />,
|
|
onPress: goTo('/'),
|
|
},
|
|
{
|
|
key: 'gallery',
|
|
label: t('navigation.gallery', 'Gallery'),
|
|
icon: <Image size={18} color={actionIconColor} />,
|
|
onPress: goTo('/gallery'),
|
|
},
|
|
tasksEnabled
|
|
? {
|
|
key: 'tasks',
|
|
label: t('navigation.tasks', 'Tasks'),
|
|
icon: <Sparkles size={18} color={actionIconColor} />,
|
|
onPress: goTo('/tasks'),
|
|
}
|
|
: {
|
|
key: 'settings',
|
|
label: t('settings.title', 'Settings'),
|
|
icon: <Settings size={18} color={actionIconColor} />,
|
|
onPress: goTo('/settings'),
|
|
},
|
|
{
|
|
key: 'share',
|
|
label: t('navigation.share', 'Share'),
|
|
icon: <Share2 size={18} color={actionIconColor} />,
|
|
onPress: goTo('/share'),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<AmbientBackground>
|
|
<YStack minHeight="100vh" position="relative">
|
|
<YStack
|
|
position="fixed"
|
|
top={0}
|
|
left={0}
|
|
right={0}
|
|
zIndex={1000}
|
|
style={{
|
|
backgroundColor: isDark ? 'rgba(10, 14, 28, 0.72)' : 'rgba(255, 255, 255, 0.85)',
|
|
backdropFilter: 'saturate(160%) blur(18px)',
|
|
WebkitBackdropFilter: 'saturate(160%) blur(18px)',
|
|
}}
|
|
>
|
|
<TopBar
|
|
eventName={event?.name ?? t('galleryPage.hero.eventFallback', 'Event')}
|
|
onProfilePress={() => {
|
|
setNotificationsOpen(false);
|
|
setSheetOpen(false);
|
|
setCompassOpen(false);
|
|
setSettingsOpen(true);
|
|
}}
|
|
onNotificationsPress={() => {
|
|
setSettingsOpen(false);
|
|
setSheetOpen(false);
|
|
setCompassOpen(false);
|
|
setNotificationsOpen(true);
|
|
}}
|
|
notificationCount={notificationCenter?.unreadCount ?? 0}
|
|
/>
|
|
</YStack>
|
|
<YStack
|
|
flex={1}
|
|
padding="$4"
|
|
gap="$4"
|
|
position="relative"
|
|
zIndex={1}
|
|
style={{ paddingTop: '88px', paddingBottom: '128px' }}
|
|
>
|
|
{children}
|
|
</YStack>
|
|
{showFab ? <FloatingActionButton onPress={openSheet} onLongPress={openCompass} /> : null}
|
|
<BottomDock />
|
|
<FabActionSheet
|
|
open={sheetOpen}
|
|
onOpenChange={(next) => setSheetOpen(next)}
|
|
title={t('appShell.fab.title', 'Create a moment')}
|
|
actions={actions}
|
|
/>
|
|
<CompassHub
|
|
open={compassOpen}
|
|
onOpenChange={setCompassOpen}
|
|
centerAction={{
|
|
key: 'capture',
|
|
label: t('appShell.compass.capture', 'Capture'),
|
|
icon: <Camera size={18} color="white" />,
|
|
onPress: goTo('/upload'),
|
|
}}
|
|
quadrants={compassQuadrants}
|
|
/>
|
|
<NotificationSheet open={notificationsOpen} onOpenChange={setNotificationsOpen} />
|
|
<SettingsSheet open={settingsOpen} onOpenChange={setSettingsOpen} />
|
|
<GuestAnalyticsNudge enabled={matomoEnabled} pathname={location.pathname} />
|
|
</YStack>
|
|
</AmbientBackground>
|
|
);
|
|
}
|