import { useEffect, useMemo, useState, type FormEvent } from 'react'; import { Helmet } from 'react-helmet-async'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { AlertTriangle, BarChart3, BadgeCheck, Check, Copy, Download, FolderClock, KeyRound, LogOut, PartyPopper, ShieldCheck, Sparkles, Trash2, UserRound, Zap, } from 'lucide-react'; import { getHistory, getUsage, getApiKeys, createApiKey, revokeApiKey, type HistoryEntry, type UsageSummary, type ApiKey, } from '@/services/api'; import { useAuthStore } from '@/stores/authStore'; type AuthMode = 'login' | 'register'; const toolKeyMap: Record = { 'pdf-to-word': 'tools.pdfToWord.title', 'word-to-pdf': 'tools.wordToPdf.title', 'compress-pdf': 'tools.compressPdf.title', 'compress-image': 'tools.compressImage.title', 'crop-pdf': 'tools.cropPdf.title', 'crop-image': 'tools.imageCrop.title', 'edit-metadata': 'tools.pdfMetadata.title', 'excel-to-pdf': 'tools.excelToPdf.title', 'extract-pages': 'tools.extractPages.title', 'extract-tables': 'tools.tableExtractor.title', 'flatten-pdf': 'tools.flattenPdf.title', 'html-to-pdf': 'tools.htmlToPdf.title', 'image-convert': 'tools.imageConvert.title', 'image-converter': 'tools.imageConvert.title', 'image-crop': 'tools.imageCrop.title', 'image-resize': 'tools.imageConvert.title', 'image-rotate-flip': 'tools.imageRotateFlip.title', 'video-to-gif': 'tools.videoToGif.title', 'merge-pdf': 'tools.mergePdf.title', 'ocr': 'tools.ocr.title', 'split-pdf': 'tools.splitPdf.title', 'pdf-metadata': 'tools.pdfMetadata.title', 'pdf-to-excel': 'tools.pdfToExcel.title', 'pdf-to-pptx': 'tools.pdfToPptx.title', 'rotate-pdf': 'tools.rotatePdf.title', 'page-numbers': 'tools.pageNumbers.title', 'pdf-to-images': 'tools.pdfToImages.title', 'images-to-pdf': 'tools.imagesToPdf.title', 'watermark-pdf': 'tools.watermarkPdf.title', 'protect-pdf': 'tools.protectPdf.title', 'unlock-pdf': 'tools.unlockPdf.title', 'repair-pdf': 'tools.repairPdf.title', 'remove-background': 'tools.removeBg.title', 'remove-bg': 'tools.removeBg.title', 'remove-watermark-pdf': 'tools.removeWatermark.title', 'reorder-pdf': 'tools.reorderPdf.title', 'sign-pdf': 'tools.signPdf.title', 'summarize-pdf': 'tools.summarizePdf.title', 'translate-pdf': 'tools.translatePdf.title', 'chat-pdf': 'tools.chatPdf.title', 'barcode': 'tools.barcode.title', 'barcode-generator': 'tools.barcode.title', 'pptx-to-pdf': 'tools.pptxToPdf.title', 'pdf-flowchart': 'tools.pdfFlowchart.title', 'pdf-flowchart-sample': 'tools.pdfFlowchart.title', 'qr-code': 'tools.qrCode.title', }; function formatHistoryTool(tool: string, t: (key: string) => string) { const translationKey = toolKeyMap[tool]; return translationKey ? t(translationKey) : tool; } export default function AccountPage() { const { t, i18n } = useTranslation(); const user = useAuthStore((state) => state.user); const authLoading = useAuthStore((state) => state.isLoading); const initialized = useAuthStore((state) => state.initialized); const login = useAuthStore((state) => state.login); const register = useAuthStore((state) => state.register); const logout = useAuthStore((state) => state.logout); const isNewAccount = useAuthStore((state) => state.isNewAccount); const clearNewAccount = useAuthStore((state) => state.clearNewAccount); const credits = useAuthStore((state) => state.credits); // Welcome celebration for new registrations useEffect(() => { if (isNewAccount && user) { toast(t('account.welcomeTitle'), { description: t('account.welcomeMessage'), icon: , duration: 6000, }); clearNewAccount(); } }, [isNewAccount, user, t, clearNewAccount]); const [mode, setMode] = useState('login'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [submitError, setSubmitError] = useState(null); const [historyItems, setHistoryItems] = useState([]); const [historyLoading, setHistoryLoading] = useState(false); const [historyError, setHistoryError] = useState(null); // Usage summary state const [usage, setUsage] = useState(null); // API Keys state (pro only) const [apiKeys, setApiKeys] = useState([]); const [apiKeysLoading, setApiKeysLoading] = useState(false); const [newKeyName, setNewKeyName] = useState(''); const [newKeyCreating, setNewKeyCreating] = useState(false); const [newKeyError, setNewKeyError] = useState(null); const [revealedKey, setRevealedKey] = useState(null); const [copiedKey, setCopiedKey] = useState(false); const dateFormatter = useMemo( () => new Intl.DateTimeFormat(i18n.language, { dateStyle: 'medium', timeStyle: 'short', }), [i18n.language] ); const dashboardMetrics = useMemo(() => { const completedItems = historyItems.filter((item) => item.status === 'completed'); const failedItems = historyItems.filter((item) => item.status !== 'completed'); const toolCounts = historyItems.reduce>((acc, item) => { acc[item.tool] = (acc[item.tool] || 0) + 1; return acc; }, {}); const favoriteToolSlug = Object.entries(toolCounts) .sort((left, right) => right[1] - left[1])[0]?.[0] || null; return { totalProcessed: historyItems.length, completedCount: completedItems.length, failedCount: failedItems.length, favoriteToolSlug, successRate: historyItems.length ? Math.round((completedItems.length / historyItems.length) * 100) : 0, topTools: Object.entries(toolCounts) .sort((left, right) => right[1] - left[1]) .slice(0, 4), recentFailures: failedItems.slice(0, 3), onboardingItems: [ { key: 'firstTask', done: historyItems.length > 0, title: t('account.onboardingFirstTaskTitle'), description: t('account.onboardingFirstTaskDesc'), }, { key: 'upgrade', done: user?.plan === 'pro', title: t('account.onboardingUpgradeTitle'), description: t('account.onboardingUpgradeDesc'), }, { key: 'apiKey', done: user?.plan !== 'pro' ? false : apiKeys.some((key) => !key.revoked_at), title: t('account.onboardingApiTitle'), description: t('account.onboardingApiDesc'), }, ], }; }, [apiKeys, historyItems, t, user?.plan]); useEffect(() => { if (!user) { setHistoryItems([]); setHistoryError(null); setUsage(null); setApiKeys([]); return; } const loadHistory = async () => { setHistoryLoading(true); setHistoryError(null); try { const items = await getHistory(); setHistoryItems(items); } catch (error) { setHistoryError(error instanceof Error ? error.message : t('account.loadFailed')); } finally { setHistoryLoading(false); } }; const loadUsage = async () => { try { const data = await getUsage(); setUsage(data); } catch { // non-critical, ignore } }; const loadApiKeys = async () => { if (user.plan !== 'pro') return; setApiKeysLoading(true); try { const keys = await getApiKeys(); setApiKeys(keys); } catch { // non-critical } finally { setApiKeysLoading(false); } }; void loadHistory(); void loadUsage(); void loadApiKeys(); }, [t, user]); const handleSubmit = async (event: FormEvent) => { event.preventDefault(); setSubmitError(null); if (mode === 'register' && password !== confirmPassword) { const msg = t('account.passwordMismatch'); setSubmitError(msg); toast.error(msg); return; } try { if (mode === 'login') { await login(email, password); } else { await register(email, password); } setPassword(''); setConfirmPassword(''); } catch (error) { const msg = error instanceof Error ? error.message : t('account.loadFailed'); setSubmitError(msg); toast.error(msg); } }; const handleLogout = async () => { setSubmitError(null); try { await logout(); setHistoryItems([]); setUsage(null); setApiKeys([]); } catch (error) { const msg = error instanceof Error ? error.message : t('account.loadFailed'); setSubmitError(msg); toast.error(msg); } }; const handleCreateApiKey = async (e: FormEvent) => { e.preventDefault(); setNewKeyError(null); const name = newKeyName.trim(); if (!name) return; setNewKeyCreating(true); try { const key = await createApiKey(name); setApiKeys((prev) => [key, ...prev]); setRevealedKey(key.raw_key ?? null); setNewKeyName(''); } catch (error) { const msg = error instanceof Error ? error.message : t('account.loadFailed'); setNewKeyError(msg); toast.error(msg); } finally { setNewKeyCreating(false); } }; const handleRevokeApiKey = async (keyId: number) => { try { await revokeApiKey(keyId); setApiKeys((prev) => prev.map((k) => k.id === keyId ? { ...k, revoked_at: new Date().toISOString() } : k ) ); } catch { // ignore } }; const handleCopyKey = async () => { if (!revealedKey) return; await navigator.clipboard.writeText(revealedKey); setCopiedKey(true); setTimeout(() => setCopiedKey(false), 2000); }; return ( <> {t('account.metaTitle')} — {t('common.appName')} {!initialized && authLoading ? (
) : user ? (
{user.plan === 'pro' ? : } {user.plan === 'pro' ? t('account.proPlanBadge') : t('account.freePlanBadge')}

{t('account.heroTitle')}

{t('account.heroSubtitle')}

{user.plan === 'free' && (

{t('account.upgradeNotice')}

)}
{t('account.signedInAs')}

{user.email}

{t('account.currentPlan')}: {user.plan === 'pro' ? t('account.plans.pro') : t('account.plans.free')}
{/* Credit Balance Cards */} {usage && usage.credits && (

{t('account.creditBalanceTitle')}

{usage.credits.credits_remaining} / {usage.credits.credits_allocated}

{usage.credits.window_end && (

{t('account.creditWindowResets')}: {new Date(usage.credits.window_end).toLocaleDateString()}

)}
{usage.api_quota?.limit != null && (

{t('account.apiQuotaTitle')}

{usage.api_quota.used} / {usage.api_quota.limit}

)}
)}

{t('account.dashboardTitle')}

{t('account.dashboardSubtitle')}

{t('account.metricProcessed')}

{dashboardMetrics.totalProcessed}

{t('account.metricSuccessRate')}

{dashboardMetrics.successRate}%

{t('account.metricFavoriteTool')}

{dashboardMetrics.favoriteToolSlug ? formatHistoryTool(dashboardMetrics.favoriteToolSlug, t) : t('account.metricFavoriteToolEmpty')}

{t('account.metricFailures')}

{dashboardMetrics.failedCount}

{t('account.topToolsTitle')}

{dashboardMetrics.topTools.length === 0 ? (

{t('account.historyEmpty')}

) : ( dashboardMetrics.topTools.map(([tool, count]) => (
{formatHistoryTool(tool, t)} {count}
)) )}

{t('account.issuesTitle')}

{dashboardMetrics.recentFailures.length === 0 ? (

{t('account.issuesEmpty')}

) : ( dashboardMetrics.recentFailures.map((item) => (

{formatHistoryTool(item.tool, t)}

{typeof item.metadata?.error === 'string' ? item.metadata.error : t('account.statusFailed')}

)) )}

{t('account.onboardingTitle')}

{t('account.onboardingSubtitle')}

{dashboardMetrics.onboardingItems.map((item) => (
{item.done ? : }

{item.title}

{item.description}

))}
{/* API Key Management — Pro only */} {user.plan === 'pro' && (

{t('account.apiKeysTitle')}

{t('account.apiKeysSubtitle')}

{/* Create key form */}
setNewKeyName(e.target.value)} placeholder={t('account.apiKeyNamePlaceholder')} maxLength={100} className="input flex-1" />
{newKeyError && (

{newKeyError}

)} {/* Revealed key — shown once after creation */} {revealedKey && (
{revealedKey}
)} {revealedKey && (

{t('account.apiKeyCopyWarning')}

)} {/* Key list */} {apiKeysLoading ? (

{t('account.historyLoading')}

) : apiKeys.length === 0 ? (

{t('account.apiKeysEmpty')}

) : (
    {apiKeys.map((key) => (
  • {key.name}

    {key.key_prefix}…

    {key.revoked_at && (

    {t('account.apiKeyRevoked')}

    )}
    {!key.revoked_at && ( )}
  • ))}
)}
)}

{t('account.historyTitle')}

{t('account.historySubtitle')}

{historyLoading ? (

{t('account.historyLoading')}

) : historyError ? (
{historyError}
) : historyItems.length === 0 ? (

{t('account.historyEmpty')}

) : ( historyItems.map((item) => { const metadataError = typeof item.metadata?.error === 'string' ? item.metadata.error : null; return (

{formatHistoryTool(item.tool, t)}

{item.output_filename || item.original_filename || formatHistoryTool(item.tool, t)}

{t('account.createdAt')}: {dateFormatter.format(new Date(item.created_at))}

{item.status === 'completed' ? t('account.statusCompleted') : t('account.statusFailed')}

{t('account.originalFile')}

{item.original_filename || '—'}

{t('account.outputFile')}

{item.output_filename || '—'}

{metadataError ? (

{metadataError}

) : null} {item.download_url && item.status === 'completed' ? ( {t('account.downloadResult')} ) : null}
); }) )}
) : (
{t('account.benefitsTitle')}

{t('account.heroTitle')}

{t('account.heroSubtitle')}

{[t('account.benefit1'), t('account.benefit2'), t('account.benefit3')].map((benefit) => (

{benefit}

))}

{mode === 'login' ? t('account.signInTitle') : t('account.registerTitle')}

{t('account.formSubtitle')}

{mode === 'register' ? ( ) : null} {submitError ? (
{submitError}
) : null} {mode === 'login' && (

{t('auth.forgotPassword.link')}

)}
)} ); }