import React from 'react'; import { Download, Maximize2, Check, MessageSquare, Star, Heart } from 'lucide-react'; import { useInView } from 'react-intersection-observer'; import { useTheme } from '../../../contexts/ThemeContext'; import { AuthenticatedImage } from '../../common'; import { FeedbackIdentityModal } from '../../gallery/FeedbackIdentityModal'; import { feedbackService } from '../../../services/feedback.service'; import type { BaseGalleryLayoutProps } from './BaseGalleryLayout'; import type { Photo } from '../../../types'; interface GridPhotoProps { photo: Photo; isSelected: boolean; isSelectionMode: boolean; onClick: (e: React.MouseEvent) => void; onDownload: (e: React.MouseEvent) => void; onToggleSelect: () => void; animationType?: string; allowDownloads?: boolean; slug?: string; protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; feedbackEnabled?: boolean; feedbackOptions?: { allowLikes?: boolean; allowRatings?: boolean; allowComments?: boolean; requireNameEmail?: boolean; }; savedIdentity?: { name: string; email: string } | null; onRequireIdentity?: (action: 'like', photoId: number) => void; onQuickComment?: () => void; onFeedbackChange?: () => void; // Immediate UI like state and callback liked?: boolean; onLikeSuccess?: () => void; } const GridPhoto: React.FC = ({ photo, isSelected, isSelectionMode, onClick, onDownload, onToggleSelect, animationType = 'fade', allowDownloads = true, slug, protectionLevel = 'standard', useEnhancedProtection = false, feedbackEnabled = false, feedbackOptions, savedIdentity, onRequireIdentity, onQuickComment, onFeedbackChange, liked = false, onLikeSuccess }) => { // handled by parent layout; kept here for type completeness but not used const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.1, }); const animationClass = animationType === 'scale' ? 'transition-transform duration-300 hover:scale-105' : animationType === 'fade' ? 'transition-opacity duration-300' : ''; const likeCount = photo.like_count ?? 0; const averageRating = photo.average_rating ?? 0; const commentCount = photo.comment_count ?? 0; const showFeedbackActions = feedbackEnabled && Boolean(feedbackOptions); return (
{inView ? ( <> { console.warn(`Protection violation on grid photo ${photo.id}: ${violationType}`); }} />
{!isSelectionMode && ( <> {allowDownloads && ( )} {showFeedbackActions && onQuickComment && ( )} {/* Quick feedback actions */} {showFeedbackActions && feedbackOptions?.allowLikes && ( )} )}
{/* Selection Checkbox (visible on hover or when selected) */} {/* Feedback Indicators (always visible, bottom-left). Show like immediately when user liked */} {(commentCount > 0 || averageRating > 0 || likeCount > 0 || liked) && (
{(likeCount > 0 || liked) && ( )} {averageRating > 0 && ( )} {commentCount > 0 && ( )}
)} {photo.type === 'collage' && (
Collage
)} ) : (
)}
); }; export const GridGalleryLayout: React.FC = ({ photos, slug, onPhotoClick, onOpenPhotoWithFeedback, onFeedbackChange, onDownload, selectedPhotos = new Set(), isSelectionMode = false, onPhotoSelect, allowDownloads = true, protectionLevel = 'standard', useEnhancedProtection = false, feedbackEnabled = false, feedbackOptions }) => { const { theme } = useTheme(); const gallerySettings = theme.gallerySettings || {}; const columns = gallerySettings.gridColumns || { mobile: 2, tablet: 3, desktop: 4 }; const spacing = gallerySettings.spacing || 'normal'; const animation = gallerySettings.photoAnimation || 'fade'; const [showIdentityModal, setShowIdentityModal] = React.useState(false); const [pendingAction, setPendingAction] = React.useState(null); const [likedPhotoIds, setLikedPhotoIds] = React.useState>(new Set()); const [savedIdentity, setSavedIdentity] = React.useState<{ name: string; email: string } | null>(null); const spacingClass = spacing === 'tight' ? 'gap-2' : spacing === 'relaxed' ? 'gap-6' : 'gap-4'; const gridClass = `grid ${spacingClass} grid-cols-${columns.mobile} sm:grid-cols-${columns.tablet} lg:grid-cols-${columns.desktop} xl:grid-cols-${columns.desktop + 1}`; return (
{photos.map((photo, index) => ( onPhotoClick(index)} onToggleSelect={() => onPhotoSelect && onPhotoSelect(photo.id)} onDownload={(e) => onDownload(photo, e)} animationType={animation} allowDownloads={allowDownloads} slug={slug} protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} feedbackEnabled={feedbackEnabled} feedbackOptions={feedbackOptions} savedIdentity={savedIdentity} onRequireIdentity={(action, photoId) => { setPendingAction({ type: action, photoId }); setShowIdentityModal(true); }} onQuickComment={() => onOpenPhotoWithFeedback && onOpenPhotoWithFeedback(index)} onFeedbackChange={onFeedbackChange} liked={likedPhotoIds.has(photo.id)} onLikeSuccess={() => { setLikedPhotoIds((prev) => { const next = new Set(prev); next.add(photo.id); return next; }); }} /> ))} { setShowIdentityModal(false); setPendingAction(null); }} onSubmit={async (name, email) => { setSavedIdentity({ name, email }); setShowIdentityModal(false); if (pendingAction) { await feedbackService.submitFeedback(slug, String(pendingAction.photoId), { feedback_type: pendingAction.type, guest_name: name, guest_email: email, }); // Immediately reflect like UI if (pendingAction.type === 'like') { setLikedPhotoIds((prev) => { const next = new Set(prev); next.add(pendingAction.photoId); return next; }); } setPendingAction(null); } }} feedbackType="like" />
); };