'use client' import { useEffect, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import Link from 'next/link' import dynamic from 'next/dynamic' import { trpc } from '@/lib/trpc/client' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Switch } from '@/components/ui/switch' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Skeleton } from '@/components/ui/skeleton' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { toast } from 'sonner' import { ArrowLeft, Save, Loader2, FileText, Video, Link as LinkIcon, File, Trash2, Eye, AlertCircle, } from 'lucide-react' // Dynamically import BlockEditor to avoid SSR issues const BlockEditor = dynamic( () => import('@/components/shared/block-editor').then((mod) => mod.BlockEditor), { ssr: false, loading: () => (
), } ) const resourceTypeOptions = [ { value: 'DOCUMENT', label: 'Document', icon: FileText }, { value: 'PDF', label: 'PDF', icon: FileText }, { value: 'VIDEO', label: 'Video', icon: Video }, { value: 'LINK', label: 'External Link', icon: LinkIcon }, { value: 'OTHER', label: 'Other', icon: File }, ] const cohortOptions = [ { value: 'ALL', label: 'All Members', description: 'Visible to everyone' }, { value: 'SEMIFINALIST', label: 'Semi-finalists', description: 'Visible to semi-finalist evaluators' }, { value: 'FINALIST', label: 'Finalists', description: 'Visible to finalist evaluators only' }, ] export default function EditLearningResourcePage() { const params = useParams() const router = useRouter() const resourceId = params.id as string // Fetch resource const { data: resource, isLoading, error } = trpc.learningResource.get.useQuery({ id: resourceId }) const { data: stats } = trpc.learningResource.getStats.useQuery({ id: resourceId }) // Form state const [title, setTitle] = useState('') const [description, setDescription] = useState('') const [contentJson, setContentJson] = useState('') const [resourceType, setResourceType] = useState('DOCUMENT') const [cohortLevel, setCohortLevel] = useState('ALL') const [externalUrl, setExternalUrl] = useState('') const [isPublished, setIsPublished] = useState(false) const [programId, setProgramId] = useState(null) // API const { data: programs } = trpc.program.list.useQuery({ status: 'ACTIVE' }) const utils = trpc.useUtils() const updateResource = trpc.learningResource.update.useMutation({ onSuccess: () => { utils.learningResource.get.invalidate({ id: resourceId }) utils.learningResource.list.invalidate() }, }) const deleteResource = trpc.learningResource.delete.useMutation({ onSuccess: () => utils.learningResource.list.invalidate(), }) const getUploadUrl = trpc.learningResource.getUploadUrl.useMutation() // Populate form when resource loads useEffect(() => { if (resource) { setTitle(resource.title) setDescription(resource.description || '') setContentJson(resource.contentJson ? JSON.stringify(resource.contentJson) : '') setResourceType(resource.resourceType) setCohortLevel(resource.cohortLevel) setExternalUrl(resource.externalUrl || '') setIsPublished(resource.isPublished) setProgramId(resource.programId) } }, [resource]) // Handle file upload for BlockNote const handleUploadFile = async (file: File): Promise => { try { const { url, bucket, objectKey } = await getUploadUrl.mutateAsync({ fileName: file.name, mimeType: file.type, }) await fetch(url, { method: 'PUT', body: file, headers: { 'Content-Type': file.type, }, }) const minioEndpoint = process.env.NEXT_PUBLIC_MINIO_ENDPOINT || 'http://localhost:9000' return `${minioEndpoint}/${bucket}/${objectKey}` } catch (error) { toast.error('Failed to upload file') throw error } } const handleSubmit = async () => { if (!title.trim()) { toast.error('Please enter a title') return } if (resourceType === 'LINK' && !externalUrl) { toast.error('Please enter an external URL') return } try { await updateResource.mutateAsync({ id: resourceId, title, description: description || undefined, contentJson: contentJson ? JSON.parse(contentJson) : undefined, resourceType: resourceType as 'PDF' | 'VIDEO' | 'DOCUMENT' | 'LINK' | 'OTHER', cohortLevel: cohortLevel as 'ALL' | 'SEMIFINALIST' | 'FINALIST', externalUrl: externalUrl || null, isPublished, }) toast.success('Resource updated successfully') router.push('/admin/learning') } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to update resource') } } const handleDelete = async () => { try { await deleteResource.mutateAsync({ id: resourceId }) toast.success('Resource deleted successfully') router.push('/admin/learning') } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to delete resource') } } if (isLoading) { return (
) } if (error || !resource) { return (
Resource not found The resource you're looking for does not exist.
) } return (
{/* Header */}

Edit Resource

Update this learning resource

Delete Resource Are you sure you want to delete "{resource.title}"? This action cannot be undone. Cancel {deleteResource.isPending ? ( ) : null} Delete
{/* Main content */}
{/* Basic Info */} Resource Details Basic information about this resource
setTitle(e.target.value)} placeholder="e.g., Ocean Conservation Best Practices" />