106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { ArrowRight, CircleDollarSign, X } from "lucide-react";
|
|
|
|
import type { PaymentGuidance } from "@/data/schemas/chat";
|
|
import { recordChatActionEventById } from "@/lib/chat/chat_action_events";
|
|
import { useActiveCharacter } from "@/providers/character-provider";
|
|
import { useAppNavigator } from "@/router/use-app-navigator";
|
|
import { useUserSelector } from "@/stores/user/user-context";
|
|
|
|
import styles from "./chat-area.module.css";
|
|
|
|
export interface PaymentGuidanceCardProps {
|
|
guidance: PaymentGuidance;
|
|
}
|
|
|
|
export function PaymentGuidanceCard({ guidance }: PaymentGuidanceCardProps) {
|
|
const character = useActiveCharacter();
|
|
const navigator = useAppNavigator();
|
|
const isVip = useUserSelector((state) => state.context.isVip);
|
|
const [dismissed, setDismissed] = useState(false);
|
|
const viewedIdRef = useRef<string | null>(null);
|
|
const belongsToCurrentCharacter = guidance.characterId === character.id;
|
|
const hasPurchaseAction =
|
|
guidance.mode === "guide" &&
|
|
guidance.target === "subscription" &&
|
|
guidance.ctaLabel !== null &&
|
|
guidance.purchaseOptions.length > 0;
|
|
|
|
useEffect(() => {
|
|
if (!belongsToCurrentCharacter) return;
|
|
if (viewedIdRef.current === guidance.guidanceId) return;
|
|
viewedIdRef.current = guidance.guidanceId;
|
|
void recordChatActionEventById(
|
|
guidance.guidanceId,
|
|
character.id,
|
|
"viewed",
|
|
).catch(() => undefined);
|
|
}, [belongsToCurrentCharacter, character.id, guidance.guidanceId]);
|
|
|
|
if (!belongsToCurrentCharacter || dismissed) return null;
|
|
|
|
const subscriptionType =
|
|
isVip || !guidance.purchaseOptions.includes("vip") ? "topup" : "vip";
|
|
|
|
return (
|
|
<aside
|
|
className={styles.commercialAction}
|
|
aria-label={guidance.title ?? guidance.characterName}
|
|
data-payment-guidance-id={guidance.guidanceId}
|
|
data-payment-guidance-mode={guidance.mode}
|
|
data-payment-guidance-scene={guidance.scene}
|
|
>
|
|
<div className={styles.commercialActionHeader}>
|
|
<CircleDollarSign size={18} aria-hidden="true" />
|
|
<button
|
|
type="button"
|
|
className={styles.commercialActionDismiss}
|
|
title="Dismiss"
|
|
aria-label="Dismiss"
|
|
onClick={() => {
|
|
setDismissed(true);
|
|
void recordChatActionEventById(
|
|
guidance.guidanceId,
|
|
character.id,
|
|
"dismissed",
|
|
).catch(() => undefined);
|
|
}}
|
|
>
|
|
<X size={16} aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
{guidance.title ? (
|
|
<strong>{guidance.title}</strong>
|
|
) : null}
|
|
<p className={styles.commercialActionCopy}>{guidance.copy}</p>
|
|
{hasPurchaseAction ? (
|
|
<button
|
|
type="button"
|
|
className={styles.commercialActionButton}
|
|
onClick={() => {
|
|
void recordChatActionEventById(
|
|
guidance.guidanceId,
|
|
character.id,
|
|
"opened",
|
|
).catch(() => undefined);
|
|
navigator.openSubscription({
|
|
type: subscriptionType,
|
|
returnTo: "chat",
|
|
chatActionId: guidance.guidanceId,
|
|
analytics: {
|
|
entryPoint: "chat_input",
|
|
triggerReason: "insufficient_credits",
|
|
},
|
|
});
|
|
}}
|
|
>
|
|
<span>{guidance.ctaLabel}</span>
|
|
<ArrowRight size={17} aria-hidden="true" />
|
|
</button>
|
|
) : null}
|
|
</aside>
|
|
);
|
|
}
|