diff --git a/resources/js/admin/mobile/DashboardPage.tsx b/resources/js/admin/mobile/DashboardPage.tsx
index cac244e..9cdbad1 100644
--- a/resources/js/admin/mobile/DashboardPage.tsx
+++ b/resources/js/admin/mobile/DashboardPage.tsx
@@ -7,6 +7,7 @@ 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';
@@ -19,6 +20,7 @@ import { useAdminTheme } from './theme';
import { buildLimitWarnings } from '../lib/limitWarnings';
import { withAlpha } from './components/colors';
import { useEventReadiness } from './hooks/useEventReadiness';
+import { SetupChecklist } from './components/SetupChecklist';
// --- HELPERS ---
@@ -156,6 +158,10 @@ 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(() => {
@@ -194,8 +200,17 @@ export default function MobileDashboardPage() {
navigate={navigate}
onSwitch={() => setEventSwitcherOpen(true)}
canSwitch={hasMultipleEvents}
+ readiness={readiness}
/>
+ {/* 1b. SETUP CHECKLIST (Only in Setup Phase) */}
+ {phase === 'setup' && !readiness.isReady && (
+
+ )}
+
{/* 2. PULSE STRIP */}
@@ -234,10 +249,9 @@ function getEventPhase(event: TenantEvent): EventPhase {
return 'setup';
}
-function LifecycleHero({ event, stats, locale, navigate, onSwitch, canSwitch }: any) {
+function LifecycleHero({ event, stats, locale, navigate, onSwitch, canSwitch, readiness }: any) {
const theme = useAdminTheme();
const { t } = useTranslation(['management', 'dashboard']);
- const { completedSteps, totalSteps, nextStep } = useEventReadiness(event, t as any);
if (!event) return null;
const phase = getEventPhase(event);
@@ -320,9 +334,9 @@ function LifecycleHero({ event, stats, locale, navigate, onSwitch, canSwitch }:
);
}
- // SETUP
- const ctaLabel = nextStep ? nextStep.ctaLabel : t('dashboard:onboarding.hero.cta', 'Setup Complete');
- const ctaAction = nextStep ? () => navigate(adminPath(nextStep.targetPath)) : undefined;
+ // SETUP PHASE
+ // We removed the big button. We show high-level status.
+ const progressPercent = (readiness.progress ?? 0) * 100;
return (
@@ -344,20 +358,17 @@ function LifecycleHero({ event, stats, locale, navigate, onSwitch, canSwitch }:
-
-
- {t('management:photobooth.checklist.title', 'Setup Status')}
-
- {completedSteps}/{totalSteps} Schritte
-
-
- : }
- onPress={ctaAction}
- disabled={!nextStep}
- />
+
+
+
+ {t('management:photobooth.checklist.title', 'Setup Status')}
+
+ {readiness.completedSteps}/{readiness.totalSteps}
+
+
+
);
@@ -407,7 +418,7 @@ function UnifiedToolGrid({ event, navigate, permissions, isMember }: any) {
items: [
{ label: t('management:photos.gallery.title', 'Photos'), icon: ImageIcon, path: `/mobile/events/${slug}/control-room`, color: theme.primary },
{ label: t('management:events.quick.liveShowSettings', 'Slide Show'), icon: Tv, path: `/mobile/events/${slug}/live-show/settings`, color: '#F59E0B' },
- { label: t('management:events.quick.tasks', 'Tasks'), icon: ListTodo, path: `/mobile/events/${slug}/tasks`, color: theme.accent },
+ { label: t('management:tasks.badge', 'Tasks'), icon: ListTodo, path: `/mobile/events/${slug}/tasks`, color: theme.accent },
{ label: t('management:events.quick.photobooth', 'Photobooth'), icon: Camera, path: `/mobile/events/${slug}/photobooth`, color: '#8B5CF6' },
]
},
@@ -417,7 +428,7 @@ function UnifiedToolGrid({ event, navigate, permissions, isMember }: any) {
{ label: t('management:invites.badge', 'QR Codes'), icon: QrCode, path: `/mobile/events/${slug}/qr`, color: '#10B981' },
{ label: t('management:events.quick.guests', 'Guests'), icon: Users, path: `/mobile/events/${slug}/members`, color: theme.text },
{ label: t('management:events.quick.guestMessages', 'Messages'), icon: Megaphone, path: `/mobile/events/${slug}/guest-notifications`, color: theme.text },
- { label: t('management:events.branding.titleShort', 'Branding'), icon: Layout, path: `/mobile/events/${slug}/branding`, color: theme.text },
+ { label: t('management:branding.titleShort', 'Branding'), icon: Layout, path: `/mobile/events/${slug}/branding`, color: theme.text },
]
},
{
@@ -578,4 +589,4 @@ function EmptyState({ canManage, onCreate }: any) {
{canManage && }
);
-}
+}
\ No newline at end of file
diff --git a/resources/js/admin/mobile/components/SetupChecklist.tsx b/resources/js/admin/mobile/components/SetupChecklist.tsx
new file mode 100644
index 0000000..76fc04f
--- /dev/null
+++ b/resources/js/admin/mobile/components/SetupChecklist.tsx
@@ -0,0 +1,91 @@
+import React from 'react';
+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 { useAdminTheme } from '../theme';
+import { ReadinessStep } from '../hooks/useEventReadiness';
+import { adminPath } from '../../constants';
+
+export function SetupChecklist({ steps, title }: { steps: ReadinessStep[]; title: string }) {
+ const theme = useAdminTheme();
+ const navigate = useNavigate();
+
+ if (steps.every(s => s.isComplete)) {
+ return null; // Don't show if all done
+ }
+
+ return (
+
+
+
+ {title}
+
+
+
+
+ {steps.map((step, index) => {
+ const isNext = !step.isComplete && steps.slice(0, index).every(s => s.isComplete);
+
+ return (
+ navigate(adminPath(step.targetPath))}
+ style={{
+ backgroundColor: isNext ? theme.surface : 'transparent',
+ }}
+ >
+
+ {step.isComplete ? (
+
+ ) : isNext ? (
+
+ ) : (
+
+ )}
+
+
+
+ {step.label}
+
+ {step.description && !step.isComplete && (
+
+ {step.description}
+
+ )}
+
+
+ {isNext && (
+
+ Start
+
+ )}
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/resources/js/admin/mobile/hooks/useEventReadiness.ts b/resources/js/admin/mobile/hooks/useEventReadiness.ts
index 86f340f..92259a1 100644
--- a/resources/js/admin/mobile/hooks/useEventReadiness.ts
+++ b/resources/js/admin/mobile/hooks/useEventReadiness.ts
@@ -4,22 +4,22 @@ import { adminPath } from '../../constants';
export type ReadinessStep = {
id: string;
label: string;
+ description?: string; // New: Contextual Why
isComplete: boolean;
ctaLabel: string;
targetPath: string;
- priority: number; // Lower is higher priority
+ priority: number;
};
export type ReadinessStatus = {
steps: ReadinessStep[];
totalSteps: number;
completedSteps: number;
- progress: number; // 0 to 1
+ progress: number;
nextStep: ReadinessStep | null;
isReady: boolean;
};
-// We pass `t` (translation function) to localise the return values
export function useEventReadiness(event: TenantEvent | null, t: (key: string, fallback?: string) => string): ReadinessStatus {
if (!event) {
return { steps: [], totalSteps: 0, completedSteps: 0, progress: 0, nextStep: null, isReady: false };
@@ -27,7 +27,6 @@ export function useEventReadiness(event: TenantEvent | null, t: (key: string, fa
const settings = (event.settings ?? {}) as Record;
- // 1. Basics: Date & Location
const hasDate = Boolean(event.event_date);
const hasLocation = Boolean(
(settings.location as string) ||
@@ -35,17 +34,16 @@ export function useEventReadiness(event: TenantEvent | null, t: (key: string, fa
(settings.city as string)
);
- // 2. Engagement: Tasks (only if tasks are enabled)
const tasksEnabled = event.engagement_mode !== 'photo_only';
const hasTasks = (event.tasks_count ?? 0) > 0;
- // 3. Access: QR / Invites
const hasInvite = (event.active_invites_count ?? 0) > 0 || (event.total_invites_count ?? 0) > 0;
const steps: ReadinessStep[] = [
{
id: 'basics',
label: t('management:events.form.date', 'Datum & Ort'),
+ description: 'Grundlage für die Gäste-Info.',
isComplete: hasDate && hasLocation,
ctaLabel: t('management:events.actions.edit', 'Bearbeiten'),
targetPath: `/mobile/events/${event.slug}/edit`,
@@ -54,6 +52,7 @@ export function useEventReadiness(event: TenantEvent | null, t: (key: string, fa
{
id: 'access',
label: t('management:invites.badge', 'QR-Codes'),
+ description: 'Der Schlüssel für deine Gäste.',
ctaLabel: t('management:invites.actions.create', 'QR-Code erstellen'),
isComplete: hasInvite,
targetPath: `/mobile/events/${event.slug}/qr`,
@@ -65,6 +64,7 @@ export function useEventReadiness(event: TenantEvent | null, t: (key: string, fa
steps.push({
id: 'tasks',
label: t('management:tasks.badge', 'Aufgaben'),
+ description: 'Sorgt für 3x mehr Interaktion.',
isComplete: hasTasks,
ctaLabel: t('management:tasks.actions.assign', 'Aufgaben hinzufügen'),
targetPath: `/mobile/events/${event.slug}/tasks`,
@@ -72,7 +72,6 @@ export function useEventReadiness(event: TenantEvent | null, t: (key: string, fa
});
}
- // Sort by priority
steps.sort((a, b) => a.priority - b.priority);
const completedSteps = steps.filter(s => s.isComplete).length;
@@ -87,4 +86,4 @@ export function useEventReadiness(event: TenantEvent | null, t: (key: string, fa
nextStep,
isReady: !nextStep
};
-}
\ No newline at end of file
+}