import { create } from "zustand"; import { apiFetch } from "../lib/api"; /** * Apple-Style Device-Approval Store. * * Hält 2 Flows getrennt: * * 1. **outgoing** — Das AKTUELLE Gerät ist das NEUE Gerät und wartet auf * Approval. UI: Modal mit Code + Spinner + "Per Email senden". * * 2. **incoming** — Das AKTUELLE Gerät ist ein EXISTIERENDES Gerät und kriegt * eine Approval-Request push (realtime). UI: Sheet mit Code + Device-Picker * + [Erlauben] / [Ablehnen]. * * Realtime: hooks/useDeviceApprovalRealtime.ts subscribet auf * rebreak.device_approval_requests filtered by user_id und ruft * setIncoming() / refresh(). */ export type DeviceApprovalRecord = { id: string; userId: string; newDeviceId: string; newPlatform: string; newModel: string | null; newName: string | null; newOsVersion: string | null; code: string; status: "pending" | "approved" | "rejected" | "expired"; approvedByDeviceRowId: string | null; approvedAt: string | null; rejectedAt: string | null; evictedDeviceRowId: string | null; emailSentAt: string | null; createdAt: string; expiresAt: string; }; type State = { // Outgoing (this device is new, waiting for approval) outgoing: DeviceApprovalRecord | null; outgoingError: string | null; outgoingEmailSent: boolean; outgoingLoading: boolean; // Incoming (this device is existing, must approve/reject) incoming: DeviceApprovalRecord | null; // Actions — outgoing requestApproval: (deviceInfo: { deviceId: string; platform: string; model?: string | null; name?: string | null; osVersion?: string | null; }) => Promise; pollOutgoing: () => Promise; sendEmailFallback: () => Promise; clearOutgoing: () => void; // Actions — incoming setIncoming: (a: DeviceApprovalRecord | null) => void; approveIncoming: (evictDeviceRowId: string | null) => Promise; rejectIncoming: () => Promise; refreshIncomingFromServer: () => Promise; }; export const useDeviceApprovalStore = create((set, get) => ({ outgoing: null, outgoingError: null, outgoingEmailSent: false, outgoingLoading: false, incoming: null, async requestApproval(deviceInfo) { set({ outgoingLoading: true, outgoingError: null }); try { const { approval } = await apiFetch<{ approval: DeviceApprovalRecord }>( "/api/devices/approvals", { method: "POST", body: deviceInfo, skipDeviceHeader: true, } ); set({ outgoing: approval, outgoingLoading: false }); return approval; } catch (err: any) { set({ outgoingError: err?.message ?? "Fehler beim Anfordern", outgoingLoading: false, }); return null; } }, async pollOutgoing() { const cur = get().outgoing; if (!cur || cur.status !== "pending") return; try { const { approval } = await apiFetch<{ approval: DeviceApprovalRecord }>( `/api/devices/approvals/${cur.id}`, { skipDeviceHeader: true } ); set({ outgoing: approval }); } catch { // Network error → just retry next tick } }, async sendEmailFallback() { const cur = get().outgoing; if (!cur) return; set({ outgoingError: null }); try { const { approval, alreadySent } = await apiFetch<{ approval: DeviceApprovalRecord; alreadySent: boolean; }>(`/api/devices/approvals/${cur.id}/email`, { method: "POST", skipDeviceHeader: true, }); set({ outgoing: approval, outgoingEmailSent: true }); if (alreadySent) { // schon einmal raus — User informieren } } catch (err: any) { set({ outgoingError: err?.message ?? "E-Mail-Versand fehlgeschlagen" }); } }, clearOutgoing() { set({ outgoing: null, outgoingError: null, outgoingEmailSent: false, outgoingLoading: false, }); }, setIncoming(a) { set({ incoming: a }); }, async approveIncoming(evictDeviceRowId) { const cur = get().incoming; if (!cur) return; await apiFetch(`/api/devices/approvals/${cur.id}/approve`, { method: "POST", body: { evictDeviceRowId }, }); set({ incoming: null }); }, async rejectIncoming() { const cur = get().incoming; if (!cur) return; await apiFetch(`/api/devices/approvals/${cur.id}/reject`, { method: "POST", }); set({ incoming: null }); }, async refreshIncomingFromServer() { try { const { approvals } = await apiFetch<{ approvals: DeviceApprovalRecord[] }>( "/api/devices/approvals" ); // Show the newest one const newest = approvals.find((a) => a.status === "pending") ?? null; // Nur setzen wenn keine andere gerade angezeigt wird ODER newer als die aktuelle const cur = get().incoming; if (!cur || (newest && newest.id !== cur.id)) { set({ incoming: newest }); } } catch { // ignore } }, }));