feat(ui,api): wave 3 — editorial pricing, real cancel, server history, DeepL purge
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m36s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m36s
Pricing: full editorial redesign — serif card headers with accent pills
replace the colored font-black blocks, tone sweep across toggle/metrics/
features/CTAs, PLAN_COLORS removed; one design system app-wide.
Translate: decorative titles one step down (CTA hierarchy restored);
glossary and image-translation blocks hidden entirely for free users
(progressive disclosure — three controls for free).
Reviews: XLIFF hint line explains the exchange format; backend errors
routed through a friendly mapper (session/not-found/rate-limit/server).
Landing: fabricated hero UI cards (fake 'Context Engine' overlay)
removed — the photo no longer promises screens that don't exist.
Nav: single DashboardNavLinks component shared by sidebar and mobile
drawer (was duplicated markup).
API: GET /api/v1/translations (user job history, paginated; completed
jobs retained 24h) and POST /api/v1/translations/{id}/cancel —
cooperative cancellation with worker checkpoints before dispatch and
before finalisation, reserved quota released immediately. Translate
monitor now offers a real 'Cancel translation' next to 'Back to start';
recent-jobs list reads server history first, localStorage fallback.
DeepL purge (backend): provider module, registry registration, config
attrs/defaults, dispatch branch, admin settings schema + test branch,
legacy availability block, validation rules, plan provider lists,
error-code mappings, MCP enums, translator prompt mention, related
tests updated/removed. Fallback resolver skips unknown providers, so
stale chains containing 'deepl' degrade gracefully.
Verified: backend 110 tests passed; frontend build exit 0, vitest 9/9,
0 missing i18n keys, eslint 63 errors (vs 64 at HEAD).
This commit is contained in:
@@ -22,6 +22,30 @@ interface StoredJob { jobId: string; fileName: string | null; savedAt: number }
|
||||
|
||||
export interface RecentJob { jobId: string; fileName: string; completedAt: number }
|
||||
|
||||
/** Server history (last jobs, newest first) — empty when offline/unauthenticated. */
|
||||
export async function fetchServerHistory(perPage = 6): Promise<RecentJob[]> {
|
||||
if (typeof window === 'undefined') return [];
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return [];
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/v1/translations?per_page=${perPage}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const json = await res.json();
|
||||
return (json.data ?? []).map((j: {
|
||||
id: string; file_name?: string | null;
|
||||
completed_at?: string | null; created_at?: string | null;
|
||||
}) => ({
|
||||
jobId: j.id,
|
||||
fileName: j.file_name ?? '',
|
||||
completedAt: Date.parse(j.completed_at ?? j.created_at ?? '') || Date.now(),
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getRecentJobs(): RecentJob[] {
|
||||
if (typeof window === 'undefined') return [];
|
||||
try {
|
||||
@@ -235,6 +259,28 @@ export function useTranslationSubmit(): UseTranslationSubmitReturn {
|
||||
// NOTE: Don't set isSubmitting(false) here - let polling handle the transition
|
||||
}, [startPolling]);
|
||||
|
||||
/** Ask the backend to cancel the current job. Returns true on success. */
|
||||
const cancelJob = useCallback(async (): Promise<boolean> => {
|
||||
const id = jobId;
|
||||
if (!id) return false;
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
const res = await fetch(`${API_BASE}/api/v1/translations/${id}/cancel`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
});
|
||||
if (!res.ok) return false;
|
||||
stopPolling();
|
||||
persistActiveJob(null);
|
||||
setIsSubmitting(false);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, [jobId, stopPolling]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
stopPolling();
|
||||
persistActiveJob(null);
|
||||
@@ -285,6 +331,7 @@ export function useTranslationSubmit(): UseTranslationSubmitReturn {
|
||||
|
||||
return {
|
||||
submitTranslation,
|
||||
cancelJob,
|
||||
jobId,
|
||||
status,
|
||||
progress,
|
||||
|
||||
Reference in New Issue
Block a user