onboarding tracking is now wired, the tour can be replayed from Settings, install‑banner reset is included, and empty states in Tasks/Members/Guest Messages now have guided CTAs.

What changed:
  - Onboarding tracking: admin_app_opened on first authenticated dashboard load; event_created, branding_configured,
    and invite_created on their respective actions.
  - Tour replay: Settings now has an “Experience” section to replay the tour (clears tour seen flag and opens via ?tour=1).
  - Empty states: Tasks, Members, and Guest Messages now include richer copy + quick actions.
  - New helpers + copy: Tour storage helpers, new translations, and related UI wiring.
This commit is contained in:
Codex Agent
2025-12-28 18:59:12 +01:00
parent d5f038d098
commit 718c129a8d
16 changed files with 454 additions and 91 deletions

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { Bell, CheckCircle2, Download, Image as ImageIcon, ListTodo, MessageCircle, QrCode, Settings, ShieldCheck, Smartphone, Users, Sparkles } from 'lucide-react';
@@ -17,7 +17,9 @@ import { useTheme } from '@tamagui/core';
import { useAdminPushSubscription } from './hooks/useAdminPushSubscription';
import { useDevicePermissions } from './hooks/useDevicePermissions';
import { useInstallPrompt } from './hooks/useInstallPrompt';
import { resolveTourStepKeys, type TourStepKey } from './lib/mobileTour';
import { getTourSeen, resolveTourStepKeys, setTourSeen, type TourStepKey } from './lib/mobileTour';
import { trackOnboarding } from '../api';
import { useAuth } from '../auth/context';
type DeviceSetupProps = {
installPrompt: ReturnType<typeof useInstallPrompt>;
@@ -26,17 +28,18 @@ type DeviceSetupProps = {
onOpenSettings: () => void;
};
const TOUR_STORAGE_KEY = 'admin-mobile-tour-v1';
export default function MobileDashboardPage() {
const navigate = useNavigate();
const location = useLocation();
const { t, i18n } = useTranslation('management');
const { events, activeEvent, hasEvents, hasMultipleEvents, isLoading, selectEvent } = useEventContext();
const { status } = useAuth();
const [fallbackEvents, setFallbackEvents] = React.useState<TenantEvent[]>([]);
const [fallbackLoading, setFallbackLoading] = React.useState(false);
const [fallbackAttempted, setFallbackAttempted] = React.useState(false);
const [tourOpen, setTourOpen] = React.useState(false);
const [tourStep, setTourStep] = React.useState(0);
const onboardingTrackedRef = React.useRef(false);
const installPrompt = useInstallPrompt();
const pushState = useAdminPushSubscription();
const devicePermissions = useDevicePermissions();
@@ -73,29 +76,55 @@ export default function MobileDashboardPage() {
const tourStepKeys = React.useMemo(() => resolveTourStepKeys(effectiveHasEvents), [effectiveHasEvents]);
React.useEffect(() => {
if (typeof window === 'undefined') {
if (status !== 'authenticated' || onboardingTrackedRef.current) {
return;
}
try {
const stored = window.localStorage.getItem(TOUR_STORAGE_KEY);
if (stored) return;
setTourOpen(true);
} catch {
setTourOpen(false);
onboardingTrackedRef.current = true;
if (typeof window !== 'undefined') {
try {
const stored = window.localStorage.getItem('admin-onboarding-opened-v1');
if (stored) {
return;
}
window.localStorage.setItem('admin-onboarding-opened-v1', '1');
} catch {
// Ignore storage errors.
}
}
}, []);
void trackOnboarding('admin_app_opened');
}, [status]);
const forceTour = React.useMemo(() => {
const params = new URLSearchParams(location.search);
return params.get('tour') === '1';
}, [location.search]);
React.useEffect(() => {
if (forceTour) {
setTourStep(0);
setTourOpen(true);
setTourSeen(false);
navigate(location.pathname, { replace: true });
return;
}
if (getTourSeen()) {
return;
}
setTourOpen(true);
}, [forceTour, location.pathname, navigate]);
const markTourSeen = React.useCallback(() => {
if (typeof window === 'undefined') {
return;
}
try {
window.localStorage.setItem(TOUR_STORAGE_KEY, 'seen');
} catch {
// Ignore storage errors; the tour will just show again.
}
setTourSeen(true);
}, []);
const closeTour = React.useCallback(() => {