modcomms/frontend/components/ProofPreview.tsx
michael 0fdaedc7ff Complete UI design system migration to Barclays brand colors
Updates all remaining frontend components to use the new Barclays
design system color tokens:
- brand-dark-blue → primary-blue (#1A2142)
- brand-accent → active-blue (#006DE3)
- brand-light-blue → cyan-brand (#00AEEF)
- brand-gray → grey-100 (#F6F6F6)

Components updated:
- CampaignDetail and ProofDetailView in Campaigns.tsx
- Projects.tsx (full component migration)
- StatusDashboard.tsx (status tiles and colors)
- CreateProjectModal.tsx (modal styling)
- FeedbackReport.tsx (remaining brand colors)
- Login.tsx and Profile.tsx
- WIPReviewer.tsx and CopyGenAI.tsx
- Header, LoadingVisual, ToggleSwitch
- AssetPreview, ProofPreview, AssetUpload
- ProofTypeManager

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 14:00:13 -06:00

143 lines
5.3 KiB
TypeScript
Executable file

import React, { useState } from 'react';
import { DocumentIcon } from './icons/DocumentIcon';
import type { PDFPage } from '../types';
interface ProofPreviewProps {
file?: File | null;
previewUrl: string | null;
fileName?: string;
pdfPages?: PDFPage[];
}
export const ProofPreview: React.FC<ProofPreviewProps> = ({ file, previewUrl, fileName, pdfPages }) => {
const [currentPage, setCurrentPage] = useState(1);
if (!previewUrl && (!pdfPages || pdfPages.length === 0)) {
return null;
}
const getMimeType = (): string => {
if (file?.type) return file.type;
if (previewUrl?.startsWith('data:')) {
const match = previewUrl.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);/);
if (match && match[1]) {
return match[1];
}
}
return 'application/octet-stream'; // Fallback
};
const fileType = getMimeType();
const displayName = fileName || file?.name || 'Proof Preview';
// Check if we have rasterized PDF pages to display
const hasPdfPages = pdfPages && pdfPages.length > 0;
const totalPages = pdfPages?.length || 0;
const handlePrevPage = () => {
setCurrentPage(prev => Math.max(1, prev - 1));
};
const handleNextPage = () => {
setCurrentPage(prev => Math.min(totalPages, prev + 1));
};
const renderPdfPages = () => {
if (!pdfPages || pdfPages.length === 0) return null;
const currentPdfPage = pdfPages[currentPage - 1];
return (
<div className="flex flex-col">
<img
src={currentPdfPage.data_url}
alt={`${displayName} - Page ${currentPage}`}
className="w-full rounded-lg shadow-2xl object-contain border border-gray-200 bg-white p-2"
style={{ maxHeight: 'calc(100vh - 12rem)' }}
/>
{totalPages > 1 && (
<div className="flex items-center justify-center gap-4 mt-4 p-2 bg-white rounded-lg shadow border border-gray-200">
<button
onClick={handlePrevPage}
disabled={currentPage === 1}
className="px-3 py-1.5 text-sm font-medium rounded-md bg-gray-100 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Previous
</button>
<span className="text-sm text-gray-600">
Page {currentPage} of {totalPages}
</span>
<button
onClick={handleNextPage}
disabled={currentPage === totalPages}
className="px-3 py-1.5 text-sm font-medium rounded-md bg-gray-100 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Next
</button>
</div>
)}
</div>
);
};
const renderPreview = () => {
// If we have rasterized PDF pages, use those
if (hasPdfPages) {
return renderPdfPages();
}
if (fileType.startsWith('image/')) {
return (
<img
src={previewUrl!}
alt={displayName}
className="w-full rounded-lg shadow-2xl object-contain border border-gray-200 bg-white p-2"
style={{ maxHeight: 'calc(100vh - 9rem)' }}
/>
);
}
if (fileType === 'video/mp4') {
return (
<video
src={previewUrl!}
controls
className="w-full rounded-lg shadow-2xl object-contain border border-gray-200 bg-white p-2"
style={{ maxHeight: 'calc(100vh - 9rem)' }}
>
Your browser does not support the video tag.
</video>
);
}
if (fileType === 'application/pdf') {
// Fallback to iframe if no rasterized pages available
return (
<iframe
src={`${previewUrl}#view=fitH`}
title={displayName}
className="w-full h-[calc(100vh-9rem)] rounded-lg shadow-2xl border border-gray-200 bg-white"
/>
);
}
// Fallback for other file types
return (
<div
className="w-full rounded-lg shadow-2xl border border-gray-200 bg-white p-8 flex flex-col items-center justify-center text-center"
style={{ minHeight: '300px', maxHeight: 'calc(100vh - 9rem)' }}
>
<DocumentIcon className="h-20 w-20 text-gray-400 mb-4" />
<p className="text-lg font-semibold text-primary-blue break-all">{displayName}</p>
<p className="text-sm text-gray-500">{fileType}</p>
<p className="text-sm text-gray-500 mt-2">No preview available for this file type.</p>
</div>
);
};
return (
<div className="sticky top-8">
{renderPreview()}
</div>
);
};