Files
project_vollidioten_website/components/GalleryManagementModal.tsx
Lars Behrends d3d7ec46e6 feat: Add DatabaseManager and LinkPlayer components, implement authentication and linking logic
- Created DatabaseManager component for managing database access via phpMyAdmin.
- Developed LinkPlayer component to link Discord accounts with game characters, including user authentication and error handling.
- Added mock data files for players, organizations, and projects to handle backend unavailability.
- Implemented AuthService for managing user authentication and session checks.
- Created DatabaseService to fetch and manage player, organization, and project data with fallback to mock data.
- Added HTML page for handling authentication unavailability.
- Developed a test script for validating Docker setup and required files.
2025-12-28 16:46:04 +01:00

206 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { Icons } from './IconSet';
interface GalleryManagementModalProps {
isOpen: boolean;
onClose: () => void;
projectId: string;
onUpdate: () => void;
}
const GalleryManagementModal: React.FC<GalleryManagementModalProps> = ({
isOpen,
onClose,
projectId,
onUpdate
}) => {
const [images, setImages] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [imageUrl, setImageUrl] = useState('');
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (isOpen && projectId) {
loadGallery();
}
}, [isOpen, projectId]);
const loadGallery = async () => {
try {
setLoading(true);
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${projectId}/gallery`);
if (response.ok) {
const data = await response.json();
setImages(data);
} else {
setError('Fehler beim Laden der Galerie');
}
} catch (err) {
console.error('Error loading gallery:', err);
setError('Netzwerkfehler');
} finally {
setLoading(false);
}
};
const addImage = async () => {
if (!imageUrl.trim()) return;
try {
setLoading(true);
setError(null);
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${projectId}/gallery`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ imageUrl: imageUrl.trim() })
});
if (response.ok) {
await loadGallery();
setImageUrl('');
onUpdate();
} else {
const errorData = await response.json();
setError(errorData.error || 'Fehler beim Hinzufügen');
}
} catch (err) {
console.error('Error adding image:', err);
setError('Netzwerkfehler');
} finally {
setLoading(false);
}
};
const removeImage = async (index: number) => {
try {
setLoading(true);
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${projectId}/gallery/${index}`, {
method: 'DELETE',
credentials: 'include'
});
if (response.ok) {
await loadGallery();
onUpdate();
} else {
setError('Fehler beim Löschen');
}
} catch (err) {
console.error('Error removing image:', err);
setError('Netzwerkfehler');
} finally {
setLoading(false);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-in fade-in duration-200">
<div className="bg-surface border border-border rounded-xl w-full max-w-4xl shadow-2xl flex flex-col max-h-[90vh]">
<div className="p-4 border-b border-border flex justify-between items-center bg-surfaceHighlight/20">
<h3 className="font-bold text-textMain flex items-center gap-2">
<Icons.Layers className="w-5 h-5" />
Galerie verwalten
</h3>
<button onClick={onClose} className="text-textMuted hover:text-white transition-colors text-xl leading-none">&times;</button>
</div>
<div className="p-6 flex-1 overflow-y-auto">
{error && (
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4 mb-6">
<p className="text-red-400">{error}</p>
</div>
)}
{/* Add Image */}
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4 mb-6">
<h4 className="font-semibold text-textMain mb-4">Bild hinzufügen</h4>
<div className="flex gap-2">
<input
type="url"
value={imageUrl}
onChange={(e) => setImageUrl(e.target.value)}
placeholder="Bild-URL eingeben..."
className="flex-1 bg-[#0b0b0d] border border-border rounded p-2 text-sm"
/>
<button
onClick={addImage}
disabled={!imageUrl.trim() || loading}
className="bg-accentInfo hover:bg-accentInfo/90 disabled:opacity-50 text-white px-4 py-2 rounded text-sm font-medium"
>
Hinzufügen
</button>
</div>
<p className="text-xs text-textMuted mt-2">
Geben Sie eine direkte URL zu einem Bild ein (z.B. von Imgur, Discord, etc.)
</p>
</div>
{/* Gallery Grid */}
<div className="mb-4">
<h4 className="font-semibold text-textMain mb-4">
Galerie-Bilder ({images.length})
</h4>
{loading ? (
<div className="flex justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
</div>
) : images.length === 0 ? (
<div className="text-center py-8 text-textMuted">
<p>Noch keine Bilder in der Galerie.</p>
<p className="text-sm mt-2">Fügen Sie Bild-URLs hinzu, um Ihre Galerie zu füllen.</p>
</div>
) : (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{images.map((imageUrl, index) => (
<div key={index} className="relative group">
<div className="aspect-square rounded-lg overflow-hidden border border-border bg-surfaceHighlight/30">
<img
src={imageUrl}
alt={`Galerie ${index + 1}`}
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = 'https://via.placeholder.com/200x200/374151/6b7280?text=Bild+fehlerhaft';
}}
/>
</div>
{/* Delete Button */}
<button
onClick={() => removeImage(index)}
disabled={loading}
className="absolute top-2 right-2 bg-red-500 hover:bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity"
title="Bild entfernen"
>
×
</button>
{/* Overlay on hover */}
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors rounded-lg pointer-events-none" />
</div>
))}
</div>
)}
</div>
</div>
<div className="p-4 border-t border-border flex justify-end">
<button
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-textMuted hover:text-white transition-colors"
>
Schließen
</button>
</div>
</div>
</div>
);
};
export default GalleryManagementModal;