fix(ui): wave 2 from critique re-run — AA contrast, unified PageHeader, checkout confirm
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m46s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m46s
Nav: teams/settings/services back out of the nav until product-ready (user decision); teams page was already half-finished (UUID member display is a backend limitation). Payment: explicit confirmation dialog (plan, amount, billing period, Stripe note) before any redirect; ?plan= URL now pre-opens the dialog instead of triggering a silent checkout. Contrast AA sweep (71 replacements): functional micro-labels raised to >=60-65% opacity and >=10px across sidebar, header, pricing, landing; decorative all-caps 'interface' label removed. Design unification: new PageHeader component (accent pill + serif base/accent title) applied to settings, services, reviews, teams; pricing header returned to the editorial voice (serif + accent pill); dead GlossaryCard deleted. i18n residuals: suggestion chips, 'Standard' provider label, notification close, model-combobox strings extracted (+broken bg-surface/border-border- subtle tokens fixed); ~45 new keys EN+FR; 13 dead keys removed; zero missing keys verified. Reviews: icon-only row actions now carry visible text; 'Approve all' is two-step armed-confirm. Accelerators: Ctrl/Cmd+Enter submits; arrow-key navigation in the language combobox; recent-jobs history cap 8->20 with per-job download; source=target config rejected; empty 'Master Quality' badge removed. Cookie consent: reopenable via footer link, emoji replaced with drawn icon. Verified: build exit 0, vitest 9/9, eslint 64 errors = previous level, 0 missing i18n keys.
This commit is contained in:
@@ -142,13 +142,13 @@ export default function TranslatePage() {
|
||||
};
|
||||
|
||||
const handleNewTranslation = () => { submit.reset(); upload.removeFile(); setElapsed(0); setRecentJobs(getRecentJobs()); };
|
||||
const handleDownload = async () => {
|
||||
if (!submit.jobId) return;
|
||||
const handleDownload = async (jobId: string = submit.jobId ?? '') => {
|
||||
if (!jobId) return;
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/v1/download/${submit.jobId}`, { headers });
|
||||
const response = await fetch(`${API_BASE}/api/v1/download/${jobId}`, { headers });
|
||||
if (!response.ok) {
|
||||
showError({ title: t('dashboard.translate.error.title'), description: t('translate.downloadFailed') });
|
||||
return;
|
||||
@@ -180,6 +180,19 @@ export default function TranslatePage() {
|
||||
const isCompleted = submit.status === 'completed';
|
||||
const isFailed = submit.status === 'failed';
|
||||
|
||||
// Ctrl/Cmd+Enter launches the translation from anywhere on the page
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && upload.file && config.isConfigValid && !submit.isSubmitting && !isProcessing) {
|
||||
e.preventDefault();
|
||||
handleTranslate();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [upload.file, config.isConfigValid, submit.isSubmitting, isProcessing]);
|
||||
|
||||
const showUpload = !upload.file && !isProcessing && !isCompleted && !isFailed;
|
||||
const showConfiguring = isConfiguring;
|
||||
const showProcessing = isProcessing;
|
||||
@@ -311,18 +324,27 @@ export default function TranslatePage() {
|
||||
{t('translate.recent.title')}
|
||||
</h4>
|
||||
<ul className="divide-y divide-black/[0.03] dark:divide-white/[0.03]">
|
||||
{recentJobs.slice(0, 4).map((job) => (
|
||||
{recentJobs.slice(0, 6).map((job) => (
|
||||
<li key={job.jobId} className="flex items-center justify-between gap-3 py-3">
|
||||
<span className="flex min-w-0 items-center gap-2.5 text-xs font-semibold text-brand-dark dark:text-white">
|
||||
<FileText className="size-3.5 shrink-0 text-brand-accent" />
|
||||
<span className="truncate">{job.fileName || job.jobId}</span>
|
||||
</span>
|
||||
<Link
|
||||
href={`/dashboard/reviews/${job.jobId}`}
|
||||
className="shrink-0 rounded-lg px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider text-brand-accent border border-brand-accent/25 hover:bg-brand-accent/10 transition-colors"
|
||||
>
|
||||
{t('translate.recent.review')}
|
||||
</Link>
|
||||
<span className="flex shrink-0 items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDownload(job.jobId)}
|
||||
className="rounded-lg px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider text-brand-dark/60 dark:text-white/60 border border-black/10 dark:border-white/10 hover:bg-brand-muted/50 transition-colors"
|
||||
>
|
||||
<Download size={11} className="inline" />
|
||||
</button>
|
||||
<Link
|
||||
href={`/dashboard/reviews/${job.jobId}`}
|
||||
className="rounded-lg px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider text-brand-accent border border-brand-accent/25 hover:bg-brand-accent/10 transition-colors"
|
||||
>
|
||||
{t('translate.recent.review')}
|
||||
</Link>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -446,21 +468,18 @@ export default function TranslatePage() {
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-[13px] font-bold uppercase tracking-[0.1em] text-brand-dark dark:text-white">
|
||||
{t('translate.header.completedTitle')}
|
||||
{t('translate.header.completedTitleBase')} {t('translate.header.completedTitleAccent')}
|
||||
</p>
|
||||
<p className="text-[11px] text-brand-dark/50 dark:text-white/50 font-semibold uppercase mt-1 tracking-wider max-w-[300px] truncate">
|
||||
{submit.fileName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="px-5 py-2 bg-white dark:bg-[#1a1a1a] rounded-full text-[10px] font-bold uppercase tracking-wider text-brand-accent border border-brand-accent/20 shadow-sm shrink-0">
|
||||
{t('translate.complete.masterQuality')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col items-center justify-center py-16 bg-brand-muted/20 dark:bg-white/5 rounded-[40px] border border-black/5 dark:border-white/5">
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
onClick={() => handleDownload()}
|
||||
className="premium-button px-24 py-6 text-xl !rounded-full flex items-center gap-6 mb-8 group cursor-pointer hover:scale-[1.02] active:scale-95"
|
||||
>
|
||||
<Download size={28} className="group-hover:translate-y-1 transition-transform" />
|
||||
|
||||
Reference in New Issue
Block a user