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,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} />
|
||||
|
||||
Reference in New Issue
Block a user