"use client"; import { useEffect, useMemo, useState, type CSSProperties } from "react"; import Image from "next/image"; import { ArrowLeft, Heart, Sparkles } from "lucide-react"; import { CharacterAvatar } from "@/app/_components"; import { MobileShell } from "@/app/_components/core"; 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 { buildTipCoffeePath, DEFAULT_TIP_COFFEE_TYPE, getTipCoffeeOption, TIP_COFFEE_OPTIONS, type TipCoffeeType, } from "@/lib/tip/tip_coffee"; import { useAppNavigator } from "@/router/use-app-navigator"; import { useActiveCharacter, useActiveCharacterRoutes, } from "@/providers/character-provider"; import { useAuthState } from "@/stores/auth/auth-context"; import { TipCheckoutButton } from "./tip-checkout-button"; import { TipCoffeeTierSelector, type TipCoffeeTierItem, } from "./tip-coffee-tier-selector"; import { findTipCoffeePlan, formatTipPrice, isRealLoginStatus, } from "./tip-screen.helpers"; import styles from "./tip-screen.module.css"; const TIP_ANALYTICS_CONTEXT: PaymentAnalyticsContext = { entryPoint: "tip_page", triggerReason: "ad_landing", }; export interface TipScreenProps { coffeeType?: TipCoffeeType; shouldResumePendingOrder?: boolean; initialPayChannel?: PayChannel | null; } export function TipScreen({ coffeeType = DEFAULT_TIP_COFFEE_TYPE, shouldResumePendingOrder = false, initialPayChannel = null, }: TipScreenProps) { const navigator = useAppNavigator(); const character = useActiveCharacter(); const characterRoutes = useActiveCharacterRoutes(); const authState = useAuthState(); const [selectedCoffeeType, setSelectedCoffeeType] = useState(coffeeType); const coffeeOption = getTipCoffeeOption(selectedCoffeeType); const returnPath = buildTipCoffeePath( selectedCoffeeType, characterRoutes.tip, ); const resolvedInitialPayChannel = initialPayChannel ?? navigator.getDefaultPayChannel(); const { payment, paymentDispatch } = usePaymentRouteFlow({ catalog: "tip", initialPayChannel: resolvedInitialPayChannel, paymentType: "tip", shouldResumePendingOrder, }); const coffeeTiers = useMemo( () => TIP_COFFEE_OPTIONS.map((option) => ({ option, plan: findTipCoffeePlan(payment.plans, option.type), })), [payment.plans], ); const coffeePlan = coffeeTiers.find(({ option }) => option.type === selectedCoffeeType)?.plan ?? null; const priceLabel = formatTipPrice(coffeePlan, selectedCoffeeType); const availableCoffeePlans = useMemo( () => coffeeTiers.flatMap(({ plan }) => (plan ? [plan] : [])), [coffeeTiers], ); const tierItems = useMemo( () => coffeeTiers.map(({ option, plan }) => ({ type: option.type, displayName: option.displayName, priceLabel: formatTipPrice(plan, option.type), unavailable: payment.status === "ready" && !payment.isLoadingPlans && plan === null, })), [coffeeTiers, payment.isLoadingPlans, payment.status], ); usePaymentPlanAnalytics(availableCoffeePlans, TIP_ANALYTICS_CONTEXT); const isPaymentBusy = payment.isCreatingOrder || payment.isPollingOrder || payment.isPaid; const canCreateOrder = coffeePlan !== null && payment.selectedPlanId === coffeePlan.planId && payment.agreed && !payment.autoRenew && !payment.isLoadingPlans && !isPaymentBusy; const showMissingPlan = payment.status === "ready" && !payment.isLoadingPlans && coffeePlan === null; const isTierSelectionDisabled = payment.status !== "ready" || payment.isLoadingPlans || isPaymentBusy; const isAuthLoading = !authState.hasInitialized || authState.isLoading; useEffect(() => { if (payment.isLoadingPlans || payment.isCreatingOrder || payment.isPollingOrder) { return; } if (!coffeePlan) return; if (payment.selectedPlanId !== coffeePlan.planId) { paymentDispatch({ type: "PaymentPlanSelected", planId: coffeePlan.planId, }); return; } if (payment.autoRenew) { paymentDispatch({ type: "PaymentAutoRenewChanged", autoRenew: false }); return; } if (!payment.agreed) { paymentDispatch({ type: "PaymentAgreementChanged", agreed: true }); } }, [ coffeePlan, payment.agreed, payment.autoRenew, payment.isCreatingOrder, payment.isLoadingPlans, payment.isPollingOrder, payment.selectedPlanId, paymentDispatch, ]); const handleOrder = () => { if (isAuthLoading) return; if (coffeePlan) { behaviorAnalytics.planClick(coffeePlan, TIP_ANALYTICS_CONTEXT); } if (!isRealLoginStatus(authState.loginStatus)) { navigator.openAuth(returnPath); return; } if (!canCreateOrder) return; paymentDispatch({ type: "PaymentCreateOrderSubmitted", recipientCharacterId: character.id, }); }; const handleCoffeeTypeChange = (type: TipCoffeeType) => { if (isTierSelectionDisabled) return; const nextPlan = findTipCoffeePlan(payment.plans, type); if (!nextPlan) return; setSelectedCoffeeType(type); if (payment.selectedPlanId !== nextPlan.planId) { paymentDispatch({ type: "PaymentPlanSelected", planId: nextPlan.planId, }); } }; const handleBack = () => { navigator.back(); }; const handleResetPaidState = () => { paymentDispatch({ type: "PaymentReset" }); }; return (
); }