All checks were successful
Build and Push Docker Image / build (push) Successful in 12m19s
- Add getTransferCandidates/transferAssignments procedures for targeted assignment moves between jurors with TOCTOU guards and audit logging - Add getOverCapPreview/redistributeOverCap for auto-redistributing assignments when a juror's cap is lowered below their current load - Add TransferAssignmentsDialog (2-step: select projects, pick destinations) - Extend InlineMemberCap with over-cap detection and redistribute banner - Extend getReassignmentHistory to show ASSIGNMENT_TRANSFER and CAP_REDISTRIBUTE events - Learning hub: replace ResourceType/CohortLevel enums with accessJson JSONB, add coverImageKey, resource detail pages for jury/mentor, shared renderer - Migration: 20260221200000_learning_hub_overhaul Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
72 lines
1.7 KiB
TypeScript
72 lines
1.7 KiB
TypeScript
'use client'
|
|
|
|
import dynamic from 'next/dynamic'
|
|
|
|
const BlockViewer = dynamic(
|
|
() => import('@/components/shared/block-editor').then((mod) => mod.BlockViewer),
|
|
{
|
|
ssr: false,
|
|
loading: () => (
|
|
<div className="min-h-[200px] rounded-lg border bg-muted/20 animate-pulse" />
|
|
),
|
|
}
|
|
)
|
|
|
|
interface ResourceRendererProps {
|
|
title: string
|
|
description?: string | null
|
|
contentJson: unknown // BlockNote PartialBlock[] stored as JSON
|
|
coverImageUrl?: string | null
|
|
className?: string
|
|
}
|
|
|
|
export function ResourceRenderer({
|
|
title,
|
|
description,
|
|
contentJson,
|
|
coverImageUrl,
|
|
className,
|
|
}: ResourceRendererProps) {
|
|
const contentString =
|
|
typeof contentJson === 'string' ? contentJson : JSON.stringify(contentJson)
|
|
|
|
return (
|
|
<article className={`mx-auto max-w-3xl ${className ?? ''}`}>
|
|
{/* Cover image */}
|
|
{coverImageUrl && (
|
|
<div className="mb-8 overflow-hidden rounded-lg">
|
|
<img
|
|
src={coverImageUrl}
|
|
alt=""
|
|
className="h-auto w-full object-cover"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Title */}
|
|
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
|
{title}
|
|
</h1>
|
|
|
|
{/* Description */}
|
|
{description && (
|
|
<p className="mt-3 text-lg text-muted-foreground leading-relaxed">
|
|
{description}
|
|
</p>
|
|
)}
|
|
|
|
{/* Divider */}
|
|
<hr className="my-6 border-border" />
|
|
|
|
{/* Content */}
|
|
{contentJson ? (
|
|
<div className="prose-renderer">
|
|
<BlockViewer content={contentString} />
|
|
</div>
|
|
) : (
|
|
<p className="text-muted-foreground italic">No content</p>
|
|
)}
|
|
</article>
|
|
)
|
|
}
|