"use client"; import Typography from "@/components/ui/Typography/Typography"; import { buildTypographyProps } from "@/lib/funnel/mappers"; import type { DefaultTexts, FunnelDefinition, SpecialOfferScreenDefinition, } from "@/lib/funnel/types"; import { TemplateLayout } from "../layouts/TemplateLayout"; import { createTemplateLayoutProps } from "@/lib/funnel/templateHelpers"; import { usePaymentPlacement } from "@/hooks/payment/usePaymentPlacement"; import { Spinner } from "@/components/ui/spinner"; import { Currency } from "@/shared/types"; import { useClientToken } from "@/hooks/auth/useClientToken"; import { getFormattedPrice } from "@/shared/utils/price"; import { formatPeriod, formatPeriodHyphen } from "@/shared/utils/period"; import { useState } from "react"; import { getTrackingCookiesForRedirect } from "@/shared/utils/cookies"; interface SpecialOfferProps { funnel: FunnelDefinition; screen: SpecialOfferScreenDefinition; onContinue: () => void; canGoBack: boolean; onBack: () => void; screenProgress?: { current: number; total: number }; defaultTexts?: DefaultTexts; } export function SpecialOfferTemplate({ funnel, screen, canGoBack, onBack, screenProgress, defaultTexts, }: SpecialOfferProps) { const token = useClientToken(); const paymentId = "main_secret_discount"; const { placement, isLoading } = usePaymentPlacement({ funnel, paymentId }); const [isLoadingRedirect, setIsLoadingRedirect] = useState(false); const trialInterval = placement?.trialInterval || 7; const trialPeriod = placement?.trialPeriod; const variant = placement?.variants?.[0]; const productId = variant?.id || ""; const placementId = placement?.placementId || ""; const paywallId = placement?.paywallId || ""; const trialPrice = variant?.trialPrice || 0; const price = variant?.price || 0; const oldPrice = variant?.price || 0; const billingPeriod = placement?.billingPeriod; const billingInterval = placement?.billingInterval || 1; const currency = placement?.currency || Currency.USD; const paymentUrl = placement?.paymentUrl || ""; const formattedTrialPrice = getFormattedPrice(trialPrice, currency); const formattedBillingPrice = getFormattedPrice(price, currency); const trialPeriodText = formatPeriod(trialPeriod, trialInterval); const billingPeriodText = formatPeriod(billingPeriod, billingInterval); const trialPeriodHyphenText = formatPeriodHyphen(trialPeriod, trialInterval); const oldTrialPeriodText = formatPeriod(trialPeriod, 7); const handlePayClick = () => { if (isLoadingRedirect) { return; } setIsLoadingRedirect(true); const redirectUrl = `${paymentUrl}?paywallId=${paywallId}&placementId=${placementId}&productId=${productId}&jwtToken=${token}&price=${( (trialPrice || 100) / 100 ).toFixed(2)}¤cy=${currency}&${getTrackingCookiesForRedirect()}`; return window.location.replace(redirectUrl); }; const computeDiscountPercent = () => { if (!oldPrice || !trialPrice || oldPrice <= 0) return undefined; const ratio = 1 - trialPrice / oldPrice; const percent = Math.max(0, Math.min(100, Math.round(ratio * 100))); return String(percent); }; const replacePlaceholders = (text: string | undefined) => { if (!text) return ""; const values: Record = { trialPrice: formattedTrialPrice, billingPrice: formattedBillingPrice, oldTrialPrice: getFormattedPrice(oldPrice || 0, currency), discountPercent: computeDiscountPercent() ?? "", trialPeriod: trialPeriodText, billingPeriod: billingPeriodText, trialPeriodHyphen: trialPeriodHyphenText, oldTrialPeriod: oldTrialPeriodText, }; let result = text; for (const [key, value] of Object.entries(values)) { result = result.replaceAll(`{{${key}}}`, value); } return result; }; const textProps = { title: buildTypographyProps<"h2">( { ...screen.text.title, text: replacePlaceholders(screen.text.title?.text), }, { as: "h2", defaults: { font: "inter", weight: "bold", size: "xl", align: "center", className: "mt-2.5 text-[#FF0707] leading-7", }, } ), subtitle: buildTypographyProps<"h3">( { ...screen.text.subtitle, text: replacePlaceholders(screen.text.subtitle?.text), }, { as: "h3", defaults: { font: "inter", weight: "black", align: "center", className: "mt-[25px] text-[#1F2937] text-[36px]", }, } ), description: { trialPrice: buildTypographyProps<"span">( { ...screen.text.description, text: replacePlaceholders(screen.text.description?.trialPrice?.text), }, { as: "span", defaults: { font: "inter", weight: "extraBold", align: "center", className: "text-[26px] text-[#2A6AEE] inline-block", }, } ), text: buildTypographyProps<"p">( { ...screen.text.description, text: replacePlaceholders(screen.text.description?.text?.text), }, { as: "p", defaults: { font: "inter", weight: "medium", align: "center", className: "mt-[11px] text-[22px] inline-block", }, } ), oldTrialPrice: buildTypographyProps<"span">( { ...screen.text.description, text: replacePlaceholders( screen.text.description?.oldTrialPrice?.text ), }, { as: "span", defaults: { font: "inter", weight: "medium", align: "center", className: "text-[22px] inline-block", }, } ), }, }; const advantagesProps = { items: screen.advantages?.items.map((item) => { return { icon: buildTypographyProps<"span">(item.icon, { as: "span", defaults: { font: "inter", weight: "medium", size: "md", className: "text-[26px] leading-[39px] inline-block mr-1", }, }), text: buildTypographyProps<"p">( { ...item.text, text: replacePlaceholders(item.text?.text), }, { as: "p", defaults: { font: "inter", weight: "medium", size: "md", className: "text-[17px] leading-[39px] inline-block", }, } ), }; }), }; const layoutProps = createTemplateLayoutProps( { ...screen, header: { ...screen.header, showProgress: false, }, }, { canGoBack, onBack }, screenProgress, { preset: "left", actionButton: { defaultText: replacePlaceholders( defaultTexts?.nextButton || "GET {{trialPeriodHyphen}} TRIAL" ), children: isLoadingRedirect ? ( ) : ( replacePlaceholders( screen.bottomActionButton?.text || defaultTexts?.nextButton || "GET {{trialPeriodHyphen}} TRIAL" ) ), disabled: false, onClick: handlePayClick, }, } ); if (isLoading || !placement || !token) { return (
); } return (
{textProps.title && } {textProps.subtitle && } {textProps.description && ( {textProps.description.trialPrice && ( )} {textProps.description.text?.children} {textProps.description.oldTrialPrice && ( )} )} {advantagesProps.items && (
    {advantagesProps.items.map((item, index) => ( {item.icon && } {item.text && } ))}
)}
); }