import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { api } from "../../api"; import { useToast } from "../../toast"; import { useConfirm } from "../../confirm"; import Icon from "../../components/Icon"; import IconChip from "../../components/IconChip"; /** * Gerätemarken für die Schnellerfassung per iOS-Kurzbefehl. * * Ein Kurzbefehl kann sich nicht anmelden, er braucht ein Zugangsmerkmal. Die * Marke darf ausschliesslich erfassen – nicht lesen, nicht ändern. Geht das * Telefon verloren, ist damit kein Datenabfluss möglich. * * Den Kurzbefehl fertig zum Download anzubieten geht nicht: Seit iOS 15 müssen * solche Dateien signiert sein, und signieren kann nur ein Mac mit Apple-Konto. * Deshalb steht hier eine Bauanleitung mit kopierfertigen Werten. */ export default function Geraetemarken() { const { t } = useTranslation(); const toast = useToast(); const confirm = useConfirm(); const [marken, setMarken] = useState([]); const [konten, setKonten] = useState([]); const [name, setName] = useState(""); const [konto, setKonto] = useState(""); const [frisch, setFrisch] = useState(null); // { token, name } – nur einmal sichtbar const [busy, setBusy] = useState(false); const basis = useMemo(() => window.location.origin, []); async function laden() { try { const [m, k] = await Promise.all([api.listTokens(), api.listAccounts()]); setMarken(m); setKonten(k); if (!konto && k.length) setKonto(String(k[0].id)); } catch (e) { toast(e.message, "error"); } } useEffect(() => { laden(); }, []); async function anlegen(e) { e.preventDefault(); if (!name.trim()) return; setBusy(true); try { const neu = await api.createToken({ name: name.trim(), default_account_id: konto ? Number(konto) : null, }); setFrisch(neu); setName(""); laden(); } catch (e) { toast(e.message, "error"); } finally { setBusy(false); } } async function widerrufen(m) { if (!(await confirm({ title: t("tokens.revoke"), message: t("tokens.revokeConfirm", { name: m.name }), danger: true, confirmLabel: t("tokens.revoke"), }))) return; try { await api.deleteToken(m.id); laden(); } catch (e) { toast(e.message, "error"); } } async function kopieren(text) { try { await navigator.clipboard.writeText(text); toast(t("tokens.copied")); } catch { toast(t("tokens.copyFailed"), "error"); } } const kontoName = (id) => konten.find((k) => k.id === id)?.name; return (

{t("tokens.title")}

{t("tokens.intro")}

{/* Gestapelt statt nebeneinander: Die Karte steht in einer schmalen Spalte, waagerecht quetschen sich Beschriftung und Feld ineinander. */}
{frisch && (
{t("tokens.onceOnly")}
{t("tokens.guideTitle")}

{t("tokens.guideNoDownload")}

  1. {t("tokens.step1")}
  2. {t("tokens.step2")}
  3. {t("tokens.step3")}
  4. {t("tokens.step4")}
  5. {t("tokens.step5")}

{t("tokens.guideHomeScreen")}

{t("tokens.guideAutomation")}

)} {marken.length > 0 && (
{marken.map((m) => (
{m.name}
{m.default_account_id ? kontoName(m.default_account_id) : t("tokens.noDefault")} {" · "} {m.last_used_at ? t("tokens.lastUsed") : t("tokens.neverUsed")}
))}
)}
); }