refactor(app): tighten chat and sync boundaries
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { ROUTE_BUILDERS } from "@/router/routes";
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||
import type { ChatUnlockPaywallRequest } from "@/stores/chat/chat-state";
|
||||
import { useUserState } from "@/stores/user/user-context";
|
||||
import {
|
||||
consumePendingChatUnlock,
|
||||
peekPendingChatUnlock,
|
||||
savePendingChatUnlock,
|
||||
type PendingChatUnlock,
|
||||
type PendingChatUnlockKind,
|
||||
} from "@/lib/navigation/chat_unlock_session";
|
||||
|
||||
import { getInsufficientCreditsSubscriptionType } from "../chat-screen.helpers";
|
||||
|
||||
export interface UseChatUnlockNavigationFlowInput {
|
||||
returnUrl: string;
|
||||
expectedKind?: PendingChatUnlockKind;
|
||||
expectedMessageId?: string;
|
||||
}
|
||||
|
||||
export interface UseChatUnlockNavigationFlowOutput {
|
||||
unlockPaywallRequest: ChatUnlockPaywallRequest | null;
|
||||
requestMessageUnlock: (
|
||||
messageId: string,
|
||||
kind: PendingChatUnlockKind,
|
||||
) => void;
|
||||
closeInsufficientCreditsDialog: () => void;
|
||||
confirmInsufficientCreditsDialog: () => void;
|
||||
}
|
||||
|
||||
export function useChatUnlockNavigationFlow({
|
||||
returnUrl,
|
||||
expectedKind,
|
||||
expectedMessageId,
|
||||
}: UseChatUnlockNavigationFlowInput): UseChatUnlockNavigationFlowOutput {
|
||||
const router = useRouter();
|
||||
const authState = useAuthState();
|
||||
const userState = useUserState();
|
||||
const chatState = useChatState();
|
||||
const chatDispatch = useChatDispatch();
|
||||
const isAuthenticatedUser =
|
||||
authState.loginStatus !== "notLoggedIn" &&
|
||||
authState.loginStatus !== "guest";
|
||||
const unlockPaywallRequest = getScopedUnlockPaywallRequest({
|
||||
request: chatState.unlockPaywallRequest,
|
||||
expectedKind,
|
||||
expectedMessageId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!chatState.historyLoaded || !isAuthenticatedUser) return;
|
||||
|
||||
const pending = peekPendingChatUnlock();
|
||||
if (
|
||||
!pending ||
|
||||
pending.returnUrl !== returnUrl ||
|
||||
!matchesPendingUnlockScope({
|
||||
pending,
|
||||
expectedKind,
|
||||
expectedMessageId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const consumed = consumePendingChatUnlock();
|
||||
if (!consumed) return;
|
||||
|
||||
chatDispatch({
|
||||
type: "ChatUnlockMessageRequested",
|
||||
messageId: consumed.messageId,
|
||||
kind: consumed.kind,
|
||||
});
|
||||
}, [
|
||||
chatDispatch,
|
||||
chatState.historyLoaded,
|
||||
expectedKind,
|
||||
expectedMessageId,
|
||||
isAuthenticatedUser,
|
||||
returnUrl,
|
||||
]);
|
||||
|
||||
function requestMessageUnlock(
|
||||
messageId: string,
|
||||
kind: PendingChatUnlockKind,
|
||||
): void {
|
||||
if (!isAuthenticatedUser) {
|
||||
savePendingChatUnlock({
|
||||
messageId,
|
||||
kind,
|
||||
returnUrl,
|
||||
stage: "auth",
|
||||
});
|
||||
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
chatDispatch({
|
||||
type: "ChatUnlockMessageRequested",
|
||||
messageId,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
function closeInsufficientCreditsDialog(): void {
|
||||
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
|
||||
}
|
||||
|
||||
function confirmInsufficientCreditsDialog(): void {
|
||||
if (!unlockPaywallRequest) return;
|
||||
|
||||
savePendingChatUnlock({
|
||||
messageId: unlockPaywallRequest.messageId,
|
||||
kind: unlockPaywallRequest.kind,
|
||||
returnUrl,
|
||||
stage: "payment",
|
||||
});
|
||||
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
|
||||
router.push(
|
||||
ROUTE_BUILDERS.subscription(
|
||||
getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
{ returnTo: "chat" },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
unlockPaywallRequest,
|
||||
requestMessageUnlock,
|
||||
closeInsufficientCreditsDialog,
|
||||
confirmInsufficientCreditsDialog,
|
||||
};
|
||||
}
|
||||
|
||||
function getScopedUnlockPaywallRequest(input: {
|
||||
request: ChatUnlockPaywallRequest | null;
|
||||
expectedKind?: PendingChatUnlockKind;
|
||||
expectedMessageId?: string;
|
||||
}): ChatUnlockPaywallRequest | null {
|
||||
const { request, expectedKind, expectedMessageId } = input;
|
||||
if (!request) 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;
|
||||
}
|
||||
Reference in New Issue
Block a user