import React, { useState, useEffect } from 'react'; import { ChevronLeft, ChevronRight, X, MoreHorizontal, Heart, MessageCircle, Play, Bookmark } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { format, parseISO } from 'date-fns'; import { Post } from '../types'; import { cn } from '../lib/utils'; import { MediaRenderer } from './MediaRenderer'; interface PostModalProps { post: Post; onClose: () => void; onNextPost?: () => void; onPrevPost?: () => void; hasNextPost?: boolean; hasPrevPost?: boolean; profilePic: string | null; } export const PostModal: React.FC = ({ post, onClose, onNextPost, onPrevPost, hasNextPost, hasPrevPost, profilePic }) => { const [currentIndex, setCurrentIndex] = useState(0); const [direction, setDirection] = useState(0); // Preloading Logic useEffect(() => { const controller = new AbortController(); const preloadMedia = async (index: number) => { if (index < 0 || index >= post.media.length) return; const media = post.media[index]; if (!media.url) return; try { if (media.type === 'image') { const img = new Image(); img.src = media.url; } else { const video = document.createElement('video'); video.src = media.url; video.preload = 'auto'; } } catch (e) {} }; // 1. Immediate preload of first two slides preloadMedia(0); preloadMedia(1); // 2. Delayed preload of the rest to stay out of the way of initial render const timeout = setTimeout(() => { for (let i = 2; i < post.media.length; i++) { if (controller.signal.aborted) break; preloadMedia(i); } }, 1000); return () => { controller.abort(); clearTimeout(timeout); }; }, [post.id, post.media]); useEffect(() => setCurrentIndex(0), [post.id]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'ArrowRight') onNextPost?.(); else if (e.key === 'ArrowLeft') onPrevPost?.(); else if (e.key === '.') paginate(1); else if (e.key === ',') paginate(-1); else if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [onNextPost, onPrevPost, currentIndex, post.media.length, onClose]); const paginate = (newDirection: number) => { const nextIndex = currentIndex + newDirection; if (nextIndex >= 0 && nextIndex < post.media.length) { setDirection(newDirection); setCurrentIndex(nextIndex); } }; const variants = { enter: (d: number) => ({ x: d > 0 ? '100%' : '-100%', opacity: 1, zIndex: 0 }), center: { zIndex: 1, x: 0, opacity: 1 }, exit: (d: number) => ({ zIndex: 0, x: d < 0 ? '100%' : '-100%', opacity: 1 }) }; const swipePower = (offset: number, velocity: number) => Math.abs(offset) * velocity; return (
{hasPrevPost && onPrevPost && } {hasNextPost && onNextPost && } { if (offset.y > 200 || velocity.y > 800) onClose(); }} className="bg-black flex flex-col md:flex-row w-full max-w-6xl h-auto md:rounded-sm overflow-hidden shadow-2xl relative text-black" onClick={e => e.stopPropagation()}>
{ const s = swipePower(offset.x, velocity.x); if (s < -15000) { if (currentIndex < post.media.length - 1) paginate(1); else if (hasNextPost && onNextPost && s < -40000) onNextPost(); } else if (s > 15000) { if (currentIndex > 0) paginate(-1); else if (hasPrevPost && onPrevPost && s > 40000) onPrevPost(); } }} className="col-start-1 row-start-1 w-full flex items-center justify-center cursor-grab active:cursor-grabbing relative text-black" >
{post.media.length > 1 && ( <> {currentIndex > 0 && } {currentIndex < post.media.length - 1 && }
{post.media.map((_, i) =>
)}
)}
{profilePic ? : {post.username[0]}}
{post.username}
{profilePic ? : {post.username[0]}}
{post.username}{post.caption}
{format(parseISO(post.date), 'MMMM d, yyyy')}
Archived Post{post.id}
); };