"use client"; /** * ExpiredView — Expired state(轉檔結果已過期、引導重做) * * Phase 0.8 conversion (見 .autoflow/03-design/wireframes/wireframe-conversion.md §3.6 + §8.2) * * 對齊: * - flow-conversion.md §6.3(Job 7 天後過期 — bootstrap / polling 自動切 expired;UI 引導重做) * - feature-converter-integration.md §F4(converter 7 天 GC,UI 顯式提醒) * - flow §11 (Q4) — 「重新轉檔」放在 hero 卡內,動線單一 * * 範圍(F-T9 sub-1): * - hero 區塊:⚠️ 過期提示(紅色但不 alarming — 過期是預期行為,不是錯誤) * - 任務摘要:source filename → target chip(與 success / failed view 對齊) * - 解釋訊息(為什麼過期 + 怎麼處理) * - 「重新轉檔」主按鈕 → store.reset() 切 idle * * a11y: * - hero 用 `role="status"` + `aria-live="polite"`(過期不是緊急錯誤,用 polite 而非 assertive) * —— 過去用 `role="alert"` + `aria-live="polite"` 是 ARIA 反 pattern:alert 隱含 assertive, * 兩者並存某些 SR 會視為矛盾(F-T9 M1 修補時順手調整) * - 「重新轉檔」按鈕有明確 aria-label * - 顏色:橘紅而非 destructive,避免暗示「失敗」 * * 不做: * - 「複製 Job ID」(過期 job 的 ID 對 ops 沒意義 — converter 已 GC) * - 倒數計時(已過期,不需要) */ import { Clock4Icon, RefreshCwIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useT } from "@/lib/i18n/context"; import { useConversionStore } from "@/stores/conversion-store"; import type { ConversionJob } from "@/types/conversion"; export function ExpiredView() { const t = useT(); const job = useConversionStore((s) => s.job); const reset = useConversionStore((s) => s.reset); // bootstrap 命中 expired 時 store 一定會塞 job;保險起見 job=null 也能渲(極端情境: // GET /active 回 404 但前端誤切 expired — 不該發生) return ; } interface ExpiredViewInnerProps { job: ConversionJob | null; reset: () => void; t: (key: string) => string; } function ExpiredViewInner({ job, reset, t }: ExpiredViewInnerProps) { const sourceFilename = job?.source_filename ?? ""; const targetChip = job?.target_chip ?? ""; const handleStartNew = () => { // store.reset() 會清掉 polling / upload controller;切回 idle 後 page.tsx 自動換 IdleForm reset(); }; return (
{/* 過期 hero — role="status" + aria-live="polite"(預期行為,不需 assertive 打斷) 注意:原本用 role="alert" 會隱含 assertive,再寫 aria-live="polite" 會與 SR 行為矛盾; 已改為 role="status"(implicit polite),與 ProcessingView banner 的寫法一致 */}
{/* 任務摘要(若 job 還有資訊則顯示,與 success / failed 視覺對齊) */} {(sourceFilename || targetChip) && (
{sourceFilename ? ( {sourceFilename} ) : null} {sourceFilename && targetChip ? ( ) : null} {targetChip ? ( {targetChip} ) : null}

{t("conversion.expired.subDescription")}

)} {/* 主動作:重新轉檔 — 單一 CTA,動線清楚 */}
); }