feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,102 +1,79 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { FileText, Key, BookText, ChevronRight } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { useUser } from './useUser';
|
||||
import { API_BASE } from '@/lib/config';
|
||||
|
||||
/**
|
||||
* /dashboard — point d'entrée après connexion.
|
||||
*
|
||||
* Unique rôle : synchroniser le paiement Stripe si `session_id` est présent
|
||||
* dans l'URL (retour depuis Stripe Checkout), puis rediriger vers /dashboard/translate.
|
||||
*/
|
||||
export default function DashboardPage() {
|
||||
const { data: user, isLoading } = useUser();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const checkoutSessionId = searchParams.get('session_id');
|
||||
const [syncError, setSyncError] = useState<string | null>(null);
|
||||
const { refetch } = useUser();
|
||||
|
||||
if (isLoading) {
|
||||
useEffect(() => {
|
||||
if (!checkoutSessionId) {
|
||||
router.replace('/dashboard/translate');
|
||||
return;
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
router.replace('/dashboard/translate');
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const runSync = async () => {
|
||||
setSyncError(null);
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE}/api/v1/auth/checkout/sync?session_id=${encodeURIComponent(checkoutSessionId)}`,
|
||||
{ headers: { Authorization: `Bearer ${token}` } }
|
||||
);
|
||||
if (!cancelled) {
|
||||
if (!res.ok) {
|
||||
const errData = await res.json().catch(() => ({}));
|
||||
setSyncError(errData.message || 'Erreur lors de la synchronisation du paiement.');
|
||||
} else {
|
||||
await refetch();
|
||||
router.replace('/dashboard/translate');
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if (!cancelled) setSyncError('Erreur réseau. Veuillez rafraîchir la page.');
|
||||
}
|
||||
};
|
||||
|
||||
runSync();
|
||||
return () => { cancelled = true; };
|
||||
}, [checkoutSessionId, refetch, router]);
|
||||
|
||||
if (syncError) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-4 border-muted border-t-foreground"></div>
|
||||
<div className="flex flex-col items-center justify-center gap-4 py-20">
|
||||
<p className="text-sm text-destructive">{syncError}</p>
|
||||
<button
|
||||
onClick={() => router.replace('/dashboard/translate')}
|
||||
className="text-xs text-muted-foreground underline"
|
||||
>
|
||||
Continuer vers la traduction
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const firstName = user?.name?.split(' ')[0] || 'User';
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl px-4 py-6 lg:px-8 lg:py-8">
|
||||
{/* Page heading */}
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-semibold tracking-tight text-foreground">
|
||||
Welcome back, {firstName}!
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Monitor your usage, manage API keys, and configure translation preferences.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick actions */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Link href="/dashboard/translate">
|
||||
<Card className="cursor-pointer transition-colors hover:bg-secondary/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Translate Document</CardTitle>
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>
|
||||
Upload and translate Excel, Word, or PowerPoint files
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
<Link href="/dashboard/api-keys">
|
||||
<Card className="cursor-pointer transition-colors hover:bg-secondary/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">API Keys</CardTitle>
|
||||
<Key className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>
|
||||
Manage your API keys for automation
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{user?.tier === 'pro' && (
|
||||
<Link href="/dashboard/glossaries">
|
||||
<Card className="cursor-pointer transition-colors hover:bg-secondary/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Glossaries</CardTitle>
|
||||
<BookText className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>
|
||||
Create custom terminology for translations
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Plan info */}
|
||||
{user?.tier === 'free' && (
|
||||
<Card className="mt-6 border-primary/50 bg-primary/5">
|
||||
<CardContent className="flex items-center justify-between pt-6">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">Upgrade to Pro</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Get unlimited translations, API access, and custom glossaries
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/pricing">
|
||||
<Button variant="premium" size="sm" className="gap-1">
|
||||
View Plans
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<Loader2 className="size-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user