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:
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user