"use client"; import { useState, useEffect, useRef, useMemo } from "react"; import Image from "next/image"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Switch } from "@/components/ui/switch"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Plus, Trash2, ImageIcon, FolderPlus, Lock, Video, Star, ChevronUp, ChevronDown, Eye, EyeOff, } from "lucide-react"; import { loadGallery, createAlbum, addPhoto, addVideo, deletePhoto, deleteVideo, deleteAlbum, setAlbumPublic, togglePhotoOnProfile, toggleVideoOnProfile, reorderProfileMedia, type GalleryAlbum, type GalleryPhoto, type GalleryVideo, } from "@/lib/gallery-store"; import { MAX_PHOTOS_PER_USER, MAX_VIDEOS_PER_USER, MAX_ALBUMS_PER_USER, MAX_PHOTO_SIZE_MB, MAX_VIDEO_DURATION_SEC, MAX_PROFILE_MEDIA_SLOTS, } from "@/lib/limits"; import { getProfileMediaItems, countProfilePinned } from "@/lib/profile-media"; import { PhotoCropModal } from "@/components/PhotoCropModal"; import { useAuth } from "@/lib/auth/context"; import { useAccess } from "@/components/Providers"; import { GuestUpgradeBanner } from "@/components/GuestUpgradeBanner"; import { cn } from "@/lib/utils"; interface MediaGalleryManagerProps { onVerifyClick?: () => void; } export function MediaGalleryManager({ onVerifyClick }: MediaGalleryManagerProps) { const { user } = useAuth(); const { capabilities, blurExplicit } = useAccess(); const [albums, setAlbums] = useState([]); const [photos, setPhotos] = useState([]); const [videos, setVideos] = useState([]); const [activeAlbum, setActiveAlbum] = useState("main"); const [newAlbumName, setNewAlbumName] = useState(""); const [showNewAlbum, setShowNewAlbum] = useState(false); const [message, setMessage] = useState(""); const [loading, setLoading] = useState(true); const [cropFile, setCropFile] = useState(null); const [cropOpen, setCropOpen] = useState(false); const photoRef = useRef(null); const videoRef = useRef(null); useEffect(() => { let cancelled = false; (async () => { const data = await loadGallery(user?.id); if (!cancelled) { setAlbums(data.albums); setPhotos(data.photos); setVideos(data.videos); setLoading(false); } })(); return () => { cancelled = true; }; }, [user?.id]); const refresh = async () => { const data = await loadGallery(user?.id); setAlbums(data.albums); setPhotos(data.photos); setVideos(data.videos); setLoading(false); }; const activeAlbumMeta = albums.find((a) => a.id === activeAlbum); const albumPhotos = photos.filter((p) => activeAlbum === "main" ? !p.album_id || p.album_id === "main" : p.album_id === activeAlbum ); const albumVideos = videos.filter((v) => activeAlbum === "main" ? !v.album_id || v.album_id === "main" : v.album_id === activeAlbum ); const profileItems = useMemo( () => getProfileMediaItems(photos, videos), [photos, videos] ); const pinnedCount = countProfilePinned(photos, videos); const handlePhotoPick = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (file.size > MAX_PHOTO_SIZE_MB * 1024 * 1024) { setMessage(`Max file size is ${MAX_PHOTO_SIZE_MB}MB.`); return; } setCropFile(file); setCropOpen(true); if (photoRef.current) photoRef.current.value = ""; }; const handleCropConfirm = async (file: File) => { const { error } = await addPhoto(file, activeAlbum, user?.id); if (error) setMessage(error); else { setMessage("Photo added!"); await refresh(); } }; const handleVideoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const { error } = await addVideo(file, activeAlbum, user?.id); if (error) setMessage(error); else { setMessage("Video added!"); await refresh(); } if (videoRef.current) videoRef.current.value = ""; }; const handleCreateAlbum = async () => { const { album, error } = await createAlbum(newAlbumName, user?.id); if (error) setMessage(error); else { setNewAlbumName(""); setShowNewAlbum(false); if (album) setActiveAlbum(album.id); await refresh(); } }; const moveProfileItem = async (index: number, dir: -1 | 1) => { const next = [...profileItems]; const target = index + dir; if (target < 0 || target >= next.length) return; [next[index], next[target]] = [next[target], next[index]]; await reorderProfileMedia( next.map((m) => ({ id: m.id, type: m.type })), user?.id ); await refresh(); }; const handleToggleProfile = async ( id: string, type: "photo" | "video", currentlyOn: boolean ) => { const { error } = type === "photo" ? await togglePhotoOnProfile(id, !currentlyOn, user?.id) : await toggleVideoOnProfile(id, !currentlyOn, user?.id); if (error) setMessage(error); else await refresh(); }; if (!capabilities.canUploadPhotos) { return ; } if (loading) { return

Loading gallery...

; } return ( <>
Photos & Videos {photos.length}/{MAX_PHOTOS_PER_USER} · {videos.length}/{MAX_VIDEOS_PER_USER} vid
{blurExplicit && ( )}
{/* Profile preview strip */}
{pinnedCount}/{MAX_PROFILE_MEDIA_SLOTS} slots

Tap ★ to pin. Reorder with arrows. Others see these first.

{profileItems.map((item, i) => (
{item.type === "video" ? (
) : ( )}
))} {profileItems.length === 0 && (

Add media below — first 5 auto-pin.

)}
{albums.map((a) => ( {a.name} {!a.is_default && ( )} ))} {albums.length < MAX_ALBUMS_PER_USER && ( )}
{activeAlbumMeta && !activeAlbumMeta.is_default && (
{activeAlbumMeta.is_public ? ( ) : ( )} Album visible to others
setAlbumPublic(activeAlbum, v, user?.id).then(refresh) } />
)} {showNewAlbum && (
setNewAlbumName(e.target.value)} className="flex-1" />
)}
{albumPhotos.map((photo) => { const onProfile = photo.show_on_profile; return (
); })} {albumVideos.map((video) => { const onProfile = video.show_on_profile; return (
); })} {photos.length < MAX_PHOTOS_PER_USER && ( )} {videos.length < MAX_VIDEOS_PER_USER && ( )}
{message && (

{message}

)}

{MAX_PHOTOS_PER_USER} photos · {MAX_VIDEOS_PER_USER} videos (≤{MAX_VIDEO_DURATION_SEC}s) ·{" "} {MAX_ALBUMS_PER_USER} albums. Photos auto-optimized on upload. Crop before saving.

{ setCropOpen(false); setCropFile(null); }} onConfirm={handleCropConfirm} /> ); } /** @deprecated */ export function PhotoGalleryManager(props: MediaGalleryManagerProps) { return ; }