feat(chat): add support action cards and live entitlements
Docker Image / Build and Push Docker Image (push) Successful in 2m2s

This commit is contained in:
Codex
2026-07-24 17:59:57 +08:00
parent 17236bd14e
commit 30ab2c2c97
52 changed files with 1151 additions and 40 deletions
+80 -1
View File
@@ -18,9 +18,13 @@ import { clearPendingChatNavigation } from "@/lib/navigation/chat_unlock_session
import { buildGlobalPageUrl } from "@/router/global-route-context";
import { resolveAuthenticatedNavigation } from "@/router/navigation-resolver";
import { getCharacterRoutes, ROUTE_BUILDERS, ROUTES } from "@/router/routes";
import type { CommercialAction } from "@/data/schemas/chat";
import type { ChatAction, CommercialAction } from "@/data/schemas/chat";
import { paymentApi } from "@/data/services/api";
import { behaviorAnalytics } from "@/lib/analytics";
import {
recordChatActionEvent,
rememberChatActionArrival,
} from "@/lib/chat/chat_action_events";
import { MobileShell } from "@/app/_components/core";
@@ -48,6 +52,7 @@ import { useChatGuestLogin } from "./hooks/use-chat-guest-login";
import { useChatMessageLimitBanner } from "./hooks/use-chat-message-limit-banner";
import { useChatPromotionBootstrap } from "./hooks/use-chat-promotion-bootstrap";
import { useSplashLatestMessageSync } from "./hooks/use-splash-latest-message-sync";
import { resolveChatActionTarget } from "./chat-action-navigation";
const chatShellStyle = {
"--chat-message-font-size": "clamp(16px, 3.333vw, 18px)",
@@ -65,6 +70,14 @@ export function ChatScreen() {
ROUTES.profile,
characterRoutes.chat,
);
const feedbackUrl = buildGlobalPageUrl(
ROUTES.feedback,
characterRoutes.chat,
);
const coinsRulesUrl = buildGlobalPageUrl(
ROUTES.coinsRules,
characterRoutes.chat,
);
const searchParams = useSearchParams();
const state = useChatState();
const chatDispatch = useChatDispatch();
@@ -325,6 +338,69 @@ export function ChatScreen() {
);
}
function handleChatActionViewed(action: ChatAction): void {
void recordChatActionEvent(action, state.characterId, "viewed").catch(
() => undefined,
);
}
async function handleChatAction(action: ChatAction): Promise<void> {
behaviorAnalytics.elementClick(
"chat.action.open",
action.ctaLabel,
{
actionId: action.actionId,
actionKind: action.kind,
actionType: action.type,
characterId: state.characterId,
ruleId: action.ruleId,
},
);
await recordChatActionEvent(
action,
state.characterId,
"opened",
).catch(() => undefined);
const targetUrl = await resolveChatActionTarget(action, {
characterRoutes,
characterSlug: character.slug,
profileUrl,
feedbackUrl,
coinsRulesUrl,
getOrderStatus: (orderId) => paymentApi.getOrderStatus(orderId),
});
const authenticatedTarget = resolveAuthenticatedNavigation({
loginStatus: authState.loginStatus,
targetUrl,
});
rememberChatActionArrival(
action,
state.characterId,
targetUrl,
);
router.push(authenticatedTarget);
}
function handleChatActionDismiss(action: ChatAction): void {
behaviorAnalytics.elementClick(
"chat.action.dismiss",
"Dismiss chat action",
{
actionId: action.actionId,
actionKind: action.kind,
actionType: action.type,
characterId: state.characterId,
ruleId: action.ruleId,
},
);
void recordChatActionEvent(
action,
state.characterId,
"dismissed",
).catch(() => undefined);
}
return (
<MobileShell>
<div
@@ -375,6 +451,9 @@ export function ChatScreen() {
onCharacterAvatarClick={handleOpenCharacterPrivateZone}
onCommercialAction={handleCommercialAction}
onCommercialActionDismiss={handleCommercialActionDismiss}
onChatActionViewed={handleChatActionViewed}
onChatAction={handleChatAction}
onChatActionDismiss={handleChatActionDismiss}
onLoadMoreHistory={() => {
chatDispatch({ type: "ChatLoadMoreHistoryRequested" });
}}