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 {
|
try {
|
||||||
const res = await fetch('/api/billing/portal', { method: 'POST' });
|
const res = await fetch('/api/billing/portal', { method: 'POST' });
|
||||||
const data = await res.json();
|
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;
|
window.location.href = data.url;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[BillingPlans] portal error:', 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) => {
|
const formatDate = (dateStr: string | null | undefined) => {
|
||||||
if (!dateStr) return '—';
|
if (!dateStr) return '—';
|
||||||
try {
|
try {
|
||||||
@@ -339,15 +344,28 @@ export function BillingPlans() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isPaid && (
|
{isPaid && (
|
||||||
<button
|
<div className="flex flex-wrap gap-3">
|
||||||
type="button"
|
<button
|
||||||
onClick={handlePortal}
|
type="button"
|
||||||
disabled={portalLoading}
|
onClick={handlePortal}
|
||||||
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"
|
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'}
|
{portalLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : <ExternalLink className="h-4 w-4" />}
|
||||||
</button>
|
{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>
|
</div>
|
||||||
|
|
||||||
@@ -490,8 +508,11 @@ export function BillingPlans() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
<div className={cn(
|
||||||
{plans.map((plan) => (
|
"grid grid-cols-1 gap-6",
|
||||||
|
plansToShow.length === 3 ? "lg:grid-cols-3" : "lg:grid-cols-4"
|
||||||
|
)}>
|
||||||
|
{plansToShow.map((plan) => (
|
||||||
<div
|
<div
|
||||||
key={plan.id}
|
key={plan.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -2878,6 +2878,7 @@
|
|||||||
"noUsage": "No usage data",
|
"noUsage": "No usage data",
|
||||||
"billingHistory": "Billing History",
|
"billingHistory": "Billing History",
|
||||||
"viewInvoices": "Manage invoices in portal",
|
"viewInvoices": "Manage invoices in portal",
|
||||||
|
"cancelSubscription": "Cancel subscription",
|
||||||
"nextBillingDate": "Next billing date",
|
"nextBillingDate": "Next billing date",
|
||||||
"billingPeriod": "Billing period",
|
"billingPeriod": "Billing period",
|
||||||
"planSince": "Member since",
|
"planSince": "Member since",
|
||||||
|
|||||||
@@ -2882,6 +2882,7 @@
|
|||||||
"noUsage": "Aucune donnée d'utilisation",
|
"noUsage": "Aucune donnée d'utilisation",
|
||||||
"billingHistory": "Historique de facturation",
|
"billingHistory": "Historique de facturation",
|
||||||
"viewInvoices": "Gérer les factures dans le portail",
|
"viewInvoices": "Gérer les factures dans le portail",
|
||||||
|
"cancelSubscription": "Résilier l'abonnement",
|
||||||
"nextBillingDate": "Prochaine date de facturation",
|
"nextBillingDate": "Prochaine date de facturation",
|
||||||
"billingPeriod": "Période de facturation",
|
"billingPeriod": "Période de facturation",
|
||||||
"planSince": "Membre depuis",
|
"planSince": "Membre depuis",
|
||||||
|
|||||||
Reference in New Issue
Block a user