Files
fotospiel-app/resources/js/guest-v2/components/AppShell.tsx
Codex Agent effe6d2390
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Adjust guest v2 fab sizing
2026-02-03 21:58:10 +01:00

214 lines
7.5 KiB
TypeScript

import React from 'react';
import { XStack, 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 isUploadRoute = /\/upload(?:\/|$)/.test(location.pathname);
const showFab = !/\/photo\/\d+/.test(location.pathname);
const dockBackground = isDark ? 'rgba(12, 16, 32, 0.72)' : 'rgba(255, 255, 255, 0.9)';
const dockBorder = isDark ? 'rgba(255,255,255,0.16)' : 'rgba(15,23,42,0.12)';
const dockShadow = isDark ? '0 14px 28px rgba(2, 6, 23, 0.5)' : '0 12px 24px rgba(15, 23, 42, 0.14)';
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 ? (
isUploadRoute ? (
<Button
size="$6"
circular
position="fixed"
bottom={28}
right={20}
zIndex={1100}
backgroundColor={dockBackground}
borderWidth={1}
borderColor={dockBorder}
width={88}
height={88}
onPress={openCompass}
style={{ boxShadow: dockShadow }}
>
<Menu size={28} color={actionIconColor} />
</Button>
) : (
<XStack
position="fixed"
bottom={24}
left="50%"
zIndex={1100}
alignItems="center"
gap="$2"
padding="$2"
borderRadius={999}
borderWidth={1}
borderColor={dockBorder}
backgroundColor={dockBackground}
style={{ transform: 'translateX(-50%)', boxShadow: dockShadow }}
>
<Button
size="$3"
circular
backgroundColor={isDark ? 'rgba(12, 16, 32, 0.75)' : 'rgba(255, 255, 255, 0.9)'}
borderWidth={1}
borderColor={dockBorder}
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')}
inline
/>
<Button
size="$3"
circular
backgroundColor={isDark ? 'rgba(12, 16, 32, 0.75)' : 'rgba(255, 255, 255, 0.9)'}
borderWidth={1}
borderColor={dockBorder}
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={16} color={actionIconColor} />
</Button>
</XStack>
)
) : 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>
);
}