"use client"; import { useEffect } from "react"; import { BackButton, CharacterAvatar } from "@/app/_components"; import { MobileShell } from "@/app/_components/core"; import { PaymentMethodSelector } from "@/app/_components/payment/payment-method-selector"; import { usePaymentMethodSelection } from "@/app/_hooks/use-payment-method-selection"; import { usePaymentPlanAnalytics } from "@/app/_hooks/use-payment-plan-analytics"; import { usePaymentRouteFlow } from "@/app/_hooks/use-payment-route-flow"; import type { PayChannel } from "@/data/schemas/payment"; import { behaviorAnalytics, type PaymentAnalyticsContext, } from "@/lib/analytics"; import { getPaymentMethodConfig } from "@/lib/payment/payment_method"; import { useHasHydrated } from "@/hooks/use-has-hydrated"; import { buildTipGiftPath } from "@/lib/tip/tip_gift"; import { useActiveCharacter, useActiveCharacterRoutes, } from "@/providers/character-provider"; import { useUserState } from "@/stores/user/user-context"; import { TipCheckoutButton } from "./tip-checkout-button"; import { TipGiftProductSelector } from "./tip-gift-product-selector"; import { TipProductImage } from "./tip-product-image"; import { getGiftImageSources } from "./tip-screen.helpers"; import { TipSuccessView } from "./tip-success-view"; import { useTipSupportPrompt } from "./use-tip-support-prompt"; import styles from "./tip-screen.module.css"; const TIP_ANALYTICS_CONTEXT: PaymentAnalyticsContext = { entryPoint: "tip_page", triggerReason: "ad_landing", }; export interface TipScreenProps { initialCategory?: string | null; initialPlanId?: string | null; shouldResumePendingOrder?: boolean; initialPayChannel?: PayChannel | null; chatActionId?: string | null; } export function TipScreen({ initialCategory = null, initialPlanId = null, shouldResumePendingOrder = false, initialPayChannel = null, chatActionId = null, }: TipScreenProps) { const character = useActiveCharacter(); const characterRoutes = useActiveCharacterRoutes(); const userState = useUserState(); const hasHydrated = useHasHydrated(); const supportPrompt = useTipSupportPrompt(); const paymentMethodConfig = getPaymentMethodConfig({ countryCode: userState.currentUser?.countryCode, requestedPayChannel: initialPayChannel, }); const renderedPaymentMethodConfig = hasHydrated ? paymentMethodConfig : { ...paymentMethodConfig, showPaymentMethodSelector: false }; const { payment, paymentDispatch } = usePaymentRouteFlow({ catalog: "tip", characterId: character.id, initialCategory, initialPlanId, initialPayChannel: paymentMethodConfig.initialPayChannel, paymentType: "tip", shouldResumePendingOrder, chatActionId, }); const selectedCategory = payment.giftCategories.find( (category) => category.category === payment.selectedGiftCategory, ) ?? null; const visibleProducts = payment.selectedGiftCategory ? payment.giftProducts.filter( (product) => product.category === payment.selectedGiftCategory, ) : []; const selectedProduct = visibleProducts.find( (product) => product.planId === payment.selectedPlanId, ) ?? null; const selectedPlan = payment.plans.find((plan) => plan.planId === payment.selectedPlanId) ?? null; const returnPath = buildTipGiftPath( { category: payment.selectedGiftCategory, planId: payment.selectedPlanId || null, }, characterRoutes.tip, ); usePaymentPlanAnalytics(payment.plans, TIP_ANALYTICS_CONTEXT); const isPaymentBusy = payment.isCreatingOrder || payment.isPollingOrder || payment.isPaid; const canCreateOrder = selectedProduct !== null && selectedPlan !== null && payment.agreed && !payment.autoRenew && !payment.isLoadingPlans && !isPaymentBusy; const isSelectionDisabled = !["ready", "failed", "expired"].includes(payment.status) || payment.isLoadingPlans || isPaymentBusy; const catalogLoaded = payment.status === "ready" && !payment.isLoadingPlans; const showCatalogError = catalogLoaded && payment.errorMessage !== null && visibleProducts.length === 0; const showEmptyCatalog = catalogLoaded && payment.errorMessage === null && visibleProducts.length === 0; const handlePaymentMethodChange = (payChannel: PayChannel) => { paymentDispatch({ type: "PaymentPayChannelChanged", payChannel, }); }; usePaymentMethodSelection({ config: paymentMethodConfig, currentPayChannel: payment.payChannel, isPaymentBusy, requestedPayChannel: initialPayChannel, onChange: handlePaymentMethodChange, }); useEffect(() => { if (!selectedProduct || isPaymentBusy || payment.isLoadingPlans) return; if (payment.autoRenew) { paymentDispatch({ type: "PaymentAutoRenewChanged", autoRenew: false }); return; } if (!payment.agreed) { paymentDispatch({ type: "PaymentAgreementChanged", agreed: true }); } }, [ isPaymentBusy, payment.agreed, payment.autoRenew, payment.isLoadingPlans, paymentDispatch, selectedProduct, ]); const handleOrder = () => { if (!canCreateOrder || !selectedPlan) return; behaviorAnalytics.planClick(selectedPlan, TIP_ANALYTICS_CONTEXT); paymentDispatch({ type: "PaymentCreateOrderSubmitted", recipientCharacterId: character.id, }); }; const handleProductChange = (planId: string) => { if (isSelectionDisabled || planId === payment.selectedPlanId) return; if (!visibleProducts.some((product) => product.planId === planId)) return; paymentDispatch({ type: "PaymentPlanSelected", planId }); }; if (payment.isPaid) { const successProduct = payment.giftProducts.find( (product) => product.planId === payment.tipMessage?.planId, ) ?? selectedProduct; const successCategory = payment.giftCategories.find( (category) => category.category === successProduct?.category, ) ?? selectedCategory; return ( paymentDispatch({ type: "PaymentTipMessageRetryRequested" }) } onSendAgain={() => paymentDispatch({ type: "PaymentReset" })} /> ); } return (
); }