Unify setup status block
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-22 17:17:10 +01:00
parent fd52f8e13d
commit 2e089f7f77
4 changed files with 75 additions and 46 deletions

View File

@@ -50,6 +50,7 @@
"readiness": { "readiness": {
"title": "Bereit für den Eventstart", "title": "Bereit für den Eventstart",
"description": "Erledige diese Schritte, damit Gäste ohne Reibung loslegen können.", "description": "Erledige diese Schritte, damit Gäste ohne Reibung loslegen können.",
"nextStepTitle": "Nächster Schritt",
"pending": "Noch offen", "pending": "Noch offen",
"complete": "Erledigt", "complete": "Erledigt",
"items": { "items": {

View File

@@ -50,6 +50,7 @@
"readiness": { "readiness": {
"title": "Ready for event day", "title": "Ready for event day",
"description": "Complete these steps so guests can join without friction.", "description": "Complete these steps so guests can join without friction.",
"nextStepTitle": "Next step",
"pending": "Pending", "pending": "Pending",
"complete": "Done", "complete": "Done",
"items": { "items": {

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { AlertCircle, Bell, CalendarDays, Camera, CheckCircle2, ChevronRight, Download, Image as ImageIcon, Layout, ListTodo, Megaphone, QrCode, Settings, ShieldCheck, Sparkles, TrendingUp, Tv, Users } from 'lucide-react'; import { AlertCircle, Bell, CalendarDays, Camera, CheckCircle2, ChevronRight, Circle, Download, Image as ImageIcon, Layout, ListTodo, Megaphone, QrCode, Settings, ShieldCheck, Sparkles, TrendingUp, Tv, Users } from 'lucide-react';
import { Button } from '@tamagui/button'; import { Button } from '@tamagui/button';
import { Card } from '@tamagui/card'; import { Card } from '@tamagui/card';
import { YGroup } from '@tamagui/group'; import { YGroup } from '@tamagui/group';
@@ -218,15 +218,6 @@ export default function MobileDashboardPage() {
variant="embedded" variant="embedded"
/> />
{/* 1b. SETUP CHECKLIST */}
{phase === 'setup' && (
<SetupChecklist
steps={readiness.steps}
title={t('dashboard:readiness.title', 'Bereit für den Eventstart')}
variant="embedded"
/>
)}
{/* 2. PULSE STRIP */} {/* 2. PULSE STRIP */}
<Separator backgroundColor={theme.border} opacity={0.6} /> <Separator backgroundColor={theme.border} opacity={0.6} />
<PulseStrip event={activeEvent} stats={stats} /> <PulseStrip event={activeEvent} stats={stats} />
@@ -398,9 +389,7 @@ function LifecycleHero({ event, stats, locale, navigate, readiness, variant = 'd
// SETUP // SETUP
const nextStep = readiness.nextStep; const nextStep = readiness.nextStep;
const ctaLabel = nextStep ? nextStep.ctaLabel : t('dashboard:onboarding.hero.cta', 'Setup Complete'); const showNextStep = Boolean(nextStep);
const ctaAction = nextStep ? () => navigate(adminPath(nextStep.targetPath)) : undefined;
const showCta = Boolean(nextStep);
return ( return (
<YStack space="$2"> <YStack space="$2">
@@ -424,24 +413,52 @@ function LifecycleHero({ event, stats, locale, navigate, readiness, variant = 'd
</YStack> </YStack>
</XStack> </XStack>
{showCta ? <Separator backgroundColor={theme.border} opacity={0.6} /> : null} {showNextStep ? (
<YStack space="$2">
{showCta ? ( <Text fontSize="$xs" fontWeight="700" color={theme.muted} textTransform="uppercase" letterSpacing={1}>
<Button {t('dashboard:readiness.nextStepTitle', 'Next step')}
onPress={ctaAction} </Text>
backgroundColor={theme.primary} <YGroup {...({ borderRadius: "$4", borderWidth: 1, borderColor: theme.border, overflow: "hidden" } as any)}>
borderColor="transparent" <YGroup.Item>
height={48} <ListItem
borderRadius={16} hoverTheme
> pressTheme
<XStack alignItems="center" space="$2"> paddingVertical="$2"
<Text fontSize="$sm" fontWeight="800" color="white"> paddingHorizontal="$3"
{ctaLabel} onPress={() => navigate(adminPath(nextStep.targetPath))}
</Text> title={
<ChevronRight size={16} color="white" /> <XStack alignItems="center" space="$2">
</XStack> <Circle size={18} color={theme.primary} strokeWidth={2.5} />
</Button> <Text fontSize="$sm" fontWeight="700" color={theme.textStrong}>
{nextStep.label}
</Text>
</XStack>
}
subTitle={
nextStep.description ? (
<Text fontSize="$xs" color={theme.muted}>
{nextStep.description}
</Text>
) : undefined
}
iconAfter={
<XStack alignItems="center" space="$1">
<PillBadge tone="success">{nextStep.ctaLabel}</PillBadge>
<ChevronRight size={16} color={theme.muted} />
</XStack>
}
/>
</YGroup.Item>
</YGroup>
</YStack>
) : null} ) : null}
<Separator backgroundColor={theme.border} opacity={0.6} />
<SetupChecklist
steps={readiness.steps}
title={t('dashboard:readiness.title', 'Bereit für den Eventstart')}
variant="inline"
/>
</YStack> </YStack>
</DashboardCard> </DashboardCard>
</YStack> </YStack>

View File

@@ -22,32 +22,21 @@ export function SetupChecklist({
}: { }: {
steps: ReadinessStep[]; steps: ReadinessStep[];
title: string; title: string;
variant?: 'card' | 'embedded'; variant?: 'card' | 'inline';
}) { }) {
const theme = useAdminTheme(); const theme = useAdminTheme();
const { t } = useTranslation('dashboard'); const { t } = useTranslation('dashboard');
const navigate = useNavigate(); const navigate = useNavigate();
const isAllComplete = steps.every(s => s.isComplete); const isAllComplete = steps.every(s => s.isComplete);
const [collapsed, setCollapsed] = React.useState(isAllComplete); const [collapsed, setCollapsed] = React.useState(true);
const isEmbedded = variant === 'embedded'; const isInline = variant === 'inline';
const completedCount = steps.filter(s => s.isComplete).length; const completedCount = steps.filter(s => s.isComplete).length;
const totalSteps = steps.length; const totalSteps = steps.length;
const progressValue = totalSteps > 0 ? Math.round((completedCount / totalSteps) * 100) : 0; const progressValue = totalSteps > 0 ? Math.round((completedCount / totalSteps) * 100) : 0;
return ( const content = (
<Card <YStack>
backgroundColor={theme.surface}
borderRadius={isEmbedded ? 16 : 20}
borderWidth={1}
borderColor={theme.border}
padding="$0"
overflow="hidden"
shadowColor={theme.shadow}
shadowOpacity={isEmbedded ? 0 : 0.16}
shadowRadius={isEmbedded ? 0 : 16}
shadowOffset={isEmbedded ? { width: 0, height: 0 } : { width: 0, height: 10 }}
>
<Pressable onPress={() => setCollapsed(!collapsed)}> <Pressable onPress={() => setCollapsed(!collapsed)}>
<YStack padding="$3" paddingVertical="$2.5" space="$2"> <YStack padding="$3" paddingVertical="$2.5" space="$2">
<XStack alignItems="center" justifyContent="space-between"> <XStack alignItems="center" justifyContent="space-between">
@@ -132,6 +121,27 @@ export function SetupChecklist({
</YGroup> </YGroup>
</YStack> </YStack>
)} )}
</YStack>
);
if (isInline) {
return content;
}
return (
<Card
backgroundColor={theme.surface}
borderRadius={20}
borderWidth={1}
borderColor={theme.border}
padding="$0"
overflow="hidden"
shadowColor={theme.shadow}
shadowOpacity={0.16}
shadowRadius={16}
shadowOffset={{ width: 0, height: 10 }}
>
{content}
</Card> </Card>
); );
} }