"use client"; import { useEffect } from "react"; import { consumePendingChatUnlock, peekPendingChatUnlock, type PendingChatUnlock, type PendingChatUnlockKind, } from "@/lib/navigation/chat_unlock_session"; import { useAppNavigator } from "@/router/use-app-navigator"; import { useChatDispatch, useChatState } from "@/stores/chat/chat-context"; import type { ChatUnlockPaywallRequest } from "@/stores/chat/chat-state"; import { useUserState } from "@/stores/user/user-context"; import { getInsufficientCreditsSubscriptionType } from "../chat-screen.helpers"; export interface UseChatUnlockNavigationFlowInput { returnUrl: string; expectedKind?: PendingChatUnlockKind; expectedMessageId?: string; ignoredKind?: PendingChatUnlockKind; enabled?: boolean; } export interface UseChatUnlockNavigationFlowOutput { unlockPaywallRequest: ChatUnlockPaywallRequest | null; requestMessageUnlock: ( messageId: string, kind: PendingChatUnlockKind, ) => void; closeInsufficientCreditsDialog: () => void; confirmInsufficientCreditsDialog: () => void; } export function useChatUnlockNavigationFlow({ returnUrl, expectedKind, expectedMessageId, ignoredKind, enabled = true, }: UseChatUnlockNavigationFlowInput): UseChatUnlockNavigationFlowOutput { const navigator = useAppNavigator(); const userState = useUserState(); const chatState = useChatState(); const chatDispatch = useChatDispatch(); const unlockPaywallRequest = getScopedUnlockPaywallRequest({ request: chatState.unlockPaywallRequest, expectedKind, expectedMessageId, ignoredKind, enabled, }); useEffect(() => { if (!enabled) return; if (!chatState.historyLoaded || !navigator.isAuthenticatedUser) return; let cancelled = false; const resumePendingUnlock = async () => { const pending = await peekPendingChatUnlock(); if ( cancelled || !pending || pending.returnUrl !== returnUrl || !matchesPendingUnlockScope({ pending, expectedKind, expectedMessageId, }) ) { return; } const consumed = await consumePendingChatUnlock(); if (cancelled || !consumed) return; chatDispatch({ type: "ChatUnlockMessageRequested", messageId: consumed.messageId, kind: consumed.kind, }); }; void resumePendingUnlock(); return () => { cancelled = true; }; }, [ chatDispatch, chatState.historyLoaded, enabled, expectedKind, expectedMessageId, ignoredKind, navigator.isAuthenticatedUser, returnUrl, ]); function requestMessageUnlock( messageId: string, kind: PendingChatUnlockKind, ): void { navigator.startMessageUnlock({ messageId, kind, returnUrl, onAuthenticated: () => { chatDispatch({ type: "ChatUnlockMessageRequested", messageId, kind, }); }, }); } function closeInsufficientCreditsDialog(): void { chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" }); } function confirmInsufficientCreditsDialog(): void { if (!unlockPaywallRequest) return; chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" }); navigator.openSubscriptionForPendingUnlock({ messageId: unlockPaywallRequest.messageId, kind: unlockPaywallRequest.kind, returnUrl, type: getInsufficientCreditsSubscriptionType(userState.isVip), }); } return { unlockPaywallRequest, requestMessageUnlock, closeInsufficientCreditsDialog, confirmInsufficientCreditsDialog, }; } function getScopedUnlockPaywallRequest(input: { request: ChatUnlockPaywallRequest | null; expectedKind?: PendingChatUnlockKind; expectedMessageId?: string; ignoredKind?: PendingChatUnlockKind; enabled?: boolean; }): ChatUnlockPaywallRequest | null { const { request, expectedKind, expectedMessageId, ignoredKind, enabled = true, } = input; if (!enabled) return null; if (!request) return null; if (ignoredKind && request.kind === ignoredKind) return null; if (expectedKind && request.kind !== expectedKind) return null; if (expectedMessageId && request.messageId !== expectedMessageId) return null; return request; } function matchesPendingUnlockScope(input: { pending: PendingChatUnlock; expectedKind?: PendingChatUnlockKind; expectedMessageId?: string; }): boolean { const { pending, expectedKind, expectedMessageId } = input; if (expectedKind && pending.kind !== expectedKind) return false; if (expectedMessageId && pending.messageId !== expectedMessageId) { return false; } return true; }