Replace entire Barclays colour palette (navy #1A2142, lime #C3FB5A, violet #7A0FF9) with Oliver brand tokens: black #1A1A1A, gold #FFCB05, orange #FF5C00, azure #0487B6, sky #5DF5EA, grey #EFEFEF, green #09821F. - Switch font from Inter/Barclays Effra to Arial (system font) - Add new Oliver logo asset (BAR-ModComms-logo-v4.png) - Sidebar: black background, new logo, azure active state - Hero: orange "Intelligent Review" text, hide AI-Powered tagline - Hide ChecksOverview on Home page per Oliver design - Toast notification: orange background with black text - All tables: sky headers, alternating white/grey rows - Campaign badges: gold "In Progress", green "Completed" - Analytics: grey KPI cards, sky accent on Key Insight, oliver trend colours - All buttons: azure fill, pill-shaped (rounded-full) - All tabs/toggles/dropdowns: azure accent colour - Update HTML title to "Mod Comms - Intelligent Review" - Default border radius set to 10px Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
143 lines
5.3 KiB
TypeScript
Executable file
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-oliver-black 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>
|
|
);
|
|
};
|