mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 23:11:12 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
410
src-old/components/upload/PhotoManagementDialog.tsx
Normal file
410
src-old/components/upload/PhotoManagementDialog.tsx
Normal file
@@ -0,0 +1,410 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Trash2, Pencil } from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
|
||||
interface Photo {
|
||||
id: string;
|
||||
cloudflare_image_id: string;
|
||||
cloudflare_image_url: string;
|
||||
title: string | null;
|
||||
caption: string | null;
|
||||
order_index: number;
|
||||
is_featured: boolean;
|
||||
}
|
||||
|
||||
interface PhotoManagementDialogProps {
|
||||
entityId: string;
|
||||
entityType: string;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onUpdate?: () => void;
|
||||
}
|
||||
|
||||
export function PhotoManagementDialog({
|
||||
entityId,
|
||||
entityType,
|
||||
open,
|
||||
onOpenChange,
|
||||
onUpdate,
|
||||
}: PhotoManagementDialogProps) {
|
||||
const [photos, setPhotos] = useState<Photo[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [editingPhoto, setEditingPhoto] = useState<Photo | null>(null);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [photoToDelete, setPhotoToDelete] = useState<Photo | null>(null);
|
||||
const [deleteReason, setDeleteReason] = useState('');
|
||||
const { toast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
fetchPhotos();
|
||||
}
|
||||
}, [open, entityId, entityType]);
|
||||
|
||||
const fetchPhotos = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('photos')
|
||||
.select('id, cloudflare_image_id, cloudflare_image_url, title, caption, order_index, is_featured')
|
||||
.eq('entity_type', entityType)
|
||||
.eq('entity_id', entityId)
|
||||
.order('order_index', { ascending: true });
|
||||
|
||||
if (error) throw error;
|
||||
setPhotos((data || []).map(p => ({
|
||||
...p,
|
||||
order_index: p.order_index ?? 0,
|
||||
is_featured: p.is_featured ?? false
|
||||
})) as Photo[]);
|
||||
} catch (error: unknown) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: getErrorMessage(error),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleDeleteClick = (photo: Photo) => {
|
||||
setPhotoToDelete(photo);
|
||||
setDeleteReason('');
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const requestPhotoDelete = async () => {
|
||||
if (!photoToDelete || !deleteReason.trim()) return;
|
||||
|
||||
try {
|
||||
// Get current user
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('Not authenticated');
|
||||
|
||||
// Fetch entity name from database based on entity type
|
||||
let entityName = 'Unknown';
|
||||
|
||||
try {
|
||||
if (entityType === 'park') {
|
||||
const { data } = await supabase.from('parks').select('name').eq('id', entityId).single();
|
||||
if (data?.name) entityName = data.name;
|
||||
} else if (entityType === 'ride') {
|
||||
const { data } = await supabase.from('rides').select('name').eq('id', entityId).single();
|
||||
if (data?.name) entityName = data.name;
|
||||
} else if (entityType === 'ride_model') {
|
||||
const { data } = await supabase.from('ride_models').select('name').eq('id', entityId).single();
|
||||
if (data?.name) entityName = data.name;
|
||||
} else if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(entityType)) {
|
||||
const { data } = await supabase.from('companies').select('name').eq('id', entityId).single();
|
||||
if (data?.name) entityName = data.name;
|
||||
}
|
||||
} catch {
|
||||
// Failed to fetch entity name - use default
|
||||
}
|
||||
|
||||
// Create content submission
|
||||
const { data: submission, error: submissionError } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert([{
|
||||
user_id: user.id,
|
||||
submission_type: 'photo_delete',
|
||||
content: {
|
||||
action: 'delete',
|
||||
photo_id: photoToDelete.id
|
||||
}
|
||||
}])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (submissionError) throw submissionError;
|
||||
|
||||
// Create submission item with all necessary data
|
||||
const { error: itemError } = await supabase
|
||||
.from('submission_items')
|
||||
.insert({
|
||||
submission_id: submission.id,
|
||||
item_type: 'photo_delete',
|
||||
item_data: {
|
||||
photo_id: photoToDelete.id,
|
||||
cloudflare_image_id: photoToDelete.cloudflare_image_id,
|
||||
entity_type: entityType,
|
||||
entity_id: entityId,
|
||||
entity_name: entityName,
|
||||
cloudflare_image_url: photoToDelete.cloudflare_image_url,
|
||||
title: photoToDelete.title,
|
||||
caption: photoToDelete.caption,
|
||||
deletion_reason: deleteReason
|
||||
},
|
||||
status: 'pending'
|
||||
});
|
||||
|
||||
if (itemError) throw itemError;
|
||||
|
||||
toast({
|
||||
title: 'Delete request submitted',
|
||||
description: 'Your photo deletion request has been submitted for moderation',
|
||||
});
|
||||
setDeleteDialogOpen(false);
|
||||
setPhotoToDelete(null);
|
||||
setDeleteReason('');
|
||||
onOpenChange(false);
|
||||
} catch (error: unknown) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: getErrorMessage(error),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const requestPhotoEdit = async () => {
|
||||
if (!editingPhoto) return;
|
||||
|
||||
try {
|
||||
// Get current user
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('Not authenticated');
|
||||
|
||||
// Get original photo data
|
||||
const originalPhoto = photos.find(p => p.id === editingPhoto.id);
|
||||
if (!originalPhoto) throw new Error('Original photo not found');
|
||||
|
||||
// Create content submission
|
||||
const { data: submission, error: submissionError } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert([{
|
||||
user_id: user.id,
|
||||
submission_type: 'photo_edit',
|
||||
content: {
|
||||
action: 'edit',
|
||||
photo_id: editingPhoto.id
|
||||
}
|
||||
}])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (submissionError) throw submissionError;
|
||||
|
||||
// Create submission item
|
||||
const { error: itemError } = await supabase
|
||||
.from('submission_items')
|
||||
.insert({
|
||||
submission_id: submission.id,
|
||||
item_type: 'photo_edit',
|
||||
item_data: {
|
||||
photo_id: editingPhoto.id,
|
||||
entity_type: entityType,
|
||||
entity_id: entityId,
|
||||
new_caption: editingPhoto.caption,
|
||||
cloudflare_image_url: editingPhoto.cloudflare_image_url,
|
||||
},
|
||||
original_data: {
|
||||
caption: originalPhoto.caption,
|
||||
},
|
||||
status: 'pending'
|
||||
});
|
||||
|
||||
if (itemError) throw itemError;
|
||||
|
||||
setEditingPhoto(null);
|
||||
toast({
|
||||
title: 'Edit request submitted',
|
||||
description: 'Your photo edit has been submitted for moderation',
|
||||
});
|
||||
onOpenChange(false);
|
||||
} catch (error: unknown) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: getErrorMessage(error),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (editingPhoto) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Photo</DialogTitle>
|
||||
<DialogDescription>Update photo caption</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="aspect-video w-full overflow-hidden rounded-lg">
|
||||
<img
|
||||
src={editingPhoto.cloudflare_image_url}
|
||||
alt={editingPhoto.caption || 'Photo'}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="caption">Caption</Label>
|
||||
<Textarea
|
||||
id="caption"
|
||||
value={editingPhoto.caption || ''}
|
||||
onChange={(e) =>
|
||||
setEditingPhoto({ ...editingPhoto, caption: e.target.value })
|
||||
}
|
||||
placeholder="Photo caption"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setEditingPhoto(null)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={requestPhotoEdit}>Submit for Review</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-2xl lg:max-w-4xl max-h-[80vh] overflow-y-auto p-3 sm:p-6">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Manage Photos</DialogTitle>
|
||||
<DialogDescription>
|
||||
Edit or delete photos for this entity
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="animate-pulse">Loading photos...</div>
|
||||
</div>
|
||||
) : photos.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
No photos to manage
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{photos.map((photo, index) => (
|
||||
<Card key={photo.id}>
|
||||
<CardContent className="p-3 sm:p-4">
|
||||
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
|
||||
<div className="w-full aspect-video sm:w-32 sm:h-32 flex-shrink-0 overflow-hidden rounded-lg">
|
||||
<img
|
||||
src={photo.cloudflare_image_url}
|
||||
alt={photo.caption || 'Photo'}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 space-y-2">
|
||||
<div>
|
||||
<p className="text-sm sm:text-base text-foreground">
|
||||
{photo.caption || (
|
||||
<span className="text-muted-foreground italic">No caption</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setEditingPhoto(photo)}
|
||||
className="flex-1 sm:flex-initial"
|
||||
>
|
||||
<Pencil className="w-4 h-4 mr-2" />
|
||||
Request Edit
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => handleDeleteClick(photo)}
|
||||
className="flex-1 sm:flex-initial"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
Request Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Request Photo Deletion</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Please provide a reason for deleting this photo. This request will be reviewed by moderators.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="delete-reason">Reason for deletion</Label>
|
||||
<Textarea
|
||||
id="delete-reason"
|
||||
value={deleteReason}
|
||||
onChange={(e) => setDeleteReason(e.target.value)}
|
||||
placeholder="Please explain why this photo should be deleted..."
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => {
|
||||
setDeleteDialogOpen(false);
|
||||
setPhotoToDelete(null);
|
||||
setDeleteReason('');
|
||||
}}>
|
||||
Cancel
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={requestPhotoDelete}
|
||||
disabled={!deleteReason.trim()}
|
||||
>
|
||||
Submit Request
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user