refactor(dashboard): refine setup checklist UI
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

- Removed progress bar from hero for cleaner look
- Made setup checklist collapsible (auto-collapsed when complete)
- Improved checklist item styling with active/inactive states
This commit is contained in:
Codex Agent
2026-01-18 10:08:39 +01:00
parent 48d4716ab1
commit 1e821a2fb4
2 changed files with 103 additions and 83 deletions

View File

@@ -7,7 +7,6 @@ import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import { Image } from '@tamagui/image';
import { Progress } from '@tamagui/progress';
import { isSameDay, isPast, isFuture, parseISO, differenceInDays, startOfDay } from 'date-fns';
import { MobileShell } from './components/MobileShell';
@@ -158,10 +157,6 @@ export default function MobileDashboardPage() {
},
});
// Calculate Readiness for Setup Checklist
const readiness = useEventReadiness(activeEvent, t as any);
const phase = activeEvent ? getEventPhase(activeEvent) : 'setup';
const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE';
React.useEffect(() => {
@@ -189,6 +184,10 @@ export default function MobileDashboardPage() {
);
}
// Calculate Readiness
const readiness = useEventReadiness(activeEvent, t as any);
const phase = activeEvent ? getEventPhase(activeEvent) : 'setup';
return (
<MobileShell activeTab="home" title={t('mobileDashboard.title', 'Dashboard')}>
@@ -203,8 +202,8 @@ export default function MobileDashboardPage() {
readiness={readiness}
/>
{/* 1b. SETUP CHECKLIST (Only in Setup Phase) */}
{phase === 'setup' && !readiness.isReady && (
{/* 1b. SETUP CHECKLIST */}
{phase === 'setup' && (
<SetupChecklist
steps={readiness.steps}
title={t('management:photobooth.checklist.title', 'Checklist')}
@@ -334,9 +333,10 @@ function LifecycleHero({ event, stats, locale, navigate, onSwitch, canSwitch, re
);
}
// SETUP PHASE
// We removed the big button. We show high-level status.
const progressPercent = (readiness.progress ?? 0) * 100;
// SETUP
const nextStep = readiness.nextStep;
const ctaLabel = nextStep ? nextStep.ctaLabel : t('dashboard:onboarding.hero.cta', 'Setup Complete');
const ctaAction = nextStep ? () => navigate(adminPath(nextStep.targetPath)) : undefined;
return (
<YStack>
@@ -358,17 +358,21 @@ function LifecycleHero({ event, stats, locale, navigate, onSwitch, canSwitch, re
<YStack height={1} backgroundColor={theme.border} />
<YStack space="$1.5">
<XStack alignItems="center" justifyContent="space-between">
<Text fontSize="$sm" color={theme.textStrong} fontWeight="600">
{t('management:photobooth.checklist.title', 'Setup Status')}
</Text>
<Text fontSize="$xs" color={theme.muted}>{readiness.completedSteps}/{readiness.totalSteps}</Text>
{/* Main CTA if not ready */}
{!readiness.isReady && (
<ModernButton
label={ctaLabel}
tone='primary'
icon={<ArrowRight size={16} color="white" />}
onPress={ctaAction}
/>
)}
{readiness.isReady && (
<XStack alignItems="center" space="$2">
<CheckCircle2 size={18} color={theme.successText} />
<Text fontSize="$sm" color={theme.successText} fontWeight="700">Ready for Liftoff</Text>
</XStack>
<Progress value={progressPercent} max={100} size="$2" backgroundColor={theme.surfaceMuted} borderWidth={0}>
<Progress.Indicator backgroundColor={theme.primary} />
</Progress>
</YStack>
)}
</ModernCard>
</YStack>
);

View File

@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import { CheckCircle2, Circle, ChevronRight, ArrowRight } from 'lucide-react';
import { CheckCircle2, Circle, ChevronDown, ChevronUp } from 'lucide-react';
import { useAdminTheme } from '../theme';
import { ReadinessStep } from '../hooks/useEventReadiness';
import { adminPath } from '../../constants';
@@ -11,10 +11,10 @@ import { adminPath } from '../../constants';
export function SetupChecklist({ steps, title }: { steps: ReadinessStep[]; title: string }) {
const theme = useAdminTheme();
const navigate = useNavigate();
const isAllComplete = steps.every(s => s.isComplete);
const [collapsed, setCollapsed] = React.useState(isAllComplete);
if (steps.every(s => s.isComplete)) {
return null; // Don't show if all done
}
const completedCount = steps.filter(s => s.isComplete).length;
return (
<YStack
@@ -26,66 +26,82 @@ export function SetupChecklist({ steps, title }: { steps: ReadinessStep[]; title
overflow="hidden"
style={{ boxShadow: `0 2px 8px ${theme.shadow}` }}
>
<YStack padding="$3.5" paddingBottom="$2">
<Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
{title}
</Text>
</YStack>
<Pressable onPress={() => setCollapsed(!collapsed)}>
<XStack padding="$3.5" paddingVertical="$3" alignItems="center" justifyContent="space-between">
<XStack alignItems="center" space="$2">
<Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
{title}
</Text>
{isAllComplete && (
<CheckCircle2 size={14} color={theme.successText} />
)}
</XStack>
<YStack>
{steps.map((step, index) => {
const isNext = !step.isComplete && steps.slice(0, index).every(s => s.isComplete);
<XStack alignItems="center" space="$2">
<Text fontSize="$xs" color={theme.muted} fontWeight="600">
{completedCount}/{steps.length}
</Text>
{collapsed ? <ChevronDown size={16} color={theme.muted} /> : <ChevronUp size={16} color={theme.muted} />}
</XStack>
</XStack>
</Pressable>
return (
<Pressable
key={step.id}
onPress={() => navigate(adminPath(step.targetPath))}
style={{
backgroundColor: isNext ? theme.surface : 'transparent',
}}
>
<XStack
paddingHorizontal="$3.5"
paddingVertical="$3"
alignItems="center"
space="$3"
borderTopWidth={1}
borderColor={theme.border}
>
{step.isComplete ? (
<CheckCircle2 size={20} color={theme.successText} />
) : isNext ? (
<Circle size={20} color={theme.primary} strokeWidth={2.5} />
) : (
<Circle size={20} color={theme.border} />
)}
{!collapsed && (
<YStack>
{steps.map((step, index) => {
const isNext = !step.isComplete && steps.slice(0, index).every(s => s.isComplete);
<YStack flex={1} space="$0.5">
<Text
fontSize="$sm"
fontWeight={isNext ? "700" : "500"}
color={step.isComplete ? theme.muted : theme.textStrong}
textDecorationLine={step.isComplete ? 'line-through' : 'none'}
>
{step.label}
</Text>
{step.description && !step.isComplete && (
<Text fontSize="$xs" color={theme.muted}>
{step.description}
</Text>
)}
</YStack>
return (
<Pressable
key={step.id}
onPress={() => navigate(adminPath(step.targetPath))}
style={{
backgroundColor: isNext ? theme.surface : 'transparent',
}}
>
<XStack
paddingHorizontal="$3.5"
paddingVertical="$3"
alignItems="center"
space="$3"
borderTopWidth={1}
borderColor={theme.border}
>
{step.isComplete ? (
<CheckCircle2 size={20} color={theme.successText} />
) : isNext ? (
<Circle size={20} color={theme.primary} strokeWidth={2.5} />
) : (
<Circle size={20} color={theme.border} />
)}
{isNext && (
<YStack backgroundColor={theme.primary} borderRadius={999} paddingHorizontal="$2.5" paddingVertical="$1.5">
<Text fontSize="$xs" color="white" fontWeight="700">Start</Text>
</YStack>
)}
</XStack>
</Pressable>
);
})}
</YStack>
<YStack flex={1} space="$0.5">
<Text
fontSize="$sm"
fontWeight={isNext ? "700" : "500"}
color={step.isComplete ? theme.muted : theme.textStrong}
textDecorationLine={step.isComplete ? 'line-through' : 'none'}
>
{step.label}
</Text>
{step.description && !step.isComplete && (
<Text fontSize="$xs" color={theme.muted}>
{step.description}
</Text>
)}
</YStack>
{isNext && (
<YStack backgroundColor={theme.primary} borderRadius={999} paddingHorizontal="$2.5" paddingVertical="$1.5">
<Text fontSize="$xs" color="white" fontWeight="700">Start</Text>
</YStack>
)}
</XStack>
</Pressable>
);
})}
</YStack>
)}
</YStack>
);
}