Добавлены новые зависимости, обновлены стили и улучшена структура проекта. Обновлен README с описанием функционала и технологий. Реализована анимация и адаптивный дизайн. Настроена авторизация с использованием Apollo Client.
This commit is contained in:
125
src/lib/apollo-client.ts
Normal file
125
src/lib/apollo-client.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client'
|
||||
import { setContext } from '@apollo/client/link/context'
|
||||
import { onError } from '@apollo/client/link/error'
|
||||
|
||||
// HTTP Link для GraphQL запросов
|
||||
const httpLink = createHttpLink({
|
||||
uri: '/api/graphql',
|
||||
})
|
||||
|
||||
// Auth Link для добавления JWT токена в заголовки
|
||||
const authLink = setContext((operation, { headers }) => {
|
||||
// Получаем токен из localStorage каждый раз
|
||||
const token = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null
|
||||
|
||||
console.log(`Apollo Client - Operation: ${operation.operationName}, Token:`, token ? `${token.substring(0, 20)}...` : 'No token')
|
||||
|
||||
const authHeaders = {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : '',
|
||||
}
|
||||
|
||||
console.log('Apollo Client - Auth headers:', { authorization: authHeaders.authorization ? 'Bearer ***' : 'No auth' })
|
||||
|
||||
return {
|
||||
headers: authHeaders
|
||||
}
|
||||
})
|
||||
|
||||
// Error Link для обработки ошибок
|
||||
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
|
||||
if (graphQLErrors) {
|
||||
graphQLErrors.forEach(({ message, locations, path, extensions }) => {
|
||||
console.error(
|
||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
||||
)
|
||||
|
||||
// Если токен недействителен, очищаем localStorage и перенаправляем на авторизацию
|
||||
// Но не делаем редирект если пользователь уже на главной странице (в процессе авторизации)
|
||||
if (extensions?.code === 'UNAUTHENTICATED') {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('authToken')
|
||||
localStorage.removeItem('userData')
|
||||
// Перенаправляем на страницу авторизации только если не находимся на ней
|
||||
if (window.location.pathname !== '/') {
|
||||
window.location.href = '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
console.error(`[Network error]: ${networkError}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Создаем Apollo Client
|
||||
export const apolloClient = new ApolloClient({
|
||||
link: from([
|
||||
errorLink,
|
||||
authLink,
|
||||
httpLink,
|
||||
]),
|
||||
cache: new InMemoryCache({
|
||||
typePolicies: {
|
||||
User: {
|
||||
fields: {
|
||||
organization: {
|
||||
merge: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Organization: {
|
||||
fields: {
|
||||
apiKeys: {
|
||||
merge: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
defaultOptions: {
|
||||
watchQuery: {
|
||||
errorPolicy: 'all',
|
||||
},
|
||||
query: {
|
||||
errorPolicy: 'all',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Утилитарные функции для работы с токеном и пользователем
|
||||
export const setAuthToken = (token: string) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('authToken', token)
|
||||
}
|
||||
}
|
||||
|
||||
export const removeAuthToken = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('authToken')
|
||||
localStorage.removeItem('userData')
|
||||
}
|
||||
}
|
||||
|
||||
export const getAuthToken = (): string | null => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('authToken')
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const setUserData = (userData: unknown) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('userData', JSON.stringify(userData))
|
||||
}
|
||||
}
|
||||
|
||||
export const getUserData = (): unknown | null => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const data = localStorage.getItem('userData')
|
||||
return data ? JSON.parse(data) : null
|
||||
}
|
||||
return null
|
||||
}
|
11
src/lib/prisma.ts
Normal file
11
src/lib/prisma.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
declare global {
|
||||
var prisma: PrismaClient | undefined
|
||||
}
|
||||
|
||||
export const prisma = globalThis.prisma || new PrismaClient()
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalThis.prisma = prisma
|
||||
}
|
25
src/lib/utils.ts
Normal file
25
src/lib/utils.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
// Функция для форматирования номера телефона
|
||||
export function formatPhone(phone: string): string {
|
||||
if (!phone) return ''
|
||||
|
||||
// Убираем все кроме цифр
|
||||
const digits = phone.replace(/\D/g, '')
|
||||
|
||||
// Если номер начинается с 8, заменяем на 7
|
||||
const normalizedDigits = digits.startsWith('8') ? '7' + digits.slice(1) : digits
|
||||
|
||||
// Проверяем длину номера
|
||||
if (normalizedDigits.length !== 11 || !normalizedDigits.startsWith('7')) {
|
||||
return phone // Возвращаем как есть, если формат неправильный
|
||||
}
|
||||
|
||||
// Форматируем как +7 (999) 999-99-99
|
||||
return `+7 (${normalizedDigits.slice(1, 4)}) ${normalizedDigits.slice(4, 7)}-${normalizedDigits.slice(7, 9)}-${normalizedDigits.slice(9, 11)}`
|
||||
}
|
Reference in New Issue
Block a user