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:
Sepehr Ramezani
2026-04-25 15:01:47 +02:00
parent 2ba4fedfc8
commit 26bd096a06
1178 changed files with 136435 additions and 3047 deletions

View File

@@ -18,15 +18,18 @@ import { Separator } from '@/components/ui/separator';
import { useUser } from './useUser';
import { useLogout } from './useLogout';
import { getNavItems } from './constants';
import { getInitials } from './utils';
import { getInitials, translateTier } from './utils';
import { ThemeToggle } from '@/components/ui/theme-toggle';
import { useI18n } from '@/lib/i18n';
export function DashboardHeader() {
const [mobileOpen, setMobileOpen] = useState(false);
const pathname = usePathname();
const { data: user, isLoading } = useUser();
const { logout } = useLogout();
const { t } = useI18n();
const navItems = getNavItems(user?.tier === 'pro');
const navItems = getNavItems(['pro', 'business', 'enterprise'].includes(user?.tier ?? ''));
return (
<>
@@ -37,7 +40,7 @@ export function DashboardHeader() {
size="icon"
className="lg:hidden"
onClick={() => setMobileOpen(!mobileOpen)}
aria-label="Toggle menu"
aria-label={t('dashboard.header.toggleMenu')}
>
{mobileOpen ? <X className="size-4" /> : <Menu className="size-4" />}
</Button>
@@ -47,35 +50,42 @@ export function DashboardHeader() {
<div className="flex size-6 items-center justify-center rounded-md bg-foreground">
<Languages className="size-3 text-background" />
</div>
<span className="text-sm font-semibold text-foreground">Office Translator</span>
<span className="text-sm font-semibold text-foreground">{t('auth.brandName')}</span>
</div>
{/* Page title - desktop */}
<div className="hidden items-center gap-3 lg:flex">
<h1 className="text-sm font-semibold text-foreground">Dashboard</h1>
<h1 className="text-sm font-semibold text-foreground">{t('dashboard.header.title')}</h1>
<Separator orientation="vertical" className="h-4" />
<span className="text-sm text-muted-foreground">Manage your API and translation settings</span>
<span className="text-sm text-muted-foreground">{t('dashboard.header.subtitle')}</span>
</div>
{/* Right side */}
{!isLoading && user && (
<div className="flex items-center gap-3">
<Badge
variant="secondary"
className={cn(
'border border-accent/20',
user.tier === 'pro' ? 'bg-accent/10 text-accent' : 'bg-muted text-muted-foreground'
)}
>
{user.tier === 'pro' ? 'Pro Plan' : 'Free Plan'}
</Badge>
<Avatar className="size-8">
<AvatarFallback className="bg-accent text-accent-foreground text-xs font-semibold">
{getInitials(user.name)}
</AvatarFallback>
</Avatar>
</div>
)}
<div className="flex items-center gap-2">
<ThemeToggle />
{!isLoading && user && (
<>
<Badge
variant="secondary"
className={cn(
'border border-accent/20',
user.tier === 'free' || !user.tier
? 'bg-muted text-muted-foreground'
: 'bg-accent/10 text-accent'
)}
>
{translateTier(t, user.tier)}
</Badge>
<Link href="/dashboard/profile" title={t('dashboard.header.profileTitle')}>
<Avatar className="size-8 cursor-pointer ring-2 ring-transparent hover:ring-accent/40 transition-all">
<AvatarFallback className="bg-accent text-accent-foreground text-xs font-semibold">
{getInitials(user.name)}
</AvatarFallback>
</Avatar>
</Link>
</>
)}
</div>
</header>
{/* Mobile navigation drawer */}
@@ -97,13 +107,17 @@ export function DashboardHeader() {
)}
>
<item.icon className="size-4 shrink-0" />
{item.label}
{t(item.labelKey)}
</Link>
);
})}
<Separator className="my-2" />
{!isLoading && user && (
<div className="flex items-center gap-3 px-3 py-2">
<Link
href="/dashboard/profile"
onClick={() => setMobileOpen(false)}
className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-secondary/60 transition-colors"
>
<Avatar className="size-8">
<AvatarFallback className="bg-accent text-accent-foreground text-xs font-semibold">
{getInitials(user.name)}
@@ -111,31 +125,31 @@ export function DashboardHeader() {
</Avatar>
<div className="flex flex-col gap-0.5">
<span className="text-sm font-medium text-foreground">{user.name}</span>
<Badge
variant="secondary"
<Badge
variant="secondary"
className={cn(
'text-xs w-fit',
user.tier === 'pro' && 'border border-accent/20 bg-accent/10 text-accent'
)}
>
{user.tier === 'pro' ? 'Pro' : 'Free'}
{translateTier(t, user.tier)}
</Badge>
</div>
</div>
</Link>
)}
<button
onClick={logout}
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
>
<LogOut className="size-4 shrink-0" />
Sign out
{t('dashboard.sidebar.signOut')}
</button>
<Link
href="/"
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
>
<ChevronLeft className="size-4 shrink-0" />
Back to home
{t('dashboard.sidebar.backHome')}
</Link>
</nav>
</div>