'use client' import { useState, useEffect } from 'react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useRouter } from 'next/navigation' import { cn } from '@/lib/utils' import { Sparkles, ChevronDown, X, Crown } from 'lucide-react' import { motion, AnimatePresence } from 'motion/react' import { useLanguage } from '@/lib/i18n' interface BalanceData { totalRemaining: number | null subscriptionRemaining: number | null purchasedRemaining: number subscriptionGranted: number | null subscriptionSpent: number spentThisPeriod: number unlimited: boolean } interface BreakdownRow { feature: string creditsUsed: number actionsCount: number percentOfSpent: number } interface UsageMeterProps { className?: string } /** Même liste que l'ancien compteur — toutes les actions IA, pas seulement les slides. */ const FEATURE_LABEL_KEYS: Record = { semantic_search: 'usageMeter.featureSearch', auto_tag: 'usageMeter.featureTags', auto_title: 'usageMeter.featureTitles', reformulate: 'usageMeter.featureReformulate', chat: 'usageMeter.featureChat', brainstorm_create: 'usageMeter.featureBrainstormSessions', brainstorm_expand: 'usageMeter.featureBrainstormExpand', brainstorm_enrich: 'usageMeter.featureBrainstormEnrich', suggest_charts: 'usageMeter.featureCharts', publish_enhance: 'usageMeter.featurePublishEnhance', ai_flashcard: 'usageMeter.featureFlashcards', voice_transcribe: 'usageMeter.featureVoice', slide_generate: 'usageMeter.featureSlides', excalidraw_generate: 'usageMeter.featureDiagrams', } export function UsageMeter({ className }: UsageMeterProps) { const { t } = useLanguage() const router = useRouter() const queryClient = useQueryClient() const [expanded, setExpanded] = useState(false) const [showModal, setShowModal] = useState(false) const { data, isLoading } = useQuery({ queryKey: ['usage', 'current'], queryFn: async () => { const res = await fetch('/api/usage/current') if (!res.ok) throw new Error('Failed to fetch usage') const json = await res.json() return { tier: json.tier as string, period: json.period as string, balance: json.balance as BalanceData, breakdown: (json.breakdown ?? []) as BreakdownRow[], } }, staleTime: 0, refetchInterval: 5000, }) useEffect(() => { const handler = () => queryClient.invalidateQueries({ queryKey: ['usage', 'current'] }) window.addEventListener('ai-usage-changed', handler) return () => window.removeEventListener('ai-usage-changed', handler) }, [queryClient]) if (isLoading || !data?.balance) { return (
) } const getPackLabel = () => { if (!data.tier) return t('usageMeter.packName') switch (data.tier) { case 'PRO': return t('usageMeter.packPro') case 'BUSINESS': return t('usageMeter.packBusiness') case 'ENTERPRISE': return t('usageMeter.packEnterprise') default: return t('usageMeter.packName') } } const isProPlus = data.tier && data.tier !== 'BASIC' const unlimited = !!data.balance.unlimited const remaining = unlimited ? Infinity : (data.balance.totalRemaining ?? 0) const granted = unlimited ? Infinity : (data.balance.subscriptionGranted ?? 0) const spent = data.balance.spentThisPeriod ?? 0 // Barre = part du forfait consommée (crédits utilisés / allocation du mois) const totalPct = !unlimited && Number.isFinite(granted) && granted > 0 ? Math.min(100, (spent / granted) * 100) : 0 const isExhausted = !unlimited && remaining <= 0 // Map breakdown → toutes les features du tableau (ordre API = déjà trié, on garde l'ordre labels) const byFeature = new Map(data.breakdown.map((r) => [r.feature, r])) const featureRows = Object.keys(FEATURE_LABEL_KEYS).map((key) => { const row = byFeature.get(key) return { key, label: t(FEATURE_LABEL_KEYS[key]), creditsUsed: row?.creditsUsed ?? 0, actionsCount: row?.actionsCount ?? 0, percent: row?.percentOfSpent ?? 0, } }) return ( <>
{expanded && (

{t('usageMeter.creditsWhereTitle')}

{!unlimited && (

{t('usageMeter.creditsSummary', { remaining: String(remaining), spent: String(spent), granted: String(granted), })}

)} {featureRows.map((f) => { // Barre = part de la conso totale, ou 0 si rien const barPct = spent > 0 && f.creditsUsed > 0 ? Math.min(100, (f.creditsUsed / spent) * 100) : 0 return (
{f.label} 0 ? 'text-ink/70' : 'text-concrete/50', )} > {f.creditsUsed > 0 ? t('usageMeter.creditsUsedLine', { credits: String(f.creditsUsed), actions: String(f.actionsCount), }) : '0'}
= 40 ? 'bg-brand-accent' : 'bg-brand-accent/50', )} style={{ width: `${barPct}%` }} />
) })} {isExhausted && ( )} {!isProPlus && !isExhausted && ( )}
)}
{showModal && (

{t('usageMeter.upgradeTitle')}

{t('usageMeter.upgradeDescription')}

)} ) }