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
|
currentUserId: string
|
||||||
onShared: () => void
|
onShared: () => void
|
||||||
}) {
|
}) {
|
||||||
const { groups, processes, settings, createGroup, isLoading } = useLibraryStore()
|
const { groups, processes, settings, createGroup, isLoading, error, load } = useLibraryStore()
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [activeTags, setActiveTags] = useState<string[]>([])
|
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)
|
// Empty state: sin grupos ni procesos (ya terminó de cargar)
|
||||||
if (groups.length === 0 && processes.length === 0) {
|
if (groups.length === 0 && processes.length === 0) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,3 +8,14 @@ if (!supabaseUrl || !supabaseAnonKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const supabase = createClient(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 { create } from 'zustand'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { supabase } from '@/lib/supabase'
|
import { getSessionSafe } from '@/lib/supabase'
|
||||||
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
||||||
import { supabaseGroupRepo } from '@/persistence/supabase/group-repo'
|
import { supabaseGroupRepo } from '@/persistence/supabase/group-repo'
|
||||||
import { supabaseSettingsRepo } from '@/persistence/supabase/settings-repo'
|
import { supabaseSettingsRepo } from '@/persistence/supabase/settings-repo'
|
||||||
@@ -12,6 +12,7 @@ interface LibraryState {
|
|||||||
processes: Process[]
|
processes: Process[]
|
||||||
settings: AppSettings
|
settings: AppSettings
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
|
error: string | null
|
||||||
|
|
||||||
load: () => Promise<void>
|
load: () => Promise<void>
|
||||||
|
|
||||||
@@ -36,27 +37,32 @@ export const useLibraryStore = create<LibraryState>((set, get) => ({
|
|||||||
processes: [],
|
processes: [],
|
||||||
settings: DEFAULT_SETTINGS,
|
settings: DEFAULT_SETTINGS,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
|
||||||
load: async () => {
|
load: async () => {
|
||||||
set({ isLoading: true })
|
set({ isLoading: true, error: null })
|
||||||
try {
|
try {
|
||||||
// Barrera única: garantiza que el cliente Supabase tenga la sesión restaurada
|
// 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)
|
// antes de salir a buscar datos en paralelo (evita 0 filas por RLS en el primer load).
|
||||||
const { data: sessionData } = await supabase.auth.getSession()
|
// Con timeout defensivo: si el cliente se cuelga, no bloquea la UI más de 4s.
|
||||||
console.log('[library-store] load session:', {
|
await getSessionSafe()
|
||||||
hasSession: !!sessionData.session,
|
|
||||||
userId: sessionData.session?.user?.id ?? 'NULL',
|
|
||||||
})
|
|
||||||
|
|
||||||
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(),
|
supabaseGroupRepo.getAll(),
|
||||||
supabaseProcessRepo.getAll(),
|
supabaseProcessRepo.getAll(),
|
||||||
supabaseSettingsRepo.get(),
|
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) {
|
} catch (err) {
|
||||||
console.error('Error al cargar la biblioteca:', 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