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 (
);
}
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" && (
)}
{tab === "submissions" && auth.role === "admin" && (
Submission queue
| ID | Pack | Version | Status | Select |
{submissions.map((s) => (
| {s.submission_id} |
{s.pack_id} |
{s.proposed_version_number} |
{s.status} |
|
))}
Review tasks
{JSON.stringify(reviewTasks, null, 2)}
Submission diff and gates
Diff summary
{JSON.stringify(submissionDiff, null, 2)}
Gate summary
{JSON.stringify(submissionGates, null, 2)}
)}
{tab === "review" && auth.role === "admin" && (
Governance and approval gates
Validation
{JSON.stringify(validation, null, 2)}
Provenance
{JSON.stringify(provenance, null, 2)}
Versions and comments
Versions
{JSON.stringify(versions, null, 2)}
Comments
{JSON.stringify(comments, null, 2)}
)}
);
}