diff --git a/apps/web/src/pages/api/report.ts b/apps/web/src/pages/api/report.ts index 76ffdc6..4b2c900 100644 --- a/apps/web/src/pages/api/report.ts +++ b/apps/web/src/pages/api/report.ts @@ -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); } diff --git a/apps/web/src/pages/report.astro b/apps/web/src/pages/report.astro index 10633fd..88a2f9b 100644 --- a/apps/web/src/pages/report.astro +++ b/apps/web/src/pages/report.astro @@ -11,6 +11,9 @@ const reportId = Astro.url.searchParams.get("report_id"); {state === "sent" &&
Улов отправлен на модерацию. Спасибо!
} {state === "screenshot_sent" &&
Скриншот добавлен к ранее созданной заявке.
} {state === "create_error" &&
Заявка не создана. Проверьте поля; введённые значения сохранены.
} + {state === "rate_limited" &&
Слишком много отправок. Подождите 10 минут перед повторной попыткой.
} + {state === "timeout" &&
Превышено время ожидания. Проверьте соединение и попробуйте снова.
} + {state === "server_error" &&
Сервер временно недоступен. Попробуйте позже.
} {state === "screenshot_error" &&
Заявка сохранена без скриншота. Изображение не загрузилось; можно повторить отдельно, не отправляя улов заново.
} {state === "screenshot_error" && reportId &&
} {unavailable ?

Форма временно недоступна

:
@@ -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)); + });