import { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { CheckCircle, XCircle, AlertCircle, FileText, Image as ImageIcon, Film, Sparkles, Globe, User } from 'lucide-react'
import { useLanguage } from '../i18n/LanguageContext'
import { useToast } from '../components/ToastContainer'
import Modal from '../components/Modal'
const STATUS_ICONS = {
copy: FileText,
design: ImageIcon,
video: Film,
other: Sparkles,
}
export default function PublicReview() {
const { token } = useParams()
const { t } = useLanguage()
const toast = useToast()
const [artefact, setArtefact] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [submitting, setSubmitting] = useState(false)
const [success, setSuccess] = useState('')
const [reviewerName, setReviewerName] = useState('')
const [feedback, setFeedback] = useState('')
const [selectedLanguage, setSelectedLanguage] = useState(0)
const [pendingAction, setPendingAction] = useState(null)
useEffect(() => {
loadArtefact()
}, [token])
const loadArtefact = async () => {
try {
const res = await fetch(`/api/public/review/${token}`)
if (!res.ok) {
const err = await res.json()
setError(err.error || t('review.loadFailed'))
setLoading(false)
return
}
const data = await res.json()
setArtefact(data)
// Auto-set reviewer name if there's exactly one approver
if (data.approvers?.length === 1 && data.approvers[0].name) {
setReviewerName(data.approvers[0].name)
}
} catch (err) {
setError(t('review.loadFailed'))
} finally {
setLoading(false)
}
}
const handleAction = (action) => {
if (!reviewerName.trim()) {
toast.error(t('review.enterName'))
return
}
if (action === 'revision' && !feedback.trim()) {
toast.error(t('review.feedbackRequired'))
return
}
if (action === 'approve' || action === 'reject') {
setPendingAction(action)
return
}
executeAction(action)
}
const executeAction = async (action) => {
setSubmitting(true)
try {
const res = await fetch(`/api/public/review/${token}/${action}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
approved_by_name: reviewerName,
feedback: feedback || undefined,
}),
})
if (!res.ok) {
const err = await res.json()
setError(err.error || t('review.actionFailed'))
setSubmitting(false)
return
}
const data = await res.json()
setSuccess(data.message || t('review.actionCompleted'))
setTimeout(() => {
loadArtefact()
}, 1500)
} catch (err) {
setError(t('review.actionFailed'))
} finally {
setSubmitting(false)
}
}
const extractDriveFileId = (url) => {
const patterns = [
/\/file\/d\/([^\/]+)/,
/id=([^&]+)/,
/\/d\/([^\/]+)/,
]
for (const pattern of patterns) {
const match = url.match(pattern)
if (match) return match[1]
}
return null
}
const getDriveEmbedUrl = (url) => {
const fileId = extractDriveFileId(url)
return fileId ? `https://drive.google.com/file/d/${fileId}/preview` : url
}
if (loading) {
return (
)
}
if (error) {
return (
{t('review.notAvailable')}
{error}
)
}
if (success) {
return (
{t('review.thankYou')}
{success}
)
}
if (!artefact) return null
const TypeIcon = STATUS_ICONS[artefact.type] || Sparkles
const isImage = (url) => /\.(jpg|jpeg|png|gif|webp)$/i.test(url)
return (
{/* Header */}
{t('review.contentReview')}
Samaya Digital Hub
{/* Artefact Info */}
{artefact.title}
{artefact.description && (
{artefact.description}
)}
{artefact.type}
{artefact.brand_name && • {artefact.brand_name}}
{artefact.version_number && • {t('review.version')} {artefact.version_number}}
{/* COPY TYPE: Multilingual Content */}
{artefact.type === 'copy' && artefact.texts && artefact.texts.length > 0 && (
{t('review.contentLanguages')}
{/* Language tabs */}
{artefact.texts.map((text, idx) => (
))}
{/* Selected language content */}
{artefact.texts[selectedLanguage].language_label} {t('review.content')}
{artefact.texts[selectedLanguage].content}
)}
{/* Legacy content field (for backward compatibility) */}
{artefact.content && (!artefact.texts || artefact.texts.length === 0) && (
)}
{/* DESIGN TYPE: Image Gallery */}
{artefact.type === 'design' && artefact.attachments && artefact.attachments.length > 0 && (
{t('review.designFiles')}
)}
{/* VIDEO TYPE: Video Player or Drive Embed */}
{artefact.type === 'video' && artefact.attachments && artefact.attachments.length > 0 && (
{t('review.videos')}
{artefact.attachments.map((att, idx) => (
{att.drive_url ? (
{t('review.googleDriveVideo')}
) : (
{att.original_name && (
{att.original_name}
)}
)}
))}
)}
{/* OTHER TYPE: Generic Attachments */}
{artefact.type === 'other' && artefact.attachments && artefact.attachments.length > 0 && (
{t('review.attachments')}
{artefact.attachments.map((att, idx) => (
))}
)}
{/* Comments */}
{artefact.comments && artefact.comments.length > 0 && (
{t('review.previousComments')}
{artefact.comments.map((comment, idx) => (
{comment.user_name || comment.author_name || 'Anonymous'}
{comment.CreatedAt && (
• {new Date(comment.CreatedAt).toLocaleDateString()}
)}
{comment.content}
))}
)}
{/* Review Form */}
{artefact.status === 'pending_review' && (
{t('review.yourReview')}
{/* Reviewer identity */}
{artefact.approvers?.length === 1 ? (
{artefact.approvers[0].name}
) : artefact.approvers?.length > 1 ? (
) : (
setReviewerName(e.target.value)}
placeholder={t('review.enterYourName')}
className="w-full px-4 py-2 text-sm border border-border rounded-lg bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary transition-colors"
/>
)}
)}
{/* Already Reviewed */}
{artefact.status !== 'pending_review' && (
{t('review.alreadyReviewed')}
{t('review.statusLabel')}: {artefact.status.replace('_', ' ')}
{artefact.approved_by_name && (
{t('review.reviewedBy')}: {artefact.approved_by_name}
)}
)}
{/* Footer */}
{/* Approve / Reject Confirmation */}
setPendingAction(null)}
title={pendingAction === 'approve' ? t('review.confirmApprove') : t('review.confirmReject')}
isConfirm
danger={pendingAction === 'reject'}
onConfirm={() => {
const action = pendingAction
setPendingAction(null)
executeAction(action)
}}
confirmText={pendingAction === 'approve' ? t('review.approve') : t('review.reject')}
>
{pendingAction === 'approve' ? t('review.confirmApproveDesc') : t('review.confirmRejectDesc')}
)
}