Files
turnosxpress/txclient/src/app/landing/subscription/[planid]/page.tsx
T
2026-07-17 21:28:41 -03:00

250 lines
14 KiB
TypeScript

"use client";
import ApiRequest from "@services/Api.Service";
import AnimatedContainer from "@core/app/components/AnimatedConainer/AnimatedContainer";
import SecurePage from "@core/app/components/Secure/Secure";
import { LOGIN_ACTIONS } from "@core/Store/LoginAction.Store";
import { useSessionStore } from "@core/Store/Sesion.Store";
import { useParams } from "next/navigation";
import { useSearchParams } from "next/navigation";
import { useEffect, useState, Suspense } from "react";
import {
CreateMPPreApprovalParams,
MP_PRE_APPROVAL_STATUS,
MPPreApprovalResponse,
} from "@core/Models/Subscription.Model";
import { useAlert } from "@core/Store/Alert.Store";
import { EVENT_TYPES, useEventHandlerStore } from "@core/Store/EventHandler.Store";
import { useNavigation } from "@core/app/hooks/goto";
import SuspenseLoading from "@core/app/components/SuspendeLoading/SuspenseLoading";
import { IPlan } from "@core/Models/Plans.model";
import styles from "./page.module.css";
type UrlData = {
planid: string;
};
function SubscriptionAddContent() {
const data = useParams<UrlData>();
const searchParams = useSearchParams();
const id = data.planid;
const isRenewal = searchParams.get("renew") === "1";
const SessionInfo = useSessionStore();
const alert = useAlert();
const eventHandler = useEventHandlerStore();
const { goTo } = useNavigation();
const [plan, setPlan] = useState<IPlan | null>(null);
const [selectedMonths, setSelectedMonths] = useState<number>(1);
const [finalPrice, setFinalPrice] = useState<number>(0);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const getPlanInfo = () => {
eventHandler.setEventType(EVENT_TYPES.LOADING);
ApiRequest.post<IPlan[]>("plans/find", { _id: id })
.then((res) => {
if (res && res.length > 0) {
setPlan(res[0]);
setFinalPrice(res[0].price);
}
})
.catch(() => {
alert.showError("No se pudo cargar la información del plan");
})
.finally(() => {
eventHandler.setEventType(EVENT_TYPES.SLEEP);
});
};
useEffect(() => {
if (id) {
getPlanInfo();
}
}, [id]);
useEffect(() => {
if (plan) {
let discount = 0;
if (selectedMonths === 3) discount = plan.discount3Months || 0;
else if (selectedMonths === 6) discount = plan.discount6Months || 0;
else if (selectedMonths === 12) discount = plan.discount12Months || 0;
const total = plan.price * selectedMonths;
const final = total - (total * (discount / 100));
setFinalPrice(final);
}
}, [selectedMonths, plan]);
const getLink = () => {
if (isSubmitting) {
return;
}
if (SessionInfo.userId && SessionInfo.loged) {
const preApprovalData: CreateMPPreApprovalParams = {
planId: id,
sessionUser: SessionInfo.userId,
months: selectedMonths,
};
eventHandler.setEventType(EVENT_TYPES.LOADING);
setIsSubmitting(true);
ApiRequest.post<MPPreApprovalResponse>("suscriptions/preapproval", preApprovalData)
.then(async (response) => {
if (response.status === MP_PRE_APPROVAL_STATUS.READY_TO_START) {
if (plan?.price === 0) {
// Actualizamos la sesión manualmente para evitar la "race condition" con los sockets
// Esto asegura que el estado de Zustand tenga la nueva suscripción antes de ir al dashboard
await SessionInfo.checkSession();
}
goTo(response.init_point);
}
})
.catch((error) => {
alert.showError(error.format ? error.format() : "Ocurrió un error");
})
.finally(() => {
setIsSubmitting(false);
eventHandler.setEventType(EVENT_TYPES.SLEEP);
});
}
};
return (
<Suspense fallback={<SuspenseLoading />}>
<SecurePage loginAction={LOGIN_ACTIONS.PLAN_SUBSCRIPTION}>
<AnimatedContainer
color="#aeaeae"
minSize={150}
maxSize={500}
items={10}
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: "20px",
}}
>
<div className={styles.container}>
<div className={styles.card}>
<>
<div className={styles.leftPanel}>
<div style={{ marginBottom: "2rem" }}>
<span style={{ background: "rgba(255,255,255,0.2)", padding: "5px 12px", borderRadius: "20px", fontSize: "0.85rem", fontWeight: "bold", textTransform: "uppercase", letterSpacing: "1px" }}>
{isRenewal ? "Resumen de renovación" : "Resumen de contratación"}
</span>
</div>
<h1 className={styles.title}>{plan?.name}</h1>
<p className={styles.subtitle}>
{plan?.price === 0
? "Activa tu plan de forma instantánea. Sin tarjetas de crédito, sin complicaciones."
: isRenewal
? "Elegí cuántos meses querés sumar. El nuevo período se agrega al vencimiento actual cuando se apruebe el pago."
: "Selecciona la duración de tu plan. ¡Aprovechá los descuentos exclusivos pagando por más meses por adelantado!"}
</p>
{plan?.features && (
<ul className={styles.featureList}>
{plan.features.slice(0, 4).map((feature, idx) => (
<li key={idx} className={styles.featureItem}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4ade80" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
{feature}
</li>
))}
{plan.features.length > 4 && (
<li className={styles.featureItem} style={{ opacity: 0.7, fontStyle: 'italic' }}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
Y mucho más...
</li>
)}
</ul>
)}
</div>
<div className={styles.rightPanel}>
<div className={styles.monthsContainer}>
{plan?.price === 0 ? (
<div className={`${styles.monthOption} ${styles.monthOptionSelected}`} style={{ cursor: 'default' }}>
<div>
<span className={styles.monthLabel}>Suscripción Base</span>
<span className={styles.discountBadge} style={{ backgroundColor: 'var(--wine-dark)' }}>
Gratis por siempre
</span>
</div>
<div className={styles.monthPrice}>
$0
</div>
</div>
) : (
[
{ months: 1, label: '1 Mes', discount: 0 },
{ months: 3, label: '3 Meses', discount: plan?.discount3Months || 0 },
{ months: 6, label: '6 Meses', discount: plan?.discount6Months || 0 },
{ months: 12, label: '12 Meses (1 Año)', discount: plan?.discount12Months || 0 },
].map((option) => (
<div
key={option.months}
className={`${styles.monthOption} ${selectedMonths === option.months ? styles.monthOptionSelected : ''}`}
onClick={() => setSelectedMonths(option.months)}
>
<div>
<span className={styles.monthLabel}>{option.label}</span>
{option.discount > 0 && (
<span className={styles.discountBadge}>
{option.discount}% OFF
</span>
)}
</div>
<div className={styles.monthPrice}>
${plan ? ((plan.price * option.months) * (1 - option.discount / 100)).toLocaleString('es-AR') : '0'}
</div>
</div>
))
)}
</div>
<div className={styles.totalSection}>
<div className={styles.totalRow}>
<span className={styles.totalLabel}>Total a pagar hoy:</span>
<span className={styles.totalValue}>
${plan?.price === 0 ? "0" : finalPrice.toLocaleString('es-AR')}
</span>
</div>
<div className={styles.actionButtons}>
<button className={styles.btnSecondary} onClick={() => goTo("/landing/pricing")} disabled={isSubmitting}>
Volver
</button>
{plan?.price === 0 ? (
<button className={`${styles.btnPrimary} ${styles.btnFree}`} style={{ margin: 0 }} onClick={getLink} disabled={isSubmitting}>
Activar Gratis Ahora
</button>
) : (
<button className={styles.btnPrimary} onClick={getLink} disabled={isSubmitting}>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line></svg>
{isSubmitting ? "Generando pago..." : isRenewal ? "Extender con MercadoPago" : "Pagar con MercadoPago"}
</button>
)}
</div>
</div>
</div>
</>
</div>
</div>
</AnimatedContainer>
</SecurePage>
</Suspense>
);
}
export default function SubscriptionAddPage() {
return (
<Suspense fallback={<SuspenseLoading />}>
<SubscriptionAddContent />
</Suspense>
);
}