Skip to content

Commit 68e1f79

Browse files
authored
Merge branch 'LordNayan:main' into main
2 parents fc28cd2 + 1fe2e40 commit 68e1f79

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

apps/web/src/pages/post/PostDetailPage.tsx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ChatBubbleLeftIcon,
1313
PhotoIcon,
1414
XMarkIcon,
15+
ChevronRightIcon,
1516
} from '@heroicons/react/24/outline';
1617
import toast from 'react-hot-toast';
1718
import * as api from '../../services/api';
@@ -37,6 +38,10 @@ export function PostDetailPage() {
3738
const [previewUrls, setPreviewUrls] = useState<string[]>([]);
3839
const fileInputRef = useRef<HTMLInputElement>(null);
3940

41+
// Image modal state for post attachments
42+
const [isImageModalOpen, setIsImageModalOpen] = useState(false);
43+
const [activeImageIndex, setActiveImageIndex] = useState(0);
44+
4045
// Get current board to retrieve available statuses
4146
const currentBoard = currentPost?.board ? boards.find(b => b.id === currentPost.board.id) : null;
4247
const availableStatuses = (currentBoard?.statuses || ['open', 'planned', 'in progress', 'complete']) as string[];
@@ -269,6 +274,29 @@ export function PostDetailPage() {
269274
</p>
270275
)}
271276

277+
{/* Post Images */}
278+
{currentPost.imageURLs && currentPost.imageURLs.length > 0 && (
279+
<div className="of-mt-4 of-flex of-flex-wrap of-gap-2">
280+
{currentPost.imageURLs.map((url, index) => (
281+
<button
282+
key={index}
283+
type="button"
284+
onClick={() => {
285+
setActiveImageIndex(index);
286+
setIsImageModalOpen(true);
287+
}}
288+
className="of-block focus:of-outline-none focus:of-ring-2 focus:of-ring-primary/40 of-rounded-lg"
289+
>
290+
<img
291+
src={url}
292+
alt={`Attachment ${index + 1}`}
293+
className="of-max-w-[200px] of-max-h-[150px] of-object-cover of-rounded-lg of-border of-border-gray-200 hover:of-opacity-90 of-transition-opacity"
294+
/>
295+
</button>
296+
))}
297+
</div>
298+
)}
299+
272300
{/* Meta */}
273301
<div className="of-mt-4 of-flex of-items-center of-gap-4 of-text-sm of-text-gray-500">
274302
<div className="of-flex of-items-center of-gap-1">
@@ -478,6 +506,99 @@ export function PostDetailPage() {
478506
</div>
479507
)}
480508
</div>
509+
510+
{/* Post image modal */}
511+
<PostImageModal
512+
isOpen={isImageModalOpen}
513+
onClose={() => setIsImageModalOpen(false)}
514+
imageURLs={currentPost.imageURLs || []}
515+
activeIndex={activeImageIndex}
516+
setActiveIndex={setActiveImageIndex}
517+
/>
518+
</div>
519+
);
520+
}
521+
522+
// Image modal for post attachments
523+
function PostImageModal({
524+
isOpen,
525+
onClose,
526+
imageURLs,
527+
activeIndex,
528+
setActiveIndex,
529+
}: {
530+
isOpen: boolean;
531+
onClose: () => void;
532+
imageURLs: string[];
533+
activeIndex: number;
534+
setActiveIndex: (index: number) => void;
535+
}) {
536+
if (!isOpen || !imageURLs || imageURLs.length === 0) return null;
537+
538+
const total = imageURLs.length;
539+
540+
const goPrev = () => {
541+
setActiveIndex((activeIndex - 1 + total) % total);
542+
};
543+
544+
const goNext = () => {
545+
setActiveIndex((activeIndex + 1) % total);
546+
};
547+
548+
return (
549+
<div
550+
className="of-fixed of-inset-0 of-z-50 of-bg-black/70 of-flex of-items-center of-justify-center"
551+
onClick={onClose}
552+
>
553+
<div
554+
className="of-relative of-max-w-4xl of-w-full of-mx-4 of-bg-transparent"
555+
onClick={(e) => e.stopPropagation()}
556+
>
557+
{/* Close button */}
558+
<button
559+
type="button"
560+
onClick={onClose}
561+
className="of-absolute of-top-0 of-right-0 of-m-2 of-inline-flex of-items-center of-justify-center of-rounded-full of-bg-black/60 of-text-white of-p-2 hover:of-bg-black/80"
562+
aria-label="Close image viewer"
563+
>
564+
<XMarkIcon className="of-w-5 of-h-5" />
565+
</button>
566+
567+
{/* Image */}
568+
<div className="of-flex of-items-center of-justify-center of-bg-black/40 of-rounded-lg of-overflow-hidden of-p-2">
569+
<img
570+
src={imageURLs[activeIndex]}
571+
alt={`Attachment ${activeIndex + 1}`}
572+
className="of-max-h-[80vh] of-w-auto of-rounded-lg of-object-contain"
573+
/>
574+
</div>
575+
576+
{/* Navigation */}
577+
{total > 1 && (
578+
<>
579+
<button
580+
type="button"
581+
onClick={goPrev}
582+
className="of-absolute of-left-0 of-top-1/2 of--translate-y-1/2 of-ml-2 of-inline-flex of-items-center of-justify-center of-rounded-full of-bg-black/60 of-text-white of-p-2 hover:of-bg-black/80"
583+
aria-label="Previous image"
584+
>
585+
<ChevronLeftIcon className="of-w-5 of-h-5" />
586+
</button>
587+
<button
588+
type="button"
589+
onClick={goNext}
590+
className="of-absolute of-right-0 of-top-1/2 of--translate-y-1/2 of-mr-2 of-inline-flex of-items-center of-justify-center of-rounded-full of-bg-black/60 of-text-white of-p-2 hover:of-bg-black/80"
591+
aria-label="Next image"
592+
>
593+
<ChevronRightIcon className="of-w-5 of-h-5" />
594+
</button>
595+
596+
<div className="of-absolute of-bottom-0 of-left-1/2 of--translate-x-1/2 of-mb-3 of-px-3 of-py-1 of-rounded-full of-bg-black/60 of-text-xs of-text-white">
597+
{activeIndex + 1} / {total}
598+
</div>
599+
</>
600+
)}
601+
</div>
481602
</div>
482603
);
483604
}

0 commit comments

Comments
 (0)