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,7 +1,7 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Shield, Bell, User, Smartphone } from 'lucide-react';
import { Shield, Bell, User, Smartphone, Sparkles } from 'lucide-react';
import { YStack, XStack } from '@tamagui/stacks';
import { YGroup } from '@tamagui/group';
import { ListItem } from '@tamagui/list-item';
@@ -17,13 +17,14 @@ import {
NotificationPreferences,
} from '../api';
import { getApiErrorMessage } from '../lib/apiError';
import { adminPath } from '../constants';
import { adminPath, ADMIN_HOME_PATH } from '../constants';
import { useAdminPushSubscription } from './hooks/useAdminPushSubscription';
import { useDevicePermissions } from './hooks/useDevicePermissions';
import { type PermissionStatus, type StorageStatus } from './lib/devicePermissions';
import { useInstallPrompt } from './hooks/useInstallPrompt';
import { resolveInstallBannerState } from './lib/installBanner';
import { getInstallBannerDismissed, setInstallBannerDismissed, shouldShowInstallBanner } from './lib/installBanner';
import { MobileInstallBanner } from './components/MobileInstallBanner';
import { setTourSeen } from './lib/mobileTour';
type PreferenceKey = keyof NotificationPreferences;
@@ -58,12 +59,16 @@ export default function MobileSettingsPage() {
const pushState = useAdminPushSubscription();
const devicePermissions = useDevicePermissions();
const installPrompt = useInstallPrompt();
const installBanner = resolveInstallBannerState({
isInstalled: installPrompt.isInstalled,
isStandalone: installPrompt.isStandalone,
canInstall: installPrompt.canInstall,
isIos: installPrompt.isIos,
});
const [installBannerDismissed, setInstallBannerDismissedState] = React.useState(() => getInstallBannerDismissed());
const installBanner = shouldShowInstallBanner(
{
isInstalled: installPrompt.isInstalled,
isStandalone: installPrompt.isStandalone,
canInstall: installPrompt.canInstall,
isIos: installPrompt.isIos,
},
installBannerDismissed,
);
const pushDescription = React.useMemo(() => {
if (!pushState.supported) {
@@ -132,6 +137,16 @@ export default function MobileSettingsPage() {
}
}, [devicePermissions.storage]);
const handleReplayTour = () => {
setTourSeen(false);
navigate(`${ADMIN_HOME_PATH}?tour=1`);
};
const handleResetInstallBanner = () => {
setInstallBannerDismissed(false);
setInstallBannerDismissedState(false);
};
const togglePref = (key: PreferenceKey) => {
setPreferences((prev) => ({
...prev,
@@ -185,6 +200,10 @@ export default function MobileSettingsPage() {
<MobileInstallBanner
state={installBanner}
onInstall={installPrompt.canInstall ? () => void installPrompt.promptInstall() : undefined}
onDismiss={() => {
setInstallBannerDismissed(true);
setInstallBannerDismissedState(true);
}}
/>
<MobileCard space="$3">
@@ -370,6 +389,32 @@ export default function MobileSettingsPage() {
) : null}
</MobileCard>
<MobileCard space="$3">
<XStack alignItems="center" space="$2">
<Sparkles size={18} color={text} />
<Text fontSize="$md" fontWeight="800" color={text}>
{t('mobileSettings.experienceTitle', 'Experience')}
</Text>
</XStack>
<Text fontSize="$sm" color={muted}>
{t('mobileSettings.experienceBody', 'Replay the quick tour or re-enable the install banner.')}
</Text>
<XStack space="$2">
<CTAButton
label={t('mobileSettings.experienceReplay', 'Replay quick tour')}
onPress={handleReplayTour}
fullWidth={false}
/>
<CTAButton
label={t('mobileSettings.experienceResetInstall', 'Show install banner')}
tone="ghost"
onPress={handleResetInstallBanner}
fullWidth={false}
disabled={!installBannerDismissed}
/>
</XStack>
</MobileCard>
<MobileCard space="$3">
<XStack alignItems="center" space="$2">
<User size={18} color={text} />