first commit

This commit is contained in:
Bivekich
2025-06-26 06:59:19 +03:00
commit 18e1f3ffb1
124 changed files with 52448 additions and 0 deletions

View 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>
)
}

View 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>
)
}

View 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}</>
}

View 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}
/>
)
}