Files
Arkie-Library-Frontend/src/components/InAppDownloadGuide.tsx

180 lines
6.4 KiB
TypeScript
Raw Normal View History

import { Copy, X } from "lucide-react";
import { useEffect, useState, type ReactNode } from "react";
import { createPortal } from "react-dom";
import { useI18n } from "../i18n";
import { useToast } from "./Toast";
import {
IN_APP_DOWNLOAD_GUIDE_EVENT,
type InAppDownloadGuideDetail,
} from "./messageStream/utils/downloadFile";
import { inAppBrowserName } from "../utils/inAppBrowser";
async function copyTextToClipboard(text: string): Promise<boolean> {
try {
if (
typeof navigator !== "undefined" &&
navigator.clipboard &&
typeof navigator.clipboard.writeText === "function"
) {
await navigator.clipboard.writeText(text);
return true;
}
} catch {
// fall through to legacy path
}
try {
const ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.top = "0";
ta.style.left = "0";
ta.style.opacity = "0";
document.body.append(ta);
ta.select();
const ok = document.execCommand("copy");
ta.remove();
return ok;
} catch {
return false;
}
}
export function InAppDownloadGuideProvider({
children,
}: {
children: ReactNode;
}) {
const { t } = useI18n();
const { showToast } = useToast();
const [detail, setDetail] = useState<InAppDownloadGuideDetail | null>(null);
useEffect(() => {
const onShow = (event: Event) => {
const ce = event as CustomEvent<InAppDownloadGuideDetail>;
if (!ce.detail) return;
setDetail(ce.detail);
};
window.addEventListener(IN_APP_DOWNLOAD_GUIDE_EVENT, onShow);
return () =>
window.removeEventListener(IN_APP_DOWNLOAD_GUIDE_EVENT, onShow);
}, []);
useEffect(() => {
if (!detail) return;
const onKey = (event: KeyboardEvent) => {
if (event.key === "Escape") setDetail(null);
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [detail]);
const close = () => setDetail(null);
const handleCopy = async () => {
if (!detail) return;
const ok = await copyTextToClipboard(detail.url);
if (ok) {
showToast(t("inAppDownloadCopied"));
} else {
showToast(t("inAppDownloadCopyFail"), "error");
}
};
const browser = inAppBrowserName();
const intro = browser
? t("inAppDownloadIntroNamed").replace("{browser}", browser)
: t("inAppDownloadIntro");
return (
<>
{children}
{detail
? createPortal(
<div
role="dialog"
aria-modal="true"
aria-labelledby="in-app-download-guide-title"
className="fixed inset-0 z-[140] flex items-center justify-center bg-black/70 px-4 backdrop-blur-sm"
onClick={close}
>
<div
className="w-full max-w-md overflow-hidden rounded-3xl border border-white/10 bg-[#1c1c21] text-neutral-100 shadow-2xl shadow-black/70"
onClick={(event) => event.stopPropagation()}
>
<div className="flex items-start justify-between gap-4 border-b border-white/10 px-5 py-4">
<div className="min-w-0">
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-ark-gold/80">
{t("download")}
</p>
<h2
id="in-app-download-guide-title"
className="mt-1 text-lg font-semibold text-white"
>
{t("inAppDownloadTitle")}
</h2>
</div>
<button
type="button"
onClick={close}
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white/10 text-white transition hover:bg-white/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80"
aria-label={t("cancel")}
>
<X className="h-5 w-5" />
</button>
</div>
<div className="space-y-4 px-5 py-5">
<p className="text-sm leading-6 text-neutral-300">{intro}</p>
<ol className="space-y-3 text-sm leading-6 text-neutral-100">
<li className="flex gap-3">
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-ark-gold text-sm font-bold text-black">
1
</span>
<span className="pt-0.5">
{t("inAppDownloadStepCopy")}
</span>
</li>
<li className="flex gap-3">
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-ark-gold text-sm font-bold text-black">
2
</span>
<span className="pt-0.5">
{t("inAppDownloadStepOpen")}
</span>
</li>
<li className="flex gap-3">
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-ark-gold text-sm font-bold text-black">
3
</span>
<span className="pt-0.5">
{t("inAppDownloadStepDownload")}
</span>
</li>
</ol>
<div className="rounded-2xl border border-white/10 bg-black/30 p-3">
<p className="break-all text-xs leading-5 text-neutral-300">
{detail.url}
</p>
</div>
<button
type="button"
onClick={handleCopy}
className="flex h-11 w-full items-center justify-center gap-2 rounded-full bg-ark-gold px-4 text-sm font-semibold text-black transition hover:bg-ark-gold2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-[#1c1c21]"
>
<Copy className="h-4 w-4" />
{t("copyLink")}
</button>
</div>
</div>
</div>,
document.body,
)
: null}
</>
);
}