99 lines
3.7 KiB
TypeScript
99 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Image from "next/image";
|
|
import { Images, Eye, Calendar } from "lucide-react";
|
|
|
|
interface Photo {
|
|
id: string;
|
|
filename: string;
|
|
originalName: string;
|
|
url: string;
|
|
views: number;
|
|
createdAt: string;
|
|
}
|
|
|
|
interface GalleryProps {
|
|
initialPhotos: Photo[];
|
|
}
|
|
|
|
export function Gallery({ initialPhotos }: GalleryProps) {
|
|
const [photos, setPhotos] = useState(initialPhotos);
|
|
|
|
useEffect(() => {
|
|
const handler = (e: Event) => {
|
|
const customEvent = e as CustomEvent<Photo>;
|
|
setPhotos((prev) => [customEvent.detail, ...prev].slice(0, 12));
|
|
};
|
|
window.addEventListener("photo-uploaded", handler);
|
|
return () => window.removeEventListener("photo-uploaded", handler);
|
|
}, []);
|
|
|
|
return (
|
|
<section id="gallery" className="px-6 py-16">
|
|
<div className="mx-auto max-w-7xl">
|
|
<div className="mb-10 text-center">
|
|
<div className="mb-3 inline-flex items-center gap-2 text-indigo-400">
|
|
<Images className="h-5 w-5" />
|
|
<span className="text-sm font-medium uppercase tracking-wider">
|
|
Галерея
|
|
</span>
|
|
</div>
|
|
<h2 className="text-3xl font-bold md:text-4xl">
|
|
Недавние загрузки
|
|
</h2>
|
|
<p className="mt-3 text-muted">
|
|
Последние фотографии, загруженные на сервис
|
|
</p>
|
|
</div>
|
|
|
|
{photos.length === 0 ? (
|
|
<div className="glass mx-auto max-w-md rounded-2xl p-12 text-center">
|
|
<Images className="mx-auto h-12 w-12 text-muted/50" />
|
|
<p className="mt-4 text-muted">
|
|
Пока нет загруженных фото. Будьте первым!
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
|
|
{photos.map((photo, index) => (
|
|
<a
|
|
key={photo.id}
|
|
href={photo.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="group relative aspect-square overflow-hidden rounded-2xl bg-surface transition-all duration-300 hover:scale-[1.02] hover:shadow-xl hover:shadow-indigo-500/10"
|
|
style={{ animationDelay: `${index * 50}ms` }}
|
|
>
|
|
<Image
|
|
src={photo.url}
|
|
alt={photo.originalName}
|
|
fill
|
|
className="object-cover transition-transform duration-500 group-hover:scale-110"
|
|
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
|
<div className="absolute right-0 bottom-0 left-0 translate-y-full p-4 transition-transform duration-300 group-hover:translate-y-0">
|
|
<p className="truncate text-sm font-medium text-white">
|
|
{photo.originalName}
|
|
</p>
|
|
<div className="mt-1 flex items-center gap-3 text-xs text-white/70">
|
|
<span className="flex items-center gap-1">
|
|
<Eye className="h-3 w-3" />
|
|
{photo.views}
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<Calendar className="h-3 w-3" />
|
|
{new Date(photo.createdAt).toLocaleDateString("ru-RU")}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|