modcomms/frontend/components/ProofPreview.tsx
Vadym Samoilenko 4302b9391a Restyle full application from Barclays to Oliver Agency brand
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>
2026-03-03 10:16:26 +00: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-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>
);
};