correct way fetching data
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 39s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 39s
This commit is contained in:
parent
4c8dd1cc52
commit
4dc8fc2350
@ -37,9 +37,10 @@ export default function App() {
|
||||
if (session?.user) fetchProfile()
|
||||
})
|
||||
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
|
||||
setUser(session?.user ?? null)
|
||||
if (session?.user) fetchProfile()
|
||||
// Only fetch profile on actual sign-in, not token refresh
|
||||
if (event === 'SIGNED_IN' && session?.user) fetchProfile()
|
||||
})
|
||||
|
||||
return () => subscription.unsubscribe()
|
||||
|
||||
@ -6,29 +6,33 @@ type ProgressMap = Map<string, { seconds: number; percent: number }>
|
||||
|
||||
let cachedProgress: ProgressMap = new Map()
|
||||
let lastFetch = 0
|
||||
let progressInFlight: Promise<void> | null = null
|
||||
|
||||
export function useListeningProgress() {
|
||||
const user = useAuthStore((s) => s.user)
|
||||
const userId = useAuthStore((s) => s.user?.id)
|
||||
const [progress, setProgress] = useState<ProgressMap>(cachedProgress)
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
if (!userId) {
|
||||
cachedProgress = new Map()
|
||||
setProgress(cachedProgress)
|
||||
return
|
||||
}
|
||||
|
||||
// Refetch at most every 10s
|
||||
// Refetch at most every 30s (was 10s — too aggressive)
|
||||
const now = Date.now()
|
||||
if (now - lastFetch < 10_000 && cachedProgress.size > 0) {
|
||||
if (now - lastFetch < 30_000 && cachedProgress.size > 0) {
|
||||
setProgress(cachedProgress)
|
||||
return
|
||||
}
|
||||
|
||||
supabase
|
||||
// Deduplicate concurrent calls
|
||||
if (progressInFlight) return
|
||||
|
||||
progressInFlight = supabase
|
||||
.from('listen_history')
|
||||
.select('podcast_id, progress_seconds, podcast:podcasts(duration_seconds)')
|
||||
.eq('user_id', user.id)
|
||||
.eq('user_id', userId)
|
||||
.eq('completed', false)
|
||||
.gt('progress_seconds', 5)
|
||||
.then(({ data }) => {
|
||||
@ -46,7 +50,8 @@ export function useListeningProgress() {
|
||||
lastFetch = Date.now()
|
||||
setProgress(map)
|
||||
})
|
||||
}, [user])
|
||||
.finally(() => { progressInFlight = null })
|
||||
}, [userId])
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ export function useNotificationPolling() {
|
||||
|
||||
fetch(user.id)
|
||||
|
||||
intervalRef.current = setInterval(() => fetch(user.id), 30000)
|
||||
intervalRef.current = setInterval(() => fetch(user.id), 60000)
|
||||
|
||||
function handleVisibility() {
|
||||
if (document.visibilityState === 'visible' && user) fetch(user.id)
|
||||
|
||||
@ -73,15 +73,14 @@ export function Home() {
|
||||
setInProgress(items)
|
||||
}
|
||||
|
||||
// Fetch real stats for hero
|
||||
// Compute stats for hero (non-logged-in) — 2 queries instead of 3
|
||||
if (!user) {
|
||||
const [{ count: podcastCount }, { data: playsData }, { count: creatorCount }] = await Promise.all([
|
||||
supabase.from('podcasts').select('*', { count: 'exact', head: true }),
|
||||
const [{ data: allPlays }, { count: creatorCount }] = await Promise.all([
|
||||
supabase.from('podcasts').select('plays_count'),
|
||||
supabase.from('profiles').select('*', { count: 'exact', head: true }),
|
||||
])
|
||||
const totalPlays = playsData?.reduce((sum: number, p: any) => sum + (p.plays_count || 0), 0) || 0
|
||||
setStats({ plays: totalPlays, creators: creatorCount || 0, podcasts: podcastCount || 0 })
|
||||
const totalPlays = allPlays?.reduce((sum: number, p: any) => sum + (p.plays_count || 0), 0) || 0
|
||||
setStats({ plays: totalPlays, creators: creatorCount || 0, podcasts: allPlays?.length || 0 })
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
|
||||
@ -13,6 +13,9 @@ interface AuthState {
|
||||
signOut: () => Promise<void>
|
||||
}
|
||||
|
||||
// Deduplicate fetchProfile — prevents double-fetch on init (getSession + onAuthStateChange)
|
||||
let profileInFlight: Promise<void> | null = null
|
||||
|
||||
export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
user: null,
|
||||
profile: null,
|
||||
@ -24,31 +27,40 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
|
||||
fetchProfile: async () => {
|
||||
const { user } = get()
|
||||
if (!user) return set({ profile: null })
|
||||
if (!user) { set({ profile: null }); return }
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.maybeSingle()
|
||||
// If already fetching, reuse the same promise
|
||||
if (profileInFlight) return profileInFlight
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching profile:', error)
|
||||
return set({ profile: null })
|
||||
}
|
||||
|
||||
// Auto-create profile if it doesn't exist (trigger may have failed)
|
||||
if (!data) {
|
||||
const username = user.user_metadata?.username || user.email?.split('@')[0] || 'user'
|
||||
const { data: newProfile } = await supabase
|
||||
profileInFlight = (async () => {
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.insert({ id: user.id, username })
|
||||
.select()
|
||||
.single()
|
||||
return set({ profile: newProfile })
|
||||
}
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.maybeSingle()
|
||||
|
||||
set({ profile: data })
|
||||
if (error) {
|
||||
console.error('Error fetching profile:', error)
|
||||
set({ profile: null })
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-create profile if it doesn't exist (trigger may have failed)
|
||||
if (!data) {
|
||||
const username = user.user_metadata?.username || user.email?.split('@')[0] || 'user'
|
||||
const { data: newProfile } = await supabase
|
||||
.from('profiles')
|
||||
.insert({ id: user.id, username })
|
||||
.select()
|
||||
.single()
|
||||
set({ profile: newProfile })
|
||||
return
|
||||
}
|
||||
|
||||
set({ profile: data })
|
||||
})().finally(() => { profileInFlight = null })
|
||||
|
||||
return profileInFlight
|
||||
},
|
||||
|
||||
signOut: async () => {
|
||||
|
||||
@ -10,23 +10,36 @@ interface NotificationsStore {
|
||||
markAllRead: (userId: string) => Promise<void>
|
||||
}
|
||||
|
||||
// Deduplicate and throttle notification fetches (min 15s between fetches)
|
||||
let notifInFlight: Promise<void> | null = null
|
||||
let notifLastFetch = 0
|
||||
|
||||
export const useNotificationsStore = create<NotificationsStore>((set) => ({
|
||||
notifications: [],
|
||||
unreadCount: 0,
|
||||
|
||||
fetch: async (userId) => {
|
||||
const { data } = await supabase
|
||||
.from('notifications')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(50)
|
||||
if (data) {
|
||||
set({
|
||||
notifications: data,
|
||||
unreadCount: data.filter((n) => !n.read).length,
|
||||
})
|
||||
}
|
||||
const now = Date.now()
|
||||
if (now - notifLastFetch < 15_000) return
|
||||
if (notifInFlight) return notifInFlight
|
||||
|
||||
notifInFlight = (async () => {
|
||||
const { data } = await supabase
|
||||
.from('notifications')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(50)
|
||||
if (data) {
|
||||
set({
|
||||
notifications: data,
|
||||
unreadCount: data.filter((n) => !n.read).length,
|
||||
})
|
||||
}
|
||||
notifLastFetch = Date.now()
|
||||
})().finally(() => { notifInFlight = null })
|
||||
|
||||
return notifInFlight
|
||||
},
|
||||
|
||||
markRead: async (id) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user