CoompanionApp/front/support.tsx

192 lines
5.7 KiB
TypeScript

import React, { useMemo, useState } from "react";
import { createRoot } from "react-dom/client";
import "./support.css";
type FormState = {
name: string;
email: string;
message: string;
};
type Status = "idle" | "sending" | "success" | "error";
const tips = [
"Session code or lobby name",
"What you were trying to do",
"Anything that looked broken or confusing",
];
const contactNotes = [
"Messages are routed to the Negopoly support desk.",
"Add an email if you want a reply.",
"No account needed. Just send and go.",
];
const MAX_NAME_LENGTH = 80;
const MAX_EMAIL_LENGTH = 120;
const MAX_MESSAGE_LENGTH = 2000;
function Support() {
const [form, setForm] = useState<FormState>({
name: "",
email: "",
message: "",
});
const [status, setStatus] = useState<Status>("idle");
const [error, setError] = useState<string | null>(null);
const canSubmit = useMemo(() => {
return (
status !== "sending" &&
form.name.trim().length > 0 &&
form.email.trim().length > 0 &&
form.message.trim().length > 0
);
}, [form, status]);
const statusLabel = useMemo(() => {
if (status === "success") return "Message sent. We'll follow up if you left contact info.";
if (status === "error") return error ?? "Something went wrong. Try again in a moment.";
if (status === "sending") return "Sending your message...";
return "We only use your contact info to reply to this request.";
}, [status, error]);
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!canSubmit) return;
setStatus("sending");
setError(null);
try {
const res = await fetch("/api/support", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: form.name,
email: form.email,
message: form.message,
}),
});
const payload = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(payload?.message ?? "Unable to send message.");
}
setStatus("success");
setForm({ name: "", email: "", message: "" });
} catch (err) {
const message = err instanceof Error ? err.message : "Unable to send message.";
setStatus("error");
setError(message);
}
}
return (
<div className="support">
<header className="support__hero">
<div className="support__badge">NegoCity support desk</div>
<div>
<a className="support__back" href="/">
Back to NegoCity
</a>
<h1>Support, reports, and lost deals.</h1>
<p>
Tell us what went wrong, what you need, or where the Banker needs a hand. We will read
every message that lands in the support vault.
</p>
</div>
</header>
<section className="support__grid">
<div className="support__info">
<div className="info-card">
<h2>What to include</h2>
<ul>
{tips.map((tip) => (
<li key={tip}>{tip}</li>
))}
</ul>
</div>
<div className="info-card accent">
<h2>How this works</h2>
<ul>
{contactNotes.map((note) => (
<li key={note}>{note}</li>
))}
</ul>
</div>
<div className="info-card link-card">
<h2>Need the rules instead?</h2>
<p>Head to the full guide to get back into the game quickly.</p>
<a className="info-link" href="/rules">
View the rulebook
</a>
</div>
</div>
<form className="support__form" onSubmit={handleSubmit}>
<div className="form-head">
<h2>Send a message</h2>
<span className={`status ${status}`} role="status" aria-live="polite">
{statusLabel}
</span>
</div>
<label className="field">
<span>Name</span>
<input
type="text"
name="name"
autoComplete="name"
placeholder="Your name"
maxLength={MAX_NAME_LENGTH}
value={form.name}
onChange={(event) => setForm({ ...form, name: event.target.value })}
required
/>
</label>
<label className="field">
<span>Email</span>
<input
type="email"
name="email"
autoComplete="email"
placeholder="you@email.com"
maxLength={MAX_EMAIL_LENGTH}
value={form.email}
onChange={(event) => setForm({ ...form, email: event.target.value })}
required
/>
</label>
<label className="field">
<span>Message</span>
<textarea
name="message"
placeholder="Describe what you need, the session code, and anything else we should know."
rows={6}
maxLength={MAX_MESSAGE_LENGTH}
value={form.message}
onChange={(event) => setForm({ ...form, message: event.target.value })}
required
/>
</label>
<div className="form-actions">
<button type="submit" className="send" disabled={!canSubmit}>
{status === "sending" ? "Sending..." : "Send message"}
</button>
<span className="char-count">
{form.message.length}/{MAX_MESSAGE_LENGTH}
</span>
</div>
</form>
</section>
</div>
);
}
const root = createRoot(document.getElementById("root")!);
root.render(<Support />);