correct way fetching data
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 39s

This commit is contained in:
ordinarthur 2026-04-13 16:30:59 +02:00
parent 4c8dd1cc52
commit 4dc8fc2350
6 changed files with 78 additions and 48 deletions

View File

@ -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()

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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 () => {

View File

@ -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) => {