capa adicional sobre lo que ya deployaste
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
This commit is contained in:
@@ -381,7 +381,7 @@ function HomeView({
|
||||
currentUserId: string
|
||||
onShared: () => void
|
||||
}) {
|
||||
const { groups, processes, settings, createGroup, isLoading } = useLibraryStore()
|
||||
const { groups, processes, settings, createGroup, isLoading, error, load } = useLibraryStore()
|
||||
const { user } = useAuth()
|
||||
const [search, setSearch] = useState('')
|
||||
const [activeTags, setActiveTags] = useState<string[]>([])
|
||||
@@ -422,6 +422,24 @@ function HomeView({
|
||||
)
|
||||
}
|
||||
|
||||
// Error state: el load() falló o se colgó (timeout) — distinto de "biblioteca vacía"
|
||||
if (error && groups.length === 0 && processes.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-24 gap-4">
|
||||
<Building2 className="h-16 w-16 text-destructive/40" />
|
||||
<p className="text-base font-medium">No se pudo cargar la biblioteca</p>
|
||||
<p className="text-[13px] text-muted-foreground max-w-sm text-center">{error}</p>
|
||||
<button
|
||||
onClick={() => load()}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium text-white transition-opacity hover:opacity-90"
|
||||
style={{ background: '#F59845' }}
|
||||
>
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Empty state: sin grupos ni procesos (ya terminó de cargar)
|
||||
if (groups.length === 0 && processes.length === 0) {
|
||||
return (
|
||||
|
||||
@@ -8,3 +8,14 @@ if (!supabaseUrl || !supabaseAnonKey) {
|
||||
}
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
|
||||
// supabase.auth.getSession() puede colgarse indefinidamente si el cliente queda
|
||||
// esperando un lock interno (navigator.locks) que nunca se libera. Esta barrera
|
||||
// nunca bloquea más de 4s: si no resuelve a tiempo, sigue de largo sin sesión confirmada
|
||||
// en vez de dejar la UI con un spinner/skeleton infinito.
|
||||
export async function getSessionSafe(): Promise<void> {
|
||||
await Promise.race([
|
||||
supabase.auth.getSession(),
|
||||
new Promise((resolve) => setTimeout(resolve, 4000)),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
import { getSessionSafe } from '@/lib/supabase'
|
||||
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
||||
import { supabaseGroupRepo } from '@/persistence/supabase/group-repo'
|
||||
import { supabaseSettingsRepo } from '@/persistence/supabase/settings-repo'
|
||||
@@ -12,6 +12,7 @@ interface LibraryState {
|
||||
processes: Process[]
|
||||
settings: AppSettings
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
|
||||
load: () => Promise<void>
|
||||
|
||||
@@ -36,27 +37,32 @@ export const useLibraryStore = create<LibraryState>((set, get) => ({
|
||||
processes: [],
|
||||
settings: DEFAULT_SETTINGS,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
load: async () => {
|
||||
set({ isLoading: true })
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
// Barrera única: garantiza que el cliente Supabase tenga la sesión restaurada
|
||||
// antes de salir a buscar datos en paralelo (evita 0 filas por RLS en el primer load)
|
||||
const { data: sessionData } = await supabase.auth.getSession()
|
||||
console.log('[library-store] load session:', {
|
||||
hasSession: !!sessionData.session,
|
||||
userId: sessionData.session?.user?.id ?? 'NULL',
|
||||
})
|
||||
// antes de salir a buscar datos en paralelo (evita 0 filas por RLS en el primer load).
|
||||
// Con timeout defensivo: si el cliente se cuelga, no bloquea la UI más de 4s.
|
||||
await getSessionSafe()
|
||||
|
||||
const [groups, processes, settings] = await Promise.all([
|
||||
// Timeout global: si el cliente Supabase queda colgado (ej. lock interno nunca liberado),
|
||||
// isLoading nunca debe quedar en true para siempre — la UI debe poder mostrar error y reintentar.
|
||||
const [groups, processes, settings] = await Promise.race([
|
||||
Promise.all([
|
||||
supabaseGroupRepo.getAll(),
|
||||
supabaseProcessRepo.getAll(),
|
||||
supabaseSettingsRepo.get(),
|
||||
]),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout: Supabase no respondió en 15s')), 15_000)
|
||||
),
|
||||
])
|
||||
set({ groups, processes, settings, isLoading: false })
|
||||
set({ groups, processes, settings, isLoading: false, error: null })
|
||||
} catch (err) {
|
||||
console.error('Error al cargar la biblioteca:', err)
|
||||
set({ isLoading: false })
|
||||
set({ isLoading: false, error: err instanceof Error ? err.message : 'Error al cargar la biblioteca' })
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user