2026-01-16 11:32:06 -08:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useAtomValue } from "jotai";
|
2026-01-30 15:07:19 +05:30
|
|
|
import { usePathname } from "next/navigation";
|
2026-01-20 00:32:31 -08:00
|
|
|
import { useEffect, useRef, useState } from "react";
|
2026-01-16 11:32:06 -08:00
|
|
|
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
2026-01-25 16:15:25 +05:30
|
|
|
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
|
2026-01-24 17:03:10 +05:30
|
|
|
import { getBearerToken } from "@/lib/auth-utils";
|
2026-01-16 11:32:06 -08:00
|
|
|
import {
|
|
|
|
|
cleanupElectric,
|
|
|
|
|
type ElectricClient,
|
2026-01-20 00:32:31 -08:00
|
|
|
initElectric,
|
|
|
|
|
isElectricInitialized,
|
2026-01-16 11:32:06 -08:00
|
|
|
} from "@/lib/electric/client";
|
|
|
|
|
import { ElectricContext } from "@/lib/electric/context";
|
|
|
|
|
|
|
|
|
|
interface ElectricProviderProps {
|
|
|
|
|
children: React.ReactNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-30 15:07:19 +05:30
|
|
|
* Initializes user-specific PGlite database with Electric SQL sync.
|
|
|
|
|
* Handles user isolation, cleanup, and re-initialization on user change.
|
2026-01-16 11:32:06 -08:00
|
|
|
*/
|
|
|
|
|
export function ElectricProvider({ children }: ElectricProviderProps) {
|
|
|
|
|
const [electricClient, setElectricClient] = useState<ElectricClient | null>(null);
|
|
|
|
|
const [error, setError] = useState<Error | null>(null);
|
|
|
|
|
const {
|
|
|
|
|
data: user,
|
|
|
|
|
isSuccess: isUserLoaded,
|
|
|
|
|
isError: isUserError,
|
|
|
|
|
} = useAtomValue(currentUserAtom);
|
|
|
|
|
const previousUserIdRef = useRef<string | null>(null);
|
|
|
|
|
const initializingRef = useRef(false);
|
2026-01-30 15:07:19 +05:30
|
|
|
const pathname = usePathname();
|
2026-01-16 11:32:06 -08:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (typeof window === "undefined") return;
|
|
|
|
|
|
2026-01-30 15:07:19 +05:30
|
|
|
// No user logged in - cleanup if previous user existed
|
2026-01-16 11:32:06 -08:00
|
|
|
if (!isUserLoaded || !user?.id) {
|
|
|
|
|
if (previousUserIdRef.current && isElectricInitialized()) {
|
|
|
|
|
console.log("[ElectricProvider] User logged out, cleaning up...");
|
|
|
|
|
cleanupElectric().then(() => {
|
|
|
|
|
previousUserIdRef.current = null;
|
|
|
|
|
setElectricClient(null);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const userId = String(user.id);
|
|
|
|
|
|
2026-01-30 15:07:19 +05:30
|
|
|
// Skip if already initialized for this user or currently initializing
|
|
|
|
|
if ((electricClient && previousUserIdRef.current === userId) || initializingRef.current) {
|
2026-01-16 11:32:06 -08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initializingRef.current = true;
|
|
|
|
|
let mounted = true;
|
|
|
|
|
|
|
|
|
|
async function init() {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`[ElectricProvider] Initializing for user: ${userId}`);
|
|
|
|
|
const client = await initElectric(userId);
|
|
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
previousUserIdRef.current = userId;
|
|
|
|
|
setElectricClient(client);
|
|
|
|
|
setError(null);
|
|
|
|
|
console.log(`[ElectricProvider] ✅ Ready for user: ${userId}`);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("[ElectricProvider] Failed to initialize:", err);
|
|
|
|
|
if (mounted) {
|
|
|
|
|
setError(err instanceof Error ? err : new Error("Failed to initialize Electric SQL"));
|
|
|
|
|
setElectricClient(null);
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
if (mounted) {
|
|
|
|
|
initializingRef.current = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init();
|
|
|
|
|
return () => {
|
|
|
|
|
mounted = false;
|
|
|
|
|
};
|
|
|
|
|
}, [user?.id, isUserLoaded, electricClient]);
|
|
|
|
|
|
2026-01-24 17:03:10 +05:30
|
|
|
const hasToken = typeof window !== "undefined" && !!getBearerToken();
|
|
|
|
|
|
2026-01-30 15:07:19 +05:30
|
|
|
// Only block UI on dashboard routes; public pages render immediately
|
|
|
|
|
const requiresElectricLoading = pathname?.startsWith("/dashboard");
|
|
|
|
|
const shouldShowLoading =
|
|
|
|
|
hasToken && isUserLoaded && !!user?.id && !electricClient && !error && requiresElectricLoading;
|
2026-01-25 16:15:25 +05:30
|
|
|
|
2026-01-27 15:28:30 +05:30
|
|
|
useGlobalLoadingEffect(shouldShowLoading);
|
2026-01-25 16:15:25 +05:30
|
|
|
|
2026-01-30 15:07:19 +05:30
|
|
|
// Render immediately for unauthenticated users or failed user queries
|
2026-01-24 17:03:10 +05:30
|
|
|
if (!hasToken || !isUserLoaded || !user?.id || isUserError) {
|
2026-01-16 11:32:06 -08:00
|
|
|
return <ElectricContext.Provider value={null}>{children}</ElectricContext.Provider>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 15:07:19 +05:30
|
|
|
// Render with null context while initializing
|
2026-01-16 11:32:06 -08:00
|
|
|
if (!electricClient && !error) {
|
2026-01-25 16:15:25 +05:30
|
|
|
return <ElectricContext.Provider value={null}>{children}</ElectricContext.Provider>;
|
2026-01-16 11:32:06 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
console.warn("[ElectricProvider] Initialization failed, sync may not work:", error.message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <ElectricContext.Provider value={electricClient}>{children}</ElectricContext.Provider>;
|
|
|
|
|
}
|