174 lines
8.0 KiB
TypeScript
174 lines
8.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Link, useTranslation } from "@/app/digital/i18n/navigation";
|
|
import DigitalThemeIcon, { type IconName } from "@/app/digital/components/DigitalThemeIcon";
|
|
|
|
type BeforeInstallPromptEvent = Event & {
|
|
prompt: () => Promise<void>;
|
|
userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }>;
|
|
};
|
|
|
|
export default function AppDownloadContent() {
|
|
const { t } = useTranslation("appDownload");
|
|
const [installPrompt, setInstallPrompt] = useState<BeforeInstallPromptEvent | null>(null);
|
|
const [installState, setInstallState] = useState<"idle" | "available" | "installed" | "manual">("idle");
|
|
|
|
useEffect(() => {
|
|
window.history.scrollRestoration = "manual";
|
|
window.scrollTo(0, 0);
|
|
return () => {
|
|
window.history.scrollRestoration = "auto";
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const onBeforeInstallPrompt = (event: Event) => {
|
|
event.preventDefault();
|
|
setInstallPrompt(event as BeforeInstallPromptEvent);
|
|
setInstallState("available");
|
|
};
|
|
const onInstalled = () => {
|
|
setInstallPrompt(null);
|
|
setInstallState("installed");
|
|
};
|
|
|
|
window.addEventListener("beforeinstallprompt", onBeforeInstallPrompt);
|
|
window.addEventListener("appinstalled", onInstalled);
|
|
return () => {
|
|
window.removeEventListener("beforeinstallprompt", onBeforeInstallPrompt);
|
|
window.removeEventListener("appinstalled", onInstalled);
|
|
};
|
|
}, []);
|
|
|
|
const features = [
|
|
{ key: "resources", icon: "book", emoji: "📚" },
|
|
{ key: "community", icon: "globe", emoji: "👥" },
|
|
{ key: "tools", icon: "tool", emoji: "🛠️" },
|
|
] as const;
|
|
|
|
const installApp = async () => {
|
|
if (!installPrompt) {
|
|
setInstallState("manual");
|
|
return;
|
|
}
|
|
|
|
await installPrompt.prompt();
|
|
const result = await installPrompt.userChoice;
|
|
setInstallPrompt(null);
|
|
setInstallState(result.outcome === "accepted" ? "installed" : "manual");
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50 dark:bg-slate-950">
|
|
<section className="pt-24 pb-20 sm:pt-28 sm:pb-28">
|
|
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
|
|
<div className="grid items-center gap-10 lg:grid-cols-[1.05fr_0.95fr]">
|
|
<div>
|
|
<div className="dn-badge mb-5 inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-600 shadow-sm dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300">
|
|
<DigitalThemeIcon name="phone" emoji="📱" className="h-4 w-4" />
|
|
{t("badge")}
|
|
</div>
|
|
<h1 className="dn-section-title text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100 sm:text-5xl">
|
|
{t("title")}
|
|
</h1>
|
|
<p className="mt-4 max-w-2xl text-lg leading-8 text-slate-600 dark:text-slate-400">
|
|
{t("subtitle")}
|
|
</p>
|
|
|
|
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
|
|
<Link
|
|
href="/"
|
|
className="dn-button-primary inline-flex items-center justify-center gap-2 rounded-full bg-[var(--dn-primary)] px-6 py-3 font-semibold text-white transition hover:bg-[var(--dn-primary-strong)]"
|
|
>
|
|
<DigitalThemeIcon name="app" emoji="📱" className="h-5 w-5" />
|
|
{t("openWeb")}
|
|
</Link>
|
|
<button
|
|
type="button"
|
|
onClick={installApp}
|
|
className="dn-button-secondary inline-flex cursor-pointer items-center justify-center gap-2 rounded-full border border-slate-200 bg-white px-6 py-3 font-semibold text-slate-700 transition hover:border-[var(--dn-primary)] hover:text-[var(--dn-primary)] dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200"
|
|
>
|
|
<DigitalThemeIcon name="phone" emoji="📱" className="h-5 w-5" />
|
|
{installState === "available" ? t("installReady") : t("install")}
|
|
</button>
|
|
</div>
|
|
|
|
<p className="mt-4 text-sm text-slate-500 dark:text-slate-400">
|
|
{installState === "installed"
|
|
? t("installed")
|
|
: installState === "manual"
|
|
? t("manualInstall")
|
|
: t("installHint")}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mx-auto w-full max-w-sm">
|
|
<div
|
|
className="dn-card relative overflow-hidden rounded-[2rem] border border-slate-200 bg-white p-4 shadow-xl dark:border-slate-800 dark:bg-slate-900"
|
|
aria-label={t("previewLabel")}
|
|
>
|
|
<div className="rounded-[1.5rem] bg-slate-950 p-3">
|
|
<div className="overflow-hidden rounded-[1.15rem] bg-slate-50 dark:bg-slate-900">
|
|
<div className="border-b border-slate-200 bg-white px-4 py-3 dark:border-slate-800 dark:bg-slate-950">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-sm font-bold text-slate-900 dark:text-slate-100">{t("previewTitle")}</span>
|
|
<span className="h-2 w-2 rounded-full bg-[var(--dn-primary)]" />
|
|
</div>
|
|
</div>
|
|
<div className="space-y-3 p-4">
|
|
<div className="rounded-xl bg-[var(--dn-primary)] p-4 text-white">
|
|
<p className="text-xs font-medium text-white/75">{t("previewKicker")}</p>
|
|
<p className="mt-1 text-lg font-bold">{t("previewRoute")}</p>
|
|
</div>
|
|
{features.map(({ key, icon, emoji }) => (
|
|
<div
|
|
key={key}
|
|
className="flex items-start gap-3 rounded-xl border border-slate-200 bg-white p-3 dark:border-slate-800 dark:bg-slate-950"
|
|
>
|
|
<span className="dn-icon-box flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-slate-50 text-[var(--dn-primary)] dark:bg-slate-800">
|
|
<DigitalThemeIcon name={icon as IconName} emoji={emoji} className="h-5 w-5" />
|
|
</span>
|
|
<span className="min-w-0">
|
|
<span className="block text-sm font-bold text-slate-900 dark:text-slate-100">
|
|
{t(`features.${key}.title`)}
|
|
</span>
|
|
<span className="mt-0.5 block text-xs leading-5 text-slate-500 dark:text-slate-400">
|
|
{t(`features.${key}.desc`)}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-12 grid gap-4 sm:grid-cols-3">
|
|
{features.map(({ key, icon, emoji }) => (
|
|
<div
|
|
key={key}
|
|
className="dn-card flex items-start gap-4 rounded-xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
|
>
|
|
<span className="dn-icon-box flex h-11 w-11 shrink-0 items-center justify-center rounded-lg bg-slate-50 text-[var(--dn-primary)] dark:bg-slate-800">
|
|
<DigitalThemeIcon name={icon as IconName} emoji={emoji} className="h-5 w-5" />
|
|
</span>
|
|
<span>
|
|
<span className="block font-bold text-slate-900 dark:text-slate-100">
|
|
{t(`features.${key}.title`)}
|
|
</span>
|
|
<span className="mt-1 block text-sm leading-6 text-slate-500 dark:text-slate-400">
|
|
{t(`features.${key}.desc`)}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|