Surface Create entry and creations, hidden when unconfigured

This commit is contained in:
Dan
2026-06-29 14:10:58 +00:00
parent 21376fd1b4
commit d89227c259
+47 -48
View File
@@ -4,6 +4,9 @@ import { LEVELS, getSubjects, getGroupedSubjects, BEGINNER_PACKS, lessonStepCoun
import { getCurrentUser } from "@/lib/session";
import { getCompletedSteps, hasBadge } from "@/lib/progress";
import { isLevelUnlocked, countCompletedLessons, isPackComplete, BEGINNER_UNLOCK_THRESHOLD } from "@/lib/gating";
import { canCreate } from "@/lib/session";
import { isCreateEnabled } from "@/lib/comfyui";
import { listCreatedForUser, listPromoted, buildCreatedLessons, type CreatedLessonRow } from "@/lib/createdLessons";
export const metadata = { title: "Learn · DrawIt" };
export const dynamic = "force-dynamic";
@@ -70,6 +73,47 @@ export default async function LearnPage() {
);
};
const creator = canCreate(user);
const createEnabled = isCreateEnabled();
const toCreatedSubject = (row: CreatedLessonRow) => ({ key: row.slug, name: row.subject, emoji: row.emoji, order: 0, lessons: buildCreatedLessons(row) });
// "Create your own" entry + the user's creations + any promoted (global) ones, for EB/Beginner.
const creationsSection = (lvlKey: string) => {
if (lvlKey !== "early-beginner" && lvlKey !== "beginner") return null;
// Create UI is hidden entirely unless ComfyUI is configured (being a Creator alone isn't enough).
const canCreateNow = creator && createEnabled;
const mine: CreatedLessonRow[] = user && creator ? listCreatedForUser(user.id, lvlKey) : [];
const featured: CreatedLessonRow[] = listPromoted(lvlKey);
const ready = mine.filter((r) => r.status === "ready");
const pending = mine.filter((r) => ["pending", "generating", "needs_review"].includes(r.status));
// Nothing to show: Create disabled and no existing or featured creations.
if (!canCreateNow && ready.length === 0 && pending.length === 0 && featured.length === 0) return null;
return (
<div style={{ border: "2px dashed var(--primary)", borderRadius: 16, padding: "12px 14px" }}>
<div className="row" style={{ justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
<strong style={{ fontSize: "1.05rem" }}>{canCreateNow ? "✨ Create your own" : "✨ My Creations"}</strong>
{canCreateNow && <Link className="btn" style={{ minHeight: 36, padding: "6px 12px" }} href={`/create?level=${lvlKey}`}>+ New lesson</Link>}
</div>
<div className="stack" style={{ marginTop: 10 }}>
{ready.map((r) => subjectAccordion(toCreatedSubject(r), false))}
{pending.map((r) => (
<div key={r.slug} style={phaseBox}>
<strong>{r.emoji} {r.subject}</strong>{" "}
<span className="muted" style={{ fontSize: "0.82rem" }}>{r.status === "needs_review" ? "⏳ Waiting for approval" : "🎨 Drawing…"}</span>
</div>
))}
{canCreateNow && ready.length === 0 && pending.length === 0 && <p className="muted" style={{ fontSize: "0.9rem", margin: 0 }}>No creations yet tap + New lesson.</p>}
{featured.length > 0 && (
<>
<strong style={{ fontSize: "0.95rem", marginTop: 6 }}>🌟 Featured creations</strong>
{featured.map((r) => subjectAccordion(toCreatedSubject(r), false))}
</>
)}
</div>
</div>
);
};
return (
<>
<SiteNav />
@@ -116,6 +160,7 @@ export default async function LearnPage() {
) : lvl.key === "beginner" ? (
// Beginner: group subjects into packs, each with a per-pack badge.
<div className="stack" style={{ marginTop: 6 }}>
{creationsSection(lvl.key)}
{BEGINNER_PACKS.map((pack) => {
const packSubjects = subjects.filter((s) => pack.subjectKeys.includes(s.key));
const packDone = !!user && isPackComplete(user.id, pack);
@@ -136,51 +181,5 @@ export default async function LearnPage() {
) : lvl.key === "early-beginner" ? (
// Early Beginner: themed groups, each holding its subjects.
<div className="stack" style={{ marginTop: 6 }}>
{getGroupedSubjects(lvl.key).map((group) => {
const groupDone = group.subjects.every((s) => subjectComplete(s));
return (
<div key={group.key} style={{ border: groupDone ? "2px solid var(--leaf)" : "2px dashed var(--line)", borderRadius: 16, padding: "12px 14px" }}>
<div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
<strong style={{ fontSize: "1.05rem" }}>{group.emoji} {group.name}</strong>
<span className="muted" style={{ fontSize: "0.82rem" }}>{group.subjects.filter((s) => subjectComplete(s)).length}/{group.subjects.length} done</span>
</div>
<div className="stack" style={{ marginTop: 10 }}>
{group.subjects.map((subj) => subjectAccordion(subj, subj.key === firstIncomplete))}
</div>
</div>
);
})}
</div>
) : (
// Any future flat level
<div className="stack" style={{ marginTop: 6 }}>
{subjects.map((subj) => {
const phased = subj.lessons.length > 1 || !!subj.lessons[0].phase;
if (!phased) {
const l = subj.lessons[0];
const done = isDone(l);
const inProg = !done && doneCount(l) > 0;
return (
<Link key={subj.key} href={`/learn/${l.level}/${l.slug}`} className="card" style={subCard(done)}>
<div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
<strong>{subj.emoji} {l.title}</strong>
{done ? <span className="pill active"> Done</span> : inProg ? <span className="pill pending">Step {Math.min(doneCount(l) + 1, lessonStepCount(l))}/{lessonStepCount(l)}</span> : <span className="muted" style={{ fontSize: "0.85rem" }}>{lessonStepCount(l)} steps</span>}
</div>
</Link>
);
}
return subjectAccordion(subj, subj.key === firstIncomplete);
})}
</div>
)}
</div>
);
})}
</div>
</main>
</>
);
}
const subCard = (done: boolean): React.CSSProperties => ({ textDecoration: "none", color: "inherit", border: done ? "2px solid var(--leaf)" : "2px solid var(--line)", boxShadow: "none", padding: 14 });
const phaseBox: React.CSSProperties = { display: "block", border: "2px solid var(--line)", borderRadius: 12, padding: "12px 14px", background: "var(--surface)" };
{creationsSection(lvl.key)}
{getGroupedSubjects(lvl.key).map((group) =