mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 16:11:12 -05:00
Add photo gallery and upload
This commit is contained in:
166
src/components/rides/RidePhotoGallery.tsx
Normal file
166
src/components/rides/RidePhotoGallery.tsx
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Camera, Upload, LogIn } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { PhotoSubmissionUpload } from '@/components/upload/PhotoSubmissionUpload';
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
|
||||||
|
interface RidePhoto {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
caption?: string;
|
||||||
|
title?: string;
|
||||||
|
user_id: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RidePhotoGalleryProps {
|
||||||
|
rideId: string;
|
||||||
|
rideName: string;
|
||||||
|
parkId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RidePhotoGallery({ rideId, rideName, parkId }: RidePhotoGalleryProps) {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [photos, setPhotos] = useState<RidePhoto[]>([]);
|
||||||
|
const [showUpload, setShowUpload] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPhotos();
|
||||||
|
}, [rideId]);
|
||||||
|
|
||||||
|
const fetchPhotos = async () => {
|
||||||
|
try {
|
||||||
|
// For now, we'll show a placeholder since approved photos would come from content_submissions
|
||||||
|
// In a real implementation, you'd fetch approved photo submissions
|
||||||
|
setPhotos([]); // Placeholder - no photos yet
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching photos:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUploadClick = () => {
|
||||||
|
if (!user) {
|
||||||
|
navigate('/auth');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setShowUpload(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmissionComplete = () => {
|
||||||
|
setShowUpload(false);
|
||||||
|
fetchPhotos(); // Refresh photos after submission
|
||||||
|
};
|
||||||
|
|
||||||
|
if (showUpload) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-lg font-semibold">Upload Photos for {rideName}</h3>
|
||||||
|
<Button variant="ghost" onClick={() => setShowUpload(false)}>
|
||||||
|
Back to Gallery
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<PhotoSubmissionUpload
|
||||||
|
rideId={rideId}
|
||||||
|
parkId={parkId}
|
||||||
|
onSubmissionComplete={handleSubmissionComplete}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<div className="animate-pulse flex items-center gap-3">
|
||||||
|
<Camera className="w-8 h-8 text-muted-foreground" />
|
||||||
|
<span className="text-muted-foreground">Loading photos...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Upload Button */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold">Photo Gallery</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Share your photos of {rideName}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleUploadClick} className="gap-2">
|
||||||
|
{user ? (
|
||||||
|
<>
|
||||||
|
<Upload className="w-4 h-4" />
|
||||||
|
Upload Photos
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LogIn className="w-4 h-4" />
|
||||||
|
Sign in to Upload
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Photo Grid */}
|
||||||
|
{photos.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||||
|
{photos.map((photo) => (
|
||||||
|
<Card key={photo.id} className="overflow-hidden">
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<img
|
||||||
|
src={photo.url}
|
||||||
|
alt={photo.title || photo.caption || 'Ride photo'}
|
||||||
|
className="w-full h-48 object-cover hover:scale-105 transition-transform cursor-pointer"
|
||||||
|
/>
|
||||||
|
{(photo.title || photo.caption) && (
|
||||||
|
<div className="p-3">
|
||||||
|
{photo.title && (
|
||||||
|
<h4 className="font-medium text-sm truncate">{photo.title}</h4>
|
||||||
|
)}
|
||||||
|
{photo.caption && (
|
||||||
|
<p className="text-xs text-muted-foreground truncate mt-1">
|
||||||
|
{photo.caption}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<Camera className="w-16 h-16 text-muted-foreground mx-auto mb-4" />
|
||||||
|
<h3 className="text-xl font-semibold mb-2">No Photos Yet</h3>
|
||||||
|
<p className="text-muted-foreground mb-4">
|
||||||
|
Be the first to share photos of {rideName}!
|
||||||
|
</p>
|
||||||
|
<Button onClick={handleUploadClick} className="gap-2">
|
||||||
|
{user ? (
|
||||||
|
<>
|
||||||
|
<Upload className="w-4 h-4" />
|
||||||
|
Upload First Photo
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LogIn className="w-4 h-4" />
|
||||||
|
Sign in to Upload
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { ReviewsSection } from '@/components/reviews/ReviewsSection';
|
import { ReviewsSection } from '@/components/reviews/ReviewsSection';
|
||||||
import { MeasurementDisplay } from '@/components/ui/measurement-display';
|
import { MeasurementDisplay } from '@/components/ui/measurement-display';
|
||||||
|
import { RidePhotoGallery } from '@/components/rides/RidePhotoGallery';
|
||||||
import { Ride } from '@/types/database';
|
import { Ride } from '@/types/database';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ export default function RideDetail() {
|
|||||||
.from('rides')
|
.from('rides')
|
||||||
.select(`
|
.select(`
|
||||||
*,
|
*,
|
||||||
park:parks!inner(name, slug, location:locations(*)),
|
park:parks!inner(id, name, slug, location:locations(*)),
|
||||||
manufacturer:companies!rides_manufacturer_id_fkey(*),
|
manufacturer:companies!rides_manufacturer_id_fkey(*),
|
||||||
designer:companies!rides_designer_id_fkey(*)
|
designer:companies!rides_designer_id_fkey(*)
|
||||||
`)
|
`)
|
||||||
@@ -567,13 +568,10 @@ export default function RideDetail() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="photos" className="mt-6">
|
<TabsContent value="photos" className="mt-6">
|
||||||
<div className="text-center py-12">
|
<RidePhotoGallery
|
||||||
<Camera className="w-16 h-16 text-muted-foreground mx-auto mb-4" />
|
rideId={ride.id}
|
||||||
<h3 className="text-xl font-semibold mb-2">Photo Gallery Coming Soon</h3>
|
rideName={ride.name}
|
||||||
<p className="text-muted-foreground">
|
/>
|
||||||
Photo galleries and media uploads will be available soon
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
Reference in New Issue
Block a user