From 0d4b6b8e1aee6a4e78845734fcf1254e7e855a8c Mon Sep 17 00:00:00 2001 From: Marc Wieland Date: Wed, 30 Apr 2025 09:05:00 +0200 Subject: [PATCH] Added gallery --- src/admin/AdminDashboard.tsx | 12 ++- src/admin/GalleryManager.tsx | 104 ++++++++++++++++++++ src/components/GallerySection.tsx | 154 +++++++++++++++++++++++------- src/main.tsx | 4 + src/pages/GalleryPage.tsx | 98 +++++++++++++++++++ 5 files changed, 335 insertions(+), 37 deletions(-) create mode 100644 src/admin/GalleryManager.tsx create mode 100644 src/pages/GalleryPage.tsx diff --git a/src/admin/AdminDashboard.tsx b/src/admin/AdminDashboard.tsx index 54c62cc3d..add635f58 100644 --- a/src/admin/AdminDashboard.tsx +++ b/src/admin/AdminDashboard.tsx @@ -19,7 +19,6 @@ const AdminDashboard = () => {
- {/* Jeder Benutzer darf News verwalten */} @@ -29,7 +28,6 @@ const AdminDashboard = () => { - {/* Nur Admins sehen diese Card */} {isAdmin && ( @@ -58,6 +56,16 @@ const AdminDashboard = () => { + + {/* 🎉 Neue Galerie-Kachel */} + + + + Galerie verwalten +

Bilder hochladen & löschen

+
+
+
); diff --git a/src/admin/GalleryManager.tsx b/src/admin/GalleryManager.tsx new file mode 100644 index 000000000..105736252 --- /dev/null +++ b/src/admin/GalleryManager.tsx @@ -0,0 +1,104 @@ +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { toast } from "sonner"; + +const apiBase = import.meta.env.VITE_API_URL; + +type GalleryImage = { + id: number; + image_url: string; +}; + +const GalleryManager = () => { + const [images, setImages] = useState([]); + const [uploading, setUploading] = useState(false); + + const fetchImages = async () => { + try { + const res = await fetch(`${apiBase}/api/gallery`); + const data = await res.json(); + setImages(data); + } catch (err) { + console.error("Fehler beim Laden der Galerie:", err); + } + }; + + useEffect(() => { + fetchImages(); + }, []); + + const handleUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + const formData = new FormData(); + formData.append("image", file); + + setUploading(true); + try { + const res = await fetch(`${apiBase}/api/gallery`, { + method: "POST", + body: formData, + }); + + if (!res.ok) throw new Error("Fehler beim Hochladen"); + toast.success("Bild erfolgreich hochgeladen!"); + await fetchImages(); + } catch (err) { + toast.error("Fehler beim Hochladen des Bildes"); + } finally { + setUploading(false); + } + }; + + const handleDelete = async (id: number) => { + if (!confirm("Bist du sicher, dass du dieses Bild löschen willst?")) return; + + try { + const res = await fetch(`${apiBase}/api/gallery/${id}`, { + method: "DELETE", + }); + + if (!res.ok) throw new Error("Fehler beim Löschen"); + toast.success("Bild gelöscht"); + await fetchImages(); + } catch (err) { + toast.error("Fehler beim Löschen des Bildes"); + } + }; + + return ( +
+

Galerie verwalten

+ +
+ + {uploading &&

Bild wird hochgeladen...

} +
+ +
+ {images.map((img) => ( + + + Galeriebild + + + + ))} +
+
+ ); +}; + +export default GalleryManager; diff --git a/src/components/GallerySection.tsx b/src/components/GallerySection.tsx index e673ead5a..b2a23e217 100644 --- a/src/components/GallerySection.tsx +++ b/src/components/GallerySection.tsx @@ -1,19 +1,66 @@ - -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; -import { ChevronLeft, ChevronRight, X } from "lucide-react"; +import { X } from "lucide-react"; +import { Link } from "react-router-dom"; + +const apiBase = import.meta.env.VITE_API_URL; + +type GalleryImage = { + id: number; + image_url: string; +}; + +type AnimatedImage = GalleryImage & { fading?: boolean }; const GallerySection = () => { + const [allImages, setAllImages] = useState([]); + const [displayImages, setDisplayImages] = useState([]); const [selectedImage, setSelectedImage] = useState(null); - const images = [ - "https://images.unsplash.com/photo-1553005746-5be7d6d48a81?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", - "https://images.unsplash.com/photo-1588286840104-8957b019727f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", - "https://images.unsplash.com/photo-1599586120429-48281b6f0ece?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", - "https://images.unsplash.com/photo-1592656094267-764a45160876?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", - "https://images.unsplash.com/photo-1547347298-4074fc3086f0?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", - "https://images.unsplash.com/photo-1544213456-e1c259fecc4f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", - ]; + useEffect(() => { + const fetchImages = async () => { + try { + const res = await fetch(`${apiBase}/api/gallery`); + const data = await res.json(); + setAllImages(data); + + const shuffled = [...data].sort(() => 0.5 - Math.random()); + setDisplayImages(shuffled.slice(0, 6)); + } catch (err) { + console.error("Fehler beim Laden der Galerie:", err); + } + }; + + fetchImages(); + }, []); + + // Bild zufällig austauschen mit Animation + useEffect(() => { + const interval = setInterval(() => { + if (allImages.length <= 6) return; + + setDisplayImages((prev) => { + const currentIds = prev.map((img) => img.id); + const remaining = allImages.filter((img) => !currentIds.includes(img.id)); + if (remaining.length === 0) return prev; + + const newImage = remaining[Math.floor(Math.random() * remaining.length)]; + const indexToReplace = Math.floor(Math.random() * prev.length); + + const updated = [...prev]; + updated[indexToReplace] = { ...updated[indexToReplace], fading: true }; + + setTimeout(() => { + updated[indexToReplace] = { ...newImage, fading: false }; + setDisplayImages([...updated]); + }, 400); // passt zur fade-out duration + + return [...updated]; + }); + }, 5000); + + return () => clearInterval(interval); + }, [allImages]); const openLightbox = (image: string) => { setSelectedImage(image); @@ -27,6 +74,33 @@ const GallerySection = () => { return (