fixed like action, better dark mode, bottom navigation working, added taskcollection

This commit is contained in:
2025-09-13 00:43:53 +02:00
parent fc1e64fea3
commit 216ee063ff
24 changed files with 2070 additions and 208 deletions

View File

@@ -5,58 +5,190 @@ import { Button } from '@/components/ui/button';
import { Heart } from 'lucide-react';
import { likePhoto } from '../services/photosApi';
type Photo = { id: number; file_path?: string; thumbnail_path?: string; likes_count?: number; created_at?: string };
type Photo = {
id: number;
file_path?: string;
thumbnail_path?: string;
likes_count?: number;
created_at?: string;
task_id?: number
};
type Task = { id: number; title: string };
export default function PhotoLightbox() {
const nav = useNavigate();
const { state } = useLocation();
const navigate = useNavigate();
const location = useLocation();
const { photoId } = useParams();
const [photo, setPhoto] = React.useState<Photo | null>((state as any)?.photo ?? null);
const [photo, setPhoto] = React.useState<Photo | null>(null);
const [task, setTask] = React.useState<Task | null>(null);
const [taskLoading, setTaskLoading] = React.useState(false);
const [likes, setLikes] = React.useState<number | null>(null);
const [liked, setLiked] = React.useState(false);
React.useEffect(() => {
if (photo) return;
(async () => {
const res = await fetch(`/api/v1/photos/${photoId}`);
if (res.ok) setPhoto(await res.json());
})();
}, [photo, photoId]);
// Extract event slug from URL path
const getEventSlug = () => {
const path = window.location.pathname;
const match = path.match(/^\/e\/([^\/]+)\/photo\/[^\/]+$/);
return match ? match[1] : null;
};
const slug = getEventSlug();
// Load photo if not passed via state
React.useEffect(() => {
if (photo && likes === null) setLikes(photo.likes_count ?? 0);
}, [photo, likes]);
const statePhoto = (location.state as any)?.photo;
if (statePhoto) {
setPhoto(statePhoto);
setLikes(statePhoto.likes_count ?? 0);
return;
}
if (!photoId) return;
(async () => {
try {
const res = await fetch(`/api/v1/photos/${photoId}`);
if (res.ok) {
const photoData = await res.json();
setPhoto(photoData);
setLikes(photoData.likes_count ?? 0);
}
} catch (error) {
console.error('Failed to load photo:', error);
}
})();
}, [photoId, location.state]);
// Load task info if photo has task_id and slug is available
React.useEffect(() => {
if (!photo?.task_id || !slug) {
setTask(null);
setTaskLoading(false);
return;
}
const taskId = photo.task_id;
(async () => {
setTaskLoading(true);
try {
const res = await fetch(`/api/v1/events/${slug}/tasks`);
if (res.ok) {
const tasks = await res.json();
const foundTask = tasks.find((t: any) => t.id === taskId);
if (foundTask) {
setTask({
id: foundTask.id,
title: foundTask.title || `Aufgabe ${taskId}`
});
} else {
setTask({
id: taskId,
title: `Unbekannte Aufgabe ${taskId}`
});
}
} else {
setTask({
id: taskId,
title: `Unbekannte Aufgabe ${taskId}`
});
}
} catch (error) {
console.error('Failed to load task:', error);
setTask({
id: taskId,
title: `Unbekannte Aufgabe ${taskId}`
});
} finally {
setTaskLoading(false);
}
})();
}, [photo?.task_id, slug]);
async function onLike() {
if (liked || !photo) return;
setLiked(true);
const c = await likePhoto(photo.id);
setLikes(c);
try {
const count = await likePhoto(photo.id);
setLikes(count);
} catch (error) {
console.error('Like failed:', error);
setLiked(false);
}
}
function onOpenChange(open: boolean) {
if (!open) nav(-1);
if (!open) navigate(-1);
}
if (!photo && !photoId) {
return null;
}
return (
<Dialog open onOpenChange={onOpenChange}>
<Dialog open={true} onOpenChange={onOpenChange}>
<DialogContent className="max-w-5xl border-0 bg-black p-0 text-white">
{/* Header with controls */}
<div className="flex items-center justify-between p-2">
<div className="flex items-center gap-2">
<Button variant="secondary" size="sm" onClick={onLike} disabled={liked}>
<Heart className="mr-1 h-4 w-4" /> {likes ?? 0}
<Button
variant="secondary"
size="sm"
onClick={onLike}
disabled={liked || !photo}
>
<Heart className={`mr-1 h-4 w-4 ${liked ? 'fill-red-400 text-red-400' : ''}`} />
{likes ?? 0}
</Button>
</div>
<Button variant="secondary" size="sm" onClick={() => nav(-1)}>Schließen</Button>
<Button variant="secondary" size="sm" onClick={() => navigate(-1)}>
Schließen
</Button>
</div>
<div className="flex items-center justify-center">
{/* Task Info Overlay */}
{task && (
<div className="absolute top-4 left-4 right-4 z-20 bg-black/60 backdrop-blur-sm rounded-xl p-3 border border-white/20 max-w-md">
<div className="text-sm">
<div className="font-semibold mb-1 text-white">Task: {task.title}</div>
{taskLoading && (
<div className="text-xs opacity-70 text-gray-300">Lade Aufgabe...</div>
)}
</div>
</div>
)}
{/* Photo Display */}
<div className="flex items-center justify-center min-h-[60vh] p-4">
{photo ? (
<img src={photo.file_path || photo.thumbnail_path} alt="Foto" className="max-h-[80vh] w-auto" />
<img
src={photo.file_path || photo.thumbnail_path}
alt="Foto"
className="max-h-[80vh] max-w-full object-contain"
onError={(e) => {
console.error('Image load error:', e);
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
) : (
<div className="p-6">Lade</div>
<div className="p-6 text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white mx-auto mb-2"></div>
<div>Lade Foto...</div>
</div>
)}
</div>
{/* Loading state for task */}
{taskLoading && !task && (
<div className="absolute top-4 left-4 right-4 z-20 bg-black/60 backdrop-blur-sm rounded-xl p-3 border border-white/20 max-w-md">
<div className="text-sm text-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mx-auto mb-1"></div>
<div className="text-xs opacity-70">Lade Aufgabe...</div>
</div>
</div>
)}
</DialogContent>
</Dialog>
);