Added onboarding + a lightweight install banner to both the mobile login screen and the settings screen, with Android/Chromium

install prompt support and iOS “Share → Add to Home Screen” guidance. Also added a small helper + tests to decide
  when/which banner variant should show, and shared copy in common.json.
This commit is contained in:
Codex Agent
2025-12-28 18:26:17 +01:00
parent b780d82d62
commit d5f038d098
17 changed files with 831 additions and 8 deletions

View File

@@ -1,11 +1,13 @@
import React from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Loader2, Lock, Mail } from 'lucide-react';
import { Download, Loader2, Lock, Mail, Share2 } 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';
type LoginResponse = {
token: string;
@@ -43,8 +45,10 @@ 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();
const safeAreaStyle: React.CSSProperties = {
paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
paddingBottom: 'calc(env(safe-area-inset-bottom, 0px) + 16px)',
@@ -76,6 +80,12 @@ 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 mutation = useMutation({
mutationKey: ['tenantAdminLoginMobile'],
@@ -179,6 +189,37 @@ 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}
<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.')}
</div>