import { useEffect, useRef, useState } from "react"; import { ActivityIndicator, Text, TouchableOpacity, View } from "react-native"; import { TrueSheet, type SheetDetent } from "@lodev09/react-native-true-sheet"; import { Ionicons } from "@expo/vector-icons"; import { useTranslation } from "react-i18next"; import { useColors } from "../lib/theme"; import { apiFetch } from "../lib/api"; import { useDeviceApprovalStore, type DeviceApprovalRecord, } from "../stores/deviceApproval"; type UserDevice = { id: string; deviceId: string; platform: string; model: string | null; name: string | null; osVersion: string | null; lastSeenAt: string; boundToPlan: string | null; isCurrent?: boolean; }; function platformIcon(p: string): React.ComponentProps["name"] { if (p === "ios") return "logo-apple"; if (p === "android") return "logo-android"; return "phone-portrait-outline"; } /** * Sheet das auf einem EXISTIERENDEN Gerät erscheint, wenn ein NEUES Gerät * Approval anfordert (Apple-Style iCloud-Sign-In Prompt). * * Zeigt: * - Info über das neue Gerät * - 6-stelligen Code (zum visuellen Vergleich) * - [Erlauben] / [Ablehnen] * * Wenn User am Device-Limit ist → zeigt zusätzlich Device-Picker: * "Welches Gerät möchtest du ersetzen?" */ export function DeviceApprovalIncomingSheet() { const { t } = useTranslation(); const colors = useColors(); const sheetRef = useRef(null); const incoming = useDeviceApprovalStore((s) => s.incoming); const approve = useDeviceApprovalStore((s) => s.approveIncoming); const reject = useDeviceApprovalStore((s) => s.rejectIncoming); const setIncoming = useDeviceApprovalStore((s) => s.setIncoming); const [devices, setDevices] = useState([]); const [max, setMax] = useState(0); const [evictId, setEvictId] = useState(null); const [busy, setBusy] = useState<"approve" | "reject" | null>(null); const [error, setError] = useState(null); useEffect(() => { if (incoming) { sheetRef.current?.present(); setError(null); setBusy(null); setEvictId(null); // Hole aktuelle Devices um zu prüfen ob Limit erreicht ist apiFetch<{ devices: UserDevice[]; max: number }>("/api/devices") .then((r) => { setDevices(r.devices ?? []); setMax(r.max ?? 0); }) .catch(() => {}); } else { sheetRef.current?.dismiss(); } }, [incoming]); if (!incoming) return null; const atLimit = devices.length >= max && max > 0; const evictableDevices = devices.filter((d) => !d.boundToPlan); const needsEviction = atLimit; async function handleApprove(rec: DeviceApprovalRecord) { setError(null); if (needsEviction && !evictId) { setError(t("device_approval.pick_to_replace")); return; } setBusy("approve"); try { await approve(needsEviction ? evictId : null); sheetRef.current?.dismiss(); } catch (e: any) { setError(e?.message ?? "Fehler"); } finally { setBusy(null); } } async function handleReject() { setBusy("reject"); try { await reject(); sheetRef.current?.dismiss(); } catch (e: any) { setError(e?.message ?? "Fehler"); } finally { setBusy(null); } } const newDeviceLabel = incoming.newName ?? incoming.newModel ?? incoming.newPlatform; return ( setIncoming(null)} > {t("device_approval.incoming_title")} {t("device_approval.incoming_subtitle", { device: newDeviceLabel })} {/* CODE BOX */} {t("device_approval.code_label")} {incoming.code} {t("device_approval.compare_hint")} {/* Eviction-Picker wenn Limit voll */} {needsEviction ? ( {t("device_approval.replace_which")} {evictableDevices.length === 0 ? ( {t("device_approval.all_devices_locked")} ) : ( evictableDevices.map((d) => { const selected = evictId === d.id; return ( setEvictId(d.id)} activeOpacity={0.7} style={{ flexDirection: "row", alignItems: "center", gap: 12, paddingHorizontal: 14, paddingVertical: 12, borderRadius: 12, borderWidth: 1, borderColor: selected ? "#007AFF" : "rgba(0,0,0,0.08)", backgroundColor: selected ? "rgba(0,122,255,0.06)" : "transparent", marginBottom: 6, }} > {d.name ?? d.model ?? d.platform} {d.model && d.name ? ( {d.model} ) : null} {selected ? ( ) : ( )} ); }) )} ) : null} {error ? ( {error} ) : null} {/* Buttons */} {busy === "reject" ? ( ) : ( {t("device_approval.reject")} )} handleApprove(incoming)} disabled={busy !== null} activeOpacity={0.7} style={{ flex: 1, paddingVertical: 14, borderRadius: 12, backgroundColor: "#007AFF", alignItems: "center", opacity: busy === "approve" ? 0.7 : 1, }} > {busy === "approve" ? ( ) : ( {t("device_approval.approve")} )} ); }