"use client"; import { useEffect, useMemo, useState } from "react"; import { BackButton } 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 type { PayChannel } from "@/data/schemas/payment"; import type { SubscriptionReturnTo } from "@/lib/navigation/subscription_exit"; import { DEFAULT_CHARACTER_SLUG } from "@/data/constants/character"; import { behaviorAnalytics, getDefaultPaymentAnalyticsContext, type PaymentAnalyticsContext, } from "@/lib/analytics"; import { getPaymentMethodConfig } from "@/lib/payment/payment_method"; import { useHasHydrated } from "@/hooks/use-has-hydrated"; import { useUserState } from "@/stores/user/user-context"; import { SubscriptionCheckoutButton, SubscriptionCoinsOfferSection, SubscriptionPaymentIssueDialog, SubscriptionPaymentSuccessDialog, SubscriptionRenewalConfirmationDialog, SubscriptionVipOfferSection, } from "./components"; import styles from "./components/subscription-screen.module.css"; import { canCheckoutSubscriptionPlan, findSelectedSubscriptionPlan, getFirstRechargeOfferView, getDefaultSubscriptionPlanId, requiresVipPlanConfirmation, toCoinsOfferPlanViews, toVipOfferPlanViews, type SubscriptionType, } from "./subscription-screen.helpers"; import { useSubscriptionPaymentFlow } from "./use-subscription-payment-flow"; export type { SubscriptionType } from "./subscription-screen.helpers"; export interface SubscriptionScreenProps { subscriptionType?: SubscriptionType; shouldResumePendingOrder?: boolean; returnTo?: SubscriptionReturnTo; initialPayChannel?: PayChannel | null; analyticsContext?: PaymentAnalyticsContext; sourceCharacterSlug?: string; initialPlanId?: string | null; commercialOfferId?: string | null; resumeOrderId?: string | null; chatActionId?: string | null; } export function SubscriptionScreen({ subscriptionType = "vip", shouldResumePendingOrder = false, returnTo = null, initialPayChannel = null, analyticsContext: providedAnalyticsContext, sourceCharacterSlug = DEFAULT_CHARACTER_SLUG, initialPlanId = null, commercialOfferId = null, resumeOrderId = null, chatActionId = null, }: SubscriptionScreenProps) { const [confirmedVipPlanIds, setConfirmedVipPlanIds] = useState< ReadonlySet >(() => new Set()); const [pendingVipPlanId, setPendingVipPlanId] = useState(null); const [showPaymentIssueDialog, setShowPaymentIssueDialog] = useState(false); const [paymentIssueNotice, setPaymentIssueNotice] = useState( null, ); const userState = useUserState(); const hasHydrated = useHasHydrated(); const countryCode = userState.currentUser?.countryCode; const paymentMethodConfig = getPaymentMethodConfig({ countryCode, requestedPayChannel: initialPayChannel, }); const renderedPaymentMethodConfig = hasHydrated ? paymentMethodConfig : { ...paymentMethodConfig, showPaymentMethodSelector: false }; const { payment, paymentDispatch, showPaymentSuccessDialog, handleBackClick, handlePaymentSuccessClose, } = useSubscriptionPaymentFlow({ subscriptionType, shouldResumePendingOrder, returnTo, sourceCharacterSlug, initialPayChannel: paymentMethodConfig.initialPayChannel, initialPlanId, commercialOfferId, resumeOrderId, chatActionId, }); const canSubscribeVip = subscriptionType === "vip"; const analyticsContext = providedAnalyticsContext ?? getDefaultPaymentAnalyticsContext(subscriptionType); const vipOfferPlans = useMemo( () => toVipOfferPlanViews(payment.plans), [payment.plans], ); const directCoinsPlans = useMemo( () => toCoinsOfferPlanViews(payment.plans), [payment.plans], ); const displayedPlans = useMemo(() => { const displayedPlanIds = [ ...(canSubscribeVip ? vipOfferPlans : []), ...directCoinsPlans, ].map((plan) => plan.id); return displayedPlanIds.flatMap((planId) => { const plan = payment.plans.find((item) => item.planId === planId); return plan ? [plan] : []; }); }, [canSubscribeVip, directCoinsPlans, payment.plans, vipOfferPlans]); usePaymentPlanAnalytics(displayedPlans, analyticsContext); const firstRechargeOffer = useMemo( () => getFirstRechargeOfferView({ isFirstRecharge: payment.isFirstRecharge, subscriptionType, vipPlans: vipOfferPlans, coinPlans: directCoinsPlans, }), [ directCoinsPlans, payment.isFirstRecharge, subscriptionType, vipOfferPlans, ], ); const selectedPlan = findSelectedSubscriptionPlan({ canSubscribeVip, selectedPlanId: payment.selectedPlanId, vipPlans: vipOfferPlans, coinPlans: directCoinsPlans, }); const isPaymentBusy = payment.isCreatingOrder || payment.isPollingOrder; const selectedPlanIsVip = vipOfferPlans.some( (plan) => plan.id === payment.selectedPlanId, ); const canActivate = selectedPlan !== null && canCheckoutSubscriptionPlan({ selectedPlanId: payment.selectedPlanId, vipPlans: vipOfferPlans, confirmedVipPlanIds, }) && !isPaymentBusy; const pendingVipPlan = vipOfferPlans.find((plan) => plan.id === pendingVipPlanId) ?? null; const handleSelectPlan = (planId: string) => { const plan = payment.plans.find((item) => item.planId === planId); if (plan) behaviorAnalytics.planClick(plan, analyticsContext); if ( requiresVipPlanConfirmation({ planId, vipPlans: vipOfferPlans, confirmedVipPlanIds, }) ) { setPendingVipPlanId(planId); return; } paymentDispatch({ type: "PaymentPlanSelected", planId }); }; const handleConfirmVipPlan = () => { if (!pendingVipPlanId) return; const planId = pendingVipPlanId; setConfirmedVipPlanIds((current) => { const next = new Set(current); next.add(planId); return next; }); paymentDispatch({ type: "PaymentPlanSelected", planId }); setPendingVipPlanId(null); }; const handlePaymentMethodChange = (payChannel: PayChannel) => { paymentDispatch({ type: "PaymentPayChannelChanged", payChannel, }); }; usePaymentMethodSelection({ config: paymentMethodConfig, currentPayChannel: payment.payChannel, isPaymentBusy, requestedPayChannel: initialPayChannel, onChange: handlePaymentMethodChange, }); useEffect(() => { const defaultPlanId = getDefaultSubscriptionPlanId({ canSubscribeVip, selectedPlanId: payment.selectedPlanId, status: payment.status, isLoadingPlans: payment.isLoadingPlans, selectedPlan, vipPlans: vipOfferPlans, coinPlans: directCoinsPlans, }); if (!defaultPlanId) return; paymentDispatch({ type: "PaymentPlanSelected", planId: defaultPlanId, }); }, [ canSubscribeVip, directCoinsPlans, payment.isLoadingPlans, paymentDispatch, payment.selectedPlanId, payment.status, selectedPlan, vipOfferPlans, ]); return (
{paymentIssueNotice ? (

{paymentIssueNotice}

) : null} {firstRechargeOffer ? (
{firstRechargeOffer.badgeText}

{firstRechargeOffer.title}

{firstRechargeOffer.subtitle}

{firstRechargeOffer.renewalNotice ? (

{firstRechargeOffer.renewalNotice}

) : null}
) : null} {payment.commercialOffer && !firstRechargeOffer ? (
Private offer

Elio got this price for you

{payment.commercialOffer.message || `Your ${payment.commercialOffer.discountPercent}% discount is already applied below.`}

) : null}
{canSubscribeVip ? ( ) : null}
setPendingVipPlanId(null)} onConfirm={handleConfirmVipPlan} /> {showPaymentIssueDialog ? ( setShowPaymentIssueDialog(false)} onSubmitted={setPaymentIssueNotice} /> ) : null}
); }