Files
Salon/frontend/src/contexts/AuthContext.jsx
T
2026-03-14 23:42:21 +03:00

103 lines
2.9 KiB
React

import { createContext, useCallback, useContext, useEffect, useState } from "react";
import { apiGet, apiPost, ApiError } from "../api/client";
const STORAGE_ACCESS = "auth_access";
const STORAGE_REFRESH = "auth_refresh";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [accessToken, setAccessToken] = useState(() => {
if (typeof window === "undefined") return null;
return localStorage.getItem(STORAGE_ACCESS);
});
const [refreshToken, setRefreshToken] = useState(() => {
if (typeof window === "undefined") return null;
return localStorage.getItem(STORAGE_REFRESH);
});
const [loading, setLoading] = useState(true);
const persistTokens = useCallback((access, refresh) => {
setAccessToken(access);
setRefreshToken(refresh);
if (typeof window !== "undefined") {
if (access) localStorage.setItem(STORAGE_ACCESS, access);
else localStorage.removeItem(STORAGE_ACCESS);
if (refresh) localStorage.setItem(STORAGE_REFRESH, refresh);
else localStorage.removeItem(STORAGE_REFRESH);
}
}, []);
const logout = useCallback(() => {
setUser(null);
persistTokens(null, null);
}, [persistTokens]);
const login = useCallback((access, refresh, userData) => {
persistTokens(access, refresh);
setUser(userData);
}, [persistTokens]);
// Restore user from token on mount
useEffect(() => {
if (!accessToken) {
setLoading(false);
return;
}
apiGet("/auth/me/", accessToken)
.then((data) => {
setUser(data);
setLoading(false);
})
.catch((err) => {
const status = ApiError && err instanceof ApiError ? err.status : err?.status;
const message = typeof err?.message === "string" ? err.message : "";
const isUnauthorized = status === 401 || message.includes("401");
if (!isUnauthorized) {
setLoading(false);
return;
}
// Token invalid, try refresh
if (!refreshToken) {
logout();
setLoading(false);
return;
}
apiPost("/auth/token/refresh/", { refresh: refreshToken })
.then(({ access }) => {
persistTokens(access, refreshToken);
return apiGet("/auth/me/", access);
})
.then((data) => {
setUser(data);
})
.catch(() => {
logout();
})
.finally(() => {
setLoading(false);
});
});
}, [accessToken, refreshToken, logout, persistTokens]);
const value = {
user,
accessToken,
loading,
login,
logout,
isAuthenticated: !!user,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) {
throw new Error("useAuth must be used within AuthProvider");
}
return ctx;
}