import { useState, useEffect, useRef } from 'react'
import { useAuth } from '@/components/AuthProvider'
import { supabase } from '@/lib/supabase'
export interface UserProfile {
id: string
email: string | null
full_name: string | null
avatar_url: string | null
created_at: string
updated_at: string
welcome_tour_completed?: boolean
welcome_tour_completed_at?: string | null
welcome_tour_started_at?: string | null
welcome_tour_current_step?: number
welcome_tour_steps_viewed?: number[]
welcome_tour_completion_type?: 'completed' | 'skipped' | 'abandoned' | null
welcome_tour_selected_plan?: string | null
welcome_tour_selected_billing?: 'month' | 'year' | null
welcome_tour_total_time_seconds?: number
welcome_tour_video_watched?: boolean
welcome_tour_version?: string
}
// Cache profile data across component re-renders
let profileCache: { userId: string; data: UserProfile | null; timestamp: number } | null = null;
let ongoingRequest: { userId: string; promise: Promise<UserProfile | null> } | null = null;
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
export function useProfile() {
const { user } = useAuth()
const [profile, setProfile] = useState<UserProfile | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const hasFetched = useRef(false)
useEffect(() => {
if (!user?.id) {
setProfile(null)
setLoading(false)
hasFetched.current = false
return
}
// Check cache first
const now = Date.now()
if (profileCache &&
profileCache.userId === user.id &&
(now - profileCache.timestamp) < CACHE_DURATION) {
setProfile(profileCache.data)
setLoading(false)
hasFetched.current = true
return
}
// Only fetch if we haven't fetched for this user yet
if (!hasFetched.current) {
fetchProfile()
}
}, [user?.id])
const fetchProfile = async (): Promise<UserProfile | null> => {
if (!user?.id) return null
// Check if there's already an ongoing request for this user
if (ongoingRequest && ongoingRequest.userId === user.id) {
try {
const result = await ongoingRequest.promise
if (result) {
setProfile(result)
}
hasFetched.current = true
setLoading(false)
return result
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch profile')
setLoading(false)
return null
}
}
// Create new request promise
const requestPromise = (async (): Promise<UserProfile | null> => {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', user.id)
.single()
if (error) {
if (error.code === 'PGRST116') {
// No profile found, create one from auth metadata
const profileData = {
id: user.id,
email: user.email,
full_name: user.user_metadata?.full_name || user.user_metadata?.name || '',
avatar_url: user.user_metadata?.avatar_url || user.user_metadata?.picture || null,
}
const { data: newProfile, error: createError } = await supabase
.from('profiles')
.insert(profileData)
.select()
.single()
if (createError) throw createError
return newProfile
} else {
throw error
}
}
return data
})()
// Store the ongoing request
ongoingRequest = {
userId: user.id,
promise: requestPromise
}
try {
setLoading(true)
setError(null)
const result = await requestPromise
// Update cache
profileCache = {
userId: user.id,
data: result,
timestamp: Date.now()
}
setProfile(result)
hasFetched.current = true
return result
} catch (err) {
console.error('Error fetching profile:', err)
setError(err instanceof Error ? err.message : 'Failed to fetch profile')
return null
} finally {
setLoading(false)
// Clear ongoing request
ongoingRequest = null
}
}
const updateProfile = async (updates: Partial<Pick<UserProfile, 'full_name' | 'avatar_url'>>) => {
if (!user?.id || !profile) return
try {
const { data, error } = await supabase
.from('profiles')
.update({
...updates,
updated_at: new Date().toISOString()
})
.eq('id', user.id)
.select()
.single()
if (error) throw error
// Update cache
profileCache = {
userId: user.id,
data: data,
timestamp: Date.now()
}
setProfile(data)
return data
} catch (err) {
console.error('Error updating profile:', err)
throw err
}
}
// Force refresh and clear cache - now returns a Promise
const refetch = async (): Promise<UserProfile | null> => {
if (!user?.id) return null
// Clear cache and force fresh fetch
profileCache = null
hasFetched.current = false
ongoingRequest = null
// Fetch fresh profile data
const result = await fetchProfile()
return result
}
return {
profile,
loading,
error,
updateProfile,
refetch
}
}