'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslations, useLocale } from 'next-intl'; import { listApiKeys, createApiKey, revokeApiKey } from '@/lib/api/api-keys'; import type { ApiKeyDto, CreateApiKeyResultDto } from '@/lib/api/api-keys'; import { useSubscription } from '@/lib/context/subscription-context'; import { Key, Plus, Trash2, Copy, Check, AlertTriangle, Clock, X, ShieldCheck, Lock, } from 'lucide-react'; import { PageHeader } from '@/components/ui/PageHeader'; function CopyButton({ text }: { text: string }) { const t = useTranslations('dashboard.apiKeys.copy'); const [copied, setCopied] = useState(false); const handleCopy = async () => { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return ( ); } function CreatedKeyModal({ result, onClose, }: { result: CreateApiKeyResultDto; onClose: () => void; }) { const t = useTranslations('dashboard.apiKeys.createdModal'); return (

{t('title')}

{result.name}

{t('warning')} {t('warningRest')}

{result.fullKey}

{t('storeHint')}

); } function CreateKeyModal({ onSuccess, onClose, }: { onSuccess: (result: CreateApiKeyResultDto) => void; onClose: () => void; }) { const t = useTranslations('dashboard.apiKeys.createModal'); const [name, setName] = useState(''); const [expiresAt, setExpiresAt] = useState(''); const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: createApiKey, onSuccess: result => { queryClient.invalidateQueries({ queryKey: ['api-keys'] }); onSuccess(result); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); mutation.mutate({ name: name.trim(), ...(expiresAt ? { expiresAt: new Date(expiresAt).toISOString() } : {}), }); }; return (

{t('title')}

setName(e.target.value)} placeholder={t('namePlaceholder')} maxLength={100} required className="w-full px-3.5 py-2.5 border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-[#34CCCD] focus:border-transparent" />

{t('nameCount', { count: name.length })}

setExpiresAt(e.target.value)} min={new Date().toISOString().split('T')[0]} className="w-full px-3.5 py-2.5 border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-[#34CCCD] focus:border-transparent" />

{t('expiryHint')}

{mutation.isError && (
{t('errorGeneric')}
)}
); } function RevokeConfirmModal({ apiKey, onConfirm, onClose, }: { apiKey: ApiKeyDto; onConfirm: () => void; onClose: () => void; }) { const t = useTranslations('dashboard.apiKeys.revokeModal'); return (

{t('title')}

{apiKey.name}

{t('description')} {t('descriptionEmphasis')} {t('descriptionRest')}

); } export default function ApiKeysPage() { const t = useTranslations('dashboard.apiKeys'); const locale = useLocale(); const dateLocale = locale === 'fr' ? 'fr-FR' : 'en-US'; const { hasFeature } = useSubscription(); const queryClient = useQueryClient(); const hasApiAccess = hasFeature('api_access'); const [showCreateModal, setShowCreateModal] = useState(false); const [createdKey, setCreatedKey] = useState(null); const [revokeTarget, setRevokeTarget] = useState(null); const { data: apiKeys, isLoading } = useQuery({ queryKey: ['api-keys'], queryFn: listApiKeys, enabled: hasApiAccess, }); const revokeMutation = useMutation({ mutationFn: revokeApiKey, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['api-keys'] }); setRevokeTarget(null); }, }); const formatDate = (iso: string | null): string => { if (!iso) return '—'; return new Intl.DateTimeFormat(dateLocale, { dateStyle: 'medium' }).format(new Date(iso)); }; const keyStatusBadge = (key: ApiKeyDto) => { if (!key.isActive) { return ( {t('status.revoked')} ); } if (key.expiresAt && new Date(key.expiresAt) < new Date()) { return ( {t('status.expired')} ); } return ( {t('status.active')} ); }; if (!hasApiAccess) { return (

{t('noAccess.title')}

{t.rich('noAccess.description', { gold: () => {t('noAccess.gold')}, platinium: () => {t('noAccess.platinium')}, })}

{t('noAccess.viewPlans')}
); } const activeKeys = apiKeys?.filter(k => k.isActive) ?? []; return ( <> {showCreateModal && ( { setShowCreateModal(false); setCreatedKey(result); }} onClose={() => setShowCreateModal(false)} /> )} {createdKey && ( setCreatedKey(null)} /> )} {revokeTarget && ( revokeMutation.mutate(revokeTarget.id)} onClose={() => setRevokeTarget(null)} /> )} setShowCreateModal(true)} disabled={activeKeys.length >= 20} className="flex items-center gap-2 px-4 py-2.5 bg-[#10183A] hover:bg-[#1a2550] disabled:opacity-50 text-white text-sm font-medium rounded-xl transition-colors" > {t('newKey')} } />

{t('infoTitle')}

{t.rich('infoBody', { code: () => ( X-API-Key: xped_live_... ), link: () => ( {t('viewDocs')} ), })}

{isLoading ? (
) : !apiKeys || apiKeys.length === 0 ? (

{t('noKeys')}

) : (
{t('table.name')} {t('table.lastUsed')} {t('table.expiry')} {t('table.status')}
{apiKeys.map(key => (

{key.name}

{key.keyPrefix}…
{formatDate(key.lastUsedAt)} {formatDate(key.expiresAt)}
{keyStatusBadge(key)}
))}
)}
{apiKeys && apiKeys.length > 0 && (

{t('quota', { active: activeKeys.length, max: 20 })}

)} ); }