mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 17:31:14 -05:00
feat: Implement complete plan for "coming soon" placeholders
This commit is contained in:
249
src/components/profile/RideCreditsManager.tsx
Normal file
249
src/components/profile/RideCreditsManager.tsx
Normal file
@@ -0,0 +1,249 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Plus, LayoutGrid, List } from 'lucide-react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { toast } from 'sonner';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { AddRideCreditDialog } from './AddRideCreditDialog';
|
||||
import { RideCreditCard } from './RideCreditCard';
|
||||
import { UserRideCredit } from '@/types/database';
|
||||
|
||||
interface RideCreditsManagerProps {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
|
||||
const [credits, setCredits] = useState<UserRideCredit[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
||||
const [sortBy, setSortBy] = useState<'date' | 'count' | 'name'>('date');
|
||||
const [filterCategory, setFilterCategory] = useState<string>('all');
|
||||
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCredits();
|
||||
}, [userId, sortBy, filterCategory]);
|
||||
|
||||
const fetchCredits = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
let query = supabase
|
||||
.from('user_ride_credits')
|
||||
.select(`
|
||||
*,
|
||||
rides:ride_id (
|
||||
id,
|
||||
name,
|
||||
slug,
|
||||
category,
|
||||
card_image_url,
|
||||
parks:park_id (
|
||||
id,
|
||||
name,
|
||||
slug
|
||||
)
|
||||
)
|
||||
`)
|
||||
.eq('user_id', userId);
|
||||
|
||||
if (filterCategory !== 'all') {
|
||||
query = query.eq('rides.category', filterCategory);
|
||||
}
|
||||
|
||||
if (sortBy === 'date') {
|
||||
query = query.order('first_ride_date', { ascending: false, nullsFirst: false });
|
||||
} else if (sortBy === 'count') {
|
||||
query = query.order('ride_count', { ascending: false });
|
||||
} else if (sortBy === 'name') {
|
||||
query = query.order('rides(name)', { ascending: true });
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
if (error) throw error;
|
||||
|
||||
// Type assertion since the query returns properly structured data
|
||||
setCredits((data || []) as UserRideCredit[]);
|
||||
} catch (error) {
|
||||
console.error('Error fetching ride credits:', error);
|
||||
toast.error(getErrorMessage(error));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreditAdded = () => {
|
||||
fetchCredits();
|
||||
setIsAddDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleCreditUpdated = () => {
|
||||
fetchCredits();
|
||||
};
|
||||
|
||||
const handleCreditDeleted = async (creditId: string) => {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('user_ride_credits')
|
||||
.delete()
|
||||
.eq('id', creditId)
|
||||
.eq('user_id', userId);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast.success('Ride credit removed');
|
||||
fetchCredits();
|
||||
} catch (error) {
|
||||
console.error('Error deleting credit:', error);
|
||||
toast.error(getErrorMessage(error));
|
||||
}
|
||||
};
|
||||
|
||||
const stats = {
|
||||
totalRides: credits.reduce((sum, c) => sum + c.ride_count, 0),
|
||||
uniqueCredits: credits.length,
|
||||
parksVisited: new Set(credits.map(c => c.rides?.parks?.id).filter(Boolean)).size,
|
||||
coasters: credits.filter(c => c.rides?.category === 'roller_coaster').length
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{Array.from({ length: 6 }, (_, i) => (
|
||||
<Card key={i} className="animate-pulse">
|
||||
<CardContent className="p-6">
|
||||
<div className="h-32 bg-muted rounded"></div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Statistics */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">{stats.totalRides}</div>
|
||||
<div className="text-sm text-muted-foreground">Total Rides</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">{stats.uniqueCredits}</div>
|
||||
<div className="text-sm text-muted-foreground">Unique Credits</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">{stats.parksVisited}</div>
|
||||
<div className="text-sm text-muted-foreground">Parks Visited</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">{stats.coasters}</div>
|
||||
<div className="text-sm text-muted-foreground">Coasters</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex flex-wrap gap-4 items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={() => setIsAddDialogOpen(true)}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Credit
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Select value={filterCategory} onValueChange={setFilterCategory}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Filter by type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Rides</SelectItem>
|
||||
<SelectItem value="roller_coaster">Roller Coasters</SelectItem>
|
||||
<SelectItem value="flat_ride">Flat Rides</SelectItem>
|
||||
<SelectItem value="water_ride">Water Rides</SelectItem>
|
||||
<SelectItem value="dark_ride">Dark Rides</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={sortBy} onValueChange={(value) => setSortBy(value as typeof sortBy)}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Sort by" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="date">Most Recent</SelectItem>
|
||||
<SelectItem value="count">Most Ridden</SelectItem>
|
||||
<SelectItem value="name">Ride Name</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="flex border rounded-md">
|
||||
<Button
|
||||
variant={viewMode === 'grid' ? 'default' : 'ghost'}
|
||||
size="icon"
|
||||
onClick={() => setViewMode('grid')}
|
||||
>
|
||||
<LayoutGrid className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={viewMode === 'list' ? 'default' : 'ghost'}
|
||||
size="icon"
|
||||
onClick={() => setViewMode('list')}
|
||||
>
|
||||
<List className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Credits Display */}
|
||||
{credits.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="py-12">
|
||||
<div className="text-center">
|
||||
<h3 className="text-xl font-semibold mb-2">No Ride Credits Yet</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Start tracking your ride experiences
|
||||
</p>
|
||||
<Button onClick={() => setIsAddDialogOpen(true)}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Your First Credit
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className={viewMode === 'grid'
|
||||
? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4'
|
||||
: 'space-y-4'
|
||||
}>
|
||||
{credits.map((credit) => (
|
||||
<RideCreditCard
|
||||
key={credit.id}
|
||||
credit={credit}
|
||||
viewMode={viewMode}
|
||||
onUpdate={handleCreditUpdated}
|
||||
onDelete={() => handleCreditDeleted(credit.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Credit Dialog */}
|
||||
<AddRideCreditDialog
|
||||
userId={userId}
|
||||
open={isAddDialogOpen}
|
||||
onOpenChange={setIsAddDialogOpen}
|
||||
onSuccess={handleCreditAdded}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user