fix(quotas): unifier le décompte IA (BYOK, rollback) et combler les fuites
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 6m55s
CI / Deploy production (on server) (push) Successful in 36s

Centralise la réserve via ai-quota, corrige admin unavailable (-1), brancher les routes sans quota et le host-pays brainstorm, avec usage-meter élargi, noms de clusters, MCP et ajustements dashboard/insights.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-07-15 20:42:25 +00:00
parent 30da592ba2
commit 4fe31ebc99
75 changed files with 2949 additions and 785 deletions

View File

@@ -62,7 +62,7 @@ export function BillingPlans() {
queryFn: async () => {
const search = typeof window !== 'undefined' ? window.location.search : '';
const res = await fetch(`/api/billing/status${search}`);
if (!res.ok) throw new Error('Failed to fetch billing status');
if (!res.ok) throw new Error(t('billing.fetchStatusFailed'));
return res.json();
},
});
@@ -71,7 +71,7 @@ export function BillingPlans() {
queryKey: ['usage', 'current'],
queryFn: async () => {
const res = await fetch('/api/usage/current');
if (!res.ok) throw new Error('Failed to fetch quotas');
if (!res.ok) throw new Error(t('billing.fetchQuotasFailed'));
return res.json();
},
refetchInterval: 30000,
@@ -122,7 +122,7 @@ export function BillingPlans() {
}
} catch (err) {
console.error('[BillingPlans] checkout error:', err);
toast.error('Failed to start checkout. Please try again.');
toast.error(t('billing.checkoutFailed'));
} finally {
setCheckoutLoading(null);
}
@@ -139,20 +139,20 @@ export function BillingPlans() {
});
const data = await res.json();
if (!res.ok) {
toast.error(data.error || 'Failed to open billing portal.');
toast.error(data.error || t('billing.portalFailed'));
return;
}
window.location.href = data.url;
} catch (err) {
console.error('[BillingPlans] portal error:', err);
toast.error('Failed to open billing portal.');
toast.error(t('billing.portalFailed'));
} finally {
setPortalLoading(false);
}
};
const handleCancelSubscription = async () => {
const confirmMsg = t('billing.cancelConfirm') || "Êtes-vous sûr de vouloir résilier votre abonnement ? Vous conserverez vos accès Pro/Business jusqu'à la fin de la période en cours.";
const confirmMsg = t('billing.cancelConfirm');
if (!window.confirm(confirmMsg)) {
return;
}
@@ -165,15 +165,15 @@ export function BillingPlans() {
});
const data = await res.json();
if (!res.ok) {
toast.error(data.error || 'Failed to cancel subscription.');
toast.error(data.error || t('billing.cancelFailed'));
return;
}
toast.success(t('billing.cancelSuccess') || "Votre abonnement a été résilié avec succès. Il prendra fin à la fin de la période de facturation en cours.");
toast.success(t('billing.cancelSuccess'));
queryClient.invalidateQueries({ queryKey: ['billing', 'status'] });
queryClient.invalidateQueries({ queryKey: ['usage', 'current'] });
} catch (err) {
console.error('[BillingPlans] cancel error:', err);
toast.error('Failed to cancel subscription.');
toast.error(t('billing.cancelFailed'));
} finally {
setCancelLoading(false);
}
@@ -229,7 +229,7 @@ export function BillingPlans() {
name: t('billing.proPlan'),
price: status?.prices?.PRO?.[interval]?.display ??
(interval === 'month' ? (t('billing.proPrice') || '9,90€') : (t('billing.proAnnualPrice') || '99€')),
period: interval === 'month' ? '/mois' : '/an',
period: interval === 'month' ? t('billing.perMonth') : t('billing.perYear'),
description: t('billing.proDescription') || 'Pour les consultants et créateurs exigeants.',
features: [
t('billing.proFeature1') || 'Notes illimitées',
@@ -252,8 +252,7 @@ export function BillingPlans() {
name: t('billing.businessPlan'),
price: status?.prices?.BUSINESS?.[interval]?.display ??
(interval === 'month' ? (t('billing.businessPrice') || '29,90€') : (t('billing.businessAnnualPrice') || '299€')),
period: interval === 'month' ? '/mois' : '/an',
description: t('billing.businessDescription') || 'Pour les équipes et chefs de produit.',
period: interval === 'month' ? t('billing.perMonth') : t('billing.perYear'),
features: [
t('billing.businessFeature1') || '10 Collaborateurs inclus',
t('billing.businessFeature2') || 'BYOK (13 fournisseurs)',
@@ -560,7 +559,7 @@ export function BillingPlans() {
)}
>
{t('billing.annual')}
<span className="ms-1 text-primary/80 dark:text-primary">{t('billing.save')} ~17%</span>
<span className="ms-1 text-primary/80 dark:text-primary">{t('billing.savePercent')}</span>
</button>
</div>
) : (