// Share view — read-only client gallery (used both for internal preview and public share) // Responsive column count hook function useColumns() { const [cols, setCols] = React.useState(() => { const w = window.innerWidth; return w < 540 ? 1 : w < 800 ? 2 : w < 1100 ? 3 : 4; }); React.useEffect(() => { const fn = () => { const w = window.innerWidth; setCols(w < 540 ? 1 : w < 800 ? 2 : w < 1100 ? 3 : 4); }; window.addEventListener('resize', fn); return () => window.removeEventListener('resize', fn); }, []); return cols; } // Pin icon (thumbtack) function IconPin({ size = 14, filled = false }) { return ( ); } const MAX_PINS = 10; function ShareView({ files, share, onOpen, onClose, onFeedback, feedback, onApprove, approvals, token, pins, onPin }) { const [photoErr, setPhotoErr] = React.useState(false); const [logoErr, setLogoErr] = React.useState(false); const [noteTarget, setNoteTarget] = React.useState(null); // filename for note modal const [noteText, setNoteText] = React.useState(''); const [showMoodBoard, setShowMoodBoard] = React.useState(false); const cols = useColumns(); const hasLogo = share.has_logo && !logoErr; const studioName = share.studio_name || ''; const tagline = share.tagline || ''; const photographer = share.photographer || ''; const likedCount = feedback ? feedback.size : 0; const approvedCount = approvals ? Object.values(approvals).filter(a => a.status === 'approved').length : 0; const changesCount = approvals ? Object.values(approvals).filter(a => a.status === 'changes').length : 0; const pinCount = pins ? pins.size : 0; const atPinLimit = pinCount >= MAX_PINS; const submitNote = () => { if (onApprove && noteTarget) { onApprove(noteTarget, 'changes', noteText.trim()); } setNoteTarget(null); setNoteText(''); }; return (
{/* Header */}
{(hasLogo || studioName) && (
{hasLogo && ( {studioName setLogoErr(true)}/> )} {!hasLogo && studioName && (
{studioName}
)} {tagline && (
{tagline}
)}
)}
Client Gallery
{share.label || 'Gallery'}
{share.username && (
{share.has_photo && !photoErr ? setPhotoErr(true)}/> : (photographer || '?')[0].toUpperCase()}
)} {files.length} photos{photographer ? ` · by ${photographer}` : ''}
{/* Mood board button */} {onPin && ( )} {likedCount > 0 && token && ( Download {likedCount} favorite{likedCount !== 1 ? 's' : ''} )} {onClose && Exit preview}
{/* Instructions bar */}
{onPin && <>{' '}pin your favorites · } to like · {onApprove ? ' ✓ approve · ✗ request changes' : ' tap to view full size'}
{/* Mood board progress */} {onPin && pinCount > 0 && ( )} {likedCount > 0 && {likedCount} ★} {approvedCount > 0 && {approvedCount} ✓} {changesCount > 0 && {changesCount} ✗}
{/* Masonry grid */}
{files.map((f, i) => { const status = approvals && approvals[f.filename] ? approvals[f.filename].status : null; const note = approvals && approvals[f.filename] ? approvals[f.filename].note : ''; const liked = feedback && feedback.has(f.filename); const pinned = pins && pins.has(f.filename); return (
onOpen(i)} style={{ breakInside: 'avoid', marginBottom: 8, position: 'relative', background: 'var(--bg-2)', borderRadius: cols === 1 ? 0 : 4, overflow: 'hidden', cursor: 'zoom-in', outline: pinned ? '2px solid var(--accent)' : status === 'approved' ? '2px solid #88ddaa' : status === 'changes' ? '2px solid #f4a442' : 'none', }} onMouseEnter={e => { if (cols > 1) e.currentTarget.style.transform = 'translateY(-2px)'; }} onMouseLeave={e => e.currentTarget.style.transform = 'none'}> {/* Status badge */} {status && (
{status === 'approved' ? '✓ Approved' : '✗ Changes'}
)} {/* Top-right action buttons: pin + favorite */}
e.stopPropagation()}> {/* Pin button */} {onPin && ( )} {/* Favorite button */} {onFeedback && ( )}
{/* Approval buttons — bottom bar */} {onApprove && (
e.stopPropagation()} style={{ position: 'absolute', bottom: 0, left: 0, right: 0, background: 'linear-gradient(to top, rgba(5,5,7,0.85) 60%, transparent)', padding: '20px 8px 8px', display: 'flex', gap: 5, justifyContent: 'center', }}> {/* Per-photo download */} {f.download_url && ( e.stopPropagation()} title="Download" style={{ width: 28, height: 28, borderRadius: 5, flexShrink: 0, background: 'rgba(255,255,255,0.1)', color: 'var(--ink-1)', display: 'flex', alignItems: 'center', justifyContent: 'center', textDecoration: 'none', }}> )}
)} {/* Download only (no approval) */} {!onApprove && f.download_url && ( e.stopPropagation()} title="Download original" style={{ position: 'absolute', top: 8, left: 8, width: 30, height: 30, borderRadius: '50%', background: 'rgba(10,10,12,0.72)', border: '1px solid rgba(255,255,255,0.18)', color: 'var(--ink-1)', backdropFilter: 'blur(8px)', display: 'flex', alignItems: 'center', justifyContent: 'center', textDecoration: 'none', }}> )} {/* Filename */}
{!onApprove && f.filename} {note && status === 'changes' && (
"{note}"
)}
); })}
{/* Studio footer */}
Powered by Live Gallery e.target.style.opacity = '1'} onMouseLeave={e => e.target.style.opacity = '0.6'}> Studio login →
{/* Mood Board panel */} {showMoodBoard && onPin && (
setShowMoodBoard(false)} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.75)', zIndex: 500, display: 'flex', alignItems: 'flex-end', justifyContent: 'center', padding: '0 0 0 0', }}>
e.stopPropagation()} style={{ background: 'var(--bg-1)', borderTop: '1px solid var(--line-2)', borderRadius: '14px 14px 0 0', width: '100%', maxWidth: 680, maxHeight: '80vh', display: 'flex', flexDirection: 'column', animation: 'fade-up 220ms ease', }}> {/* Panel header */}
0}/> My Selection
{pinCount === 0 ? 'No photos pinned yet — tap 📌 on photos to add them here' : pinCount >= MAX_PINS ? `${pinCount}/${MAX_PINS} photos selected · selection complete` : `${pinCount}/${MAX_PINS} photos · ${MAX_PINS - pinCount} more to go`}
{/* Progress bar */}
{/* Pinned photos grid */}
{pinCount === 0 ? (
Browse the gallery and tap on photos you love
) : (
{files.filter(f => pins.has(f.filename)).map(f => (
{f.filename}
))}
)}
{/* Panel footer */}
)} {/* Note modal for "Request Changes" */} {noteTarget && (
setNoteTarget(null)} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.7)', zIndex: 500, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20, }}>
e.stopPropagation()} style={{ background: 'var(--bg-1)', border: '1px solid var(--line-2)', borderRadius: 10, padding: 22, width: '100%', maxWidth: 380, }}>
Request Changes
Add a note for the photographer (optional)