Refresh mobile dashboard and header
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-16 22:06:41 +01:00
parent b316beb522
commit 7e77dd2931
6 changed files with 511 additions and 100 deletions

View File

@@ -1,6 +1,6 @@
import React, { Suspense } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { ChevronLeft, Bell, QrCode } from 'lucide-react';
import { ChevronLeft, Bell, QrCode, ChevronsUpDown } from 'lucide-react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
@@ -8,7 +8,7 @@ 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 { ADMIN_EVENTS_PATH, adminPath } from '../../constants';
import { MobileCard, CTAButton } from './Primitives';
import { useNotificationsBadge } from '../hooks/useNotificationsBadge';
import { useOnlineStatus } from '../hooks/useOnlineStatus';
@@ -31,7 +31,7 @@ type MobileShellProps = {
};
export function MobileShell({ title, subtitle, children, activeTab, onBack, headerActions }: MobileShellProps) {
const { events, activeEvent, selectEvent } = useEventContext();
const { events, activeEvent, hasMultipleEvents, selectEvent } = useEventContext();
const { user } = useAuth();
const { go } = useMobileNav(activeEvent?.slug, activeTab);
const navigate = useNavigate();
@@ -137,7 +137,13 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
}, []);
const pageTitle = title ?? t('header.appName', 'Event Admin');
const eventContext = !isCompactHeader && effectiveActive ? resolveEventDisplayName(effectiveActive) : null;
const eventContext = !isCompactHeader
? effectiveActive
? resolveEventDisplayName(effectiveActive)
: hasMultipleEvents
? t('header.selectEvent', 'Select an event')
: null
: null;
const subtitleText = subtitle ?? eventContext ?? '';
const isMember = user?.role === 'member';
const memberPermissions = Array.isArray(effectiveActive?.member_permissions) ? effectiveActive?.member_permissions ?? [] : [];
@@ -164,22 +170,55 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
) : (
<XStack width={28} />
);
const headerTitle = (
<XStack alignItems="center" space="$1" flex={1} minWidth={0} justifyContent="flex-end">
<YStack alignItems="flex-end" maxWidth="100%">
<Text fontSize="$lg" fontWeight="800" fontFamily="$display" color={textColor} textAlign="right" numberOfLines={1}>
{pageTitle}
const headerTitleRight = (
<YStack alignItems="flex-end" maxWidth="100%">
<Text fontSize="$lg" fontWeight="800" fontFamily="$display" color={textColor} textAlign="right" numberOfLines={1}>
{pageTitle}
</Text>
{subtitleText ? (
<Text fontSize="$xs" color={mutedText} textAlign="right" numberOfLines={1} fontFamily="$body">
{subtitleText}
</Text>
{subtitleText ? (
<Text fontSize="$xs" color={mutedText} textAlign="right" numberOfLines={1} fontFamily="$body">
{subtitleText}
</Text>
) : null}
</YStack>
</XStack>
) : null}
</YStack>
);
const headerTitleCenter = (
<YStack alignItems="center" maxWidth="100%">
<Text fontSize="$lg" fontWeight="800" fontFamily="$display" color={textColor} textAlign="center" numberOfLines={1}>
{pageTitle}
</Text>
{subtitleText ? (
<Text fontSize="$xs" color={mutedText} textAlign="center" numberOfLines={1} fontFamily="$body">
{subtitleText}
</Text>
) : null}
</YStack>
);
const isEventsIndex = location.pathname === ADMIN_EVENTS_PATH;
const canSwitchEvents = hasMultipleEvents && !isEventsIndex;
const headerActionsRow = (
<XStack alignItems="center" space="$2">
{canSwitchEvents ? (
<HeaderActionButton onPress={() => navigate(ADMIN_EVENTS_PATH)} ariaLabel={t('header.switchEvent', 'Switch event')}>
<XStack
width={34}
height={34}
borderRadius={12}
backgroundColor={actionSurface}
borderWidth={1}
borderColor={actionBorder}
alignItems="center"
justifyContent="center"
style={{
boxShadow: `0 10px 18px ${actionShadow}`,
backdropFilter: 'blur(12px)',
WebkitBackdropFilter: 'blur(12px)',
}}
>
<ChevronsUpDown size={16} color={textColor} />
</XStack>
</HeaderActionButton>
) : null}
<HeaderActionButton
onPress={() => navigate(adminPath('/mobile/notifications'))}
ariaLabel={t('mobile.notifications', 'Notifications')}
@@ -273,22 +312,28 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
}}
>
{isCompactHeader ? (
<YStack space="$2">
<XStack alignItems="center" justifyContent="space-between" minHeight={48} space="$3">
{headerBackButton}
<XStack flex={1} minWidth={0} justifyContent="flex-end">
{headerTitle}
</XStack>
<XStack
alignItems="center"
justifyContent="space-between"
minHeight={48}
space="$3"
flexWrap="wrap"
>
{headerBackButton}
<XStack flex={1} minWidth={120} justifyContent="center">
{headerTitleCenter}
</XStack>
<XStack alignItems="center" justifyContent="flex-end">
<XStack justifyContent="flex-end" flexShrink={0} style={{ marginLeft: 'auto' }}>
{headerActionsRow}
</XStack>
</YStack>
</XStack>
) : (
<XStack alignItems="center" justifyContent="space-between" minHeight={48} space="$3">
{headerBackButton}
<XStack alignItems="center" space="$2.5" flex={1} justifyContent="flex-end" minWidth={0}>
{headerTitle}
<XStack flex={1} minWidth={0} justifyContent="flex-end">
{headerTitleRight}
</XStack>
{headerActionsRow}
</XStack>
</XStack>