119 lines
3.1 KiB
TypeScript
119 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import {
|
|
ArrowRight,
|
|
CircleDollarSign,
|
|
CircleHelp,
|
|
Gift,
|
|
Images,
|
|
LoaderCircle,
|
|
MessageSquareWarning,
|
|
UserRound,
|
|
WalletCards,
|
|
X,
|
|
type LucideIcon,
|
|
} from "lucide-react";
|
|
|
|
import type { ChatAction } from "@/data/schemas/chat";
|
|
|
|
import styles from "./chat-area.module.css";
|
|
|
|
const ACTION_ICONS: Record<ChatAction["type"], LucideIcon> = {
|
|
giftOffer: Gift,
|
|
privateAlbumOffer: Images,
|
|
openProfile: UserRound,
|
|
openWallet: WalletCards,
|
|
openCoinsRules: WalletCards,
|
|
activateVip: CircleDollarSign,
|
|
topUp: CircleDollarSign,
|
|
openFeedback: MessageSquareWarning,
|
|
resumePayment: CircleHelp,
|
|
};
|
|
|
|
export interface ChatActionCardProps {
|
|
action: ChatAction;
|
|
onViewed?: (action: ChatAction) => void;
|
|
onActivate?: (action: ChatAction) => void | Promise<void>;
|
|
onDismiss?: (action: ChatAction) => void;
|
|
}
|
|
|
|
export function ChatActionCard({
|
|
action,
|
|
onViewed,
|
|
onActivate,
|
|
onDismiss,
|
|
}: ChatActionCardProps) {
|
|
const [dismissed, setDismissed] = useState(false);
|
|
const [activating, setActivating] = useState(false);
|
|
const [activationError, setActivationError] = useState<string | null>(null);
|
|
const viewedActionIdRef = useRef<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (viewedActionIdRef.current === action.actionId) return;
|
|
viewedActionIdRef.current = action.actionId;
|
|
onViewed?.(action);
|
|
}, [action, onViewed]);
|
|
|
|
if (dismissed) return null;
|
|
const Icon = ACTION_ICONS[action.type];
|
|
|
|
return (
|
|
<aside
|
|
className={styles.commercialAction}
|
|
aria-label={action.ctaLabel}
|
|
data-chat-action={action.type}
|
|
data-chat-action-kind={action.kind}
|
|
>
|
|
<div className={styles.commercialActionHeader}>
|
|
<Icon size={18} aria-hidden="true" />
|
|
<button
|
|
type="button"
|
|
className={styles.commercialActionDismiss}
|
|
title="Dismiss"
|
|
aria-label="Dismiss"
|
|
onClick={() => {
|
|
setDismissed(true);
|
|
onDismiss?.(action);
|
|
}}
|
|
>
|
|
<X size={16} aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
<p className={styles.commercialActionCopy}>{action.copy}</p>
|
|
{activationError ? (
|
|
<p className={styles.commercialActionError} role="alert">
|
|
{activationError}
|
|
</p>
|
|
) : null}
|
|
<button
|
|
type="button"
|
|
className={styles.commercialActionButton}
|
|
disabled={activating}
|
|
onClick={() => {
|
|
setActivating(true);
|
|
setActivationError(null);
|
|
void Promise.resolve(onActivate?.(action))
|
|
.catch(() =>
|
|
setActivationError(
|
|
"This option is unavailable right now. Please try again.",
|
|
),
|
|
)
|
|
.finally(() => setActivating(false));
|
|
}}
|
|
>
|
|
<span>{action.ctaLabel}</span>
|
|
{activating ? (
|
|
<LoaderCircle
|
|
className={styles.commercialActionSpinner}
|
|
size={17}
|
|
aria-hidden="true"
|
|
/>
|
|
) : (
|
|
<ArrowRight size={17} aria-hidden="true" />
|
|
)}
|
|
</button>
|
|
</aside>
|
|
);
|
|
}
|