diff --git a/cshare-app/src/pages/FileViewPage.jsx b/cshare-app/src/pages/FileViewPage.jsx new file mode 100644 index 0000000..5925b01 --- /dev/null +++ b/cshare-app/src/pages/FileViewPage.jsx @@ -0,0 +1,576 @@ +import { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { DownloadIcon, CopyIcon, CheckIcon, ClockIcon, ShieldIcon } from '../components/Icons'; +import { sharedFile, formatFileSize, formatDate, getFileIcon, getFileIconColor } from '../data/mockData'; +import { useToast } from '../components/ToastContext'; +import { downloadUrl, previewUrl, resolveLink } from '../lib/api'; + +function useCountdown(targetDate) { + const [timeLeft, setTimeLeft] = useState(''); + + useEffect(() => { + const update = () => { + const diff = new Date(targetDate) - new Date(); + if (diff <= 0) { setTimeLeft('Expired'); return; } + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + const hours = Math.floor((diff / (1000 * 60 * 60)) % 24); + const mins = Math.floor((diff / (1000 * 60)) % 60); + const secs = Math.floor((diff / 1000) % 60); + if (days > 0) { + setTimeLeft(`${days}d ${hours}h ${mins}m ${secs}s`); + } else { + setTimeLeft(`${hours}h ${mins}m ${secs}s`); + } + }; + update(); + const id = setInterval(update, 1000); + return () => clearInterval(id); + }, [targetDate]); + + return timeLeft; +} + +function FilePreview({ file, linkId }) { + const [text, setText] = useState(''); + const [textError, setTextError] = useState(''); + const [zoom, setZoom] = useState(1); + const [rotation, setRotation] = useState(0); + const [copiedText, setCopiedText] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + const addToast = useToast(); + const preview = file.preview; + + useEffect(() => { + if (!linkId || preview?.kind !== 'text') return; + let active = true; + fetch(previewUrl(linkId)) + .then((response) => { + if (!response.ok) throw new Error('Preview unavailable'); + return response.text(); + }) + .then((body) => { + if (active) setText(body.slice(0, 120_000)); + }) + .catch((err) => { + if (active) setTextError(err.message); + }); + return () => { + active = false; + }; + }, [linkId, preview?.kind]); + + const copyTextContent = () => { + navigator.clipboard?.writeText(text); + setCopiedText(true); + addToast('Preview text copied to clipboard', 'success'); + setTimeout(() => setCopiedText(false), 2000); + }; + + if (!linkId || !preview?.available) { + return ( +
+ Preview is available for small images, PDFs, text, audio, and video files. +
+ ); + } + + const src = previewUrl(linkId); + const shellStyle = { + marginBottom: '20px', + background: 'var(--bg-surface)', + border: '1px solid var(--border-subtle)', + borderRadius: 'var(--radius-md)', + overflow: 'hidden', + }; + + if (preview.kind === 'image') { + return ( +
+ {/* Toolbar */} +
+ + + + +
+ {/* Canvas container */} +
+ {file.name} + {/* Zoom badge */} +
+ {Math.round(zoom * 100)}% +
+
+
+ ); + } + + if (preview.kind === 'video') { + return ( +
+
+ ); + } + + if (preview.kind === 'audio') { + return ( +
+
+ ); + } + + if (preview.kind === 'pdf') { + return ( +
+