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

View File

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