import { useStore } from '@nanostores/react' import { useQueryClient } from '@tanstack/react-query' import { useCallback, useEffect, useRef, useState } from 'react' import { $gateway } from '@/store/gateway' import { useBillingApi } from './api' import { useBillingCopy } from './copy' import { resolveRefusal } from './errors' export type StepUpPhase = 'idle' | 'verifying' | 'waiting' export interface StepUpVerification { code: string | null url: string } export interface StepUpMessage { kind: 'error' | 'success' text: string title: string } interface StepUpVerificationPayload { user_code?: unknown verification_url?: unknown } export function useStepUpFlow() { const copy = useBillingCopy() const api = useBillingApi() const gateway = useStore($gateway) const queryClient = useQueryClient() const offRef = useRef<(() => void) | null>(null) const runningRef = useRef(false) const runIdRef = useRef(0) const [message, setMessage] = useState(null) const [phase, setPhase] = useState('idle') const [verification, setVerification] = useState(null) const unsubscribe = useCallback(() => { offRef.current?.() offRef.current = null }, []) const dismiss = useCallback(() => { runIdRef.current += 1 runningRef.current = false unsubscribe() setMessage(null) setPhase('idle') setVerification(null) }, [unsubscribe]) useEffect( () => () => { runIdRef.current += 1 runningRef.current = false unsubscribe() }, [unsubscribe] ) const openVerification = useCallback(() => { if (!verification?.url) { return } void window.hermesDesktop?.openExternal?.(verification.url) }, [verification?.url]) const start = useCallback(async () => { if (runningRef.current) { return } runningRef.current = true const runId = runIdRef.current + 1 runIdRef.current = runId unsubscribe() setMessage(null) setVerification(null) setPhase('waiting') offRef.current = gateway?.on('billing.step_up.verification', event => { const payload = event.payload const url = typeof payload?.verification_url === 'string' ? payload.verification_url : null if (!url) { return } setVerification({ code: typeof payload?.user_code === 'string' ? payload.user_code : null, url }) setPhase('verifying') }) ?? null const result = await api.stepUp() if (runIdRef.current !== runId) { return } runningRef.current = false unsubscribe() if (!result.ok) { const resolved = resolveRefusal(result.refusal, copy) setMessage({ kind: 'error', text: resolved.message, title: resolved.title }) return } if (!result.data.granted) { setMessage({ kind: 'error', text: copy.verificationDeniedMessage, title: copy.verificationDeniedTitle }) return } await Promise.all([ queryClient.invalidateQueries({ queryKey: ['billing', 'state'] }), queryClient.invalidateQueries({ queryKey: ['billing', 'subscription'] }) ]) setMessage({ kind: 'success', text: copy.verificationDoneMessage, title: copy.verificationDoneTitle }) }, [api, gateway, queryClient, unsubscribe, copy]) return { dismiss, message, openVerification, phase, start, verification } }