dressed_for_succes_store/frontend/lib/auth.tsx
2025-04-01 23:52:37 +07:00

301 lines
11 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 React, { createContext, useContext, useState, useEffect, ReactNode, useRef } from "react";
import { useRouter, usePathname } from "next/navigation";
import api from "./api";
import { authApi } from "./auth-api";
// Расширенный интерфейс пользователя с 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<boolean>;
logout: () => void;
}
// Создание контекста аутентификации
const AuthContext = createContext<AuthContextType | undefined>(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<User | null>(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 authApi.getProfile();
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.is_admin,
role: userProfile.role
};
setUser(userData);
// Обновляем глобальное состояние аутентификации
globalAuthState.isAuthenticated = true;
globalAuthState.isAdmin = userData.is_admin;
globalAuthState.redirectAttempted = false;
console.log("Пользователь авторизован при загрузке:", userData);
} else {
console.log("Токен недействителен или неверный формат данных");
setUser(null);
// Сбрасываем глобальное состояние
globalAuthState.isAuthenticated = false;
globalAuthState.isAdmin = false;
}
} else {
console.log("Токен отсутствует");
setUser(null);
// Сбрасываем глобальное состояние
globalAuthState.isAuthenticated = false;
globalAuthState.isAdmin = false;
}
} catch (error) {
console.error("Ошибка при проверке аутентификации:", error);
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);
if (response && response.success) {
// Получение данных пользователя
const userProfile = await authApi.getProfile();
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.is_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 = () => {
authApi.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 (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// Экспорт контекста для использования в других файлах
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<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,
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 <div>Загрузка...</div>;
}
// Если пользователь авторизован и является администратором, показываем содержимое
if (user && isAdmin) {
return <>{children}</>;
}
// В противном случае ничего не показываем (перенаправление уже должно быть запущено)
return null;
}