add tappable PWA install banner, tab bar safe area for notched devices

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 17:40:11 +01:00
parent 795662461a
commit b09a9855c9
3 changed files with 78 additions and 12 deletions

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#09090b" />
<title>TherapyFinder</title>

View File

@@ -10,7 +10,7 @@ import { dbExec } from "@/shared/hooks/use-db";
import { GKV_KASSEN } from "@/shared/lib/gkv-kassen";
import { cn } from "@/shared/lib/utils";
function detectPlatform(): "ios" | "android" | "unknown" {
export function detectPlatform(): "ios" | "android" | "unknown" {
const ua = navigator.userAgent;
if (/iPad|iPhone|iPod/.test(ua)) return "ios";
if (/Android/.test(ua)) return "android";

View File

@@ -4,9 +4,19 @@ import {
Outlet,
useMatchRoute,
} from "@tanstack/react-router";
import { ListChecks, Settings, Users } from "lucide-react";
import { useMemo } from "react";
import { isInstalledPwa } from "@/features/onboarding/components/onboarding-form";
import {
ChevronDown,
ChevronUp,
ListChecks,
Settings,
Share,
Users,
} from "lucide-react";
import { useMemo, useState } from "react";
import {
detectPlatform,
isInstalledPwa,
} from "@/features/onboarding/components/onboarding-form";
export const Route = createRootRoute({
component: () => {
@@ -18,17 +28,12 @@ export const Route = createRootRoute({
return (
<div className="flex min-h-screen flex-col bg-background text-foreground">
{!hideNav && !installed && (
<div className="bg-amber-500 px-4 py-2 text-center text-sm font-medium text-white dark:bg-amber-600">
App nicht installiert füge TherapyFinder zum Home-Bildschirm
hinzu, um Datenverlust zu vermeiden.
</div>
)}
{!hideNav && !installed && <InstallBanner />}
<main className="mx-auto w-full max-w-2xl flex-1 px-4 pb-20 pt-6">
<Outlet />
</main>
{!hideNav && (
<nav className="fixed inset-x-0 bottom-0 border-t bg-background">
<nav className="fixed inset-x-0 bottom-0 border-t bg-background pb-[env(safe-area-inset-bottom)]">
<div className="mx-auto flex max-w-2xl">
<Link
to="/prozess"
@@ -58,3 +63,64 @@ export const Route = createRootRoute({
);
},
});
function InstallBanner() {
const [expanded, setExpanded] = useState(false);
const platform = useMemo(() => detectPlatform(), []);
return (
<div className="bg-amber-500 dark:bg-amber-600">
<button
type="button"
onClick={() => setExpanded(!expanded)}
className="flex w-full items-center justify-center gap-2 px-4 py-2 text-center text-sm font-medium text-white"
>
{expanded
? "Anleitung ausblenden"
: "Hier tippen für Installationsanleitung"}
{expanded ? (
<ChevronUp className="size-4" />
) : (
<ChevronDown className="size-4" />
)}
</button>
{expanded && (
<div className="space-y-3 px-4 pb-4 text-white">
<p className="text-sm">
Installiere TherapyFinder als App, damit deine Daten sicher
gespeichert bleiben auch offline.
</p>
{platform === "ios" && (
<ol className="list-inside list-decimal space-y-1 text-sm">
<li>
Tippe unten auf das Teilen-Symbol{" "}
<Share className="inline size-4 align-text-bottom" />
</li>
<li>Wähle Zum Home-Bildschirm"</li>
<li>Tippe auf „Hinzufügen"</li>
</ol>
)}
{platform === "android" && (
<ol className="list-inside list-decimal space-y-1 text-sm">
<li>
Tippe auf das Menü{" "}
<span className="font-mono font-bold"></span> oben rechts
</li>
<li>Wähle Zum Startbildschirm hinzufügen"</li>
<li>Bestätige mit „Hinzufügen"</li>
</ol>
)}
{platform === "unknown" && (
<p className="text-sm">
Nutze die Zum Home-Bildschirm hinzufügen"-Funktion deines
Browsers.
</p>
)}
</div>
)}
</div>
);
}