Skip to content

Commit 9486c4c

Browse files
committed
Updated review queue
1 parent 207ee0b commit 9486c4c

3 files changed

Lines changed: 89 additions & 28 deletions

File tree

heritage_graph_ui/src/app/(dashboard)/curation/contributions/page.tsx

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -227,20 +227,42 @@ export default function ContributionQueuePage() {
227227
}
228228
description={
229229
moderateConfirm ? (
230-
<>
231-
<span className="font-medium text-foreground">{moderateConfirm.contribution.name}</span>
232-
{moderateConfirm.action === 'reject' ? (
233-
<span>
234-
{' '}
235-
will be marked rejected and removed from the pending queue.
230+
<span className="space-y-2 block">
231+
<span className="block">
232+
<span className="font-medium text-foreground">{moderateConfirm.contribution.name}</span>
233+
{moderateConfirm.action === 'reject' ? (
234+
<span>
235+
{' '}
236+
will be marked rejected and removed from the pending queue.
237+
</span>
238+
) : (
239+
<span>
240+
{' '}
241+
will be published immediately without the full triage workspace.
242+
</span>
243+
)}
244+
</span>
245+
{moderateConfirm.action === 'accept' && (
246+
<span className="block text-sm text-muted-foreground">
247+
Prefer{' '}
248+
<button
249+
type="button"
250+
className="underline text-foreground"
251+
onClick={() => {
252+
const id = moderateConfirm.contribution.entity_id;
253+
setModerateConfirm(null);
254+
router.push(`/curation/review/${id}`);
255+
}}
256+
>
257+
Open full review
258+
</button>{' '}
259+
when sources, conflicts, or confidence need a recorded decision.
236260
</span>
237-
) : (
238-
<span> will be marked accepted.</span>
239261
)}
240-
</>
262+
</span>
241263
) : undefined
242264
}
243-
confirmLabel={moderateConfirm?.action === 'accept' ? 'Accept' : 'Reject'}
265+
confirmLabel={moderateConfirm?.action === 'accept' ? 'Quick accept' : 'Reject'}
244266
confirmVariant={moderateConfirm?.action === 'reject' ? 'destructive' : 'default'}
245267
onConfirm={async () => {
246268
if (!moderateConfirm) return;
@@ -264,7 +286,16 @@ export default function ContributionQueuePage() {
264286
Curation <span className="text-white/90">Queues</span>
265287
</h1>
266288
<p className="text-blue-100 max-w-2xl">
267-
New submitted entities and contributions must be accepted by an editor. These queues list items in need of moderation.
289+
Fast accept/reject for straightforward submissions. For source-tier triage,
290+
conflicts, and provenance notes, open the{" "}
291+
<button
292+
type="button"
293+
className="underline underline-offset-2 font-medium text-white hover:text-blue-50"
294+
onClick={() => router.push('/curation/review')}
295+
>
296+
Epistemic Review Queue
297+
</button>{" "}
298+
(or the eye icon on a row) before publishing.
268299
</p>
269300
{showPendingCta && (
270301
<p className="text-sm text-amber-200 border border-amber-400/40 bg-amber-500/20 rounded-lg px-4 py-2 max-w-2xl">
@@ -471,11 +502,11 @@ export default function ContributionQueuePage() {
471502
<TableCell className="text-right">
472503
<div className="flex justify-end gap-1" onClick={e => e.stopPropagation()}>
473504
<Button variant="ghost" size="icon" className="h-8 w-8 text-blue-600 hover:text-blue-800 hover:bg-blue-50 dark:hover:bg-blue-900/30"
474-
onClick={() => router.push(`/curation/review/${c.entity_id}`)} title="Review"><Eye className="h-4 w-4" /></Button>
505+
onClick={() => router.push(`/curation/review/${c.entity_id}`)} title="Full epistemic review"><Eye className="h-4 w-4" /></Button>
475506
{c.status === 'pending_review' && (
476507
<>
477508
<Button variant="ghost" size="icon" className="h-8 w-8 text-green-600 hover:text-green-700 hover:bg-green-50"
478-
onClick={() => setModerateConfirm({ contribution: c, action: 'accept' })} title="Accept"><CheckCircle className="h-4 w-4" /></Button>
509+
onClick={() => setModerateConfirm({ contribution: c, action: 'accept' })} title="Quick accept (no triage workspace)"><CheckCircle className="h-4 w-4" /></Button>
479510
<Button variant="ghost" size="icon" className="h-8 w-8 text-red-600 hover:text-red-700 hover:bg-red-50"
480511
onClick={() => setModerateConfirm({ contribution: c, action: 'reject' })} title="Reject"><XCircle className="h-4 w-4" /></Button>
481512
</>

heritage_graph_ui/src/app/(dashboard)/curation/review/[id]/page.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
9191

9292
export default function ReviewWorkspacePage() {
9393
const { data: session } = useSession();
94-
const { isReviewer } = useUserRoles();
94+
const { isReviewer, isLoading: rolesLoading } = useUserRoles();
9595
const router = useRouter();
9696
const params = useParams();
9797
const entityId = params.id as string;
@@ -100,11 +100,6 @@ export default function ReviewWorkspacePage() {
100100
const [isLoading, setIsLoading] = useState(true);
101101
const [isSubmitting, setIsSubmitting] = useState(false);
102102
const [error, setError] = useState<string | null>(null);
103-
104-
if (!isReviewer) {
105-
return <AccessDenied requiredRole="reviewer" userEmail={session?.user?.email} />;
106-
}
107-
108103
const [verdict, setVerdict] = useState<Verdict | ''>('');
109104
const [conflictHandling, setConflictHandling] = useState<ConflictHandling>('not_applicable');
110105
const [confidenceOverride, setConfidenceOverride] = useState<Confidence>('');
@@ -135,7 +130,10 @@ export default function ReviewWorkspacePage() {
135130
finally { setIsLoading(false); }
136131
}, [entityId, getHeaders]);
137132

138-
useEffect(() => { if (session && entityId) fetchWorkspace(); }, [session, entityId, fetchWorkspace]);
133+
useEffect(() => {
134+
if (!session || !entityId || !isReviewer) return;
135+
fetchWorkspace();
136+
}, [session, entityId, isReviewer, fetchWorkspace]);
139137

140138
const openSubmitDecisionConfirm = () => {
141139
if (!verdict) {
@@ -185,6 +183,21 @@ export default function ReviewWorkspacePage() {
185183
}
186184
};
187185

186+
if (rolesLoading) {
187+
return (
188+
<div className="flex items-center justify-center min-h-[60vh]">
189+
<div className="text-center">
190+
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-2 text-blue-500" />
191+
<p className="text-blue-700 dark:text-blue-300">Checking reviewer access…</p>
192+
</div>
193+
</div>
194+
);
195+
}
196+
197+
if (!isReviewer) {
198+
return <AccessDenied requiredRole="reviewer" userEmail={session?.user?.email} />;
199+
}
200+
188201
if (isLoading) {
189202
return (
190203
<div className="flex items-center justify-center min-h-[60vh]">

heritage_graph_ui/src/app/(dashboard)/curation/review/page.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,14 @@ export default function ReviewQueuePage() {
7070

7171
function ReviewQueuePageInner() {
7272
const { data: session } = useSession();
73-
const { isReviewer, isModerator } = useUserRoles();
73+
const { isReviewer, isModerator, isLoading: rolesLoading } = useUserRoles();
7474
const router = useRouter();
7575
const pathname = usePathname();
7676
const searchParams = useSearchParams();
7777
const API_BASE = useMemo(() => getPublicApiUrl(), []);
7878
const [contributions, setContributions] = useState<Contribution[]>([]);
7979
const [isLoading, setIsLoading] = useState(true);
8080
const [error, setError] = useState<string | null>(null);
81-
82-
if (!isReviewer) {
83-
return <AccessDenied requiredRole="reviewer" userEmail={session?.user?.email} />;
84-
}
8581
const [currentPage, setCurrentPage] = useState(1);
8682
const [totalPages, setTotalPages] = useState(1);
8783
const [totalCount, setTotalCount] = useState(0);
@@ -110,6 +106,7 @@ function ReviewQueuePageInner() {
110106
[pathname, router, searchParams],
111107
);
112108

109+
// Hooks must run unconditionally — gate rendering after all hooks below.
113110
useEffect(() => {
114111
const qt = (searchParams.get('queue_type') || 'all') as QueueTab;
115112
if (['all', 'new_claims', 'conflicts', 'flagged', 'expiring'].includes(qt)) setActiveTab(qt);
@@ -189,10 +186,10 @@ function ReviewQueuePageInner() {
189186
}, [API_BASE, contradictionsOnly, getHeaders, minWorstSourceRank, myDomainOnly, ordering, staleDays]);
190187

191188
useEffect(() => {
192-
if (!session) return;
189+
if (!session || !isReviewer) return;
193190
fetchContributions(currentPage, activeTab, selectedCategory, appliedSearch);
194191
fetchQueueCounts();
195-
}, [session, currentPage, activeTab, selectedCategory, appliedSearch, ordering, staleDays, contradictionsOnly, minWorstSourceRank, myDomainOnly, fetchContributions, fetchQueueCounts]);
192+
}, [session, isReviewer, currentPage, activeTab, selectedCategory, appliedSearch, ordering, staleDays, contradictionsOnly, minWorstSourceRank, myDomainOnly, fetchContributions, fetchQueueCounts]);
196193

197194
const handleTabChange = (tab: string) => {
198195
setActiveTab(tab as QueueTab); setCurrentPage(1);
@@ -256,6 +253,15 @@ function ReviewQueuePageInner() {
256253
const formatUserName = (u: UserInfo) => `${u.first_name} ${u.last_name}`.trim() || u.username;
257254
const getRevisionInfo = (c: Contribution) => c.latest_revision ? `Rev. ${c.latest_revision.revision_number}` : '—';
258255

256+
if (rolesLoading) {
257+
return (
258+
<div className="p-8 text-center text-muted-foreground">Checking reviewer access…</div>
259+
);
260+
}
261+
if (!isReviewer) {
262+
return <AccessDenied requiredRole="reviewer" userEmail={session?.user?.email} />;
263+
}
264+
259265
return (
260266
<TooltipProvider>
261267

@@ -273,7 +279,18 @@ function ReviewQueuePageInner() {
273279
<h1 className="text-3xl font-black text-white">
274280
Review <span className="text-white/90">Queue</span>
275281
</h1>
276-
<p className="text-blue-100 max-w-lg">Evaluate claims, resolve conflicts, maintain provenance</p>
282+
<p className="text-blue-100 max-w-lg">
283+
Evaluate claims, resolve conflicts, maintain provenance. Prefer this queue for
284+
source-tier triage — use{" "}
285+
<button
286+
type="button"
287+
className="underline underline-offset-2 hover:text-white"
288+
onClick={() => router.push("/curation/contributions")}
289+
>
290+
Curation Queues
291+
</button>{" "}
292+
only for quick bulk accept/reject.
293+
</p>
277294
</div>
278295
<div className="flex gap-2 flex-wrap">
279296
<Button onClick={() => router.push('/curation/dashboard')} variant="outline" className="border-white/30 text-white hover:bg-white/20">Dashboard</Button>

0 commit comments

Comments
 (0)