fix(billing): filter out free plan for paid users, add direct cancel subscription button, and handle portal errors without crashing Next.js overlay
This commit is contained in:
@@ -117,7 +117,10 @@ export function BillingPlans() {
|
||||
try {
|
||||
const res = await fetch('/api/billing/portal', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error ?? 'Failed to open portal');
|
||||
if (!res.ok) {
|
||||
toast.error(data.error || 'Failed to open billing portal.');
|
||||
return;
|
||||
}
|
||||
window.location.href = data.url;
|
||||
} catch (err) {
|
||||
console.error('[BillingPlans] portal error:', err);
|
||||
@@ -237,6 +240,8 @@ export function BillingPlans() {
|
||||
},
|
||||
];
|
||||
|
||||
const plansToShow = isPaid ? plans.filter((p) => p.id !== 'free') : plans;
|
||||
|
||||
const formatDate = (dateStr: string | null | undefined) => {
|
||||
if (!dateStr) return '—';
|
||||
try {
|
||||
@@ -339,15 +344,28 @@ export function BillingPlans() {
|
||||
</div>
|
||||
|
||||
{isPaid && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handlePortal}
|
||||
disabled={portalLoading}
|
||||
className="flex items-center gap-2 px-5 py-2.5 bg-ink text-white dark:bg-white dark:text-black rounded-xl text-xs font-semibold hover:opacity-90 disabled:opacity-60 transition-all shadow-md shadow-black/5"
|
||||
>
|
||||
{portalLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : <ExternalLink className="h-4 w-4" />}
|
||||
{t('billing.manageBilling') || 'Gérer la facturation'}
|
||||
</button>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handlePortal}
|
||||
disabled={portalLoading}
|
||||
className="flex items-center gap-2 px-5 py-2.5 bg-ink text-white dark:bg-white dark:text-black rounded-xl text-xs font-semibold hover:opacity-90 disabled:opacity-60 transition-all shadow-md shadow-black/5"
|
||||
>
|
||||
{portalLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : <ExternalLink className="h-4 w-4" />}
|
||||
{t('billing.manageBilling') || 'Gérer la facturation'}
|
||||
</button>
|
||||
|
||||
{status?.hasStripeSubscription && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handlePortal}
|
||||
disabled={portalLoading}
|
||||
className="flex items-center gap-2 px-5 py-2.5 border border-rose-200 text-rose-600 dark:border-rose-800/40 dark:text-rose-400 hover:bg-rose-50/50 dark:hover:bg-rose-950/15 rounded-xl text-xs font-semibold transition-all"
|
||||
>
|
||||
{t('billing.cancelSubscription') || "Résilier l'abonnement"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -490,8 +508,11 @@ export function BillingPlans() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||
{plans.map((plan) => (
|
||||
<div className={cn(
|
||||
"grid grid-cols-1 gap-6",
|
||||
plansToShow.length === 3 ? "lg:grid-cols-3" : "lg:grid-cols-4"
|
||||
)}>
|
||||
{plansToShow.map((plan) => (
|
||||
<div
|
||||
key={plan.id}
|
||||
className={cn(
|
||||
|
||||
Reference in New Issue
Block a user