feat(web): exibe erros ao enfileirar e melhora acessibilidade e contraste
- Mostra no formulário o erro devolvido pela API ao enfileirar (antes só ia ao console) e desabilita o botão durante o envio. - Adiciona aria-label no campo de URL, role=alert no erro e role=progressbar na barra. - Clareia a cor destructive (contraste do texto de erro sobre o fundo escuro ~2:1 para ~4,5:1). - Corrige o título da página (era 'web').
This commit is contained in:
1 parent
6fbc1222b4
commit
bc9a43d6de
6 files changed
+33
-19
No files matched your search
+1
-1
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>web</title>
|
||||
<title>Ephemeral Downloader</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
+1
-9
@@ -35,14 +35,6 @@ function DownloadList() {
|
||||
function Main() {
|
||||
const { queueDownload } = useSession();
|
||||
|
||||
const handleDownload = async (url: string) => {
|
||||
try {
|
||||
await queueDownload(url);
|
||||
} catch (error) {
|
||||
console.error("Failed to queue", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="grid gap-8 lg:grid-cols-12">
|
||||
@@ -56,7 +48,7 @@ function Main() {
|
||||
Paste a link to start. Files are downloaded to the server first.
|
||||
</p>
|
||||
</div>
|
||||
<DownloadForm onDownload={handleDownload} />
|
||||
<DownloadForm onDownload={queueDownload} />
|
||||
</section>
|
||||
|
||||
<Alert variant="destructive">
|
||||
|
||||
@@ -9,14 +9,15 @@ const formSchema = z.object({
|
||||
});
|
||||
|
||||
interface DownloadFormProps {
|
||||
onDownload: (url: string) => void;
|
||||
onDownload: (url: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export function DownloadForm({ onDownload }: DownloadFormProps) {
|
||||
const [url, setUrl] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
|
||||
@@ -27,8 +28,15 @@ export function DownloadForm({ onDownload }: DownloadFormProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
onDownload(url);
|
||||
setUrl("");
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await onDownload(url);
|
||||
setUrl("");
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to queue download");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -37,14 +45,21 @@ export function DownloadForm({ onDownload }: DownloadFormProps) {
|
||||
<div className="flex-1 space-y-1">
|
||||
<Input
|
||||
type="url"
|
||||
aria-label="File URL"
|
||||
aria-invalid={error ? true : undefined}
|
||||
aria-describedby={error ? "url-error" : undefined}
|
||||
placeholder="https://example.com/file.zip"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
className={error ? "border-destructive focus-visible:ring-destructive" : ""}
|
||||
/>
|
||||
{error && <p className="text-xs text-destructive">{error}</p>}
|
||||
{error && (
|
||||
<p id="url-error" role="alert" className="text-xs text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button type="submit">
|
||||
<Button type="submit" disabled={submitting}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download
|
||||
</Button>
|
||||
|
||||
@@ -14,6 +14,10 @@ const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={max}
|
||||
aria-valuenow={percent}
|
||||
className={cn(
|
||||
"relative h-2 w-full overflow-hidden rounded-full bg-secondary",
|
||||
className
|
||||
|
||||
@@ -90,7 +90,7 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
|
||||
}, [sessionId]);
|
||||
|
||||
const queueDownload = async (url: string) => {
|
||||
if (!sessionId) return;
|
||||
if (!sessionId) throw new Error("Session not ready, try again in a moment");
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/queue`, {
|
||||
method: "POST",
|
||||
@@ -101,7 +101,10 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
|
||||
body: JSON.stringify({ url }),
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Failed to queue download");
|
||||
if (!res.ok) {
|
||||
const body = (await res.json().catch(() => null)) as { error?: string } | null;
|
||||
throw new Error(body?.error || "Failed to queue download");
|
||||
}
|
||||
|
||||
await refreshTasks(); // Immediate refresh
|
||||
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive: 0 72% 54%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
|
||||
Reference in new issue
Block a user