Files
cozsweet-frontend-nextjs/src/app/chat/hooks/use-chat-unlock-navigation-flow.ts
T

177 lines
4.6 KiB
TypeScript

"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;
}