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() if (session?.user) fetchProfile()
}) })
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user ?? null) 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() return () => subscription.unsubscribe()

View File

@ -6,29 +6,33 @@ type ProgressMap = Map<string, { seconds: number; percent: number }>
let cachedProgress: ProgressMap = new Map() let cachedProgress: ProgressMap = new Map()
let lastFetch = 0 let lastFetch = 0
let progressInFlight: Promise<void> | null = null
export function useListeningProgress() { export function useListeningProgress() {
const user = useAuthStore((s) => s.user) const userId = useAuthStore((s) => s.user?.id)
const [progress, setProgress] = useState<ProgressMap>(cachedProgress) const [progress, setProgress] = useState<ProgressMap>(cachedProgress)
useEffect(() => { useEffect(() => {
if (!user) { if (!userId) {
cachedProgress = new Map() cachedProgress = new Map()
setProgress(cachedProgress) setProgress(cachedProgress)
return return
} }
// Refetch at most every 10s // Refetch at most every 30s (was 10s — too aggressive)
const now = Date.now() const now = Date.now()
if (now - lastFetch < 10_000 && cachedProgress.size > 0) { if (now - lastFetch < 30_000 && cachedProgress.size > 0) {
setProgress(cachedProgress) setProgress(cachedProgress)
return return
} }
supabase // Deduplicate concurrent calls
if (progressInFlight) return
progressInFlight = supabase
.from('listen_history') .from('listen_history')
.select('podcast_id, progress_seconds, podcast:podcasts(duration_seconds)') .select('podcast_id, progress_seconds, podcast:podcasts(duration_seconds)')
.eq('user_id', user.id) .eq('user_id', userId)
.eq('completed', false) .eq('completed', false)
.gt('progress_seconds', 5) .gt('progress_seconds', 5)
.then(({ data }) => { .then(({ data }) => {
@ -46,7 +50,8 @@ export function useListeningProgress() {
lastFetch = Date.now() lastFetch = Date.now()
setProgress(map) setProgress(map)
}) })
}, [user]) .finally(() => { progressInFlight = null })
}, [userId])
return progress return progress
} }

View File

@ -12,7 +12,7 @@ export function useNotificationPolling() {
fetch(user.id) fetch(user.id)
intervalRef.current = setInterval(() => fetch(user.id), 30000) intervalRef.current = setInterval(() => fetch(user.id), 60000)
function handleVisibility() { function handleVisibility() {
if (document.visibilityState === 'visible' && user) fetch(user.id) if (document.visibilityState === 'visible' && user) fetch(user.id)

View File

@ -73,15 +73,14 @@ export function Home() {
setInProgress(items) setInProgress(items)
} }
// Fetch real stats for hero // Compute stats for hero (non-logged-in) — 2 queries instead of 3
if (!user) { if (!user) {
const [{ count: podcastCount }, { data: playsData }, { count: creatorCount }] = await Promise.all([ const [{ data: allPlays }, { count: creatorCount }] = await Promise.all([
supabase.from('podcasts').select('*', { count: 'exact', head: true }),
supabase.from('podcasts').select('plays_count'), supabase.from('podcasts').select('plays_count'),
supabase.from('profiles').select('*', { count: 'exact', head: true }), supabase.from('profiles').select('*', { count: 'exact', head: true }),
]) ])
const totalPlays = playsData?.reduce((sum: number, p: any) => sum + (p.plays_count || 0), 0) || 0 const totalPlays = allPlays?.reduce((sum: number, p: any) => sum + (p.plays_count || 0), 0) || 0
setStats({ plays: totalPlays, creators: creatorCount || 0, podcasts: podcastCount || 0 }) setStats({ plays: totalPlays, creators: creatorCount || 0, podcasts: allPlays?.length || 0 })
} }
setLoading(false) setLoading(false)

View File

@ -13,6 +13,9 @@ interface AuthState {
signOut: () => Promise<void> 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) => ({ export const useAuthStore = create<AuthState>((set, get) => ({
user: null, user: null,
profile: null, profile: null,
@ -24,31 +27,40 @@ export const useAuthStore = create<AuthState>((set, get) => ({
fetchProfile: async () => { fetchProfile: async () => {
const { user } = get() const { user } = get()
if (!user) return set({ profile: null }) if (!user) { set({ profile: null }); return }
const { data, error } = await supabase // If already fetching, reuse the same promise
.from('profiles') if (profileInFlight) return profileInFlight
.select('*')
.eq('id', user.id)
.maybeSingle()
if (error) { profileInFlight = (async () => {
console.error('Error fetching profile:', error) const { data, error } = await supabase
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
.from('profiles') .from('profiles')
.insert({ id: user.id, username }) .select('*')
.select() .eq('id', user.id)
.single() .maybeSingle()
return set({ profile: newProfile })
}
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 () => { signOut: async () => {

View File

@ -10,23 +10,36 @@ interface NotificationsStore {
markAllRead: (userId: string) => Promise<void> 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) => ({ export const useNotificationsStore = create<NotificationsStore>((set) => ({
notifications: [], notifications: [],
unreadCount: 0, unreadCount: 0,
fetch: async (userId) => { fetch: async (userId) => {
const { data } = await supabase const now = Date.now()
.from('notifications') if (now - notifLastFetch < 15_000) return
.select('*') if (notifInFlight) return notifInFlight
.eq('user_id', userId)
.order('created_at', { ascending: false }) notifInFlight = (async () => {
.limit(50) const { data } = await supabase
if (data) { .from('notifications')
set({ .select('*')
notifications: data, .eq('user_id', userId)
unreadCount: data.filter((n) => !n.read).length, .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) => { markRead: async (id) => {