import { useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb";
import { useAuth } from "@/contexts/AuthContext";
import { useProfile } from "@/contexts/ProfileProvider";
import { useClientTiers } from "@/contexts/ClientTiersProvider";
import { PageMeta } from "@/contexts/MetadataContext";
import {
ArrowLeft, BookOpen, CalendarDays, Check,
House, Loader2, ShieldCheck, Tag, Zap, LockIcon,
} from "lucide-react";
import { useDateFormat } from "@/hooks/useDateFormat";
import api from "@/utils/api.util";
function formatDuration(days, unit) {
if (!days) return "Lifetime";
const UNIT_TO_DAYS = { minute: 1 / 1440, hour: 1 / 24, day: 1, month: 30, year: 365 };
const multiplier = UNIT_TO_DAYS[unit] ?? 1;
const value = Math.round((days / multiplier) * 1000) / 1000;
const label = unit ?? "day";
return `${value} ${label}${value !== 1 ? "s" : ""}`;
}
function formatCourseDuration(seconds = 0) {
if (!seconds) return null;
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
if (h && m) return `${h}h ${m}m`;
if (h) return `${h}h`;
return `${m}m`;
}
const TIER_STYLES = {
free: { badge: "bg-gradient-to-r from-lime-400 to-lime-600 text-white", icon: Tag, label: "Free" },
premium: { badge: "bg-gradient-to-r from-fuchsia-600 to-purple-600 text-white", icon: Zap, label: "Premium" },
exclusive: { badge: "bg-gradient-to-r from-rose-500 to-red-600 text-white", icon: LockIcon, label: "Exclusive" },
};
const CheckoutSkeleton = () => (
);
const Checkout = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { fmtCurrency } = useDateFormat();
const planId = searchParams.get("plan_id");
const returnToken = searchParams.get("token");
const wasCancelled = searchParams.get("cancelled") === "true";
const { user } = useAuth();
const { givenName, lastName: lastNameFromProfile, getProfile, profile } = useProfile();
const {
plans, plansLoading,
myTier, tierLoading,
checkoutLoading,
promoLoading,
getPlans, getMyTier,
validatePromo,
createOrder, captureOrder, cancelOrder,
} = useClientTiers();
const [promoCode, setPromoCode] = useState("");
const [promoResult, setPromoResult] = useState(null); // { valid, code, type, value, discount }
const [capturing, setCapturing] = useState(false);
const capturingRef = useRef(false);
useEffect(() => {
getProfile();
}, []);
useEffect(() => {
getMyTier();
if (!plans.length) getPlans();
}, [getMyTier, getPlans, plans.length]);
const plan = useMemo(
() => plans.find((p) => String(p.plan_id) === String(planId)) ?? null,
[plans, planId]
);
// Handle PayPal return after approval
useEffect(() => {
if (!returnToken || capturingRef.current) return;
capturingRef.current = true;
setCapturing(true);
captureOrder(returnToken).then((result) => {
if (result) {
navigate("/plans", { replace: true });
} else {
setCapturing(false);
capturingRef.current = false;
}
});
}, [returnToken, captureOrder, navigate]);
// Handle PayPal return after cancellation
useEffect(() => {
if (!wasCancelled) return;
const orderId = searchParams.get("token");
if (orderId) cancelOrder(orderId);
toast("PayPal checkout was cancelled.");
navigate(`/plans/checkout?plan_id=${planId}`, { replace: true });
}, [wasCancelled]); // eslint-disable-line react-hooks/exhaustive-deps
// const personalInfo = user?.personal_info ?? {};
const firstName = givenName || user?.personal_info?.name?.given_name || "";
const lastName = lastNameFromProfile || user?.personal_info?.name?.last_name || "";
const email = user?.email ?? "";
const isLoading = plansLoading || tierLoading;
// A user can hold more than one active tier concurrently — check membership
// in the full active set, not just the best one.
const isCurrent = !!plan && (myTier?.active_tiers ?? []).some((t) => t.tier === plan.tier);
const style = TIER_STYLES[plan?.tier] ?? TIER_STYLES.free;
const Icon = style.icon;
const effectivePrice = plan ? Number(plan.price) : 0;
const effectiveCurrency = plan?.currency ?? 'USD';
const subtotal = effectivePrice;
const discount = promoResult?.discount ?? 0;
const total = Math.max(subtotal - discount, 0);
const duration = formatDuration(plan?.duration_days, plan?.duration_unit);
const breadcrumbItems = [
{ label: "Home", icon: , to: "/" },
{ label: "Plans", to: "/plans" },
{ label: "Checkout" },
];
const handleApplyPromo = async () => {
if (!plan) return;
const result = await validatePromo(plan.plan_id, promoCode.trim().toUpperCase());
if (result?.valid) {
setPromoResult(result);
toast("Promo code applied.");
} else {
toast(result?.reason ?? "Invalid promo code.");
}
};
const handleRemovePromo = () => {
setPromoResult(null);
setPromoCode("");
};
const handlePayPal = async () => {
const order = await createOrder(
plan.plan_id,
promoResult?.code ?? null,
);
if (!order) return;
const approvalUrl = order.approval_url;
if (!approvalUrl) { toast("Could not get PayPal approval URL."); return; }
window.location.href = approvalUrl;
};
if (capturing) {
return (
);
}
if (isLoading) return ;
if (!planId || !plan) {
return (
No plan selected
Select a subscription plan before continuing to checkout.
);
}
if (!plan.is_active) {
return (
Plan Not Available
The {plan.label} plan
is not available for purchase at the moment.
);
}
return (
Review Plan
Confirm the subscription you are about to purchase.
{style.label}
{duration}
{plan.label}
{plan.course_count ?? plan.courses?.length ?? 0} course
{(plan.course_count ?? plan.courses?.length ?? 0) === 1 ? "" : "s"} included
{fmtCurrency(effectivePrice, effectiveCurrency)}
Bundle
{plan.courses?.length > 0 ? (
{plan.courses.map((course) => (
{course.title}
{course.level && (
{course.level}
)}
{formatCourseDuration(course.duration_seconds) && (
{formatCourseDuration(course.duration_seconds)}
)}
))}
) : (
Access to free course content.
)}
Billing Information
This uses the profile details on your account.
Price Breakdown
Payment will be processed through PayPal.
Plan Price
{fmtCurrency(subtotal, effectiveCurrency)}
{promoResult?.valid && (
Promo ({promoResult.code})
-{fmtCurrency(promoResult.discount, effectiveCurrency)}
)}
{promoResult?.valid ? (
{promoResult.code}
) : (
setPromoCode(e.target.value)}
disabled={promoLoading || checkoutLoading}
onKeyDown={(e) => e.key === "Enter" && promoCode.trim() && handleApplyPromo()}
/>
)}
Total
{fmtCurrency(total, effectiveCurrency)}
{isCurrent && (
You already have an active {plan.tier} plan — this purchase will extend it by {duration}, instead of starting a new one.
)}
Secure payment powered by PayPal
Access starts after successful payment capture.
);
};
export default Checkout;