Добавлены новые зависимости, обновлены стили и улучшена структура проекта. Обновлен README с описанием функционала и технологий. Реализована анимация и адаптивный дизайн. Настроена авторизация с использованием Apollo Client.

This commit is contained in:
Bivekich
2025-07-16 18:00:41 +03:00
parent d260749bc9
commit 823ef9a28c
69 changed files with 15539 additions and 210 deletions

View File

@ -0,0 +1,12 @@
import { useEffect } from 'react'
import { apolloClient } from '@/lib/apollo-client'
export const useApolloRefresh = () => {
const refreshApolloClient = async () => {
// Сбрасываем кэш и перезапрашиваем все активные запросы
console.log('useApolloRefresh - Resetting Apollo cache and refetching queries')
await apolloClient.resetStore()
}
return { refreshApolloClient }
}

367
src/hooks/useAuth.ts Normal file
View File

@ -0,0 +1,367 @@
import { useMutation } from '@apollo/client'
import { useState, useEffect } from 'react'
import {
SEND_SMS_CODE,
VERIFY_SMS_CODE,
REGISTER_FULFILLMENT_ORGANIZATION,
REGISTER_SELLER_ORGANIZATION
} from '@/graphql/mutations'
import { GET_ME } from '@/graphql/queries'
import { setAuthToken, setUserData, removeAuthToken, getAuthToken, apolloClient } from '@/lib/apollo-client'
import { useApolloRefresh } from './useApolloRefresh'
interface User {
id: string
phone: string
avatar?: string
createdAt?: string
organization?: {
id: string
inn: string
kpp?: string
name?: string
fullName?: string
address?: string
addressFull?: string
ogrn?: string
ogrnDate?: string
status?: string
actualityDate?: string
registrationDate?: string
liquidationDate?: string
managementName?: string
managementPost?: string
opfCode?: string
opfFull?: string
opfShort?: string
okato?: string
oktmo?: string
okpo?: string
okved?: string
employeeCount?: number
revenue?: string
taxSystem?: string
phones?: unknown
emails?: unknown
type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
apiKeys: Array<{
id: string
marketplace: 'WILDBERRIES' | 'OZON'
isActive: boolean
validationData?: unknown
}>
}
}
interface UseAuthReturn {
// SMS методы
sendSmsCode: (phone: string) => Promise<{ success: boolean; message: string }>
verifySmsCode: (phone: string, code: string) => Promise<{
success: boolean
message: string
user?: User
}>
// Регистрация организаций
registerFulfillmentOrganization: (phone: string, inn: string) => Promise<{
success: boolean
message: string
user?: User
}>
registerSellerOrganization: (data: {
phone: string
wbApiKey?: string
ozonApiKey?: string
ozonClientId?: string
}) => Promise<{
success: boolean
message: string
user?: User
}>
// Состояние
user: User | null
isAuthenticated: boolean
isLoading: boolean
checkAuth: () => Promise<void>
logout: () => void
}
export const useAuth = (): UseAuthReturn => {
// Инициализируем состояния с проверкой токена
const [isLoading, setIsLoading] = useState(false)
const [user, setUser] = useState<User | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(() => {
// Проверяем наличие токена при инициализации
return !!getAuthToken()
})
const [isCheckingAuth, setIsCheckingAuth] = useState(false) // Защита от повторных вызовов
const { refreshApolloClient } = useApolloRefresh()
const [sendSmsCodeMutation] = useMutation(SEND_SMS_CODE)
const [verifySmsCodeMutation] = useMutation(VERIFY_SMS_CODE)
const [registerFulfillmentMutation] = useMutation(REGISTER_FULFILLMENT_ORGANIZATION)
const [registerSellerMutation] = useMutation(REGISTER_SELLER_ORGANIZATION)
// Проверка авторизации при инициализации
const checkAuth = async () => {
if (isCheckingAuth) {
console.log('useAuth - checkAuth already in progress, skipping')
return
}
const token = getAuthToken()
console.log('useAuth - checkAuth called, token exists:', !!token)
if (!token) {
setIsAuthenticated(false)
setUser(null)
setIsCheckingAuth(false)
return
}
setIsCheckingAuth(true)
try {
console.log('useAuth - Making GET_ME query')
const { data } = await apolloClient.query({
query: GET_ME,
errorPolicy: 'all',
fetchPolicy: 'network-only' // Всегда делаем свежий запрос
})
console.log('useAuth - GET_ME response:', !!data?.me)
if (data?.me) {
setUser(data.me)
setIsAuthenticated(true)
setUserData(data.me)
console.log('useAuth - User authenticated:', data.me.phone)
} else {
setIsAuthenticated(false)
setUser(null)
}
} catch (error: unknown) {
console.log('useAuth - GET_ME error:', error)
if ((error as { graphQLErrors?: Array<{ extensions?: { code?: string } }> })?.graphQLErrors?.some((e) => e.extensions?.code === 'UNAUTHENTICATED')) {
logout()
} else {
setIsAuthenticated(false)
setUser(null)
}
} finally {
setIsCheckingAuth(false)
}
}
// Проверяем авторизацию при загрузке компонента только если нет данных пользователя
useEffect(() => {
const token = getAuthToken()
console.log('useAuth - useEffect init, token exists:', !!token, 'user exists:', !!user, 'isChecking:', isCheckingAuth)
if (token && !user && !isCheckingAuth) {
console.log('useAuth - Running checkAuth because token exists but no user data')
checkAuth()
} else if (!token) {
console.log('useAuth - No token, setting unauthenticated state')
setIsAuthenticated(false)
setUser(null)
}
}, []) // eslint-disable-line react-hooks/exhaustive-deps
const sendSmsCode = async (phone: string) => {
try {
setIsLoading(true)
const { data } = await sendSmsCodeMutation({
variables: { phone }
})
return {
success: data.sendSmsCode.success,
message: data.sendSmsCode.message
}
} catch (error) {
console.error('Error sending SMS code:', error)
return {
success: false,
message: 'Ошибка при отправке SMS кода'
}
} finally {
setIsLoading(false)
}
}
const verifySmsCode = async (phone: string, code: string) => {
try {
setIsLoading(true)
console.log('useAuth - Starting verifySmsCode mutation with:', { phone, code })
const { data } = await verifySmsCodeMutation({
variables: { phone, code }
})
console.log('useAuth - GraphQL response data:', data)
const result = data.verifySmsCode
console.log('useAuth - SMS verification result:', {
success: result.success,
hasToken: !!result.token,
hasUser: !!result.user,
message: result.message
})
if (result.success && result.token && result.user) {
// Сохраняем токен и данные пользователя
console.log('useAuth - Saving token:', result.token ? `${result.token.substring(0, 20)}...` : 'No token')
setAuthToken(result.token)
setUserData(result.user)
// Обновляем состояние хука
setUser(result.user)
setIsAuthenticated(true)
console.log('useAuth - State updated: user set, isAuthenticated=true')
// Проверяем что токен действительно сохранился
const savedToken = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null
console.log('useAuth - Token saved to localStorage:', savedToken ? `${savedToken.substring(0, 20)}...` : 'Not saved')
// Принудительно обновляем Apollo Client
refreshApolloClient()
return {
success: true,
message: result.message,
user: result.user
}
}
return {
success: false,
message: result.message
}
} catch (error) {
console.error('Error verifying SMS code:', error)
console.error('Error details:', {
message: error instanceof Error ? error.message : 'Unknown error',
graphQLErrors: (error as { graphQLErrors?: unknown })?.graphQLErrors,
networkError: (error as { networkError?: unknown })?.networkError
})
return {
success: false,
message: 'Ошибка при проверке SMS кода'
}
} finally {
setIsLoading(false)
}
}
const registerFulfillmentOrganization = async (phone: string, inn: string) => {
try {
setIsLoading(true)
// Проверяем токен перед запросом
const currentToken = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null
console.log('useAuth - Token before registerFulfillment request:', currentToken ? `${currentToken.substring(0, 20)}...` : 'No token')
const { data } = await registerFulfillmentMutation({
variables: {
input: { phone, inn }
}
})
const result = data.registerFulfillmentOrganization
if (result.success && result.user) {
// Обновляем данные пользователя
setUserData(result.user)
return {
success: true,
message: result.message,
user: result.user
}
}
return {
success: false,
message: result.message
}
} catch (error) {
console.error('Error registering fulfillment organization:', error)
return {
success: false,
message: 'Ошибка при регистрации фулфилмент организации'
}
} finally {
setIsLoading(false)
}
}
const registerSellerOrganization = async (data: {
phone: string
wbApiKey?: string
ozonApiKey?: string
ozonClientId?: string
}) => {
try {
setIsLoading(true)
const { data: result } = await registerSellerMutation({
variables: { input: data }
})
const registerResult = result.registerSellerOrganization
if (registerResult.success && registerResult.user) {
// Обновляем данные пользователя
setUserData(registerResult.user)
return {
success: true,
message: registerResult.message,
user: registerResult.user
}
}
return {
success: false,
message: registerResult.message
}
} catch (error) {
console.error('Error registering seller organization:', error)
return {
success: false,
message: 'Ошибка при регистрации селлер организации'
}
} finally {
setIsLoading(false)
}
}
const logout = () => {
console.log('useAuth - Logging out')
removeAuthToken()
setUser(null)
setIsAuthenticated(false)
refreshApolloClient()
// Перенаправляем на главную страницу
if (typeof window !== 'undefined') {
window.location.href = '/'
}
}
return {
// SMS методы
sendSmsCode,
verifySmsCode,
// Регистрация организаций
registerFulfillmentOrganization,
registerSellerOrganization,
// Состояние
user,
isAuthenticated,
isLoading,
checkAuth,
logout
}
}