"use client"; import { useCallback, useEffect, useState } from "react"; import { ApiError } from "@/lib/api-client"; import { getWalletLink, getWalletUpdate, pushWalletUpdate, type WalletUpdate, } from "@/lib/wallet-updates"; export type WalletSyncState = | "idle" | "pending" | "approved" | "denied" | "error"; // Shared wallet-sync logic for create/edit dialogs. It resolves whether the // selected patient has a linked wallet, then (after the primary save) can push // the change to their phone and poll until they approve/deny it — the same // mechanism as the standalone WalletPushDialog, lifted out for reuse. export function useWalletSync(fileNumber: string | null | undefined) { const [linked, setLinked] = useState(false); const [checking, setChecking] = useState(false); const [state, setState] = useState("idle"); const [update, setUpdate] = useState(null); const [error, setError] = useState(null); // Resolve link status whenever the chosen patient changes. A 404 simply means // "not wallet-backed", so failures collapse to `linked = false`. useEffect(() => { if (!fileNumber) { setLinked(false); return; } let active = true; setChecking(true); getWalletLink(fileNumber) .then(() => { if (active) setLinked(true); }) .catch(() => { if (active) setLinked(false); }) .finally(() => { if (active) setChecking(false); }); return () => { active = false; }; }, [fileNumber]); // Poll the pushed update until the patient approves/denies it. useEffect(() => { if (state !== "pending" || !update || update.resolvedAt) return; let active = true; const timer = setInterval(async () => { try { const fresh = await getWalletUpdate(update.id); if (!active) return; setUpdate(fresh); if (fresh.status === "approved") setState("approved"); else if (fresh.status === "denied") setState("denied"); if (fresh.resolvedAt) clearInterval(timer); } catch { /* keep polling */ } }, 3000); return () => { active = false; clearInterval(timer); }; }, [state, update]); // Authoritatively resolve link status at submit time. The `linked` state above // is populated asynchronously by the effect, so a fast save (or a transient // failure that collapsed it to false) can leave a wallet-backed patient looking // unlinked. Callers await this before deciding whether to show the wallet step, // so the decision is never made on an unresolved check. const ensureLinked = useCallback(async (): Promise => { if (!fileNumber) { setLinked(false); return false; } setChecking(true); try { await getWalletLink(fileNumber); setLinked(true); return true; } catch { setLinked(false); return false; } finally { setChecking(false); } }, [fileNumber]); const push = useCallback( async (changes: string[]) => { if (!fileNumber) return; // Never push an empty/whitespace change set — the backend rejects it (400). const clean = changes.map((c) => c.trim()).filter(Boolean); if (clean.length === 0) { setError("generic"); setState("error"); return; } setError(null); setState("pending"); try { const created = await pushWalletUpdate({ fileNumber, changes: clean }); setUpdate(created); } catch (err) { setError(err instanceof ApiError ? err.message : "generic"); setState("error"); } }, [fileNumber], ); const reset = useCallback(() => { setState("idle"); setUpdate(null); setError(null); }, []); return { linked, checking, state, update, error, ensureLinked, push, reset }; } export type UseWalletSync = ReturnType;