"use client"; import { useParams } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { api } from "@/lib/api/client"; import type { components } from "@/lib/api/schema"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; type Proposal = components["schemas"]["ChangeProposalRead"]; type Op = { op: string; entity_type: string; entity_id?: string | null; payload?: Record }; const STATUS_STYLE: Record = { pending: "border-bronze/40 text-bronze", applied: "border-green-600/40 text-green-700 dark:text-green-400", rejected: "border-[var(--border)] text-[var(--muted)]", }; export default function ProposalsPage() { const { id: treeId } = useParams<{ id: string }>(); const [proposals, setProposals] = useState([]); const [meRole, setMeRole] = useState(null); const [ready, setReady] = useState(false); const [busy, setBusy] = useState(null); const load = useCallback(async () => { const [props, me, members] = await Promise.all([ api.GET("/api/v1/trees/{tree_id}/proposals", { params: { path: { tree_id: treeId } } }), api.GET("/api/v1/users/me"), api.GET("/api/v1/trees/{tree_id}/members", { params: { path: { tree_id: treeId } } }), ]); const myId = me.data?.id; setMeRole((members.data ?? []).find((m) => m.user_id === myId)?.role ?? null); setProposals(props.data ?? []); setReady(true); }, [treeId]); useEffect(() => { load(); }, [load]); const canReview = meRole === "owner" || meRole === "editor"; async function act(pid: string, action: "apply" | "reject") { setBusy(pid); const { error, response } = await api.POST( `/api/v1/trees/{tree_id}/proposals/{proposal_id}/${action}` as "/api/v1/trees/{tree_id}/proposals/{proposal_id}/apply", { params: { path: { tree_id: treeId, proposal_id: pid } } }, ); setBusy(null); if (error) { // 409 = couldn't apply (e.g. references a missing record); reload shows apply_error. if (response.status !== 409) alert("Action failed."); } load(); } if (!ready) return

Loading…

; return (

Change proposals

Suggested edits (from the assistant or contributors). Nothing changes until you approve — approving applies it as your own edit, through the normal checks and audit log.

{proposals.length === 0 ? (

No proposals.

) : (
    {proposals.map((p) => { const ops = (p.operations as Op[]) ?? []; return (
  • {p.summary}
    {p.origin} · {ops.length} change{ops.length === 1 ? "" : "s"}
    {p.status}
    {p.rationale &&

    {p.rationale}

    }
      {ops.map((o, i) => (
    • {o.op}{" "} {o.entity_type} {o.payload && Object.keys(o.payload).length > 0 && ( {" "} ·{" "} {Object.entries(o.payload) .map(([k, v]) => `${k}: ${String(v)}`) .join(", ")} )}
    • ))}
    {p.apply_error && (

    Couldn’t apply: {p.apply_error}

    )} {p.status === "pending" && canReview && (
    )} {p.status === "pending" && !canReview && (

    Only an editor can approve this.

    )}
  • ); })}
)}
); }