Add Create form with subject input and status polling

This commit is contained in:
Dan committed 2026-06-29 14:10:57 +00:00
1 parent b39bdaaa78
commit 21376fd1b4
1 file changed
+131
+131
View File
@@ -0,0 +1,131 @@
"use client";
import { useEffect, useRef, useState } from "react";
import Link from "next/link";
type Phase = "idle" | "submitting" | "generating" | "review" | "ready" | "failed";
export default function CreateForm({ level: initialLevel, enabled }: { level: string; enabled: boolean }) {
const [level, setLevel] = useState(initialLevel);
const [subject, setSubject] = useState("");
const [phase, setPhase] = useState<Phase>("idle");
const [msg, setMsg] = useState("");
const [slug, setSlug] = useState("");
const poll = useRef<ReturnType<typeof setInterval> | undefined>(undefined);
useEffect(() => () => clearInterval(poll.current), []);
async function start(e: React.FormEvent) {
e.preventDefault();
setMsg("");
setSlug("");
setPhase("submitting");
try {
const res = await fetch("/api/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ level, subject }),
});
const data = await res.json();
if (!res.ok) {
setPhase("failed");
setMsg(data.error || "Something went wrong.");
return;
}
if (data.status === "needs_review") {
setPhase("review");
return;
}
setSlug(data.slug);
setPhase("generating");
watch(data.id, data.slug);
} catch {
setPhase("failed");
setMsg("Couldn't reach the server.");
}
}
function watch(id: number, baseSlug: string) {
clearInterval(poll.current);
poll.current = setInterval(async () => {
try {
const r = await fetch(`/api/create/${id}`);
const d = await r.json();
if (d.status === "ready") {
clearInterval(poll.current);
setSlug(baseSlug);
setPhase("ready");
} else if (d.status === "failed" || d.status === "blocked") {
clearInterval(poll.current);
setPhase("failed");
setMsg(d.error || "The drawing couldn't be made. Try a different subject.");
}
} catch {
/* keep polling */
}
}, 2500);
}
if (!enabled) {
return (
<div className="card" style={{ marginTop: 16 }}>
<p className="muted" style={{ margin: 0 }}>
The Create feature isn&apos;t turned on for this DrawIt instance yet. Ask your administrator to configure a
drawing server (ComfyUI).
</p>
</div>
);
}
const busy = phase === "submitting" || phase === "generating";
return (
<div style={{ marginTop: 16, maxWidth: 560 }}>
<form onSubmit={start} className="card" style={{ display: "grid", gap: 12 }}>
<label className="field">
<span style={{ fontWeight: 700 }}>What do you want to draw?</span>
<input
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder="e.g. Zebra"
maxLength={30}
disabled={busy}
required
/>
</label>
<label className="field">
<span style={{ fontWeight: 700 }}>Level</span>
<select value={level} onChange={(e) => setLevel(e.target.value)} disabled={busy}>
<option value="early-beginner">Early Beginner</option>
<option value="beginner">Beginner</option>
</select>
</label>
<button className="btn big" type="submit" disabled={busy || subject.trim().length < 2}>
{busy ? "Drawing…" : "✨ Make my lesson"}
</button>
</form>
{phase === "generating" && (
<div className="card" style={{ marginTop: 12, textAlign: "center" }}>
<p style={{ margin: 0 }}>🎨 Drawing your {subject || "picture"}… this can take a little while.</p>
<p className="muted" style={{ marginTop: 6, fontSize: "0.9rem" }}>You can stay on this page.</p>
</div>
)}
{phase === "review" && (
<div className="card" style={{ marginTop: 12 }}>
<p style={{ margin: 0 }}>📨 Thanks! &quot;{subject}&quot; was sent to a grown-up to approve. Check back soon.</p>
</div>
)}
{phase === "ready" && (
<div className="card" style={{ marginTop: 12, textAlign: "center" }}>
<p style={{ marginTop: 0 }}>🎉 Your lesson is ready!</p>
<Link className="btn big" href={`/learn/${level}/${slug}-trace`}>Start drawing</Link>
</div>
)}
{phase === "failed" && (
<div className="notice" style={{ marginTop: 12 }}>
<p style={{ margin: 0 }}>{msg || "Something went wrong. Try a different subject."}</p>
</div>
)}
</div>
);
}