// community.jsx — Q&A community and the activity feed. // Loaded from index.html as an ordered `text/babel` classic script (position 6 of 8). // Classic scripts share one global scope — see docs/engineering/Patterns.md §9. // ─── Community Page ───────────────────────────────────────────────────────── function CommunityPage() { const [tab, setTab] = useState("faq"); const [questions, setQuestions] = useState([]); const [features, setFeatures] = useState([]); const [selectedQ, setSelectedQ] = useState(null); const [answers, setAnswers] = useState([]); const [showAsk, setShowAsk] = useState(false); const [askTitle, setAskTitle] = useState(""); const [askBody, setAskBody] = useState(""); const [answerBody, setAnswerBody] = useState(""); const isLoggedIn = !!localStorage.getItem("zs_token"); const loadQuestions = (cat) => { fetch(`${API_BASE}/community/questions?category=${cat}&limit=50`) .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); }).then(d => setQuestions(d.questions || [])); }; const loadFeatures = () => { fetch(`${API_BASE}/community/features`) .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); }).then(d => setFeatures(d.features || [])); }; useEffect(() => { if (tab === "features") loadFeatures(); else loadQuestions(tab); }, [tab]); const openQuestion = (q) => { setSelectedQ(q); fetch(`${API_BASE}/community/questions/${q.id}/answers`) .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); }).then(d => setAnswers(d.answers || [])); }; const submitQuestion = async () => { if (!askTitle || !askBody) return; try { await apiCall("/community/questions", { method: "POST", body: JSON.stringify({ title: askTitle, body: askBody }), }); setShowAsk(false); setAskTitle(""); setAskBody(""); loadQuestions(tab); } catch (e) { alert(e.message); } }; const submitAnswer = async () => { if (!answerBody || !selectedQ) return; try { await apiCall(`/community/questions/${selectedQ.id}/answers`, { method: "POST", body: JSON.stringify({ body: answerBody }), }); setAnswerBody(""); openQuestion(selectedQ); } catch (e) { alert(e.message); } }; const vote = async (featureId, dir) => { try { await apiCall(`/community/features/${featureId}/vote`, { method: "POST", body: JSON.stringify({ direction: dir }), }); loadFeatures(); } catch (e) { alert(e.message); } }; const tabStyle = (t) => ({ padding: "10px 20px", fontSize: 14, fontWeight: tab === t ? 600 : 400, color: tab === t ? "#22c55e" : "#666", border: "none", borderBottom: tab === t ? "2px solid #22c55e" : "2px solid transparent", background: "none", cursor: "pointer", fontFamily: "'DM Sans', sans-serif", }); const statusColor = { open: "#22c55e", planned: "#3b82f6", in_progress: "#f59e0b", shipped: "#d97706", declined: "#6e6e6e" }; return (

Community

Get help, share knowledge, and vote on what we build next.

{tab === "features" ? (
{features.map(f => (
{f.up_votes - f.down_votes}
{f.title} {f.status.replace("_", " ")}
{f.description &&

{f.description}

}
))} {features.length === 0 &&

No feature requests yet.

}
) : selectedQ ? (

{selectedQ.title}

{selectedQ.pinned && PINNED}

{selectedQ.body}

{selectedQ.author_name} {selectedQ.author_type === "admin" && TEAM} {" — "}{new Date(selectedQ.created_at).toLocaleDateString()}

{answers.length} {answers.length === 1 ? "answer" : "answers"}

{answers.map(a => (
{a.accepted &&
ACCEPTED ANSWER
}

{a.body}

{a.author_name} {a.author_type === "admin" && TEAM} {" — "}{new Date(a.created_at).toLocaleDateString()}
))} {isLoggedIn ? (