fix(ui): design critique overhaul — a11y, honest metrics, i18n repair (critique 2026-08-30)
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m51s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m51s
P0 a11y: keyboard-accessible dropzone (role/tabIndex/Enter-Space), ARIA combobox + listbox pattern for language selector, role=switch on glossary toggle, role=status live region on notifications, aria-labels on password toggles, visible-on-focus close buttons, RTL logical positioning (start-*). Trust: third-party Memento promo removed from translate page, sidebar and all 13 locales; fabricated stats (99.9%, Turbo, computed layout-integrity bars) replaced with real measurements incl. API estimated remaining time; silent download failure now surfaces an error notification. Honesty: fake 100-byte file injections removed (format chips are now informational); cancel-that-doesn't renamed 'Back to start' with hint; Enterprise contact placeholder replaced with contact@wordly.art. i18n: t() no longer returns raw keys (empty string + defaultValue support, ~30 dead || fallbacks now work); ~170 new keys EN+FR across new reviews/ teams namespaces, glossaries context tab, translate monitor, settings, services, pricing, landing, fileUploader; split-key italic titles replace lastIndexOf() surgery (zh/ja-safe); key-audit script added 0 missing. Flow: active job persisted across refresh with polling resume (24h TTL); client-side recent-jobs history with review links; review page linked from complete state; settings/services added to dashboard nav; Business/ Enterprise regain glossary access (tier gate unified). Typeset (sober-tool direction): 7.5-9px labels raised to 10-12px, /30 opacity to /45-/55, uppercase tracking reduced, trust footer legible, country flags removed from language switcher, localized dates. Cleanup: 5 orphaned translate components, dead site header/footer, fossil tailwind.config.js, PipelineStepper, duplicate pill+H1 titles, two-step confirm for cache clear, dead landing footer links. Verified: next build exit 0, vitest 9/9, eslint 64 errors = HEAD (no regression, -3 warnings), detector 4 -> 3 findings.
This commit is contained in:
@@ -16,6 +16,9 @@ import { API_BASE } from "@/lib/config";
|
||||
import { ANNUAL_DISCOUNT_PERCENT } from "@/lib/pricing";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
||||
// Enterprise contact — single place to update the sales address.
|
||||
const SUPPORT_EMAIL = "contact@wordly.art";
|
||||
|
||||
/* ─────────────────────────────────────────────
|
||||
Types
|
||||
───────────────────────────────────────────── */
|
||||
@@ -359,7 +362,7 @@ export default function PricingPage() {
|
||||
return;
|
||||
}
|
||||
if (planId === "enterprise") {
|
||||
window.location.href = "mailto:contact@votre-domaine.com?subject=Offre Enterprise";
|
||||
window.location.href = `mailto:${SUPPORT_EMAIL}?subject=${encodeURIComponent(t('pricing.enterprise.subject'))}`;
|
||||
return;
|
||||
}
|
||||
if (planId === currentPlan) return;
|
||||
@@ -387,7 +390,7 @@ export default function PricingPage() {
|
||||
const backendMessage = data.message ?? data.error ?? data.data?.error;
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(backendMessage || `Erreur serveur ${res.status}`);
|
||||
throw new Error(backendMessage || t('pricing.error.server', { status: res.status }));
|
||||
}
|
||||
|
||||
if (url) {
|
||||
@@ -431,7 +434,7 @@ export default function PricingPage() {
|
||||
|
||||
const url = data.data?.url ?? data.url;
|
||||
if (!res.ok) {
|
||||
throw new Error(data.message ?? data.error ?? `Erreur serveur ${res.status}`);
|
||||
throw new Error(data.message ?? data.error ?? t('pricing.error.server', { status: res.status }));
|
||||
}
|
||||
if (url) {
|
||||
window.location.replace(url);
|
||||
@@ -488,7 +491,7 @@ export default function PricingPage() {
|
||||
)}>
|
||||
<span className="text-lg">{toastMsg.type === 'ok' ? t('pricing.okSymbol') : t('pricing.errSymbol')}</span>
|
||||
<p className="text-sm flex-1">{toastMsg.text}</p>
|
||||
<button onClick={() => setToastMsg(null)} className="text-muted-foreground hover:text-foreground text-lg leading-none">×</button>
|
||||
<button onClick={() => setToastMsg(null)} aria-label={t('pricing.toast.close')} className="text-muted-foreground hover:text-foreground text-lg leading-none">×</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -499,9 +502,9 @@ export default function PricingPage() {
|
||||
<span className="text-[10px] font-black tracking-[0.3em] opacity-40">{t('pricing.header.badge')}</span>
|
||||
</span>
|
||||
<h1 className="text-5xl md:text-7xl font-black uppercase tracking-tighter mb-6 leading-none">
|
||||
{t('pricing.header.title').split(' ').slice(0, -2).join(' ')}{" "}
|
||||
{t('pricing.header.titleBase')}{" "}
|
||||
<span className="text-accent">
|
||||
{t('pricing.header.title').split(' ').slice(-2).join(' ')}
|
||||
{t('pricing.header.titleAccent')}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-foreground/40 font-medium text-xl max-w-2xl mx-auto leading-relaxed">
|
||||
|
||||
Reference in New Issue
Block a user