refactor: scope providers by route and use XState selectors
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||
import { shallowEqual } from "@xstate/react";
|
||||
|
||||
import {
|
||||
useChatDispatch,
|
||||
useChatSelector,
|
||||
} from "@/stores/chat/chat-context";
|
||||
import type { ChatUnlockPaywallRequest } from "@/stores/chat/chat-state";
|
||||
|
||||
import { HistoryUnlockDialog } from "./history-unlock-dialog";
|
||||
@@ -19,7 +24,15 @@ export function ChatUnlockDialogs({
|
||||
onConfirmInsufficientCreditsDialog,
|
||||
includeHistoryUnlock = true,
|
||||
}: ChatUnlockDialogsProps) {
|
||||
const chatState = useChatState();
|
||||
const chatState = useChatSelector(
|
||||
(state) => ({
|
||||
isUnlockingHistory: state.context.isUnlockingHistory,
|
||||
lockedHistoryCount: state.context.lockedHistoryCount,
|
||||
unlockHistoryError: state.context.unlockHistoryError,
|
||||
unlockHistoryPromptVisible: state.context.unlockHistoryPromptVisible,
|
||||
}),
|
||||
shallowEqual,
|
||||
);
|
||||
const chatDispatch = useChatDispatch();
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* - AI:[Avatar] [Content] [占位 spacer]
|
||||
* - 用户:[占位 spacer] [Content] [Avatar]
|
||||
*/
|
||||
import { useUserState } from "@/stores/user/user-context";
|
||||
import { useUserSelector } from "@/stores/user/user-context";
|
||||
|
||||
import { MessageAvatar } from "./message-avatar";
|
||||
import { MessageContent } from "./message-content";
|
||||
@@ -49,7 +49,7 @@ export function MessageBubble({
|
||||
onUnlockImageMessage,
|
||||
onOpenImage,
|
||||
}: MessageBubbleProps) {
|
||||
const { avatarUrl } = useUserState();
|
||||
const avatarUrl = useUserSelector((state) => state.context.avatarUrl);
|
||||
|
||||
if (isFromAI) {
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 { useUserSelector } from "@/stores/user/user-context";
|
||||
|
||||
import {
|
||||
getInsufficientCreditsMessageLimitView,
|
||||
@@ -31,7 +31,7 @@ export function useChatMessageLimitBanner({
|
||||
loginStatus,
|
||||
}: UseChatMessageLimitBannerInput): ChatMessageLimitBannerView {
|
||||
const navigator = useAppNavigator();
|
||||
const userState = useUserState();
|
||||
const isVip = useUserSelector((state) => state.context.isVip);
|
||||
const view = getInsufficientCreditsMessageLimitView(loginStatus);
|
||||
|
||||
function unlock(): void {
|
||||
@@ -41,7 +41,7 @@ export function useChatMessageLimitBanner({
|
||||
}
|
||||
|
||||
navigator.openSubscription({
|
||||
type: getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
type: getInsufficientCreditsSubscriptionType(isVip),
|
||||
returnTo: "chat",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { shallowEqual } from "@xstate/react";
|
||||
|
||||
import type { ChatLockType } from "@/data/schemas/chat";
|
||||
import {
|
||||
@@ -11,9 +12,12 @@ import {
|
||||
type PendingChatPromotion,
|
||||
} from "@/lib/navigation/chat_unlock_session";
|
||||
import { useAppNavigator } from "@/router/use-app-navigator";
|
||||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||
import {
|
||||
useChatDispatch,
|
||||
useChatSelector,
|
||||
} from "@/stores/chat/chat-context";
|
||||
import type { ChatUnlockPaywallRequest } from "@/stores/chat/chat-state";
|
||||
import { useUserState } from "@/stores/user/user-context";
|
||||
import { useUserSelector } from "@/stores/user/user-context";
|
||||
|
||||
import { getInsufficientCreditsSubscriptionType } from "../chat-screen.helpers";
|
||||
|
||||
@@ -55,8 +59,14 @@ export function useChatUnlockNavigationFlow({
|
||||
enabled = true,
|
||||
}: UseChatUnlockNavigationFlowInput): UseChatUnlockNavigationFlowOutput {
|
||||
const navigator = useAppNavigator();
|
||||
const userState = useUserState();
|
||||
const chatState = useChatState();
|
||||
const isVip = useUserSelector((state) => state.context.isVip);
|
||||
const chatState = useChatSelector(
|
||||
(state) => ({
|
||||
historyLoaded: state.context.historyLoaded,
|
||||
unlockPaywallRequest: state.context.unlockPaywallRequest,
|
||||
}),
|
||||
shallowEqual,
|
||||
);
|
||||
const chatDispatch = useChatDispatch();
|
||||
const unlockPaywallRequest = getScopedUnlockPaywallRequest({
|
||||
request: chatState.unlockPaywallRequest,
|
||||
@@ -161,7 +171,7 @@ export function useChatUnlockNavigationFlow({
|
||||
clientLockId: unlockPaywallRequest.clientLockId,
|
||||
promotion: unlockPaywallRequest.promotion,
|
||||
returnUrl,
|
||||
type: getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
type: getInsufficientCreditsSubscriptionType(isVip),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { shallowEqual } from "@xstate/react";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import { useAppNavigator } from "@/router/use-app-navigator";
|
||||
import { usePaymentDispatch, usePaymentState } from "@/stores/payment";
|
||||
import { useUserState } from "@/stores/user/user-context";
|
||||
import { usePaymentDispatch, usePaymentSelector } from "@/stores/payment";
|
||||
import { useUserSelector } from "@/stores/user/user-context";
|
||||
import { getDefaultPayChannelForCountryCode } from "@/lib/payment/default_pay_channel";
|
||||
import {
|
||||
getFirstRechargeOfferBannerDismissed,
|
||||
@@ -55,12 +56,24 @@ export function useFirstRechargeOfferBanner({
|
||||
loginStatus,
|
||||
}: UseFirstRechargeOfferBannerInput): UseFirstRechargeOfferBannerOutput {
|
||||
const navigator = useAppNavigator();
|
||||
const payment = usePaymentState();
|
||||
const payment = usePaymentSelector(
|
||||
(state) => ({
|
||||
isFirstRecharge: state.context.isFirstRecharge,
|
||||
status: String(state.value),
|
||||
}),
|
||||
shallowEqual,
|
||||
);
|
||||
const paymentDispatch = usePaymentDispatch();
|
||||
const userState = useUserState();
|
||||
const user = useUserSelector(
|
||||
(state) => ({
|
||||
countryCode: state.context.currentUser?.countryCode,
|
||||
id: state.context.currentUser?.id ?? null,
|
||||
}),
|
||||
shallowEqual,
|
||||
);
|
||||
const isAuthenticatedUser =
|
||||
loginStatus !== "notLoggedIn" && loginStatus !== "guest";
|
||||
const userId = userState.currentUser?.id ?? null;
|
||||
const userId = user.id;
|
||||
const hasUserIdentity = !isAuthenticatedUser || Boolean(userId);
|
||||
const [dismissalState, setDismissalState] = useState<DismissalState>(() => ({
|
||||
userId,
|
||||
@@ -73,14 +86,14 @@ export function useFirstRechargeOfferBanner({
|
||||
if (!isAuthenticatedUser) return;
|
||||
if (payment.status !== "idle") return;
|
||||
const defaultPayChannel = getDefaultPayChannelForCountryCode(
|
||||
userState.currentUser?.countryCode,
|
||||
user.countryCode,
|
||||
);
|
||||
paymentDispatch({ type: "PaymentInit", payChannel: defaultPayChannel });
|
||||
}, [
|
||||
payment.status,
|
||||
paymentDispatch,
|
||||
isAuthenticatedUser,
|
||||
userState.currentUser?.countryCode,
|
||||
user.countryCode,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { ChatRouteProviders } from "@/providers/chat-route-providers";
|
||||
|
||||
export default function ChatLayout({ children }: { children: ReactNode }) {
|
||||
return <ChatRouteProviders>{children}</ChatRouteProviders>;
|
||||
}
|
||||
Reference in New Issue
Block a user