PDF-Assit_RAG / frontend /src /lib /auth.tsx
Param20h's picture
fix(lint): lazy-init loading state to avoid setLoading(false) inside effect body
23eb242 unverified
"use client";
import React, { createContext, useContext, useEffect, useState, useCallback } from "react";
import { api } from "./api";
interface User {
id: string;
username: string;
email: string;
is_admin: boolean;
created_at: string;
}
interface AuthContextType {
user: User | null;
token: string | null;
loading: boolean;
login: (email: string, password: string) => Promise<void>;
register: (username: string, email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
// Lazy initializer reads localStorage once β€” avoids setState-in-effect lint error
const [token, setToken] = useState<string | null>(
() => (typeof window !== "undefined" ? localStorage.getItem("token") : null)
);
// loading=true only when a token exists and needs server validation.
// If there's no token we're already done β€” no effect setState needed.
const [loading, setLoading] = useState<boolean>(
() => typeof window !== "undefined" && !!localStorage.getItem("token")
);
// ── Validate saved token on mount ─────────────────
// NOTE: no synchronous setState here β€” setLoading/setUser/setToken are
// only called inside async callbacks (.then / .catch / .finally).
useEffect(() => {
if (!token) return; // loading is already false when token is null
api
.get<User>("/api/v1/auth/me", { token })
.then(setUser)
.catch(() => {
localStorage.removeItem("token");
setToken(null);
})
.finally(() => setLoading(false));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // intentionally runs once on mount only
const login = useCallback(async (email: string, password: string) => {
const data = await api.post<{ access_token: string; user: User }>(
"/api/v1/auth/login",
{ email, password }
);
localStorage.setItem("token", data.access_token);
setToken(data.access_token);
setUser(data.user);
}, []);
const register = useCallback(async (username: string, email: string, password: string) => {
const data = await api.post<{ access_token: string; user: User }>(
"/api/v1/auth/register",
{ username, email, password }
);
localStorage.setItem("token", data.access_token);
setToken(data.access_token);
setUser(data.user);
}, []);
const logout = useCallback(() => {
localStorage.removeItem("token");
setToken(null);
setUser(null);
}, []);
return (
<AuthContext.Provider value={{ user, token, loading, login, register, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}