Refactor profile stats calculation

This commit is contained in:
gpt-engineer-app[bot]
2025-09-28 21:08:19 +00:00
parent 0f2841f8aa
commit 2f295b4879

View File

@@ -48,6 +48,11 @@ export default function Profile() {
const [formErrors, setFormErrors] = useState<Record<string, string>>({}); const [formErrors, setFormErrors] = useState<Record<string, string>>({});
const [avatarUrl, setAvatarUrl] = useState<string>(''); const [avatarUrl, setAvatarUrl] = useState<string>('');
const [avatarImageId, setAvatarImageId] = useState<string>(''); const [avatarImageId, setAvatarImageId] = useState<string>('');
const [calculatedStats, setCalculatedStats] = useState({
rideCount: 0,
coasterCount: 0,
parkCount: 0
});
// Username validation // Username validation
const usernameValidation = useUsernameValidation(editForm.username, profile?.username); const usernameValidation = useUsernameValidation(editForm.username, profile?.username);
@@ -59,6 +64,50 @@ export default function Profile() {
fetchCurrentUserProfile(); fetchCurrentUserProfile();
} }
}, [username]); }, [username]);
const fetchCalculatedStats = async (userId: string) => {
try {
// Fetch ride credits stats
const { data: ridesData, error: ridesError } = await supabase
.from('user_ride_credits')
.select(`
ride_count,
rides!inner(category, park_id)
`)
.eq('user_id', userId);
if (ridesError) throw ridesError;
// Calculate total rides count (sum of all ride_count values)
const totalRides = ridesData?.reduce((sum, credit) => sum + (credit.ride_count || 0), 0) || 0;
// Calculate coasters count (distinct rides where category is roller_coaster)
const coasterRides = ridesData?.filter(credit =>
credit.rides?.category === 'roller_coaster'
) || [];
const uniqueCoasters = new Set(coasterRides.map(credit => credit.rides));
const coasterCount = uniqueCoasters.size;
// Calculate parks count (distinct parks where user has ridden at least one ride)
const parkRides = ridesData?.map(credit => credit.rides?.park_id).filter(Boolean) || [];
const uniqueParks = new Set(parkRides);
const parkCount = uniqueParks.size;
setCalculatedStats({
rideCount: totalRides,
coasterCount: coasterCount,
parkCount: parkCount
});
} catch (error: any) {
console.error('Error fetching calculated stats:', error);
// Set defaults on error
setCalculatedStats({
rideCount: 0,
coasterCount: 0,
parkCount: 0
});
}
};
const getCurrentUser = async () => { const getCurrentUser = async () => {
const { const {
data: { data: {
@@ -69,11 +118,14 @@ export default function Profile() {
}; };
const fetchProfile = async (profileUsername: string) => { const fetchProfile = async (profileUsername: string) => {
try { try {
const { const { data, error } = await supabase
data, .from('profiles')
error .select(`*, location:locations(*)`)
} = await supabase.from('profiles').select(`*, location:locations(*)`).eq('username', profileUsername).maybeSingle(); .eq('username', profileUsername)
.maybeSingle();
if (error) throw error; if (error) throw error;
if (data) { if (data) {
setProfile(data as ProfileType); setProfile(data as ProfileType);
setEditForm({ setEditForm({
@@ -83,6 +135,9 @@ export default function Profile() {
}); });
setAvatarUrl(data.avatar_url || ''); setAvatarUrl(data.avatar_url || '');
setAvatarImageId(data.avatar_image_id || ''); setAvatarImageId(data.avatar_image_id || '');
// Fetch calculated stats for this user
await fetchCalculatedStats(data.user_id);
} }
} catch (error: any) { } catch (error: any) {
console.error('Error fetching profile:', error); console.error('Error fetching profile:', error);
@@ -97,20 +152,20 @@ export default function Profile() {
}; };
const fetchCurrentUserProfile = async () => { const fetchCurrentUserProfile = async () => {
try { try {
const { const { data: { user } } = await supabase.auth.getUser();
data: {
user
}
} = await supabase.auth.getUser();
if (!user) { if (!user) {
navigate('/auth'); navigate('/auth');
return; return;
} }
const {
data, const { data, error } = await supabase
error .from('profiles')
} = await supabase.from('profiles').select(`*, location:locations(*)`).eq('user_id', user.id).maybeSingle(); .select(`*, location:locations(*)`)
.eq('user_id', user.id)
.maybeSingle();
if (error) throw error; if (error) throw error;
if (data) { if (data) {
setProfile(data as ProfileType); setProfile(data as ProfileType);
setEditForm({ setEditForm({
@@ -120,6 +175,9 @@ export default function Profile() {
}); });
setAvatarUrl(data.avatar_url || ''); setAvatarUrl(data.avatar_url || '');
setAvatarImageId(data.avatar_image_id || ''); setAvatarImageId(data.avatar_image_id || '');
// Fetch calculated stats for the current user
await fetchCalculatedStats(user.id);
} }
} catch (error: any) { } catch (error: any) {
console.error('Error fetching profile:', error); console.error('Error fetching profile:', error);
@@ -410,19 +468,19 @@ export default function Profile() {
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<Card> <Card>
<CardContent className="p-4 text-center"> <CardContent className="p-4 text-center">
<div className="text-2xl font-bold text-primary">{profile.ride_count}</div> <div className="text-2xl font-bold text-primary">{calculatedStats.rideCount}</div>
<div className="text-sm text-muted-foreground">Rides</div> <div className="text-sm text-muted-foreground">Rides</div>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card>
<CardContent className="p-4 text-center"> <CardContent className="p-4 text-center">
<div className="text-2xl font-bold text-accent">{profile.coaster_count}</div> <div className="text-2xl font-bold text-accent">{calculatedStats.coasterCount}</div>
<div className="text-sm text-muted-foreground">Coasters</div> <div className="text-sm text-muted-foreground">Coasters</div>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card>
<CardContent className="p-4 text-center"> <CardContent className="p-4 text-center">
<div className="text-2xl font-bold text-secondary">{profile.park_count}</div> <div className="text-2xl font-bold text-secondary">{calculatedStats.parkCount}</div>
<div className="text-sm text-muted-foreground">Parks</div> <div className="text-sm text-muted-foreground">Parks</div>
</CardContent> </CardContent>
</Card> </Card>