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,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Download, Loader2, Lock, Mail, Share2 } from 'lucide-react';
|
||||
import { Loader2, Lock, Mail } from 'lucide-react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { adminPath, ADMIN_DEFAULT_AFTER_LOGIN_PATH, ADMIN_EVENTS_PATH } from '../constants';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { resolveReturnTarget } from '../lib/returnTo';
|
||||
import { useInstallPrompt } from './hooks/useInstallPrompt';
|
||||
import { resolveInstallBannerState } from './lib/installBanner';
|
||||
import { getInstallBannerDismissed, setInstallBannerDismissed, shouldShowInstallBanner } from './lib/installBanner';
|
||||
import { MobileInstallBanner } from './components/MobileInstallBanner';
|
||||
|
||||
type LoginResponse = {
|
||||
token: string;
|
||||
@@ -45,7 +46,6 @@ async function performLogin(payload: { login: string; password: string; return_t
|
||||
export default function MobileLoginPage() {
|
||||
const { status, applyToken, abilities } = useAuth();
|
||||
const { t } = useTranslation('auth');
|
||||
const { t: tc } = useTranslation('common');
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const installPrompt = useInstallPrompt();
|
||||
@@ -80,12 +80,16 @@ export default function MobileLoginPage() {
|
||||
const [login, setLogin] = React.useState('');
|
||||
const [password, setPassword] = React.useState('');
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
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 mutation = useMutation({
|
||||
mutationKey: ['tenantAdminLoginMobile'],
|
||||
@@ -189,36 +193,15 @@ export default function MobileLoginPage() {
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{installBanner ? (
|
||||
<div className="rounded-2xl border border-white/10 bg-white/5 px-4 py-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-white/10">
|
||||
{installBanner.variant === 'prompt' ? (
|
||||
<Download className="h-5 w-5 text-white/80" />
|
||||
) : (
|
||||
<Share2 className="h-5 w-5 text-white/80" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-semibold text-white">{tc('installBanner.title', 'Install Fotospiel Admin')}</p>
|
||||
<p className="text-xs text-white/70">
|
||||
{installBanner.variant === 'prompt'
|
||||
? tc('installBanner.body', 'Add the app to your home screen for faster access and offline support.')
|
||||
: tc('installBanner.iosHint', 'On iOS: Share → Add to Home Screen.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{installBanner.variant === 'prompt' ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void installPrompt.promptInstall()}
|
||||
className="mt-3 inline-flex items-center justify-center rounded-full bg-white/10 px-4 py-2 text-xs font-semibold text-white transition hover:bg-white/20"
|
||||
>
|
||||
{tc('installBanner.action', 'Install')}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<MobileInstallBanner
|
||||
state={installBanner}
|
||||
density="compact"
|
||||
onInstall={installPrompt.canInstall ? () => void installPrompt.promptInstall() : undefined}
|
||||
onDismiss={() => {
|
||||
setInstallBannerDismissed(true);
|
||||
setInstallBannerDismissedState(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="text-center text-xs text-white/60">
|
||||
{t('login.support', 'Fragen? Schreib uns an support@fotospiel.de oder antworte direkt auf deine Einladung.')}
|
||||
|
||||
Reference in New Issue
Block a user