186 lines
6.4 KiB
TypeScript
186 lines
6.4 KiB
TypeScript
import React from 'react';
|
|
import { YStack } from '@tamagui/stacks';
|
|
import { Button } from '@tamagui/button';
|
|
import { Sparkles, Share2, Image, Camera, Settings, Home, Menu } from 'lucide-react';
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
import TopBar from './TopBar';
|
|
import FloatingActionButton from './FloatingActionButton';
|
|
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 { useGuestThemeVariant } from '../lib/guestTheme';
|
|
|
|
type AppShellProps = {
|
|
children: React.ReactNode;
|
|
};
|
|
|
|
export default function AppShell({ children }: AppShellProps) {
|
|
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 { isDark } = useGuestThemeVariant();
|
|
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) => () => {
|
|
setCompassOpen(false);
|
|
setNotificationsOpen(false);
|
|
setSettingsOpen(false);
|
|
const target = buildEventPath(token, path);
|
|
if (location.pathname === target) {
|
|
return;
|
|
}
|
|
navigate(target);
|
|
};
|
|
|
|
const openCompass = () => {
|
|
setNotificationsOpen(false);
|
|
setSettingsOpen(false);
|
|
setCompassOpen((prev) => !prev);
|
|
};
|
|
|
|
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: 'transparent',
|
|
backdropFilter: 'saturate(120%) blur(8px)',
|
|
WebkitBackdropFilter: 'saturate(120%) blur(8px)',
|
|
}}
|
|
>
|
|
<TopBar
|
|
eventName={event?.name ?? t('galleryPage.hero.eventFallback', 'Event')}
|
|
eventIcon={event?.type?.icon ?? null}
|
|
onProfilePress={() => {
|
|
setNotificationsOpen(false);
|
|
setCompassOpen(false);
|
|
setSettingsOpen(true);
|
|
}}
|
|
onNotificationsPress={() => {
|
|
setSettingsOpen(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: '142px' }}
|
|
>
|
|
{children}
|
|
</YStack>
|
|
{showFab ? (
|
|
<>
|
|
<Button
|
|
size="$3"
|
|
circular
|
|
position="fixed"
|
|
bottom={28}
|
|
left="calc(50% - 84px)"
|
|
zIndex={1100}
|
|
backgroundColor={isDark ? 'rgba(12, 16, 32, 0.75)' : 'rgba(255, 255, 255, 0.9)'}
|
|
borderWidth={1}
|
|
borderColor={isDark ? 'rgba(255,255,255,0.16)' : 'rgba(15,23,42,0.12)'}
|
|
onPress={goTo('/')}
|
|
style={{ boxShadow: isDark ? '0 10px 20px rgba(2, 6, 23, 0.45)' : '0 8px 16px rgba(15, 23, 42, 0.14)' }}
|
|
>
|
|
<Home size={16} color={actionIconColor} />
|
|
</Button>
|
|
<FloatingActionButton
|
|
onPress={goTo('/upload')}
|
|
/>
|
|
<Button
|
|
size="$4"
|
|
circular
|
|
position="fixed"
|
|
bottom={28}
|
|
left="calc(50% + 52px)"
|
|
zIndex={1100}
|
|
backgroundColor={isDark ? 'rgba(12, 16, 32, 0.75)' : 'rgba(255, 255, 255, 0.9)'}
|
|
borderWidth={1}
|
|
borderColor={isDark ? 'rgba(255,255,255,0.16)' : 'rgba(15,23,42,0.12)'}
|
|
width={54}
|
|
height={54}
|
|
onPress={openCompass}
|
|
style={{ boxShadow: isDark ? '0 10px 20px rgba(2, 6, 23, 0.45)' : '0 8px 16px rgba(15, 23, 42, 0.14)' }}
|
|
>
|
|
<Menu size={18} color={actionIconColor} />
|
|
</Button>
|
|
</>
|
|
) : null}
|
|
<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>
|
|
);
|
|
}
|