"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Bug, ImagePlus, Send, Sparkles } from "lucide-react"; import { api } from "@/lib/client-api"; import { bugReportSchema, bugMessageSchema, screenshotLimit, type BugReportView, } from "@/shared/bug-reports"; import { useI18n } from "./I18nProvider"; import { Username } from "./Username"; type Inbox = { ready: boolean; signedIn: boolean; displayName: string | null; nameGlow: boolean; reports: { id: string; title: string; state: string; created_at: string; linear_identifier: string | null; }[]; }; export function SupportInbox({ pagePath }: { pagePath: string }) { const { t, formatDate } = useI18n(), router = useRouter(); const [inbox, setInbox] = useState(null), [error, setError] = useState(""), [busy, setBusy] = useState(false); const [title, setTitle] = useState(""), [description, setDescription] = useState(""), [name, setName] = useState(""), [file, setFile] = useState(null); const nonce = useRef(null); useEffect(() => { let stopped = false; api("bug-reports") .then((value) => { if (!stopped) setInbox(value); }) .catch((failure) => { if (!stopped) setError(failure.message); }); return () => { stopped = true; }; }, []); async function submit() { if (busy) return; const clientId = (nonce.current ??= crypto.randomUUID()); const body = { clientId, title, description, pagePath, displayName: inbox?.signedIn ? undefined : name, screenshot: !!file, }; if (!bugReportSchema.safeParse(body).success) { setError( "Add a short title, at least 15 characters describing the bug, and your player name.", ); return; } if ( file && (!["image/png", "image/jpeg", "image/webp"].includes(file.type) || file.size > screenshotLimit) ) { setError("Choose a PNG, JPG, or WebP screenshot smaller than 5 MB"); return; } setBusy(true); setError(""); try { const report = await api<{ id: string; state: string; upload: string | null; }>("bug-reports", body); if (report.upload && file) { const response = await fetch(report.upload, { method: "PUT", headers: { "Content-Type": file.type }, body: file, signal: AbortSignal.timeout(60_000), }); if (!response.ok) throw new Error("Screenshot upload failed. Please try again."); } await api(`bug-reports/${report.id}/submit`, {}); router.push(`/support/${report.id}`); } catch (failure) { setError((failure as Error).message); setBusy(false); } } return (
{t("PLAYER SUPPORT")}

{t("Found a bug?")}

{t("Tell us what happened. We’ll reply here.")}

{!inbox?.signedIn && (

{t( "Guest reports stay in this browser. Sign in to keep your conversation in your account.", )}{" "} {t("Log in")}

)} {error && (

{t(error)}

)} {inbox && !inbox.ready && (

{t( "Bug reporting is temporarily unavailable. Please try again later.", )}

)}
{ event.preventDefault(); void submit(); }} > {!inbox?.signedIn && ( <> { nonce.current = null; setName(event.target.value); }} required minLength={2} maxLength={32} autoComplete="nickname" disabled={busy} /> )} { nonce.current = null; setTitle(event.target.value); }} minLength={5} maxLength={120} required disabled={busy} />