"use client" import { useState, useEffect, useRef, useCallback } from "react" import Image from "next/image" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import Link from "next/link" import { Instagram, ExternalLink, Loader2, DollarSign } from "lucide-react" import { useArtist } from "@/hooks/use-artist-data" import { useIsMobile } from "@/hooks/use-mobile" import { type CarouselApi, Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel" import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area" interface ArtistPortfolioProps { artistId: string } export function ArtistPortfolio({ artistId }: ArtistPortfolioProps) { const [selectedCategory, setSelectedCategory] = useState("All") const [selectedImage, setSelectedImage] = useState(null) const [scrollY, setScrollY] = useState(0) const isMobile = useIsMobile() // carousel indicator state (mobile) const [carouselApi, setCarouselApi] = useState(null) const [carouselCount, setCarouselCount] = useState(0) const [carouselCurrent, setCarouselCurrent] = useState(0) const [showSwipeHint, setShowSwipeHint] = useState(true) const [showFullBio, setShowFullBio] = useState(false) // Fetch artist data from API const { data: artist, isLoading, error } = useArtist(artistId) // keep a reference to the last focused thumbnail so we can return focus on modal close const lastFocusedRef = useRef(null) const closeButtonRef = useRef(null) const touchStartX = useRef(null) useEffect(() => { // Enable parallax only on desktop to avoid jank on mobile if (isMobile) return const handleScroll = () => setScrollY(window.scrollY) window.addEventListener("scroll", handleScroll) return () => window.removeEventListener("scroll", handleScroll) }, [isMobile]) // Fade swipe hint after a short delay useEffect(() => { const t = setTimeout(() => setShowSwipeHint(false), 2500) return () => clearTimeout(t) }, []) // Preserve scroll position when modal opens/closes useEffect(() => { if (!selectedImage) return const y = window.scrollY const { body } = document body.style.position = "fixed" body.style.top = `-${y}px` body.style.left = "0" body.style.right = "0" return () => { const top = body.style.top body.style.position = "" body.style.top = "" body.style.left = "" body.style.right = "" const restoreY = Math.abs(parseInt(top || "0", 10)) window.scrollTo(0, restoreY) } }, [selectedImage]) // Carousel indicators state wiring useEffect(() => { if (!carouselApi) return setCarouselCount(carouselApi.scrollSnapList().length) setCarouselCurrent(carouselApi.selectedScrollSnap()) const onSelect = () => setCarouselCurrent(carouselApi.selectedScrollSnap()) carouselApi.on("select", onSelect) return () => { carouselApi.off("select", onSelect) } }, [carouselApi]) // Derived lists (safe when `artist` is undefined during initial renders) const portfolioImages = artist?.portfolioImages || [] // Get unique categories from tags const allTags = portfolioImages.flatMap(img => img.tags) const categories = ["All", ...Array.from(new Set(allTags))] const filteredPortfolio = selectedCategory === "All" ? portfolioImages : portfolioImages.filter(img => img.tags.includes(selectedCategory)) // keyboard navigation for modal (kept as hooks so they run in same order every render) const goToIndex = useCallback( (index: number) => { const item = filteredPortfolio[index] if (item) setSelectedImage(item.id) }, [filteredPortfolio], ) useEffect(() => { if (!selectedImage) return const handleKey = (e: KeyboardEvent) => { if (e.key === "Escape") { setSelectedImage(null) } else if (e.key === "ArrowRight") { const currentIndex = filteredPortfolio.findIndex((p) => p.id === selectedImage) const nextIndex = (currentIndex + 1) % filteredPortfolio.length goToIndex(nextIndex) } else if (e.key === "ArrowLeft") { const currentIndex = filteredPortfolio.findIndex((p) => p.id === selectedImage) const prevIndex = (currentIndex - 1 + filteredPortfolio.length) % filteredPortfolio.length goToIndex(prevIndex) } } document.addEventListener("keydown", handleKey) // move focus to close button for keyboard users setTimeout(() => closeButtonRef.current?.focus(), 0) return () => { document.removeEventListener("keydown", handleKey) } }, [selectedImage, filteredPortfolio, goToIndex]) const openImageFromElement = (id: string, el: HTMLElement | null) => { if (el) lastFocusedRef.current = el setSelectedImage(id) } const closeModal = () => { setSelectedImage(null) // return focus to last focused thumbnail setTimeout(() => lastFocusedRef.current?.focus(), 0) } const currentIndex = selectedImage ? filteredPortfolio.findIndex((p) => p.id === selectedImage) : -1 const currentItem = selectedImage ? filteredPortfolio.find((p) => p.id === selectedImage) : null // Loading state if (isLoading) { return (
) } // Error state if (error) { return (

Failed to load artist

Please try again later

) } // Artist not found if (!artist) { return (

Artist not found

) } // Get profile image const profileImage = portfolioImages.find(img => img.tags.includes('profile'))?.url || portfolioImages[0]?.url || "/placeholder.svg" const bioText = artist.bio || "" return (
{/* Removed Back to Artists button per request */} {/* Hero Section with Split Screen (Desktop only) */}
{/* Left Side - Artist Image */}
{artist.name}
{artist.isActive ? "Available" : "Unavailable"}
{/* Right Side - Artist Info */}

{artist.name}

{artist.specialties.join(", ")}

{artist.bio}

{artist.instagramHandle && ( )} {artist.hourlyRate && (
Starting at ${artist.hourlyRate}/hr
)}

Specializes in:

{artist.specialties.map((style) => ( {style} ))}
{/* Curved Border */}
{/* Hero Section - Mobile stacked */}
{artist.name}
{artist.isActive ? "Available" : "Unavailable"}

{artist.name}

{artist.specialties.join(", ")}

{showFullBio ? bioText : bioText.slice(0, 180)}{bioText.length > 180 && !showFullBio ? "…" : ""}

{bioText.length > 180 && ( )}
{/* Portfolio Section with Split Screen Layout (Desktop only) */}
{/* Left Side - Portfolio Grid */}
{filteredPortfolio.length === 0 ? (

No portfolio images available

) : (
{filteredPortfolio.map((item) => (
{ openImageFromElement(item.id, (e.currentTarget as HTMLElement) || null) }} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault() openImageFromElement(item.id, e.currentTarget as HTMLElement) } }} >
{item.caption
{item.caption &&

{item.caption}

}
))}
)}
{/* Right Side - Sticky Header and Info */}

Featured Work

{filteredPortfolio.length}

Explore the portfolio of {artist.name} showcasing their expertise in{" "} {artist.specialties.join(", ")}. Each piece represents a unique collaboration between artist and client.

{/* Category Filter */} {categories.length > 1 && (

Filter by Style

{categories.map((category) => { const count = category === "All" ? portfolioImages.length : portfolioImages.filter(img => img.tags.includes(category)).length return ( ) })}
)} {/* Quick Stats */}
{portfolioImages.length}
Pieces
{artist.isActive ? "Active" : "Inactive"}
Status
{/* Mobile Portfolio: Carousel + Filters (simplified) */}
{/* Removed mobile category filters for simplicity */} {/* Carousel only */}
{filteredPortfolio.length === 0 ? (

No portfolio images available

) : (
{filteredPortfolio.map((item) => (
{item.caption
) )}
{filteredPortfolio.length} pieces
{/* Swipe hint */} {showSwipeHint && (
Swipe left or right
)} {/* Dots indicators */}
{Array.from({ length: carouselCount }).map((_, i) => (
)}
{/* Contact Section */}

Ready to Get Started?

Book a consultation with {artist.name} to discuss your next tattoo. We can help plan the design and schedule the session.

{artist.specialties.length}+
Specialties
{portfolioImages.length}
Portfolio Pieces
{artist.hourlyRate ? `$${artist.hourlyRate}` : "Contact"}
Starting Rate
{/* Image Modal / Lightbox */} {selectedImage && currentItem && (
closeModal()} >
e.stopPropagation()} onTouchStart={(e) => { touchStartX.current = e.touches[0].clientX }} onTouchEnd={(e) => { if (touchStartX.current == null) return const dx = e.changedTouches[0].clientX - touchStartX.current const threshold = 40 if (Math.abs(dx) > threshold) { if (dx < 0) { const next = (currentIndex + 1) % filteredPortfolio.length goToIndex(next) } else { const prev = (currentIndex - 1 + filteredPortfolio.length) % filteredPortfolio.length goToIndex(prev) } } touchStartX.current = null }} > {/* Prev */}
{currentItem.caption
{/* Next */}
)}
) }