Merge pull request #4 from ArnasDon/fix/auth-hook-infinite-loading

Fix: dashboard stuck on infinite 'Loading...' spinner
This commit is contained in:
Arnas Donauskas 2026-04-16 19:53:17 +03:00 committed by GitHub
commit 928a67e12b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -16,43 +16,79 @@ export function useAuth() {
const [profile, setProfile] = useState<Profile | null>(null); const [profile, setProfile] = useState<Profile | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const supabase = createClient();
const fetchProfile = useCallback(
async (userId: string) => {
const { data } = await supabase
.from("profiles")
.select("id, full_name, email, avatar_url")
.eq("id", userId)
.single();
if (data) {
setProfile(data);
}
},
[supabase]
);
useEffect(() => { useEffect(() => {
const getUser = async () => { const supabase = createClient();
const { let mounted = true;
data: { user },
} = await supabase.auth.getUser();
setUser(user); // Safety net: if something hangs, surface it instead of an infinite spinner.
const safetyTimer = setTimeout(() => {
if (user) { if (mounted) {
await fetchProfile(user.id); console.warn("[useAuth] getUser() timed out after 10s, clearing loading state");
setLoading(false);
} }
}, 10000);
setLoading(false); const fetchProfile = async (userId: string) => {
try {
// profiles.user_id (NOT profiles.id) references auth.users.id
const { data, error } = await supabase
.from("profiles")
.select("id, full_name, email, avatar_url")
.eq("user_id", userId)
.maybeSingle();
if (error) {
console.error("[useAuth] fetchProfile error:", {
message: error.message,
details: error.details,
hint: error.hint,
code: error.code,
});
return;
}
if (data && mounted) {
setProfile(data);
}
} catch (err) {
console.error("[useAuth] fetchProfile threw:", err);
}
}; };
getUser(); const init = async () => {
try {
const {
data: { user },
error,
} = await supabase.auth.getUser();
if (error) {
// AuthSessionMissingError is expected when not logged in — don't log as error
if (error.name !== "AuthSessionMissingError") {
console.error("[useAuth] getUser error:", error.message);
}
}
if (!mounted) return;
setUser(user);
if (user) {
await fetchProfile(user.id);
}
} catch (err) {
console.error("[useAuth] init threw:", err);
} finally {
if (mounted) setLoading(false);
clearTimeout(safetyTimer);
}
};
init();
const { const {
data: { subscription }, data: { subscription },
} = supabase.auth.onAuthStateChange(async (event, session) => { } = supabase.auth.onAuthStateChange(async (_event, session) => {
if (!mounted) return;
const currentUser = session?.user ?? null; const currentUser = session?.user ?? null;
setUser(currentUser); setUser(currentUser);
@ -66,16 +102,21 @@ export function useAuth() {
}); });
return () => { return () => {
mounted = false;
clearTimeout(safetyTimer);
subscription.unsubscribe(); subscription.unsubscribe();
}; };
}, [supabase, fetchProfile]); // Intentionally run once on mount — createClient() is a singleton
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const signOut = useCallback(async () => { const signOut = useCallback(async () => {
const supabase = createClient();
await supabase.auth.signOut(); await supabase.auth.signOut();
setUser(null); setUser(null);
setProfile(null); setProfile(null);
window.location.href = "/login"; window.location.href = "/login";
}, [supabase]); }, []);
return { user, profile, loading, signOut }; return { user, profile, loading, signOut };
} }