fixing and creatign mess
This commit is contained in:
@@ -28,10 +28,12 @@ import {
|
||||
TagIcon,
|
||||
LockIcon,
|
||||
ShootingStarIcon,
|
||||
DollarLineIcon,
|
||||
} from '../../icons';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
import Button from '../../components/ui/button/Button';
|
||||
import Label from '../../components/form/Label';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import PageHeader from '../../components/common/PageHeader';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
@@ -41,7 +43,7 @@ import {
|
||||
getCreditBalance,
|
||||
getCreditPackages,
|
||||
getInvoices,
|
||||
getAvailablePaymentMethods,
|
||||
getAccountPaymentMethods,
|
||||
purchaseCreditPackage,
|
||||
downloadInvoicePDF,
|
||||
getPayments,
|
||||
@@ -64,6 +66,7 @@ import {
|
||||
type PaymentGateway,
|
||||
} from '../../services/billing.api';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import PayInvoiceModal from '../../components/billing/PayInvoiceModal';
|
||||
|
||||
export default function PlansAndBillingPage() {
|
||||
const { startLoading, stopLoading } = usePageLoading();
|
||||
@@ -80,13 +83,15 @@ export default function PlansAndBillingPage() {
|
||||
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
||||
const [selectedBillingCycle, setSelectedBillingCycle] = useState<'monthly' | 'annual'>('monthly');
|
||||
const [selectedGateway, setSelectedGateway] = useState<PaymentGateway>('stripe');
|
||||
const [showPayInvoiceModal, setShowPayInvoiceModal] = useState(false);
|
||||
const [selectedInvoice, setSelectedInvoice] = useState<Invoice | null>(null);
|
||||
|
||||
// Data States
|
||||
const [creditBalance, setCreditBalance] = useState<CreditBalance | null>(null);
|
||||
const [packages, setPackages] = useState<CreditPackage[]>([]);
|
||||
const [invoices, setInvoices] = useState<Invoice[]>([]);
|
||||
const [payments, setPayments] = useState<Payment[]>([]);
|
||||
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
|
||||
const [userPaymentMethods, setUserPaymentMethods] = useState<PaymentMethod[]>([]);
|
||||
const [plans, setPlans] = useState<Plan[]>([]);
|
||||
const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string | undefined>(undefined);
|
||||
@@ -99,16 +104,51 @@ export default function PlansAndBillingPage() {
|
||||
useEffect(() => {
|
||||
if (hasLoaded.current) return;
|
||||
hasLoaded.current = true;
|
||||
loadData();
|
||||
|
||||
// Handle payment gateway return URLs
|
||||
// Handle payment gateway return URLs BEFORE loadData
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const success = params.get('success');
|
||||
const canceled = params.get('canceled');
|
||||
const purchase = params.get('purchase');
|
||||
const paypalStatus = params.get('paypal');
|
||||
const paypalToken = params.get('token'); // PayPal order ID
|
||||
const planIdParam = params.get('plan_id');
|
||||
const packageIdParam = params.get('package_id');
|
||||
const { refreshUser } = useAuthStore.getState();
|
||||
|
||||
if (success === 'true') {
|
||||
// Handle PayPal return - MUST capture the order to complete payment
|
||||
// Do this BEFORE loadData to ensure payment is processed first
|
||||
if (paypalStatus === 'success' && paypalToken) {
|
||||
// Import and capture PayPal order
|
||||
import('../../services/billing.api').then(({ capturePayPalOrder }) => {
|
||||
toast?.info?.('Completing PayPal payment...');
|
||||
capturePayPalOrder(paypalToken, {
|
||||
plan_id: planIdParam || undefined,
|
||||
package_id: packageIdParam || undefined,
|
||||
})
|
||||
.then(() => {
|
||||
toast?.success?.('Payment completed successfully!');
|
||||
refreshUser().catch(() => {});
|
||||
// Reload the page to get fresh data
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
window.location.reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('PayPal capture error:', err);
|
||||
toast?.error?.(err?.message || 'Failed to complete PayPal payment');
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
});
|
||||
});
|
||||
return; // Don't load data yet, wait for capture to complete
|
||||
} else if (paypalStatus === 'cancel') {
|
||||
toast?.info?.('PayPal payment was cancelled');
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
}
|
||||
// Handle Stripe success
|
||||
else if (success === 'true') {
|
||||
toast?.success?.('Subscription activated successfully!');
|
||||
// Refresh user to get updated account status (removes pending_payment banner)
|
||||
refreshUser().catch(() => {});
|
||||
// Clean up URL
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
} else if (canceled === 'true') {
|
||||
@@ -116,11 +156,16 @@ export default function PlansAndBillingPage() {
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
} else if (purchase === 'success') {
|
||||
toast?.success?.('Credits purchased successfully!');
|
||||
// Refresh user to get updated credit balance and account status
|
||||
refreshUser().catch(() => {});
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
} else if (purchase === 'canceled') {
|
||||
toast?.info?.('Credit purchase was cancelled');
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
}
|
||||
|
||||
// Load data after handling return URLs
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const handleError = (err: any, fallback: string) => {
|
||||
@@ -138,7 +183,7 @@ export default function PlansAndBillingPage() {
|
||||
const packagesPromise = getCreditPackages();
|
||||
const invoicesPromise = getInvoices({});
|
||||
const paymentsPromise = getPayments({});
|
||||
const methodsPromise = getAvailablePaymentMethods();
|
||||
const userMethodsPromise = getAccountPaymentMethods();
|
||||
const plansData = await getPlans();
|
||||
await wait(400);
|
||||
|
||||
@@ -152,8 +197,8 @@ export default function PlansAndBillingPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const [packagesData, invoicesData, paymentsData, methodsData] = await Promise.all([
|
||||
packagesPromise, invoicesPromise, paymentsPromise, methodsPromise
|
||||
const [packagesData, invoicesData, paymentsData, userMethodsData] = await Promise.all([
|
||||
packagesPromise, invoicesPromise, paymentsPromise, userMethodsPromise
|
||||
]);
|
||||
|
||||
setCreditBalance(balanceData);
|
||||
@@ -161,13 +206,19 @@ export default function PlansAndBillingPage() {
|
||||
setInvoices(invoicesData.results || []);
|
||||
setPayments(paymentsData.results || []);
|
||||
|
||||
const methods = (methodsData.results || []).filter((m) => m.is_enabled !== false);
|
||||
setPaymentMethods(methods);
|
||||
if (methods.length > 0) {
|
||||
const bank = methods.find((m) => m.type === 'bank_transfer');
|
||||
const manual = methods.find((m) => m.type === 'manual');
|
||||
const selected = bank || manual || methods.find((m) => m.is_default) || methods[0];
|
||||
setSelectedPaymentMethod((prev) => prev || selected.type || selected.id);
|
||||
// Load user's verified payment methods (AccountPaymentMethod)
|
||||
const userMethods = (userMethodsData.results || []).filter((m: any) => m.is_enabled !== false);
|
||||
setUserPaymentMethods(userMethods);
|
||||
|
||||
// Select the user's default/verified payment method
|
||||
if (userMethods.length > 0) {
|
||||
const defaultMethod = userMethods.find((m: any) => m.is_default && m.is_verified) ||
|
||||
userMethods.find((m: any) => m.is_verified) ||
|
||||
userMethods.find((m: any) => m.is_default) ||
|
||||
userMethods[0];
|
||||
if (defaultMethod) {
|
||||
setSelectedPaymentMethod(defaultMethod.type);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter plans
|
||||
@@ -191,12 +242,32 @@ export default function PlansAndBillingPage() {
|
||||
}
|
||||
setSubscriptions(subs);
|
||||
|
||||
// Load available payment gateways
|
||||
// Load available payment gateways and sync with user's payment method
|
||||
try {
|
||||
const gateways = await getAvailablePaymentGateways();
|
||||
setAvailableGateways(gateways);
|
||||
// Auto-select first available gateway
|
||||
if (gateways.stripe) {
|
||||
|
||||
// Use user's verified payment method to set gateway
|
||||
// userMethods was already loaded above
|
||||
const verifiedMethod = userMethods.find(
|
||||
(m: any) => m.is_verified && m.is_default
|
||||
) || userMethods.find((m: any) => m.is_verified) || userMethods[0];
|
||||
|
||||
let userPreferredGateway: PaymentGateway | null = null;
|
||||
if (verifiedMethod) {
|
||||
if (verifiedMethod.type === 'stripe' && gateways.stripe) {
|
||||
userPreferredGateway = 'stripe';
|
||||
} else if (verifiedMethod.type === 'paypal' && gateways.paypal) {
|
||||
userPreferredGateway = 'paypal';
|
||||
} else if (['bank_transfer', 'local_wallet', 'manual'].includes(verifiedMethod.type)) {
|
||||
userPreferredGateway = 'manual';
|
||||
}
|
||||
}
|
||||
|
||||
// Set selected gateway based on user preference or available gateways
|
||||
if (userPreferredGateway) {
|
||||
setSelectedGateway(userPreferredGateway);
|
||||
} else if (gateways.stripe) {
|
||||
setSelectedGateway('stripe');
|
||||
} else if (gateways.paypal) {
|
||||
setSelectedGateway('paypal');
|
||||
@@ -312,6 +383,10 @@ export default function PlansAndBillingPage() {
|
||||
const currentPlan = plans.find((p) => p.id === effectivePlanId) || user?.account?.plan;
|
||||
const hasActivePlan = Boolean(effectivePlanId);
|
||||
const hasPendingPayment = payments.some((p) => p.status === 'pending_approval');
|
||||
const hasPendingInvoice = invoices.some((inv) => inv.status === 'pending');
|
||||
|
||||
// Combined check: disable Buy Credits if no active plan OR has pending invoice
|
||||
const canBuyCredits = hasActivePlan && !hasPendingInvoice;
|
||||
|
||||
// Credit usage percentage
|
||||
const creditUsage = creditBalance && creditBalance.plan_credits_per_month > 0
|
||||
@@ -374,14 +449,14 @@ export default function PlansAndBillingPage() {
|
||||
{/* SECTION 1: Current Plan Hero */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Main Plan Card */}
|
||||
<Card className="lg:col-span-2 p-6 bg-gradient-to-br from-brand-50 to-purple-50 dark:from-brand-900/20 dark:to-purple-900/20 border-0">
|
||||
<Card className="lg:col-span-2 p-6 bg-gradient-to-br from-brand-500/10 via-purple-500/10 to-indigo-500/10 dark:from-brand-600/20 dark:via-purple-600/20 dark:to-indigo-600/20 border border-brand-200/50 dark:border-brand-700/50">
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{currentPlan?.name || 'No Plan'}
|
||||
</h2>
|
||||
<Badge variant="soft" tone={hasActivePlan ? 'success' : 'warning'}>
|
||||
<Badge variant="solid" tone={hasActivePlan ? 'success' : 'warning'}>
|
||||
{hasActivePlan ? 'Active' : 'Inactive'}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -546,71 +621,96 @@ export default function PlansAndBillingPage() {
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Buy Additional Credits */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-success-100 dark:bg-success-900/30 rounded-lg">
|
||||
<PlusIcon className="w-5 h-5 text-success-600 dark:text-success-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">Buy Credits</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Top up your credit balance</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-success-100 dark:bg-success-900/30 rounded-lg">
|
||||
<PlusIcon className="w-5 h-5 text-success-600 dark:text-success-400" />
|
||||
</div>
|
||||
{/* Compact Payment Gateway Selector for Credits */}
|
||||
{(availableGateways.stripe || availableGateways.paypal) && (
|
||||
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 p-1 rounded-lg">
|
||||
{availableGateways.stripe && (
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">Buy Credits</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Top up your credit balance</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Show message if no active plan */}
|
||||
{!hasActivePlan ? (
|
||||
<div className="text-center py-8 border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-xl">
|
||||
<LockIcon className="w-8 h-8 mx-auto text-gray-400 mb-2" />
|
||||
<p className="text-gray-500 dark:text-gray-400 text-sm">
|
||||
Subscribe to a plan first to purchase additional credits
|
||||
</p>
|
||||
</div>
|
||||
) : hasPendingInvoice ? (
|
||||
<div className="text-center py-8 border-2 border-dashed border-warning-200 dark:border-warning-700 bg-warning-50 dark:bg-warning-900/20 rounded-xl">
|
||||
<AlertCircleIcon className="w-8 h-8 mx-auto text-warning-500 mb-2" />
|
||||
<p className="text-warning-700 dark:text-warning-300 text-sm font-medium">
|
||||
Please pay your pending invoice first
|
||||
</p>
|
||||
<p className="text-warning-600 dark:text-warning-400 text-xs mt-1">
|
||||
Credit purchases are disabled until your outstanding balance is settled
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Payment Method Selector - Clear buttons */}
|
||||
{(availableGateways.stripe || availableGateways.paypal) && (
|
||||
<div className="mb-4">
|
||||
<Label className="text-xs text-gray-500 dark:text-gray-400 mb-2 block">Payment Method</Label>
|
||||
<div className="flex gap-2">
|
||||
{availableGateways.stripe && (
|
||||
<button
|
||||
onClick={() => setSelectedGateway('stripe')}
|
||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
|
||||
selectedGateway === 'stripe'
|
||||
? 'border-brand-500 bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
<CreditCardIcon className={`w-5 h-5 ${selectedGateway === 'stripe' ? 'text-brand-600' : 'text-gray-500'}`} />
|
||||
<span className="text-sm font-medium">Credit/Debit Card</span>
|
||||
</button>
|
||||
)}
|
||||
{availableGateways.paypal && (
|
||||
<button
|
||||
onClick={() => setSelectedGateway('paypal')}
|
||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
|
||||
selectedGateway === 'paypal'
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
<WalletIcon className={`w-5 h-5 ${selectedGateway === 'paypal' ? 'text-blue-600' : 'text-gray-500'}`} />
|
||||
<span className="text-sm font-medium">PayPal</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{packages.slice(0, 4).map((pkg) => (
|
||||
<button
|
||||
onClick={() => setSelectedGateway('stripe')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
selectedGateway === 'stripe'
|
||||
? 'bg-white dark:bg-gray-700 shadow-sm'
|
||||
: 'hover:bg-white/50 dark:hover:bg-gray-700/50'
|
||||
}`}
|
||||
title="Pay with Card"
|
||||
key={pkg.id}
|
||||
onClick={() => handlePurchaseCredits(pkg.id)}
|
||||
disabled={purchaseLoadingId === pkg.id}
|
||||
className="p-4 border border-gray-200 dark:border-gray-700 rounded-xl hover:border-brand-500 dark:hover:border-brand-500 hover:bg-brand-50/50 dark:hover:bg-brand-900/20 transition-all text-left group"
|
||||
>
|
||||
<CreditCardIcon className={`w-4 h-4 ${selectedGateway === 'stripe' ? 'text-brand-600' : 'text-gray-500'}`} />
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-lg font-bold text-gray-900 dark:text-white">
|
||||
{pkg.credits.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mb-1">credits</div>
|
||||
<div className="font-semibold text-brand-600 dark:text-brand-400 group-hover:text-brand-700 dark:group-hover:text-brand-300">
|
||||
${pkg.price}
|
||||
</div>
|
||||
{purchaseLoadingId === pkg.id && (
|
||||
<Loader2Icon className="w-4 h-4 animate-spin mt-2" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{availableGateways.paypal && (
|
||||
<button
|
||||
onClick={() => setSelectedGateway('paypal')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
selectedGateway === 'paypal'
|
||||
? 'bg-white dark:bg-gray-700 shadow-sm'
|
||||
: 'hover:bg-white/50 dark:hover:bg-gray-700/50'
|
||||
}`}
|
||||
title="Pay with PayPal"
|
||||
>
|
||||
<WalletIcon className={`w-4 h-4 ${selectedGateway === 'paypal' ? 'text-blue-600' : 'text-gray-500'}`} />
|
||||
</button>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{packages.slice(0, 4).map((pkg) => (
|
||||
<button
|
||||
key={pkg.id}
|
||||
onClick={() => handlePurchaseCredits(pkg.id)}
|
||||
disabled={purchaseLoadingId === pkg.id}
|
||||
className="p-4 border border-gray-200 dark:border-gray-700 rounded-xl hover:border-brand-500 dark:hover:border-brand-500 hover:bg-brand-50/50 dark:hover:bg-brand-900/20 transition-all text-left group"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-lg font-bold text-gray-900 dark:text-white">
|
||||
{pkg.credits.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mb-1">credits</div>
|
||||
<div className="font-semibold text-brand-600 dark:text-brand-400 group-hover:text-brand-700 dark:group-hover:text-brand-300">
|
||||
${pkg.price}
|
||||
</div>
|
||||
{purchaseLoadingId === pkg.id && (
|
||||
<Loader2Icon className="w-4 h-4 animate-spin mt-2" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Quick Upgrade Options */}
|
||||
@@ -719,15 +819,31 @@ export default function PlansAndBillingPage() {
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-6 py-3 text-end">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
tone="neutral"
|
||||
startIcon={<DownloadIcon className="w-4 h-4" />}
|
||||
onClick={() => handleDownloadInvoice(invoice.id)}
|
||||
>
|
||||
PDF
|
||||
</Button>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{invoice.status === 'pending' && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
startIcon={<DollarLineIcon className="w-4 h-4" />}
|
||||
onClick={() => {
|
||||
setSelectedInvoice(invoice);
|
||||
setShowPayInvoiceModal(true);
|
||||
}}
|
||||
>
|
||||
Pay Now
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
tone="neutral"
|
||||
startIcon={<DownloadIcon className="w-4 h-4" />}
|
||||
onClick={() => handleDownloadInvoice(invoice.id)}
|
||||
>
|
||||
PDF
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
@@ -737,7 +853,7 @@ export default function PlansAndBillingPage() {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* SECTION 4: Payment Methods */}
|
||||
{/* SECTION 4: Payment Methods - User's verified payment methods */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -746,12 +862,12 @@ export default function PlansAndBillingPage() {
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">Payment Methods</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Manage how you pay</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Your saved payment methods</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{paymentMethods.map((method) => (
|
||||
{userPaymentMethods.map((method: any) => (
|
||||
<div
|
||||
key={method.id}
|
||||
className={`p-4 border rounded-xl transition-all ${
|
||||
@@ -762,32 +878,48 @@ export default function PlansAndBillingPage() {
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
{method.type === 'bank_transfer' ? (
|
||||
{method.type === 'bank_transfer' || method.type === 'local_wallet' ? (
|
||||
<Building2Icon className="w-6 h-6 text-gray-500" />
|
||||
) : method.type === 'paypal' ? (
|
||||
<WalletIcon className="w-6 h-6 text-blue-500" />
|
||||
) : (
|
||||
<CreditCardIcon className="w-6 h-6 text-gray-500" />
|
||||
<CreditCardIcon className="w-6 h-6 text-brand-500" />
|
||||
)}
|
||||
<div>
|
||||
<div className="font-medium text-gray-900 dark:text-white">{method.display_name}</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 capitalize">{method.type?.replace('_', ' ')}</div>
|
||||
</div>
|
||||
</div>
|
||||
{method.is_default && (
|
||||
<Badge variant="soft" tone="success" size="sm">Default</Badge>
|
||||
)}
|
||||
<div className="flex flex-col gap-1 items-end">
|
||||
{method.is_verified && (
|
||||
<Badge variant="soft" tone="success" size="sm">Verified</Badge>
|
||||
)}
|
||||
{method.is_default && (
|
||||
<Badge variant="soft" tone="brand" size="sm">Default</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{selectedPaymentMethod !== method.type && (
|
||||
{selectedPaymentMethod !== method.type ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
tone="neutral"
|
||||
className="w-full"
|
||||
onClick={() => setSelectedPaymentMethod(method.type)}
|
||||
onClick={() => {
|
||||
setSelectedPaymentMethod(method.type);
|
||||
// Sync gateway selection
|
||||
if (method.type === 'stripe' && availableGateways.stripe) {
|
||||
setSelectedGateway('stripe');
|
||||
} else if (method.type === 'paypal' && availableGateways.paypal) {
|
||||
setSelectedGateway('paypal');
|
||||
} else {
|
||||
setSelectedGateway('manual');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Select
|
||||
</Button>
|
||||
)}
|
||||
{selectedPaymentMethod === method.type && (
|
||||
) : (
|
||||
<div className="flex items-center gap-2 text-sm text-brand-600 dark:text-brand-400">
|
||||
<CheckCircleIcon className="w-4 h-4" />
|
||||
Selected for payment
|
||||
@@ -795,9 +927,11 @@ export default function PlansAndBillingPage() {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{paymentMethods.length === 0 && (
|
||||
{userPaymentMethods.length === 0 && (
|
||||
<div className="col-span-full text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
No payment methods available
|
||||
<CreditCardIcon className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p>No payment methods saved yet.</p>
|
||||
<p className="text-xs mt-1">Complete a payment to save your method.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1034,6 +1168,29 @@ export default function PlansAndBillingPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pay Invoice Modal */}
|
||||
{showPayInvoiceModal && selectedInvoice && (
|
||||
<PayInvoiceModal
|
||||
isOpen={showPayInvoiceModal}
|
||||
onClose={() => {
|
||||
setShowPayInvoiceModal(false);
|
||||
setSelectedInvoice(null);
|
||||
}}
|
||||
onSuccess={async () => {
|
||||
setShowPayInvoiceModal(false);
|
||||
setSelectedInvoice(null);
|
||||
// Refresh user and billing data
|
||||
const { refreshUser } = useAuthStore.getState();
|
||||
await refreshUser();
|
||||
await loadData();
|
||||
toast?.success?.('Payment processed successfully!');
|
||||
}}
|
||||
invoice={selectedInvoice}
|
||||
userCountry={(user?.account as any)?.billing_country || 'US'}
|
||||
defaultPaymentMethod={selectedPaymentMethod}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user