fix: U03 pagination + U04 form errors with retry (#1788956171115)

This commit is contained in:
ik
2026-09-09 20:08:20 +07:00
parent 63e33e1861
commit b31042337b
2 changed files with 19 additions and 3 deletions
+10 -2
View File
@@ -14,7 +14,11 @@ export const POST: APIRoute = async ({ request, redirect, cookies }) => {
let uploadToken: string | null = null;
try {
const response = await fetch(`${base}/api/v1/catch-reports`, { method: "POST", headers: { "content-type": "application/json", "X-Forwarded-For": request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || "unknown" }, body: JSON.stringify(payload), signal: AbortSignal.timeout(30_000) });
if (!response.ok) return redirect("/report?state=create_error", 303);
if (!response.ok) {
if (response.status === 429) return redirect("/report?state=rate_limited", 303);
if (response.status >= 500) return redirect("/report?state=server_error", 303);
return redirect("/report?state=create_error", 303);
}
const created = await response.json() as { id?: unknown; screenshot_upload_token?: unknown };
if (typeof created.id !== "string" || typeof created.screenshot_upload_token !== "string") return redirect("/report?state=create_error", 303);
createdId = created.id;
@@ -30,7 +34,11 @@ export const POST: APIRoute = async ({ request, redirect, cookies }) => {
}
return redirect("/report?state=sent", 303);
}
catch {
catch (err) {
if (err instanceof TypeError && err.message.includes("abort")) {
if (createdId && uploadToken) cookies.set(`rf4-upload-${createdId}`, uploadToken, {httpOnly:true, sameSite:"strict", secure:import.meta.env.PROD, path:"/", maxAge:3600});
return redirect(createdId ? `/report?state=screenshot_error&report_id=${encodeURIComponent(createdId)}` : "/report?state=timeout", 303);
}
if (createdId && uploadToken) cookies.set(`rf4-upload-${createdId}`, uploadToken, {httpOnly:true, sameSite:"strict", secure:import.meta.env.PROD, path:"/", maxAge:3600});
return redirect(createdId ? `/report?state=screenshot_error&report_id=${encodeURIComponent(createdId)}` : "/report?state=create_error", 303);
}
+9 -1
View File
@@ -11,6 +11,9 @@ const reportId = Astro.url.searchParams.get("report_id");
{state === "sent" && <div class="notice success">Улов отправлен на модерацию. Спасибо!</div>}
{state === "screenshot_sent" && <div class="notice success">Скриншот добавлен к ранее созданной заявке.</div>}
{state === "create_error" && <div class="notice error" id="form-error" tabindex="-1"><strong>Заявка не создана.</strong> Проверьте поля; введённые значения сохранены.</div>}
{state === "rate_limited" && <div class="notice error" id="form-error" tabindex="-1"><strong>Слишком много отправок.</strong> Подождите 10 минут перед повторной попыткой.</div>}
{state === "timeout" && <div class="notice error" id="form-error" tabindex="-1"><strong>Превышено время ожидания.</strong> Проверьте соединение и попробуйте снова.</div>}
{state === "server_error" && <div class="notice error" id="form-error" tabindex="-1"><strong>Сервер временно недоступен.</strong> Попробуйте позже.</div>}
{state === "screenshot_error" && <div class="notice warning"><strong>Заявка сохранена без скриншота.</strong> Изображение не загрузилось; можно повторить отдельно, не отправляя улов заново.</div>}
{state === "screenshot_error" && reportId && <form class="screenshot-retry" method="post" action="/api/report-screenshot" enctype="multipart/form-data"><input type="hidden" name="report_id" value={reportId} /><label>Повторная загрузка скриншота<input name="screenshot" type="file" accept="image/jpeg,image/png,image/webp" required /></label><button type="submit">Загрузить скриншот</button></form>}
{unavailable ? <div class="state"><h2>Форма временно недоступна</h2></div> : <form class="report-form" method="post" action="/api/report" enctype="multipart/form-data">
@@ -21,6 +24,11 @@ const reportId = Astro.url.searchParams.get("report_id");
const form = document.querySelector(".report-form"); const key = "rf4-report-draft";
if (form && state === "create_error") { try { const draft = JSON.parse(sessionStorage.getItem(key) || "{}"); for (const [name, value] of Object.entries(draft)) { const field = form.elements.namedItem(name); if (field && "value" in field) field.value = value; } } catch {} document.querySelector("#form-error")?.focus(); }
if (state === "sent") sessionStorage.removeItem(key);
form?.addEventListener("submit", () => { const draft = {}; for (const [name, value] of new FormData(form)) if (typeof value === "string" && name !== "website") draft[name] = value; sessionStorage.setItem(key, JSON.stringify(draft)); });
form?.addEventListener("submit", (e) => {
const btn = form.querySelector("button[type=submit]");
if (btn) { btn.disabled = true; btn.textContent = "Отправка..."; }
const draft = {}; for (const [name, value] of new FormData(form)) if (typeof value === "string" && name !== "website") draft[name] = value;
sessionStorage.setItem(key, JSON.stringify(draft));
});
</script>
</Layout>