feat(admin): modernize tenant admin PWA with cockpit layout and slate theme
- Replaced rainbow grid with phase-aware cockpit layout - Implemented smart lifecycle hero with readiness logic - Introduced dark command bar header with context pill and search placeholder - Updated global Tamagui theme to slate/indigo palette - Refined bottom navigation with minimalist spotlight style
This commit is contained in:
90
resources/js/admin/mobile/hooks/useEventReadiness.ts
Normal file
90
resources/js/admin/mobile/hooks/useEventReadiness.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { TenantEvent } from '../../api';
|
||||
import { adminPath } from '../../constants';
|
||||
|
||||
export type ReadinessStep = {
|
||||
id: string;
|
||||
label: string;
|
||||
isComplete: boolean;
|
||||
ctaLabel: string;
|
||||
targetPath: string;
|
||||
priority: number; // Lower is higher priority
|
||||
};
|
||||
|
||||
export type ReadinessStatus = {
|
||||
steps: ReadinessStep[];
|
||||
totalSteps: number;
|
||||
completedSteps: number;
|
||||
progress: number; // 0 to 1
|
||||
nextStep: ReadinessStep | null;
|
||||
isReady: boolean;
|
||||
};
|
||||
|
||||
export function useEventReadiness(event: TenantEvent | null): ReadinessStatus {
|
||||
if (!event) {
|
||||
return { steps: [], totalSteps: 0, completedSteps: 0, progress: 0, nextStep: null, isReady: false };
|
||||
}
|
||||
|
||||
const settings = (event.settings ?? {}) as Record<string, unknown>;
|
||||
|
||||
// 1. Basics: Date & Location
|
||||
const hasDate = Boolean(event.event_date);
|
||||
const hasLocation = Boolean(
|
||||
(settings.location as string) ||
|
||||
(settings.address as string) ||
|
||||
(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
|
||||
// We consider it "done" if there is at least one active invite token (default is created on event creation usually, but let's check)
|
||||
const hasInvite = (event.active_invites_count ?? 0) > 0 || (event.total_invites_count ?? 0) > 0;
|
||||
|
||||
const steps: ReadinessStep[] = [
|
||||
{
|
||||
id: 'basics',
|
||||
label: 'Date & Location',
|
||||
isComplete: hasDate && hasLocation,
|
||||
ctaLabel: 'Set Date & Location',
|
||||
targetPath: `/mobile/events/${event.slug}/edit`,
|
||||
priority: 1
|
||||
},
|
||||
{
|
||||
id: 'access',
|
||||
label: 'QR Codes',
|
||||
isComplete: hasInvite,
|
||||
ctaLabel: 'Get QR Code',
|
||||
targetPath: `/mobile/events/${event.slug}/qr`,
|
||||
priority: 3 // Access is usually needed after setup
|
||||
}
|
||||
];
|
||||
|
||||
if (tasksEnabled) {
|
||||
steps.push({
|
||||
id: 'tasks',
|
||||
label: 'Photo Tasks',
|
||||
isComplete: hasTasks,
|
||||
ctaLabel: 'Add Photo Tasks',
|
||||
targetPath: `/mobile/events/${event.slug}/tasks`,
|
||||
priority: 2 // Content comes before distribution
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by priority
|
||||
steps.sort((a, b) => a.priority - b.priority);
|
||||
|
||||
const completedSteps = steps.filter(s => s.isComplete).length;
|
||||
const totalSteps = steps.length;
|
||||
const nextStep = steps.find(s => !s.isComplete) ?? null;
|
||||
|
||||
return {
|
||||
steps,
|
||||
totalSteps,
|
||||
completedSteps,
|
||||
progress: totalSteps > 0 ? completedSteps / totalSteps : 0,
|
||||
nextStep,
|
||||
isReady: !nextStep
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user