"use client"; import React, { createContext, useContext, useState, useEffect, ReactNode, useRef } from "react"; import { useRouter, usePathname } from "next/navigation"; import api from "./api"; import authService from "./auth"; // Расширенный интерфейс пользователя с API export interface User { id: number; email: string; first_name: string; last_name: string; is_admin: boolean; role?: string; // Добавляем опциональное поле role для совместимости с API } // Интерфейс пользователя из API interface UserProfile { id: number; email?: string; first_name?: string; last_name?: string; role?: string; [key: string]: any; // Для других полей, которые могут быть в ответе API } // Интерфейс контекста аутентификации interface AuthContextType { user: User | null; loading: boolean; isAdmin: boolean; authChecked: boolean; login: (email: string, password: string) => Promise; logout: () => void; } // Создание контекста аутентификации const AuthContext = createContext(undefined); // Константа для ключа токена в localStorage const TOKEN_KEY = 'token'; // Глобальная переменная для отслеживания состояния аутентификации между рендерами let globalAuthState = { isAuthenticated: false, isAdmin: false, redirectAttempted: false }; // Функция для получения токена из localStorage const getToken = (): string | null => { try { return localStorage.getItem(TOKEN_KEY); } catch (error) { console.error('Ошибка при получении токена (auth.tsx):', error); return null; } }; // Провайдер аутентификации export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [authChecked, setAuthChecked] = useState(false); const router = useRouter(); // Проверка аутентификации при загрузке useEffect(() => { const checkAuth = async () => { try { const token = getToken(); console.log('Проверка токена в AuthProvider:', token ? token.substring(0, 20) + '...' : 'нет токена'); if (token) { const userProfile = await authService.getProfile() as UserProfile | null; if (userProfile && 'id' in userProfile) { // Создаем объект пользователя const userData: User = { id: userProfile.id, email: userProfile.email || '', first_name: userProfile.first_name || '', last_name: userProfile.last_name || '', is_admin: userProfile.role === 'admin', role: userProfile.role }; setUser(userData); // Обновляем глобальное состояние аутентификации globalAuthState.isAuthenticated = true; globalAuthState.isAdmin = userData.is_admin; globalAuthState.redirectAttempted = false; console.log("Пользователь авторизован при загрузке:", userData); } else { console.log("Токен недействителен или неверный формат данных"); // Не удаляем токен, чтобы предотвратить циклические перезагрузки // localStorage.removeItem(TOKEN_KEY); 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_KEY); setUser(null); // Сбрасываем глобальное состояние globalAuthState.isAuthenticated = false; globalAuthState.isAdmin = false; } finally { setLoading(false); setAuthChecked(true); } }; checkAuth(); }, []); // Функция входа const login = async (email: string, password: string): Promise => { try { console.log("Попытка входа:", email); const response = await authService.login({ username: email, password: password }); if (response && response.access_token) { // Токен уже сохранен в localStorage внутри authService.login // Получение данных пользователя const userProfile = await authService.getProfile() as UserProfile | null; if (userProfile && 'id' in userProfile) { // Создаем объект пользователя const userData: User = { id: userProfile.id, email: userProfile.email || '', first_name: userProfile.first_name || '', last_name: userProfile.last_name || '', is_admin: userProfile.role === 'admin', role: userProfile.role }; setUser(userData); // Обновляем глобальное состояние globalAuthState.isAuthenticated = true; globalAuthState.isAdmin = userData.is_admin; globalAuthState.redirectAttempted = false; console.log("Пользователь авторизован:", userData); 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 = () => { authService.logout(); // Уже удаляет токен из localStorage 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 ( {children} ); } // Экспорт контекста для использования в других файлах export { AuthContext }; // Компонент для защиты маршрутов администратора export function AdminProtected({ children }: { children: ReactNode }) { const { user, loading, isAdmin, authChecked } = useContext(AuthContext) || { user: null, loading: true, isAdmin: false, authChecked: false }; const router = useRouter(); const pathname = usePathname(); const [redirected, setRedirected] = useState(false); const [redirectionTimer, setRedirectionTimer] = useState(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, globalAuthState }); // Проверяем, авторизован ли пользователь и является ли он администратором if (!user || !isAdmin) { // Предотвращаем множественные редиректы if (!redirectAttemptedRef.current && !redirected) { redirectAttemptedRef.current = true; setRedirected(true); console.log("Перенаправление на страницу входа для администратора"); // Очищаем предыдущий таймер, если он существует if (redirectionTimer) { clearTimeout(redirectionTimer); } // Устанавливаем новый таймер для перенаправления const timer = setTimeout(() => { router.push('/admin/login'); }, 100); setRedirectionTimer(timer); } } else { // Сбрасываем флаг перенаправления, если пользователь авторизован и является администратором redirectAttemptedRef.current = false; setRedirected(false); } }, [user, isAdmin, loading, authChecked, pathname, router, redirected, redirectionTimer]); // Эффект для очистки таймера при размонтировании компонента useEffect(() => { return () => { if (redirectionTimer) { clearTimeout(redirectionTimer); } }; }, [redirectionTimer]); // Показываем загрузку во время проверки аутентификации if (loading || !authChecked) { return
Загрузка...
; } // Если пользователь авторизован и является администратором, показываем содержимое if (user && isAdmin) { return <>{children}; } // В противном случае ничего не показываем (перенаправление уже должно быть запущено) return null; }