Dev_Socialbuddy_Frontend/components/pricing-table/components-pricing-table-toggle.tsx
2026-02-21 19:04:54 +00:00

415 lines
15 KiB
TypeScript

"use client";
import React, { useState } from "react";
import { ApiBaseUrl, ApiServerBaseUrl } from "@/utils/baseurl.utils";
interface Plan {
name: string;
desc: string;
amountMonthly: number;
amountYearly: number;
features: string[];
planIdMonthly: string;
planIdYearly: string;
popular?: boolean;
}
const ComponentsPricingTableToggle = () => {
const [billingCycle, setBillingCycle] =
useState<"monthly" | "yearly">("monthly");
const [loadingPlan, setLoadingPlan] = useState<string | null>(null);
// Trial State
const [trialStatus, setTrialStatus] = useState<{
hasUsedTrial: boolean;
isTrialActive: boolean;
trialEndsAt: string | null;
stripeSessionId: string | null;
} | null>(null);
const [timeLeft, setTimeLeft] = useState("");
// Fetch subscription status on mount
React.useEffect(() => {
const fetchStatus = async () => {
const userId = localStorage.getItem("userId");
if (!userId) return;
try {
const res = await fetch(`${ApiServerBaseUrl}/payment/status?userId=${userId}`);
const data = await res.json();
setTrialStatus(data);
} catch (err) {
console.error("Failed to fetch sub status", err);
}
};
fetchStatus();
}, []);
// Timer Effect
React.useEffect(() => {
if (!trialStatus?.isTrialActive || !trialStatus.trialEndsAt) return;
const interval = setInterval(() => {
const now = new Date().getTime();
const end = new Date(trialStatus.trialEndsAt!).getTime();
const diff = end - now;
if (diff <= 0) {
clearInterval(interval);
setTimeLeft("Trial Expired");
// Refresh status to ensure strict consistency
window.location.reload();
} else {
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
setTimeLeft(`${days}d ${hours}h ${minutes}m left`);
}
}, 1000);
return () => clearInterval(interval);
}, [trialStatus]);
const handleCancelTrial = async () => {
if (!confirm("Are you sure you want to cancel your free trial? You will lose access immediately.")) return;
setLoadingPlan("cancel_trial");
try {
const res = await fetch(`${ApiServerBaseUrl}/payment/cancel`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_id: trialStatus?.stripeSessionId })
});
const data = await res.json();
localStorage.removeItem('payment_session');
localStorage.removeItem('paymentDetails');
alert(data.message);
window.location.reload();
} catch (err) {
alert("Failed to cancel trial");
}
setLoadingPlan(null);
};
const plans: Plan[] = [
{
name: "Basic Buddy",
desc: "Perfect for individual creators.",
amountMonthly: 10,
amountYearly: 100,
features: [
"1 Social Profiles",
"Basic Analytics",
"Content Scheduling",
"Standard Support",
],
planIdMonthly: "basic_monthly",
planIdYearly: "basic_yearly",
},
{
name: "Standard Buddy",
desc: "For growing businesses and influencers.",
amountMonthly: 50,
amountYearly: 500,
features: [
"10 Social Profiles",
"Advanced Analytics",
"AI Content Generation",
"Priority Support",
"Team Collaboration (up to 3)",
],
popular: true,
planIdMonthly: "standard_monthly",
planIdYearly: "standard_yearly",
},
{
name: "Premium Buddy",
desc: "Ultimate solution for agencies.",
amountMonthly: 150,
amountYearly: 1500,
features: [
"25 Social Profiles",
"White Label Reports",
"Dedicated Account Manager",
"API Access",
"Unlimited Team Members",
],
planIdMonthly: "premium_monthly",
planIdYearly: "premium_yearly",
},
];
const freeTrialPlan: Plan = {
name: "7-Day Free Trial",
desc: "Experience SocialBuddy with no commitment. Full access to all features.",
amountMonthly: 0,
amountYearly: 0,
features: [
"Full Platform Access",
"All Premium Features",
"No Credit Card Required",
"Cancel Anytime",
],
planIdMonthly: "free_trial",
planIdYearly: "free_trial",
};
const handleSubscribe = async (plan: Plan) => {
const selectedPriceId =
billingCycle === "monthly" ? plan.planIdMonthly : plan.planIdYearly;
setLoadingPlan(plan.name);
try {
const email = localStorage.getItem("user_email");
const userId: any = localStorage.getItem("userId");
if (plan.planIdMonthly === "free_trial") {
// --- FREE TRIAL FLOW ---
const res = await fetch(`${ApiServerBaseUrl}/payment/activate-trial`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, userId }),
});
const data = await res.json();
if (data.success) {
if (data.payment && data.payment.sessionId) {
localStorage.setItem('payment_session', data.payment.sessionId);
localStorage.setItem('paymentDetails', JSON.stringify(data.payment));
}
alert(data.message);
window.location.reload();
} else {
alert(data.error || "Failed to activate trial");
}
} else {
// --- STRIPE FLOW ---
const res = await fetch(
`${ApiServerBaseUrl}/payment/create-checkout-session`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email,
userId,
planId: selectedPriceId,
}),
}
);
const data = await res.json();
if (data.url) {
window.location.href = data.url;
} else {
alert("Stripe session creation failed.");
}
}
} catch (error) {
alert("Payment error — check console.");
console.error(error);
}
setLoadingPlan(null);
};
return (
<div className="">
{/* Billing Toggle */}
<div className="flex justify-center mb-16">
<div className="glass-card rounded-full p-1 flex">
<button
onClick={() => setBillingCycle("monthly")}
className={`px-8 py-3 rounded-full text-sm font-bold transition-all ${billingCycle === "monthly"
? "bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white"
: "text-gray-400"
}`}
>
Monthly
</button>
<button
onClick={() => setBillingCycle("yearly")}
className={`px-8 py-3 rounded-full text-sm font-bold transition-all ${billingCycle === "yearly"
? "bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white"
: "text-gray-400"
}`}
>
Yearly (Save 20%)
</button>
</div>
</div>
{/* Free Trial Banner */}
<div className="mb-12 w-full p-1 bg-gradient-to-r from-[#4361ee] to-[#c026d3] rounded-3xl animate-fade-in-up">
<div className="bg-black/90 backdrop-blur-xl rounded-[22px] p-8 md:p-10 flex flex-col md:flex-row items-center justify-between text-center md:text-left gap-6">
<div className="flex-1">
<div className="flex items-center justify-center md:justify-start gap-3 mb-2">
<span className="bg-white/20 text-white text-xs px-3 py-1 rounded-full uppercase tracking-wider font-bold">Limited Offer</span>
{!(trialStatus?.hasUsedTrial && !trialStatus?.isTrialActive) && (
<span className="text-yellow-400 text-xs font-bold"> Recommended</span>
)}
</div>
<h3 className="text-3xl md:text-4xl font-bold text-white mb-3">
{trialStatus?.isTrialActive
? "Your Free Trial is Active!"
: trialStatus?.hasUsedTrial
? "Free Trial Ended"
: freeTrialPlan.name}
</h3>
<p className="text-gray-300 text-lg mb-6">
{trialStatus?.isTrialActive
? "Enjoy full access to all premium features. Check your dashboard for insights."
: trialStatus?.hasUsedTrial
? "You have already used your free trial. Please select a plan to continue."
: freeTrialPlan.desc}
</p>
{/* Show features list only if not used or active */}
{!trialStatus?.isTrialActive && !trialStatus?.hasUsedTrial && (
<div className="flex flex-wrap gap-3 justify-center md:justify-start">
{freeTrialPlan.features.map((f, i) => (
<div key={i} className="flex items-center gap-2 bg-white/5 px-4 py-2 rounded-lg border border-white/10">
<div className="w-5 h-5 rounded-full bg-green-500/20 text-green-400 flex items-center justify-center text-xs"></div>
<span className="text-sm text-gray-200">{f}</span>
</div>
))}
</div>
)}
</div>
<div className="w-full md:w-auto min-w-[200px]">
{trialStatus?.isTrialActive ? (
// --- ACTIVE TRIAL STATE ---
<div className="text-center">
<div className="mb-4">
<p className="text-gray-400 text-sm mb-1">Time Remaining</p>
<div className="text-2xl font-mono font-bold text-white bg-white/10 py-2 px-4 rounded-lg">
{timeLeft || "Calculating..."}
</div>
</div>
<button
onClick={handleCancelTrial}
disabled={loadingPlan === "cancel_trial"}
className="w-full py-3 rounded-xl font-bold text-sm transition-all border border-red-500/50 text-red-400 hover:bg-red-500/10"
>
{loadingPlan === "cancel_trial" ? "Cancelling..." : "Cancel Trial"}
</button>
<p className="text-center text-xs text-gray-500 mt-2 max-w-[200px] mx-auto">
Cancelling will end your access immediately.
</p>
</div>
) : trialStatus?.hasUsedTrial ? (
// --- USED / EXPIRED STATE ---
<div className="text-center">
<div className="mb-3">
<span className="text-3xl font-bold text-gray-400">Used</span>
</div>
<button
disabled
className="w-full py-4 rounded-xl font-bold text-lg transition-all bg-white/5 text-gray-500 cursor-not-allowed border border-white/5"
>
Trial Unavailable
</button>
<p className="text-center text-xs text-gray-500 mt-2">One-time offer only.</p>
</div>
) : (
// --- SUBSCRIBE STATE ---
<>
<div className="text-center mb-3">
<span className="text-4xl font-bold text-white">$0</span>
<span className="text-gray-400"> / 7 days</span>
</div>
<button
onClick={() => handleSubscribe(freeTrialPlan)}
disabled={loadingPlan === freeTrialPlan.name}
className="w-full py-4 rounded-xl font-bold text-lg transition-all bg-white text-black hover:bg-gray-100 shadow-[0_0_20px_rgba(255,255,255,0.3)] hover:shadow-[0_0_30px_rgba(255,255,255,0.5)] transform hover:-translate-y-1"
>
{loadingPlan === freeTrialPlan.name ? "Processing..." : "Start Free Trial"}
</button>
<p className="text-center text-xs text-gray-500 mt-2">No commitment, cancel anytime.</p>
</>
)}
</div>
</div>
</div>
{/* Pricing Plans */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{plans.map((plan) => (
<div
key={plan.name}
className={`relative glass-card rounded-3xl p-8 flex flex-col border ${plan.popular ? "border-purple-500/50" : "border-white/10"
} hover:-translate-y-2 transition-all`}
>
{plan.popular && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white px-4 py-1 rounded-full text-xs">
MOST POPULAR
</div>
)}
<h3 className="text-2xl font-bold text-white mb-2">{plan.name}</h3>
<p className="text-gray-400 mb-6 text-sm">{plan.desc}</p>
{/* Price */}
<div className="mb-8">
<span className="text-5xl font-extrabold text-white">
$
{billingCycle === "monthly"
? plan.amountMonthly
: plan.amountYearly}
</span>
<span className="text-gray-400 text-sm">
/{billingCycle === "monthly" ? "mo" : "yr"}
</span>
</div>
{/* Features */}
<ul className="space-y-4 text-gray-300 text-sm flex-1 mb-10">
{plan.features.map((f, i) => (
<li key={i} className="flex items-center gap-3">
<div className="w-6 h-6 rounded-full bg-gradient-to-r from-[#4361ee] to-[#c026d3] flex items-center justify-center p-[1px]">
<div className="bg-black w-full h-full rounded-full flex items-center justify-center">
<svg
className="w-3.5 h-3.5 text-white"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M5 13l4 4L19 7"
/>
</svg>
</div>
</div>
{f}
</li>
))}
</ul>
{/* FIXED BUTTON */}
<button
onClick={() => handleSubscribe(plan)}
disabled={loadingPlan === plan.name}
className="w-full py-4 rounded-xl font-bold transition-all
bg-gradient-to-r from-[#4361ee] to-[#c026d3]
text-white hover:opacity-90"
>
{loadingPlan === plan.name ? "Processing..." : "Get Started"}
</button>
</div>
))}
</div>
</div>
);
};
export default ComponentsPricingTableToggle;