import { useState, useEffect } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import { formatDistanceToNow } from 'date-fns'; import { User as SupabaseUser } from '@supabase/supabase-js'; import { Header } from '@/components/layout/Header'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; import { useAuth } from '@/hooks/useAuth'; import { useProfile } from '@/hooks/useProfile'; import { UserReviewsList } from '@/components/profile/UserReviewsList'; import { UserListManager } from '@/components/lists/UserListManager'; import { RideCreditsManager } from '@/components/profile/RideCreditsManager'; import { useUsernameValidation } from '@/hooks/useUsernameValidation'; import { User, MapPin, Calendar, Star, Trophy, Settings, Camera, Edit3, Save, X, ArrowLeft, Check, AlertCircle, Loader2, UserX, FileText, Image } from 'lucide-react'; import { Profile as ProfileType } from '@/types/database'; import { supabase } from '@/integrations/supabase/client'; import { useToast } from '@/hooks/use-toast'; import { getErrorMessage } from '@/lib/errorHandler'; import { PhotoUpload } from '@/components/upload/PhotoUpload'; import { profileEditSchema } from '@/lib/validation'; import { LocationDisplay } from '@/components/profile/LocationDisplay'; import { UserBlockButton } from '@/components/profile/UserBlockButton'; import { PersonalLocationDisplay } from '@/components/profile/PersonalLocationDisplay'; import { useUserRole } from '@/hooks/useUserRole'; import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { useOpenGraph } from '@/hooks/useOpenGraph'; import { useProfileActivity } from '@/hooks/profile/useProfileActivity'; import { useProfileStats } from '@/hooks/profile/useProfileStats'; import { useQueryInvalidation } from '@/lib/queryInvalidation'; // Activity type definitions interface SubmissionActivity { id: string; type: 'submission'; submission_type: 'park' | 'ride' | 'photo' | 'company' | 'ride_model'; status: string; created_at: string; content?: { action?: 'edit' | 'create'; name?: string; slug?: string; entity_slug?: string; entity_name?: string; park_slug?: string; park_name?: string; company_type?: string; manufacturer_slug?: string; description?: string; }; photo_preview?: string; photo_count?: number; entity_type?: 'park' | 'ride' | 'company'; entity_id?: string; } interface RankingActivity { id: string; type: 'ranking'; title: string; description?: string; list_type: string; created_at: string; } interface ReviewActivity { id: string; type: 'review'; rating: number; title?: string; content?: string; created_at: string; moderation_status?: string; park_id?: string; ride_id?: string; parks?: { name: string; slug: string; } | null; rides?: { name: string; slug: string; parks?: { name: string; slug: string; } | null; } | null; } interface CreditActivity { id: string; type: 'credit'; ride_count: number; first_ride_date?: string; created_at: string; rides?: { name: string; slug: string; parks?: { name: string; slug: string; } | null; } | null; } type ActivityEntry = SubmissionActivity | RankingActivity | ReviewActivity | CreditActivity; // Type guards const isSubmissionActivity = (act: ActivityEntry): act is SubmissionActivity => act.type === 'submission'; const isRankingActivity = (act: ActivityEntry): act is RankingActivity => act.type === 'ranking'; const isReviewActivity = (act: ActivityEntry): act is ReviewActivity => act.type === 'review'; const isCreditActivity = (act: ActivityEntry): act is CreditActivity => act.type === 'credit'; export default function Profile() { const { username } = useParams<{ username?: string; }>(); const navigate = useNavigate(); const { toast } = useToast(); const { user: authUser } = useAuth(); const { refreshProfile } = useProfile(authUser?.id); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const [editing, setEditing] = useState(false); const [currentUser, setCurrentUser] = useState(null); const [editForm, setEditForm] = useState({ username: '', display_name: '', bio: '' }); const [showUsernameDialog, setShowUsernameDialog] = useState(false); const [formErrors, setFormErrors] = useState>({}); const [avatarUrl, setAvatarUrl] = useState(''); const [avatarImageId, setAvatarImageId] = useState(''); // Query invalidation for cache updates const { invalidateProfileActivity, invalidateProfileStats } = useQueryInvalidation(); // User role checking const { isModerator, loading: rolesLoading } = useUserRole(); // Update document title when profile changes useDocumentTitle(profile?.username ? `${profile.username}'s Profile` : 'Profile'); useOpenGraph({ title: profile ? `${profile.display_name || profile.username} - ThrillWiki` : 'User Profile', description: profile?.bio || (profile ? `${profile.display_name || profile.username}'s profile on ThrillWiki` : undefined), imageUrl: profile?.avatar_url, imageId: profile?.avatar_image_id, type: 'profile', enabled: !!profile }); // Username validation const usernameValidation = useUsernameValidation(editForm.username, profile?.username); // Optimized activity and stats hooks const isOwnProfile = currentUser && profile && currentUser.id === profile.user_id; const { data: calculatedStats = { rideCount: 0, coasterCount: 0, parkCount: 0 } } = useProfileStats(profile?.user_id); const { data: recentActivity = [], isLoading: activityLoading } = useProfileActivity( profile?.user_id, isOwnProfile || false, isModerator() ); // Cast activity to local types for type safety const typedActivity = recentActivity as ActivityEntry[]; useEffect(() => { getCurrentUser(); if (username) { fetchProfile(username); } else { fetchCurrentUserProfile(); } }, [username]); const getCurrentUser = async () => { const { data: { user } } = await supabase.auth.getUser(); setCurrentUser(user); }; const fetchProfile = async (profileUsername: string) => { try { // Use filtered_profiles view for privacy-respecting queries // This view enforces field-level privacy based on user settings const { data, error } = await supabase .from('filtered_profiles') .select(`*`) .eq('username', profileUsername) .maybeSingle(); if (error) throw error; if (data) { // Fetch location separately if location_id is visible let locationData = null; if (data.location_id) { const { data: location } = await supabase .from('locations') .select('*') .eq('id', data.location_id) .single(); locationData = location; } const profileWithLocation = { ...data, location: locationData }; setProfile(profileWithLocation as ProfileType); setEditForm({ username: data.username || '', display_name: data.display_name || '', bio: data.bio || '' }); setAvatarUrl(data.avatar_url || ''); setAvatarImageId(data.avatar_image_id || ''); } } catch (error) { console.error('Error fetching profile:', error); toast({ variant: "destructive", title: "Error loading profile", description: getErrorMessage(error) }); } finally { setLoading(false); } }; const fetchCurrentUserProfile = async () => { try { const { data: { user } } = await supabase.auth.getUser(); if (!user) { navigate('/auth'); return; } const { data, error } = await supabase .from('profiles') .select(`*, location:locations(*)`) .eq('user_id', user.id) .maybeSingle(); if (error) throw error; if (data) { setProfile(data as ProfileType); setEditForm({ username: data.username || '', display_name: data.display_name || '', bio: data.bio || '' }); setAvatarUrl(data.avatar_url || ''); setAvatarImageId(data.avatar_image_id || ''); } } catch (error) { console.error('Error fetching profile:', error); toast({ variant: "destructive", title: "Error loading profile", description: getErrorMessage(error) }); } finally { setLoading(false); } }; const validateForm = () => { const result = profileEditSchema.safeParse(editForm); if (!result.success) { const errors: Record = {}; result.error.issues.forEach(issue => { if (issue.path[0]) { errors[issue.path[0] as string] = issue.message; } }); setFormErrors(errors); return false; } if (!usernameValidation.isValid && editForm.username !== profile?.username) { setFormErrors({ username: usernameValidation.error || 'Invalid username' }); return false; } setFormErrors({}); return true; }; const handleSaveProfile = async () => { if (!profile || !currentUser) return; if (!validateForm()) return; const usernameChanged = editForm.username !== profile.username; if (usernameChanged && !showUsernameDialog) { setShowUsernameDialog(true); return; } try { const updateData: any = { display_name: editForm.display_name, bio: editForm.bio, avatar_url: avatarUrl, avatar_image_id: avatarImageId }; if (usernameChanged) { updateData.username = editForm.username; } const { error } = await supabase.from('profiles').update(updateData).eq('user_id', currentUser.id); if (error) throw error; // Invalidate profile caches across the app if (currentUser.id) { invalidateProfileActivity(currentUser.id); invalidateProfileStats(currentUser.id); } setProfile(prev => prev ? { ...prev, ...updateData } : null); setEditing(false); setShowUsernameDialog(false); if (usernameChanged) { toast({ title: "Profile updated", description: "Your username and profile URL have been updated successfully." }); // Navigate to new username URL navigate(`/profile/${editForm.username}`); } else { toast({ title: "Profile updated", description: "Your profile has been updated successfully." }); } } catch (error) { toast({ variant: "destructive", title: "Error updating profile", description: getErrorMessage(error) }); } }; const confirmUsernameChange = () => { setShowUsernameDialog(false); handleSaveProfile(); }; const handleAvatarUpload = async (urls: string[], imageId?: string) => { if (!currentUser || !urls[0]) return; const newAvatarUrl = urls[0]; const newImageId = imageId || ''; // Update local state immediately setAvatarUrl(newAvatarUrl); setAvatarImageId(newImageId); try { // Update database immediately const { error } = await supabase.from('profiles').update({ avatar_url: newAvatarUrl, avatar_image_id: newImageId }).eq('user_id', currentUser.id); if (error) throw error; // Invalidate profile activity cache (avatar shows in activity) if (currentUser.id) { invalidateProfileActivity(currentUser.id); } // Update local profile state setProfile(prev => prev ? { ...prev, avatar_url: newAvatarUrl, avatar_image_id: newImageId } : null); // Refresh the auth context to update header avatar if (refreshProfile) { await refreshProfile(); } toast({ title: "Avatar updated", description: "Your profile picture has been updated successfully." }); } catch (error) { // Revert local state on error setAvatarUrl(profile?.avatar_url || ''); setAvatarImageId(profile?.avatar_image_id || ''); toast({ variant: "destructive", title: "Error updating avatar", description: getErrorMessage(error) }); } }; if (loading) { return
; } if (!profile) { return

Profile Not Found

The profile you're looking for doesn't exist.

; } return
{/* Profile Header */}
{ toast({ title: "Upload Error", description: error, variant: "destructive" }); }} className="mb-4" />
{isOwnProfile && !editing && } {!isOwnProfile && }
{editing && isOwnProfile ?
setEditForm(prev => ({ ...prev, username: e.target.value }))} placeholder="your_username" className={`pr-10 ${formErrors.username ? 'border-destructive' : ''}`} />
{usernameValidation.isChecking ? : editForm.username === profile?.username ? : usernameValidation.isValid ? : usernameValidation.error ? : null}
{formErrors.username &&

{formErrors.username}

} {usernameValidation.error && editForm.username !== profile?.username &&

{usernameValidation.error}

} {usernameValidation.isValid && editForm.username !== profile?.username &&

Username is available!

}

Your profile URL will be /profile/{editForm.username}

setEditForm(prev => ({ ...prev, display_name: e.target.value }))} placeholder="Your display name" className={formErrors.display_name ? 'border-destructive' : ''} /> {formErrors.display_name &&

{formErrors.display_name}

}