first commit
This commit is contained in:
16
src/components/providers/ApolloProvider.tsx
Normal file
16
src/components/providers/ApolloProvider.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
"use client"
|
||||
|
||||
import { ApolloProvider as BaseApolloProvider } from '@apollo/client'
|
||||
import { apolloClient } from '@/lib/apollo-client'
|
||||
|
||||
interface ApolloProviderProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const ApolloProvider = ({ children }: ApolloProviderProps) => {
|
||||
return (
|
||||
<BaseApolloProvider client={apolloClient}>
|
||||
{children}
|
||||
</BaseApolloProvider>
|
||||
)
|
||||
}
|
118
src/components/providers/AuthProvider.tsx
Normal file
118
src/components/providers/AuthProvider.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
"use client"
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { useMutation } from '@apollo/client'
|
||||
import Cookies from 'js-cookie'
|
||||
import { LOGIN, LOGOUT } from '@/lib/graphql/queries'
|
||||
|
||||
interface User {
|
||||
id: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
avatar?: string
|
||||
role: string
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null
|
||||
token: string | null
|
||||
login: (email: string, password: string) => Promise<void>
|
||||
logout: () => void
|
||||
isLoading: boolean
|
||||
isAuthenticated: boolean
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const AuthProvider = ({ children }: AuthProviderProps) => {
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
const [token, setToken] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const [loginMutation] = useMutation(LOGIN)
|
||||
const [logoutMutation] = useMutation(LOGOUT)
|
||||
|
||||
// Проверяем токен при загрузке
|
||||
useEffect(() => {
|
||||
const savedToken = Cookies.get('auth-token')
|
||||
const savedUser = Cookies.get('auth-user')
|
||||
|
||||
if (savedToken && savedUser) {
|
||||
try {
|
||||
const parsedUser = JSON.parse(savedUser)
|
||||
setToken(savedToken)
|
||||
setUser(parsedUser)
|
||||
} catch (error) {
|
||||
console.error('Ошибка парсинга данных пользователя:', error)
|
||||
Cookies.remove('auth-token')
|
||||
Cookies.remove('auth-user')
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
}, [])
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
try {
|
||||
const { data } = await loginMutation({
|
||||
variables: {
|
||||
input: { email, password }
|
||||
}
|
||||
})
|
||||
|
||||
const { token: newToken, user: newUser } = data.login
|
||||
|
||||
// Сохраняем в cookies
|
||||
Cookies.set('auth-token', newToken, { expires: 7 }) // 7 дней
|
||||
Cookies.set('auth-user', JSON.stringify(newUser), { expires: 7 })
|
||||
|
||||
setToken(newToken)
|
||||
setUser(newUser)
|
||||
} catch (error) {
|
||||
console.error('Ошибка входа:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
await logoutMutation()
|
||||
} catch (error) {
|
||||
console.error('Ошибка выхода:', error)
|
||||
} finally {
|
||||
// Удаляем данные независимо от результата запроса
|
||||
Cookies.remove('auth-token')
|
||||
Cookies.remove('auth-user')
|
||||
setToken(null)
|
||||
setUser(null)
|
||||
}
|
||||
}
|
||||
|
||||
const value: AuthContextType = {
|
||||
user,
|
||||
token,
|
||||
login,
|
||||
logout,
|
||||
isLoading,
|
||||
isAuthenticated: !!user && !!token,
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
99
src/components/providers/InitializationProvider.tsx
Normal file
99
src/components/providers/InitializationProvider.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter, usePathname } from 'next/navigation'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { HAS_USERS } from '@/lib/graphql/queries'
|
||||
import { useAuth } from './AuthProvider'
|
||||
|
||||
interface InitializationProviderProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const InitializationProvider = ({ children }: InitializationProviderProps) => {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const [isChecking, setIsChecking] = useState(true)
|
||||
const { isAuthenticated, isLoading: authLoading } = useAuth()
|
||||
|
||||
const { data, loading, error } = useQuery(HAS_USERS, {
|
||||
fetchPolicy: 'network-only', // Всегда проверяем актуальные данные
|
||||
errorPolicy: 'all',
|
||||
notifyOnNetworkStatusChange: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
console.log('InitializationProvider: проверка состояния', {
|
||||
loading,
|
||||
error,
|
||||
data,
|
||||
pathname,
|
||||
isAuthenticated,
|
||||
authLoading
|
||||
})
|
||||
|
||||
if (loading || authLoading) return
|
||||
|
||||
if (error) {
|
||||
console.error('Ошибка проверки инициализации:', error)
|
||||
console.error('Детали ошибки:', {
|
||||
message: error.message,
|
||||
graphQLErrors: error.graphQLErrors,
|
||||
networkError: error.networkError,
|
||||
})
|
||||
setIsChecking(false)
|
||||
return
|
||||
}
|
||||
|
||||
const hasUsers = data?.hasUsers
|
||||
console.log('InitializationProvider: hasUsers =', hasUsers)
|
||||
|
||||
// Если пользователей нет и мы не на странице настройки
|
||||
if (!hasUsers && pathname !== '/setup') {
|
||||
console.log('InitializationProvider: перенаправление на /setup')
|
||||
router.push('/setup')
|
||||
return
|
||||
}
|
||||
|
||||
// Если пользователи есть
|
||||
if (hasUsers) {
|
||||
// Если мы на странице настройки - перенаправляем на логин
|
||||
if (pathname === '/setup') {
|
||||
console.log('InitializationProvider: перенаправление на /login')
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
|
||||
// Если не авторизованы и не на странице логина - перенаправляем на логин
|
||||
if (!isAuthenticated && pathname !== '/login') {
|
||||
console.log('InitializationProvider: перенаправление на /login (не авторизован)')
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
|
||||
// Если авторизованы и на странице логина - перенаправляем в дашборд
|
||||
if (isAuthenticated && pathname === '/login') {
|
||||
console.log('InitializationProvider: перенаправление на /dashboard')
|
||||
router.push('/dashboard')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log('InitializationProvider: инициализация завершена')
|
||||
setIsChecking(false)
|
||||
}, [data, loading, error, pathname, router, isAuthenticated, authLoading])
|
||||
|
||||
// Показываем загрузку пока проверяем инициализацию
|
||||
if (isChecking || loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Проверка системы...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
}
|
15
src/components/providers/ToastProvider.tsx
Normal file
15
src/components/providers/ToastProvider.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
"use client"
|
||||
|
||||
import { Toaster } from 'sonner'
|
||||
|
||||
export const ToastProvider = () => {
|
||||
return (
|
||||
<Toaster
|
||||
position="top-right"
|
||||
richColors
|
||||
closeButton
|
||||
expand={false}
|
||||
duration={4000}
|
||||
/>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user