import React, { useEffect, useState } from "react"; import { login, refresh, fetchPacks, createContribution, fetchAdminPacks, fetchPackValidation, fetchPackProvenance, fetchPackVersions, fetchPackComments, fetchSubmissions, fetchSubmissionDiff, fetchSubmissionGates, fetchReviewTasks, upsertPack, publishPack, governanceAction, addReviewComment } from "./api"; import { loadAuth, saveAuth, clearAuth } from "./authStore"; function LoginView({ onAuth }) { const [username, setUsername] = useState("wesley"); const [password, setPassword] = useState("demo-pass"); const [error, setError] = useState(""); async function doLogin() { try { const result = await login(username, password); saveAuth(result); onAuth(result); } catch { setError("Login failed"); } } return (

Didactopus login

{error ?
{error}
: null}
); } function NavTabs({ tab, setTab, role }) { return (
{role === "admin" ? <> : null}
); } export default function App() { const [auth, setAuth] = useState(loadAuth()); const [tab, setTab] = useState("contribute"); const [packs, setPacks] = useState([]); const [adminPacks, setAdminPacks] = useState([]); const [selectedPackId, setSelectedPackId] = useState(""); const [validation, setValidation] = useState(null); const [provenance, setProvenance] = useState(null); const [versions, setVersions] = useState([]); const [comments, setComments] = useState([]); const [submissions, setSubmissions] = useState([]); const [selectedSubmissionId, setSelectedSubmissionId] = useState(null); const [submissionDiff, setSubmissionDiff] = useState(null); const [submissionGates, setSubmissionGates] = useState(null); const [reviewTasks, setReviewTasks] = useState([]); const [commentText, setCommentText] = useState("Looks structurally plausible."); const [reviewSummary, setReviewSummary] = useState("Reviewed and ready for next stage."); const [message, setMessage] = useState(""); const [contribPack, setContribPack] = useState({ id: "bayes-pack", title: "Bayesian Reasoning", subtitle: "Contributor revision scaffold", level: "novice-friendly", concepts: [{ id: "prior", title: "Prior", prerequisites: [], masteryDimension: "mastery", exerciseReward: "Prior badge earned" }], onboarding: { headline: "Start here", body: "Begin", checklist: [] }, compliance: { sources: 1, attributionRequired: true, shareAlikeRequired: false, noncommercialOnly: false, flags: [] } }); async function refreshAuthToken() { if (!auth?.refresh_token) return null; try { const result = await refresh(auth.refresh_token); saveAuth(result); setAuth(result); return result; } catch { clearAuth(); setAuth(null); return null; } } async function guarded(fn) { try { return await fn(auth.access_token); } catch { const next = await refreshAuthToken(); if (!next) throw new Error("auth failed"); return await fn(next.access_token); } } useEffect(() => { if (!auth) return; async function load() { const p = await guarded((token) => fetchPacks(token)); setPacks(p); setSelectedPackId((prev) => prev || p[0]?.id || ""); if (auth.role === "admin") { setAdminPacks(await guarded((token) => fetchAdminPacks(token))); setSubmissions(await guarded((token) => fetchSubmissions(token))); setReviewTasks(await guarded((token) => fetchReviewTasks(token))); } } load(); }, [auth]); useEffect(() => { if (!auth?.role || auth.role !== "admin" || !selectedPackId) return; async function loadPackReview() { setValidation(await guarded((token) => fetchPackValidation(token, selectedPackId))); setProvenance(await guarded((token) => fetchPackProvenance(token, selectedPackId))); setVersions(await guarded((token) => fetchPackVersions(token, selectedPackId))); setComments(await guarded((token) => fetchPackComments(token, selectedPackId))); } loadPackReview(); }, [auth, selectedPackId]); useEffect(() => { if (!auth?.role || auth.role !== "admin" || !selectedSubmissionId) return; async function loadSubmission() { setSubmissionDiff(await guarded((token) => fetchSubmissionDiff(token, selectedSubmissionId))); setSubmissionGates(await guarded((token) => fetchSubmissionGates(token, selectedSubmissionId))); } loadSubmission() }, [auth, selectedSubmissionId]); async function submitContribution() { const result = await guarded((token) => createContribution(token, { pack: contribPack, submission_summary: "Contributor-submitted revision from UI scaffold" })); setMessage(`Submission created: ${result.submission_id}`); } async function doGovernance(status) { await guarded((token) => governanceAction(token, selectedPackId, { status, review_summary: reviewSummary })); setAdminPacks(await guarded((token) => fetchAdminPacks(token))); setVersions(await guarded((token) => fetchPackVersions(token, selectedPackId))); setMessage(`Pack moved to ${status}`); } async function addCommentNow() { const versionNumber = versions[0]?.version_number || 1; await guarded((token) => addReviewComment(token, selectedPackId, versionNumber, { comment_text: commentText, disposition: "comment" })); setComments(await guarded((token) => fetchPackComments(token, selectedPackId))); setMessage("Review comment added"); } if (!auth) return ; return (

Didactopus contribution management layer

Contributor submissions, diffs, QA/provenance gates, and reviewer task queue scaffolding.

Signed in as {auth.username} ({auth.role})
{message ?
{message}
: null}
{auth.role === "admin" ? ( ) : ( )}
{tab === "contribute" && (

Contributor submission