dressed_for_succes_store/frontend/lib/auth.tsx

349 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { createContext, useContext, useState, useEffect, ReactNode, useRef } from "react";
import { useRouter, usePathname } from "next/navigation";
import { authApi } from "./api";
// Интерфейс пользователя
export interface User {
id: number;
email: string;
first_name: string;
last_name: string;
is_admin: boolean;
}
// Интерфейс ответа API с данными пользователя
interface UserApiResponse {
user: {
id: number;
email: string;
first_name: string;
last_name: string;
is_admin: boolean;
[key: string]: any; // Для других полей, которые могут быть в ответе
};
[key: string]: any; // Для других полей, которые могут быть в ответе
}
// Интерфейс контекста аутентификации
interface AuthContextType {
user: User | null;
loading: boolean;
isAdmin: boolean;
authChecked: boolean;
login: (email: string, password: string) => Promise<boolean>;
logout: () => void;
}
// Создание контекста аутентификации
const AuthContext = createContext<AuthContextType | undefined>(undefined);
// Глобальная переменная для отслеживания состояния аутентификации между рендерами
let globalAuthState = {
isAuthenticated: false,
isAdmin: false,
redirectAttempted: false
};
// Провайдер аутентификации
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [authChecked, setAuthChecked] = useState(false);
const router = useRouter();
// Проверка аутентификации при загрузке
useEffect(() => {
const checkAuth = async () => {
try {
const token = localStorage.getItem("token");
if (token) {
const response = await authApi.getProfile();
console.log("Проверка аутентификации:", response);
if (response.data && typeof response.data === 'object' && response.data !== null && 'user' in response.data) {
// Приводим ответ к нужному типу
const apiResponse = response.data as UserApiResponse;
// Создаем объект пользователя
const userData: User = {
id: apiResponse.user.id,
email: apiResponse.user.email,
first_name: apiResponse.user.first_name,
last_name: apiResponse.user.last_name,
is_admin: apiResponse.user.is_admin
};
setUser(userData);
// Обновляем глобальное состояние аутентификации
globalAuthState.isAuthenticated = true;
globalAuthState.isAdmin = userData.is_admin;
globalAuthState.redirectAttempted = false;
console.log("Пользователь авторизован при загрузке:", userData);
console.log("Глобальное состояние обновлено:", globalAuthState);
} else {
console.log("Токен недействителен или неверный формат данных, удаляем");
localStorage.removeItem("token");
setUser(null);
// Сбрасываем глобальное состояние
globalAuthState.isAuthenticated = false;
globalAuthState.isAdmin = false;
}
} else {
console.log("Токен отсутствует");
setUser(null);
// Сбрасываем глобальное состояние
globalAuthState.isAuthenticated = false;
globalAuthState.isAdmin = false;
}
} catch (error) {
console.error("Ошибка при проверке аутентификации:", error);
localStorage.removeItem("token");
setUser(null);
// Сбрасываем глобальное состояние
globalAuthState.isAuthenticated = false;
globalAuthState.isAdmin = false;
} finally {
setLoading(false);
setAuthChecked(true);
}
};
checkAuth();
}, []);
// Функция входа
const login = async (email: string, password: string): Promise<boolean> => {
try {
console.log("Попытка входа:", email);
const response = await authApi.login(email, password);
console.log("Ответ API при входе:", response);
if (response.data?.access_token) {
localStorage.setItem("token", response.data.access_token);
// Получение данных пользователя
const userResponse = await authApi.getProfile();
console.log("Данные пользователя:", userResponse);
if (userResponse.data && typeof userResponse.data === 'object' && userResponse.data !== null && 'user' in userResponse.data) {
// Приводим ответ к нужному типу
const apiResponse = userResponse.data as UserApiResponse;
// Создаем объект пользователя
const userData: User = {
id: apiResponse.user.id,
email: apiResponse.user.email,
first_name: apiResponse.user.first_name,
last_name: apiResponse.user.last_name,
is_admin: apiResponse.user.is_admin
};
setUser(userData);
// Обновляем глобальное состояние
globalAuthState.isAuthenticated = true;
globalAuthState.isAdmin = userData.is_admin;
globalAuthState.redirectAttempted = false;
console.log("Пользователь авторизован:", userData);
console.log("Глобальное состояние обновлено:", globalAuthState);
return true;
}
}
console.log("Ошибка входа: неверные учетные данные");
// Сбрасываем глобальное состояние
globalAuthState.isAuthenticated = false;
globalAuthState.isAdmin = false;
return false;
} catch (error) {
console.error("Ошибка при входе:", error);
// Сбрасываем глобальное состояние
globalAuthState.isAuthenticated = false;
globalAuthState.isAdmin = false;
return false;
}
};
// Функция выхода
const logout = () => {
localStorage.removeItem("token");
setUser(null);
// Сбрасываем глобальное состояние
globalAuthState.isAuthenticated = false;
globalAuthState.isAdmin = false;
globalAuthState.redirectAttempted = false;
console.log("Пользователь вышел из системы, глобальное состояние сброшено");
// Добавляем небольшую задержку перед перенаправлением
setTimeout(() => {
router.push("/admin/login");
}, 100);
};
// Проверка на администратора
const isAdmin = user?.is_admin || false;
const value = {
user,
loading,
isAdmin,
login,
logout,
authChecked
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// Хук для использования контекста аутентификации
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth должен использоваться внутри AuthProvider");
}
return context;
}
// Компонент для защиты маршрутов администратора
export function AdminProtected({ children }: { children: ReactNode }) {
const { user, loading, isAdmin, authChecked } = useAuth();
const router = useRouter();
const pathname = usePathname();
const [redirected, setRedirected] = useState(false);
const [redirectionTimer, setRedirectionTimer] = useState<NodeJS.Timeout | null>(null);
const redirectAttemptedRef = useRef(false);
// Обновляем глобальное состояние при изменении пользователя
useEffect(() => {
if (user) {
globalAuthState.isAuthenticated = true;
globalAuthState.isAdmin = isAdmin;
}
}, [user, isAdmin]);
useEffect(() => {
// Пропускаем проверку во время загрузки или если проверка аутентификации еще не завершена
if (loading || !authChecked) return;
console.log("AdminProtected проверка:", {
user: user ? `${user.email} (admin: ${user.is_admin})` : "null",
isAdmin,
pathname,
authChecked,
redirectAttempted: redirectAttemptedRef.current,
globalState: globalAuthState
});
// Очищаем предыдущий таймер, если он существует
if (redirectionTimer) {
clearTimeout(redirectionTimer);
setRedirectionTimer(null);
}
// Если пользователь уже аутентифицирован в глобальном состоянии, пропускаем перенаправление
if (globalAuthState.isAuthenticated && globalAuthState.isAdmin) {
console.log("Доступ разрешен: пользователь авторизован в глобальном состоянии");
return;
}
// Предотвращаем бесконечные перенаправления
if (redirected || redirectAttemptedRef.current || globalAuthState.redirectAttempted) return;
// Если пользователь не авторизован, перенаправляем на страницу входа
if (!user) {
console.log("Перенаправление: неавторизованный пользователь -> /admin/login");
setRedirected(true);
redirectAttemptedRef.current = true;
globalAuthState.redirectAttempted = true;
// Используем таймер для перенаправления
const timer = setTimeout(() => {
router.push("/admin/login");
}, 100);
setRedirectionTimer(timer);
return;
}
// Если пользователь не админ, перенаправляем на главную страницу
if (!isAdmin) {
console.log("Перенаправление: авторизованный не-админ -> /");
setRedirected(true);
redirectAttemptedRef.current = true;
globalAuthState.redirectAttempted = true;
// Используем таймер для перенаправления
const timer = setTimeout(() => {
router.push("/");
}, 100);
setRedirectionTimer(timer);
return;
}
// Если пользователь авторизован и является админом, обновляем глобальное состояние
globalAuthState.isAuthenticated = true;
globalAuthState.isAdmin = true;
console.log("Доступ разрешен: авторизованный админ");
}, [user, loading, isAdmin, router, pathname, redirected, authChecked]);
// Сбрасываем флаг перенаправления при размонтировании компонента
useEffect(() => {
return () => {
if (redirectionTimer) {
clearTimeout(redirectionTimer);
}
// Сбрасываем флаг при размонтировании
redirectAttemptedRef.current = false;
};
}, [redirectionTimer]);
// Показываем индикатор загрузки
if (loading) {
return (
<div className="flex h-screen items-center justify-center">
<div className="text-lg">Загрузка...</div>
</div>
);
}
// Если пользователь авторизован в глобальном состоянии, рендерим содержимое
if (globalAuthState.isAuthenticated && globalAuthState.isAdmin) {
return <>{children}</>;
}
// Если пользователь не авторизован или не админ, не рендерим содержимое
if (!user || !isAdmin) {
return (
<div className="flex h-screen items-center justify-center">
<div className="text-lg">Перенаправление...</div>
</div>
);
}
// Если все проверки пройдены, рендерим содержимое
return <>{children}</>;
}