refactor(chat): split screen orchestration
Docker Image / Build and Push Docker Image (push) Successful in 14m9s
Docker Image / Build and Push Docker Image (push) Successful in 14m9s
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { type Dispatch, useEffect, useRef } from "react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { AuthEvent } from "@/stores/auth/auth-events";
|
||||
|
||||
export interface GuestLoginState {
|
||||
hasInitialized: boolean;
|
||||
isLoading: boolean;
|
||||
loginStatus: LoginStatus;
|
||||
}
|
||||
|
||||
export interface ShouldSubmitGuestLoginInput extends GuestLoginState {
|
||||
alreadyRequested: boolean;
|
||||
}
|
||||
|
||||
export interface UseChatGuestLoginInput extends GuestLoginState {
|
||||
dispatch: Dispatch<AuthEvent>;
|
||||
}
|
||||
|
||||
export function shouldSubmitGuestLogin({
|
||||
alreadyRequested,
|
||||
hasInitialized,
|
||||
isLoading,
|
||||
loginStatus,
|
||||
}: ShouldSubmitGuestLoginInput): boolean {
|
||||
if (!hasInitialized || isLoading) return false;
|
||||
if (loginStatus !== "notLoggedIn") return false;
|
||||
return !alreadyRequested;
|
||||
}
|
||||
|
||||
export function useChatGuestLogin({
|
||||
dispatch,
|
||||
hasInitialized,
|
||||
isLoading,
|
||||
loginStatus,
|
||||
}: UseChatGuestLoginInput): void {
|
||||
const guestLoginRequestedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasInitialized || isLoading) return;
|
||||
|
||||
if (loginStatus !== "notLoggedIn") {
|
||||
guestLoginRequestedRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!shouldSubmitGuestLogin({
|
||||
alreadyRequested: guestLoginRequestedRef.current,
|
||||
hasInitialized,
|
||||
isLoading,
|
||||
loginStatus,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
guestLoginRequestedRef.current = true;
|
||||
dispatch({ type: "AuthGuestLoginSubmitted" });
|
||||
}, [dispatch, hasInitialized, isLoading, loginStatus]);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { useAppNavigator } from "@/router/use-app-navigator";
|
||||
import type { ChatUpgradeReason } from "@/stores/chat/chat-state";
|
||||
import { useUserState } from "@/stores/user/user-context";
|
||||
|
||||
import {
|
||||
getInsufficientCreditsMessageLimitView,
|
||||
getInsufficientCreditsSubscriptionType,
|
||||
shouldShowMessageLimitBanner,
|
||||
type InsufficientCreditsMessageLimitView,
|
||||
} from "../chat-screen.helpers";
|
||||
|
||||
export interface UseChatMessageLimitBannerInput {
|
||||
upgradePromptVisible: boolean;
|
||||
upgradeReason: ChatUpgradeReason | null;
|
||||
loginStatus: LoginStatus;
|
||||
}
|
||||
|
||||
export interface ChatMessageLimitBannerView
|
||||
extends InsufficientCreditsMessageLimitView {
|
||||
visible: boolean;
|
||||
unlock: () => void;
|
||||
}
|
||||
|
||||
export function useChatMessageLimitBanner({
|
||||
upgradePromptVisible,
|
||||
upgradeReason,
|
||||
loginStatus,
|
||||
}: UseChatMessageLimitBannerInput): ChatMessageLimitBannerView {
|
||||
const navigator = useAppNavigator();
|
||||
const userState = useUserState();
|
||||
const view = getInsufficientCreditsMessageLimitView(loginStatus);
|
||||
|
||||
function unlock(): void {
|
||||
if (view.action === "auth") {
|
||||
navigator.openAuth(ROUTES.chat);
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.openSubscription({
|
||||
type: getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
returnTo: "chat",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...view,
|
||||
visible: shouldShowMessageLimitBanner({
|
||||
upgradePromptVisible,
|
||||
upgradeReason,
|
||||
}),
|
||||
unlock,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
openChatInExternalBrowser,
|
||||
recordExternalBrowserPromptShown,
|
||||
resolveExternalBrowserPromptEligibility,
|
||||
} from "@/lib/chat/chat_external_browser";
|
||||
|
||||
import {
|
||||
type ExternalBrowserPromptState,
|
||||
shouldStartExternalBrowserPrompt,
|
||||
} from "../chat-screen.helpers";
|
||||
|
||||
const EXTERNAL_BROWSER_PROMPT_DELAY_MS = 3000;
|
||||
|
||||
export interface ExternalBrowserPromptController {
|
||||
open: boolean;
|
||||
close: () => void;
|
||||
confirm: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useExternalBrowserPrompt({
|
||||
hasInitialized,
|
||||
isLoading,
|
||||
loginStatus,
|
||||
}: ExternalBrowserPromptState): ExternalBrowserPromptController {
|
||||
const [open, setOpen] = useState(false);
|
||||
const promptShownRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!shouldStartExternalBrowserPrompt({
|
||||
hasInitialized,
|
||||
isLoading,
|
||||
loginStatus,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mounted = true;
|
||||
let timer: number | undefined;
|
||||
|
||||
const init = async () => {
|
||||
if (promptShownRef.current) return;
|
||||
|
||||
const eligibility = await resolveExternalBrowserPromptEligibility();
|
||||
if (!mounted || !eligibility.canShow) return;
|
||||
|
||||
promptShownRef.current = true;
|
||||
timer = window.setTimeout(() => {
|
||||
if (!mounted) return;
|
||||
setOpen(true);
|
||||
void recordExternalBrowserPromptShown(eligibility.today);
|
||||
}, EXTERNAL_BROWSER_PROMPT_DELAY_MS);
|
||||
};
|
||||
|
||||
void init();
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
if (timer !== undefined) window.clearTimeout(timer);
|
||||
};
|
||||
}, [hasInitialized, isLoading, loginStatus]);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, []);
|
||||
|
||||
const confirm = useCallback(async () => {
|
||||
setOpen(false);
|
||||
await openChatInExternalBrowser();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
open,
|
||||
close,
|
||||
confirm,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user