2026-02-21 19:04:54 +00:00

556 lines
30 KiB
TypeScript

"use client";
import React, { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import axios from "axios";
import { ApiBaseUrl, ApiServerBaseUrl } from "@/utils/baseurl.utils";
import Swal from 'sweetalert2';
interface Invoice {
id: string;
number: string;
date: string;
status: "Paid" | "Pending" | "Overdue";
amount: number;
}
interface UserDetails {
id?: string;
name?: string;
email?: string;
mobileNumber?: string;
role?: string;
_id?: string; // Add this as MongoDB uses _id
}
const AccountSettings: React.FC = () => {
const router = useRouter();
const [activeTab, setActiveTab] = useState<"subscription" | "billing" | "profile">("subscription");
const [paymentData, setPaymentData] = useState<any>(null);
const [subStatus, setSubStatus] = useState<any>(null);
const [billingInfo, setBillingInfo] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [userDetails, setUserDetails] = useState<UserDetails | null>(null)
useEffect(() => {
const fetchData = async () => {
try {
const sessionId = localStorage.getItem('payment_session');
const storedUser = localStorage.getItem("userDetails");
let currentUser: UserDetails | null = null;
if (storedUser) {
const parsedUser = JSON.parse(storedUser);
setUserDetails(parsedUser);
currentUser = parsedUser;
}
// Fetch Subscription Status (Trial / Active Plan)
if (currentUser && (currentUser.id || currentUser._id)) {
try {
const userId = currentUser.id || currentUser._id;
// 1. Status
const statusRes = await axios.get(`${ApiServerBaseUrl}/payment/status`, {
params: { userId }
});
setSubStatus(statusRes.data);
// 2. Billing Info
const billingRes = await axios.get(`${ApiServerBaseUrl}/payment/billing-info`, {
params: { userId }
});
setBillingInfo(billingRes.data);
} catch (statusErr) {
console.error("Error fetching status/billing:", statusErr);
}
}
// Fetch Payment Details (if session exists)
if (sessionId) {
try {
const res: any = await axios.get(
`${ApiServerBaseUrl}/payment/details`,
{ params: { session_id: sessionId } }
);
setPaymentData(res.data.data);
} catch (paymentErr: any) {
console.error("Error fetching payment data:", paymentErr);
}
}
} catch (err: any) {
console.error("Error in fetchData:", err);
setError("Failed to load account details");
} finally {
setLoading(false);
}
};
fetchData();
}, []);
const handleManageBilling = async () => {
if (!userDetails || (!userDetails.id && !userDetails._id)) return;
try {
const res = await axios.post(`${ApiServerBaseUrl}/payment/create-portal-session`, {
userId: userDetails.id || userDetails._id
});
if (res.data.url) {
window.location.href = res.data.url;
}
} catch (err: any) {
Swal.fire({
icon: 'error',
title: 'Error',
text: err.response?.data?.error || "Could not redirect to billing portal",
background: '#111',
color: '#fff',
confirmButtonColor: '#d33'
});
}
};
const handleActivateTrial = async () => {
if (!userDetails || (!userDetails.id && !userDetails._id)) return;
try {
const res = await axios.post(`${ApiServerBaseUrl}/payment/activate-trial`, {
userId: userDetails.id || userDetails._id,
email: userDetails.email
});
if (res.data.success) {
if (res.data.payment && res.data.payment.sessionId) {
localStorage.setItem('payment_session', res.data.payment.sessionId);
localStorage.setItem('paymentDetails', JSON.stringify(res.data.payment));
}
Swal.fire({
icon: 'success',
title: 'Trial Activated!',
text: 'Your 7-day free trial has started.',
background: '#111',
color: '#fff',
confirmButtonColor: '#4361ee'
});
// Refresh status
const statusRes = await axios.get(`${ApiServerBaseUrl}/payment/status`, {
params: { userId: userDetails.id || userDetails._id }
});
setSubStatus(statusRes.data);
// Redirect for immediate access to dashboard/feature pages
router.push('/');
}
} catch (err: any) {
Swal.fire({
icon: 'error',
title: 'Activation Failed',
text: err.response?.data?.error || "Could not activate trial",
background: '#111',
color: '#fff',
confirmButtonColor: '#d33'
});
}
};
const handleCancelTrial = async () => {
if (!userDetails || (!userDetails.id && !userDetails._id)) return;
const result = await Swal.fire({
title: 'Are you sure?',
text: "Your free trial will end immediately and you will lose access to premium features.",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#4361ee',
confirmButtonText: 'Yes, cancel it!',
background: '#111',
color: '#fff',
});
if (result.isConfirmed) {
try {
const res = await axios.post(`${ApiServerBaseUrl}/payment/cancel`, {
session_id: subStatus?.stripeSessionId
});
if (res.data) {
localStorage.removeItem('payment_session');
localStorage.removeItem('paymentDetails');
Swal.fire({
title: 'Cancelled!',
text: 'Your trial has been cancelled.',
icon: 'success',
background: '#111',
color: '#fff',
confirmButtonColor: '#4361ee'
});
// Refresh status
const userId = userDetails.id || userDetails._id;
const statusRes = await axios.get(`${ApiServerBaseUrl}/payment/status`, {
params: { userId }
});
setSubStatus(statusRes.data);
}
} catch (err: any) {
Swal.fire({
icon: 'error',
title: 'Cancellation Failed',
text: err.response?.data?.error || "Could not cancel trial",
background: '#111',
color: '#fff',
confirmButtonColor: '#d33'
});
}
}
};
// Construct invoices from either Payment Data or Subscription Status
let invoices: Invoice[] = [];
if (paymentData) {
invoices.push({
id: paymentData.id || "1",
number: paymentData.stripeSessionId?.substring(0, 12) || "INV-001",
date: paymentData.subscriptionStartDate ? new Date(paymentData.subscriptionStartDate).toLocaleDateString() : "N/A",
status: paymentData.status === "active" ? "Paid" : "Pending",
amount: paymentData.amount || 0,
});
} else if (subStatus && subStatus.hasUsedTrial) {
// If no payment data but Trial was used/active
invoices.push({
id: "trial-inv",
number: subStatus.stripeSessionId ? subStatus.stripeSessionId.substring(0, 12) : "TRIAL-START",
date: subStatus.trialStartDate ? new Date(subStatus.trialStartDate).toLocaleDateString() : "N/A",
status: "Paid", // Free trial is considered 'paid' for access
amount: 0,
});
}
const handleViewInvoice = (invoiceId: string) => {
router.push(`/account-settings/invoice/${invoiceId}`);
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen text-gray-400">
Loading account details...
</div>
);
}
// Removed the blocking error page so users can still see trial options even if they haven't paid yet.
return (
<div className="min-h-screen bg-[#111111] flex items-center justify-center p-4 relative overflow-hidden">
{/* Background Glows by User Request */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px] bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5] animate-zoomslow"></div>
<div className="absolute top-10 left-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] animate-zoomslower"></div>
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px] bg-[#db21d9] blur-3xl opacity-1 animate-zoomslow"></div>
<div className="absolute bottom-20 left-[440px] w-[60px] h-[60px] bg-[#db21d9] rounded-full blur-xl opacity-80 -translate-x-1/2 translate-y-1/2"></div>
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px] bg-[#f28f50] rounded-full blur-2xl opacity-80 animate-zoomfast"></div>
<div className="absolute top-10 right-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] animate-zoomslower"></div>
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px] bg-[#783e8d] rounded-full blur-2xl opacity-80 animate-zoomslow"></div>
<div className="absolute top-[280px] -right-[20px] w-[160px] h-[160px] bg-[#f1b74d] rounded-full blur-2xl opacity-1 animate-zoomslower"></div>
<div className="max-w-7xl mx-auto w-full">
{/* Header */}
<div className="mb-10">
<h1 className="text-4xl font-extrabold text-white mb-2">
Account <span className="text-gradient">Settings</span>
</h1>
<p className="text-gray-400">Manage your account preferences and billing</p>
</div>
{/* Tabs */}
<div className="flex gap-4 mb-8 border-b border-white/10">
<button
onClick={() => setActiveTab("subscription")}
className={`px-6 py-3 font-semibold transition-all relative ${activeTab === "subscription"
? "text-white"
: "text-gray-400 hover:text-gray-300"
}`}
>
Subscription
{activeTab === "subscription" && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-[#4361ee] to-[#c026d3]" />
)}
</button>
<button
onClick={() => setActiveTab("billing")}
className={`px-6 py-3 font-semibold transition-all relative ${activeTab === "billing"
? "text-white"
: "text-gray-400 hover:text-gray-300"
}`}
>
Billing
{activeTab === "billing" && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-[#4361ee] to-[#c026d3]" />
)}
</button>
<button
onClick={() => setActiveTab("profile")}
className={`px-6 py-3 font-semibold transition-all relative ${activeTab === "profile"
? "text-white"
: "text-gray-400 hover:text-gray-300"
}`}
>
Profile
{activeTab === "profile" && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-[#4361ee] to-[#c026d3]" />
)}
</button>
</div>
{/* Content */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Subscription Section */}
{activeTab === "subscription" && (
<>
<div className="glass-card rounded-2xl p-6 col-span-1 lg:col-span-2">
<h3 className="text-xl font-bold text-white mb-4">Subscription Details</h3>
{/* 1. Active Subscription (Paid or Trial) */}
{subStatus?.isTrialActive ? (
// CASE A: ACTIVE FREE TRIAL
<div className="space-y-4">
<div className="p-4 rounded-xl bg-gradient-to-r from-[#4361ee]/20 to-[#c026d3]/20 border border-white/10">
<div className="flex items-center justify-between mb-2">
<h4 className="text-lg font-bold text-white">Free Trial Active</h4>
<span className="px-3 py-1 bg-green-500/20 text-green-400 text-xs font-bold rounded-full">
ACTIVE
</span>
</div>
<p className="text-gray-300 text-sm">
You are currently enjoying the 7-day free trial.
</p>
</div>
<div className="glass-card bg-white/5 p-4 rounded-xl space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Plan Type</span>
<span className="text-white font-semibold capitalize">Free Trial</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Ends On</span>
<span className="text-white">
{subStatus?.trialEndsAt
? new Date(subStatus.trialEndsAt).toLocaleDateString()
: "N/A"}
</span>
</div>
</div>
<button
onClick={handleCancelTrial}
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 mt-4"
>
Cancel Free Trial
</button>
</div>
) : (paymentData && paymentData.planId !== 'free_trial') ? (
// CASE B: ACTIVE PAID SUBSCRIPTION
<div className="space-y-4">
<div className="p-4 rounded-xl bg-gradient-to-r from-[#4361ee]/20 to-[#c026d3]/20 border border-white/10">
<div className="flex items-center justify-between mb-2">
<h4 className="text-lg font-bold text-white">
{paymentData?.planId ? paymentData.planId.replace(/_/g, " ").toUpperCase() : "CURRENT PLAN"}
</h4>
<span className="px-3 py-1 bg-green-500/20 text-green-400 text-xs font-bold rounded-full">
ACTIVE
</span>
</div>
<p className="text-gray-300 text-sm">
Your subscription is active and running.
</p>
</div>
<div className="glass-card bg-white/5 p-4 rounded-xl space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Plan Type</span>
<span className="text-white font-semibold capitalize">
{paymentData?.planId?.replace(/_/g, " ") || "N/A"}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Renews On</span>
<span className="text-white">
{paymentData?.subscriptionEndDate
? new Date(paymentData.subscriptionEndDate).toLocaleDateString()
: "N/A"}
</span>
</div>
</div>
</div>
) : (
// CASE C: NO ACTIVE SUBSCRIPTION
<div className="text-center py-8">
<p className="text-gray-400 mb-6">You don't have an active subscription.</p>
{/* Show Activate Trial if not used yet */}
{!subStatus?.hasUsedTrial ? (
<div className="bg-gradient-to-br from-[#4361ee]/10 to-[#c026d3]/10 border border-white/10 p-6 rounded-2xl">
<h4 className="text-xl font-bold text-white mb-2">Start Your 7-Day Free Trial</h4>
<p className="text-gray-400 text-sm mb-6">
Experience all premium features for free. No credit card required (optional).
</p>
<button
onClick={handleActivateTrial}
className="px-8 py-3 rounded-xl font-bold bg-white text-black hover:bg-gray-200 transition-all"
>
Activate Free Trial
</button>
</div>
) : (
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-xl text-red-400">
Your free trial has expired. Please upgrade to continue.
</div>
)}
<button
onClick={() => router.push('/pricing')}
className="mt-6 px-6 py-3 rounded-xl font-bold bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white hover:opacity-90 transition-all"
>
View Pricing Plans
</button>
</div>
)}
</div>
</>
)}
{/* Billing Tab Content */}
{activeTab === "billing" && (
<div className="lg:col-span-3">
<div className="glass-card rounded-2xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Billing Methods</h3>
{billingInfo?.hasPaymentMethod ? (
<div className="space-y-4 mb-6">
<div className="flex items-center justify-between p-4 bg-white/5 rounded-xl border border-white/10">
<div className="flex items-center gap-4">
<div className="w-12 h-8 bg-white/10 rounded flex items-center justify-center text-xs font-bold font-mono">
{billingInfo.brand?.toUpperCase()}
</div>
<div>
<p className="text-white font-medium">
{billingInfo.brand?.charAt(0).toUpperCase() + billingInfo.brand?.slice(1)} ending in {billingInfo.last4}
</p>
<p className="text-gray-400 text-sm">
Expires {billingInfo.exp_month}/{billingInfo.exp_year}
</p>
</div>
</div>
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded">Default</span>
</div>
</div>
) : (
<div className="text-center py-6 bg-white/5 rounded-xl border border-dashed border-white/20 mb-6">
<p className="text-gray-400 mb-2">No payment method saved.</p>
</div>
)}
<div className="flex flex-col sm:flex-row gap-3">
<button
onClick={handleManageBilling}
className="py-3 px-6 rounded-xl font-bold bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white hover:opacity-90 transition-all"
>
{billingInfo?.hasPaymentMethod ? "Update Payment Method" : "Add Payment Method"}
</button>
<p className="text-xs text-gray-500 mt-2 sm:mt-0 sm:self-center">
Redirects to secure Stripe Billing Portal
</p>
</div>
</div>
</div>
)}
{/* Profile Tab Content */}
{activeTab === "profile" && (
<div className="lg:col-span-3">
<div className="glass-card rounded-2xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Profile</h3>
<div className="space-y-3 mb-6">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Full Name</span>
<span className="text-white">{userDetails?.name || "N/A"}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Email</span>
<span className="text-white">{userDetails?.email || "N/A"}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Phone</span>
<span className="text-white">{userDetails?.mobileNumber || "N/A"}</span>
</div>
</div>
</div>
</div>
)}
</div>
{/* Billing History - Always visible */}
<div className="mt-10">
<div className="glass-card rounded-2xl p-6">
<h3 className="text-xl font-bold text-white mb-6">Billing History</h3>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-white/10">
<th className="text-left py-4 px-4 text-sm font-semibold text-[#4361ee] uppercase tracking-wider">
Invoice Number
</th>
<th className="text-left py-4 px-4 text-sm font-semibold text-[#4361ee] uppercase tracking-wider">
Date
</th>
<th className="text-left py-4 px-4 text-sm font-semibold text-[#4361ee] uppercase tracking-wider">
Status
</th>
<th className="text-left py-4 px-4 text-sm font-semibold text-[#4361ee] uppercase tracking-wider">
Amount
</th>
</tr>
</thead>
<tbody>
{invoices.length > 0 ? invoices.map((invoice) => (
<tr key={invoice.id} className="border-b border-white/5 hover:bg-white/5 transition-colors">
<td className="py-4 px-4 text-white">{invoice.number}</td>
<td className="py-4 px-4 text-gray-300">{invoice.date}</td>
<td className="py-4 px-4">
<span
className={`px-3 py-1 rounded-full text-xs font-semibold ${invoice.status === "Paid"
? "bg-green-500/20 text-green-400"
: invoice.status === "Pending"
? "bg-yellow-500/20 text-yellow-400"
: "bg-red-500/20 text-red-400"
}`}
>
{invoice?.status}
</span>
</td>
<td className="py-4 px-4 text-white font-semibold">
${invoice?.amount}
</td>
</tr>
)) : (
<tr>
<td colSpan={4} className="py-8 text-center text-gray-500">
No billing history found.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
};
export default AccountSettings;