Add the user-submission flow for the catalog
Lets users submit a private catalog entry (port type, cable type, device template) for promotion to the public catalog, or suggest an edit to an existing public entry — both go into the catalog_submissions review queue per organized-ideas.md §3. No Admin review UI yet (next sub-phase); this covers the submitter's side only. - data/SubmissionRepository + SupabaseSubmissionRepository: submit, resubmit, withdraw, list-mine, backed by the existing catalog_submissions RLS policies (no schema changes needed). - data/catalogRowMapping.ts: shared domain<->row mappers, in both directions, so a submission's proposed_data is always shaped like the underlying table row (what an eventual admin-approval would write directly) and can be turned back into form-editable fields for revision. - state/submissionStore.ts: mySubmissions + submit/resubmit/withdraw, plus syncProposedData — called from catalogStore's updateCustom* actions so a submission about your own still-private entry never goes stale relative to it (edits from the library and from My Submissions are the same action and always agree). - UI: "Submit"/"Suggest edit" wired into PortTypeManager, CableTypeManager, DevicePalette/DeviceTemplateEditor; new MySubmissionsModal (opened from TopBar, with a pending-count badge) shows status, rejection reasons, and lets you edit/resubmit or withdraw. - Deliberately deferred: device_category submissions (no listing UI to hang a button on yet) and the normalized manufacturer catalog. Verified: tsc -b and oxlint clean; supabase db reset + 23/23 pgTAP RLS tests still pass (no schema changes this round); manually tested submit, suggest-edit, edit-from-either-side sync, reject/resubmit, and withdraw.
This commit is contained in:
@@ -1,17 +1,26 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { downloadProjectFile, readProjectFile, ProjectImportError } from '../../data/exportImport'
|
||||
import { supabase } from '../../data/supabaseClient'
|
||||
import { useProjectStore } from '../../state/projectStore'
|
||||
import { useSubmissionStore } from '../../state/submissionStore'
|
||||
import MySubmissionsModal from '../submissions/MySubmissionsModal'
|
||||
import DiagramManagerModal from './DiagramManagerModal'
|
||||
|
||||
export default function TopBar() {
|
||||
const project = useProjectStore((s) => s.project)
|
||||
const renameProject = useProjectStore((s) => s.renameProject)
|
||||
const importProject = useProjectStore((s) => s.importProject)
|
||||
const mySubmissions = useSubmissionStore((s) => s.mySubmissions)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [diagramManagerOpen, setDiagramManagerOpen] = useState(false)
|
||||
const [submissionsOpen, setSubmissionsOpen] = useState(false)
|
||||
const [username, setUsername] = useState<string | null>(null)
|
||||
|
||||
const pendingSubmissionCount = useMemo(
|
||||
() => mySubmissions.filter((s) => s.status === 'pending').length,
|
||||
[mySubmissions],
|
||||
)
|
||||
|
||||
// One-time fetch — nothing else in the app changes your own username after
|
||||
// CompleteProfileScreen sets it, so there's no need to keep this live.
|
||||
useEffect(() => {
|
||||
@@ -60,6 +69,17 @@ export default function TopBar() {
|
||||
>
|
||||
Diagrams
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSubmissionsOpen(true)}
|
||||
className="rounded px-2.5 py-1.5 text-xs text-slate-600 hover:bg-slate-100"
|
||||
>
|
||||
My Submissions
|
||||
{pendingSubmissionCount > 0 && (
|
||||
<span className="ml-1 rounded-full bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold text-amber-700">
|
||||
{pendingSubmissionCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button onClick={handleImportClick} className="rounded px-2.5 py-1.5 text-xs text-slate-600 hover:bg-slate-100">
|
||||
Import
|
||||
</button>
|
||||
@@ -79,6 +99,7 @@ export default function TopBar() {
|
||||
</button>
|
||||
</div>
|
||||
{diagramManagerOpen && <DiagramManagerModal onClose={() => setDiagramManagerOpen(false)} />}
|
||||
{submissionsOpen && <MySubmissionsModal onClose={() => setSubmissionsOpen(false)} />}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user