fix: compact glossary dropdown, sticky translate button, and mode badge
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m10s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m10s
- Restructure right sidebar as flex-col with scrollable config and fixed translate button (never scrolls away) - Replace expanded GlossarySelector list with compact dropdown (~80px instead of ~400px), collapsible templates section, click-outside close - Add visible Classic/IA mode badge after provider selector with hint about glossary availability for Pro users in classic mode Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -438,116 +438,138 @@ export default function TranslatePage() {
|
||||
|
||||
{/* ── CONFIG (upload / configuring / failed) ──────────── */}
|
||||
{(showUpload || showConfiguring || showFailed) && (
|
||||
<div className="space-y-8">
|
||||
<div className="editorial-card bg-white border-none shadow-editorial dark:bg-[#141414] overflow-hidden">
|
||||
<div className="p-10 pb-6">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] mb-10 text-brand-dark/30 border-b border-black/5 pb-6 dark:text-white/30 dark:border-white/5">
|
||||
{t('landing.translate.configuration')}
|
||||
</h4>
|
||||
<div className="editorial-card bg-white border-none shadow-editorial dark:bg-[#141414] overflow-hidden flex flex-col lg:sticky lg:top-8 lg:max-h-[calc(100vh-6rem)]">
|
||||
{/* Scrollable config content */}
|
||||
<div className="flex-1 overflow-y-auto p-10 pb-6">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] mb-10 text-brand-dark/30 border-b border-black/5 pb-6 dark:text-white/30 dark:border-white/5">
|
||||
{t('landing.translate.configuration')}
|
||||
</h4>
|
||||
|
||||
<div className="space-y-8">
|
||||
<LanguageSelector
|
||||
sourceLang={config.sourceLang} targetLang={config.targetLang}
|
||||
languages={config.languages} isLoading={config.isLoadingLanguages}
|
||||
error={config.languagesError} onSourceChange={config.setSourceLang}
|
||||
onTargetChange={config.setTargetLang}
|
||||
/>
|
||||
<div className="space-y-8">
|
||||
<LanguageSelector
|
||||
sourceLang={config.sourceLang} targetLang={config.targetLang}
|
||||
languages={config.languages} isLoading={config.isLoadingLanguages}
|
||||
error={config.languagesError} onSourceChange={config.setSourceLang}
|
||||
onTargetChange={config.setTargetLang}
|
||||
/>
|
||||
|
||||
<ProviderSelector
|
||||
provider={config.provider} onProviderChange={config.setProvider}
|
||||
availableProviders={config.availableProviders} isLoadingProviders={config.isLoadingProviders}
|
||||
isPro={config.isPro}
|
||||
/>
|
||||
<ProviderSelector
|
||||
provider={config.provider} onProviderChange={config.setProvider}
|
||||
availableProviders={config.availableProviders} isLoadingProviders={config.isLoadingProviders}
|
||||
isPro={config.isPro}
|
||||
/>
|
||||
|
||||
{/* Glossary — Pro + LLM mode only */}
|
||||
{config.isPro && config.mode === 'llm' && (
|
||||
<GlossarySelector
|
||||
sourceLang={config.sourceLang}
|
||||
targetLang={config.targetLang}
|
||||
isPro={config.isPro}
|
||||
glossaryId={config.glossaryId}
|
||||
onChange={config.setGlossaryId}
|
||||
disabled={submit.isSubmitting}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* PDF mode selector */}
|
||||
{isPdf && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-[9px] font-black text-brand-dark/40 uppercase tracking-[0.2em] block mb-3 dark:text-white/40">
|
||||
{t('dashboard.translate.pdfMode.title')}
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPdfMode('layout')}
|
||||
className={cn(
|
||||
'flex flex-col items-start rounded-2xl border-2 p-3 text-start transition-all',
|
||||
pdfMode === 'layout'
|
||||
? 'border-brand-accent bg-brand-accent/5'
|
||||
: 'border-black/5 bg-brand-muted/30 hover:border-brand-accent/20 dark:border-white/10 dark:bg-white/5'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<FileText className="size-4 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.preserveLayout')}
|
||||
</div>
|
||||
<p className="mt-1 text-[9px] text-brand-dark/50 font-bold uppercase tracking-widest leading-relaxed dark:text-white/40">
|
||||
{t('dashboard.translate.pdfMode.preserveLayoutDesc')}
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPdfMode('text_only')}
|
||||
className={cn(
|
||||
'flex flex-col items-start rounded-2xl border-2 p-3 text-start transition-all',
|
||||
pdfMode === 'text_only'
|
||||
? 'border-brand-accent bg-brand-accent/5'
|
||||
: 'border-black/5 bg-brand-muted/30 hover:border-brand-accent/20 dark:border-white/10 dark:bg-white/5'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<Languages className="size-4 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.textOnly')}
|
||||
</div>
|
||||
<p className="mt-1 text-[9px] text-brand-dark/50 font-bold uppercase tracking-widest leading-relaxed dark:text-white/40">
|
||||
{t('dashboard.translate.pdfMode.textOnlyDesc')}
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── TRANSLATE BUTTON — sticky at bottom, always visible ── */}
|
||||
<div className="sticky bottom-0 p-6 pt-4 bg-white dark:bg-[#141414] border-t border-black/5 dark:border-white/5">
|
||||
<button
|
||||
disabled={!config.isConfigValid || submit.isSubmitting || !upload.file}
|
||||
onClick={handleTranslate}
|
||||
className={cn(
|
||||
'w-full py-5 rounded-2xl text-[13px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-3 transition-all duration-200',
|
||||
config.isConfigValid && upload.file && !submit.isSubmitting
|
||||
? 'bg-brand-dark text-white hover:bg-brand-dark/90 shadow-lg shadow-brand-dark/20 dark:bg-brand-accent dark:text-brand-dark dark:hover:bg-brand-accent/90'
|
||||
: 'bg-black/5 dark:bg-white/5 text-brand-dark/25 dark:text-white/25 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
{submit.isSubmitting ? (
|
||||
<><Loader2 className="size-5 animate-spin" />Envoi en cours...</>
|
||||
) : (
|
||||
<><ArrowRight size={20} />Lancer la traduction</>
|
||||
)}
|
||||
</button>
|
||||
{!upload.file && (
|
||||
<p className="text-center text-[10px] text-brand-dark/30 dark:text-white/30 mt-2 font-bold uppercase tracking-widest">↑ Déposez d'abord un fichier</p>
|
||||
{/* Active mode badge */}
|
||||
{config.provider && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[9px] font-black uppercase tracking-[0.15em]",
|
||||
config.mode === 'llm'
|
||||
? "bg-brand-accent/10 text-brand-accent border border-brand-accent/20"
|
||||
: "bg-brand-muted/50 text-brand-dark/40 dark:bg-white/5 dark:text-white/40 border border-transparent"
|
||||
)}>
|
||||
{config.mode === 'llm' ? (
|
||||
<><Zap size={10} /> Mode IA</>
|
||||
) : (
|
||||
<><Languages size={10} /> Mode Classique</>
|
||||
)}
|
||||
</span>
|
||||
{config.mode === 'classic' && config.isPro && (
|
||||
<span className="text-[9px] text-brand-dark/20 dark:text-white/20 italic">
|
||||
Glossaires disponibles en mode IA
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{upload.file && !config.targetLang && (
|
||||
<p className="text-center text-[10px] text-brand-dark/30 dark:text-white/30 mt-2 font-bold uppercase tracking-widest">↑ Sélectionnez une langue cible</p>
|
||||
|
||||
{/* Glossary — Pro + LLM mode only */}
|
||||
{config.isPro && config.mode === 'llm' && (
|
||||
<GlossarySelector
|
||||
sourceLang={config.sourceLang}
|
||||
targetLang={config.targetLang}
|
||||
isPro={config.isPro}
|
||||
glossaryId={config.glossaryId}
|
||||
onChange={config.setGlossaryId}
|
||||
disabled={submit.isSubmitting}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* PDF mode selector */}
|
||||
{isPdf && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-[9px] font-black text-brand-dark/40 uppercase tracking-[0.2em] block mb-3 dark:text-white/40">
|
||||
{t('dashboard.translate.pdfMode.title')}
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPdfMode('layout')}
|
||||
className={cn(
|
||||
'flex flex-col items-start rounded-2xl border-2 p-3 text-start transition-all',
|
||||
pdfMode === 'layout'
|
||||
? 'border-brand-accent bg-brand-accent/5'
|
||||
: 'border-black/5 bg-brand-muted/30 hover:border-brand-accent/20 dark:border-white/10 dark:bg-white/5'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<FileText className="size-4 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.preserveLayout')}
|
||||
</div>
|
||||
<p className="mt-1 text-[9px] text-brand-dark/50 font-bold uppercase tracking-widest leading-relaxed dark:text-white/40">
|
||||
{t('dashboard.translate.pdfMode.preserveLayoutDesc')}
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPdfMode('text_only')}
|
||||
className={cn(
|
||||
'flex flex-col items-start rounded-2xl border-2 p-3 text-start transition-all',
|
||||
pdfMode === 'text_only'
|
||||
? 'border-brand-accent bg-brand-accent/5'
|
||||
: 'border-black/5 bg-brand-muted/30 hover:border-brand-accent/20 dark:border-white/10 dark:bg-white/5'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<Languages className="size-4 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.textOnly')}
|
||||
</div>
|
||||
<p className="mt-1 text-[9px] text-brand-dark/50 font-bold uppercase tracking-widest leading-relaxed dark:text-white/40">
|
||||
{t('dashboard.translate.pdfMode.textOnlyDesc')}
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between px-6 text-[9px] font-black uppercase tracking-[0.2em] text-brand-dark/20 dark:text-white/20">
|
||||
{/* ── TRANSLATE BUTTON — fixed at bottom, never scrolls away ── */}
|
||||
<div className="shrink-0 p-6 pt-4 bg-white dark:bg-[#141414] border-t border-black/5 dark:border-white/5">
|
||||
<button
|
||||
disabled={!config.isConfigValid || submit.isSubmitting || !upload.file}
|
||||
onClick={handleTranslate}
|
||||
className={cn(
|
||||
'w-full py-5 rounded-2xl text-[13px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-3 transition-all duration-200',
|
||||
config.isConfigValid && upload.file && !submit.isSubmitting
|
||||
? 'bg-brand-dark text-white hover:bg-brand-dark/90 shadow-lg shadow-brand-dark/20 dark:bg-brand-accent dark:text-brand-dark dark:hover:bg-brand-accent/90'
|
||||
: 'bg-black/5 dark:bg-white/5 text-brand-dark/25 dark:text-white/25 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
{submit.isSubmitting ? (
|
||||
<><Loader2 className="size-5 animate-spin" />Envoi en cours...</>
|
||||
) : (
|
||||
<><ArrowRight size={20} />Lancer la traduction</>
|
||||
)}
|
||||
</button>
|
||||
{!upload.file && (
|
||||
<p className="text-center text-[10px] text-brand-dark/30 dark:text-white/30 mt-2 font-bold uppercase tracking-widest">↑ Déposez d'abord un fichier</p>
|
||||
)}
|
||||
{upload.file && !config.targetLang && (
|
||||
<p className="text-center text-[10px] text-brand-dark/30 dark:text-white/30 mt-2 font-bold uppercase tracking-widest">↑ Sélectionnez une langue cible</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 flex justify-between px-6 pb-5 pt-1 text-[9px] font-black uppercase tracking-[0.2em] text-brand-dark/20 dark:text-white/20">
|
||||
<span className="flex items-center gap-2">
|
||||
<ShieldCheck size={12} /> {t('landing.translate.zeroRetention')}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user