Refactor: Implement optimistic updates

This commit is contained in:
gpt-engineer-app[bot]
2025-10-16 15:43:52 +00:00
parent 0d9926a5ae
commit b95b06a7f2
4 changed files with 96 additions and 21 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useMemo } 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';
@@ -139,16 +139,76 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
}
};
const handleCreditAdded = () => {
fetchCredits();
setIsAddDialogOpen(false);
const handleCreditAdded = async (newCreditId: string) => {
try {
// Fetch just the new credit with all necessary joins
const { data, error } = await supabase
.from('user_ride_credits')
.select(`
*,
rides:ride_id (
id,
name,
slug,
category,
status,
coaster_type,
seating_type,
intensity_level,
max_speed_kmh,
max_height_meters,
length_meters,
inversions,
card_image_url,
parks:park_id (
id,
name,
slug,
park_type,
locations:location_id (
country,
state_province,
city
)
),
manufacturer:manufacturer_id (
id,
name
),
designer:designer_id (
id,
name
)
)
`)
.eq('id', newCreditId)
.single();
if (error) throw error;
// Add to existing array
setCredits(prev => [...prev, data as any]);
setIsAddDialogOpen(false);
} catch (error) {
console.error('Error fetching new credit:', error);
toast.error(getErrorMessage(error));
}
};
const handleCreditUpdated = () => {
fetchCredits();
const handleCreditUpdated = (creditId: string, updates: Partial<UserRideCredit>) => {
// Optimistic update - update only the affected credit
setCredits(prev => prev.map(c =>
c.id === creditId ? { ...c, ...updates } : c
));
};
const handleCreditDeleted = async (creditId: string) => {
// Store for rollback
const deletedCredit = credits.find(c => c.id === creditId);
// Optimistic removal
setCredits(prev => prev.filter(c => c.id !== creditId));
try {
const { error } = await supabase
.from('user_ride_credits')
@@ -159,10 +219,14 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
if (error) throw error;
toast.success('Ride credit removed');
fetchCredits();
} catch (error) {
console.error('Error deleting credit:', error);
toast.error(getErrorMessage(error));
// Rollback on error
if (deletedCredit) {
setCredits(prev => [...prev, deletedCredit]);
}
}
};
@@ -175,8 +239,7 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
if (error) throw error;
// Refetch to get accurate sort_order values
await fetchCredits();
// No refetch - optimistic update is already applied
} catch (error) {
console.error('Error reordering credit:', error);
throw error;
@@ -193,6 +256,9 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
if (oldIndex === -1 || newIndex === -1) return;
// Store old order for rollback
const oldCredits = credits;
// Optimistic update
const newCredits = arrayMove(credits, oldIndex, newIndex);
setCredits(newCredits);
@@ -202,17 +268,18 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
toast.success('Order updated');
} catch (error) {
toast.error(getErrorMessage(error));
// Revert on error
fetchCredits();
// Rollback to old order
setCredits(oldCredits);
}
};
const stats = {
// Memoize statistics to only recalculate when credits change
const stats = useMemo(() => ({
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
};
}), [credits]);
// Use filtered credits for display
const displayCredits = filteredCredits;