feat: migrate from useAuth to AuthContext for centralized auth state

• Полная миграция 64 компонентов с useAuth на AuthContext
• Исправлена race condition в SMS регистрации
• Улучшена SSR совместимость с таймаутами
• Удалена дублирующая система регистрации
• Обновлена документация архитектуры аутентификации

Технические изменения:
- AuthContext.tsx: централизованная система состояния
- auth-flow.tsx: убрана агрессивная логика logout
- confirmation-step.tsx: исправлена передача телефона
- page.tsx: добавлена синхронизация состояния
- 64 файла: миграция useAuth → useAuthContext

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-09-19 17:21:52 +03:00
parent d19530a985
commit 24a6ff74b5
91 changed files with 3626 additions and 7296 deletions

View File

@ -155,11 +155,15 @@ const CardContent = React.forwardRef<HTMLDivElement, CardProps>(
## 🎮 HOOKS ПАТТЕРНЫ
### useAuth - Централизованная авторизация
### AuthContext - Централизованная авторизация ✅ НОВАЯ АРХИТЕКТУРА
**⚡ МИГРАЦИЯ ЗАВЕРШЕНА (19.09.2025): useAuth → AuthContext**
```typescript
// src/hooks/useAuth.ts
export const useAuth = (): UseAuthReturn => {
// src/contexts/AuthContext.tsx - НОВАЯ АРХИТЕКТУРА
export const AuthContext = createContext<AuthContextType | undefined>(undefined)
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(() => !!getAuthToken())
const [isLoading, setIsLoading] = useState(false)
@ -230,12 +234,32 @@ export const useAuth = (): UseAuthReturn => {
}
```
**Ключевые паттерны useAuth:**
// Использование в компонентах:
export const useAuthContext = (): AuthContextType => {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuthContext must be used within an AuthProvider')
}
return context
}
```
- **Lazy initialization** - проверка токена при инициализации
- **Error handling** - обработка GraphQL ошибок
- **Token persistence** - автоматическое сохранение токенов
- **Apollo integration** - синхронизация с GraphQL клиентом
**✅ Ключевые преимущества AuthContext (vs старый useAuth):**
- **Централизованное состояние** - единое состояние для всего приложения
- **Устранение изоляции** - нет дублирования состояния между компонентами
- **React Context API** - стандартный React паттерн
- **SSR совместимость** - корректная работа с Next.js
- **Race conditions fix** - исправлены проблемы с синхронизацией
**📋 Паттерн использования:**
```typescript
// ✅ Новый способ (все компоненты)
const { user, isAuthenticated, logout } = useAuthContext()
// ❌ Старый способ (удален после миграции)
// const { user, isAuthenticated, logout } = useAuth()
```
### useSidebar - Управление состоянием сайдбара
@ -268,7 +292,7 @@ export const useSidebar = () => {
```typescript
// src/components/dashboard/sidebar.tsx
export function Sidebar({ isRootInstance = false }: { isRootInstance?: boolean }) {
const { user, logout } = useAuth()
const { user, logout } = useAuthContext()
const { isCollapsed, toggleSidebar } = useSidebar()
const pathname = usePathname()
@ -375,7 +399,7 @@ export function Sidebar({ isRootInstance = false }: { isRootInstance?: boolean }
```typescript
// src/components/messenger/messenger-chat.tsx
export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatProps) {
const { user } = useAuth()
const { user } = useAuthContext()
const [message, setMessage] = useState('')
const messagesEndRef = useRef<HTMLDivElement>(null)

View File

@ -253,7 +253,7 @@ glass-input: прозрачные инпуты с размытием
### Обязательные Хуки
- `useSidebar()` - управление боковой панелью
- `useAuth()` - аутентификация и права доступа
- `useAuthContext()` - аутентификация и права доступа
- Для WB: проверка `hasWBApiKey` перед загрузкой
### GraphQL Операции

View File

@ -0,0 +1,305 @@
# АРХИТЕКТУРА АУТЕНТИФИКАЦИИ SFERA
**Дата последнего обновления:** 19 сентября 2025
**Статус:** ✅ АКТУАЛЬНО (после миграции useAuth → AuthContext)
---
## 🎯 ТЕКУЩАЯ АРХИТЕКТУРА
### AuthContext - Централизованное управление аутентификацией
**Файл:** `src/contexts/AuthContext.tsx`
```typescript
interface AuthContextType {
// Состояние пользователя
user: User | null
isAuthenticated: boolean
isLoading: boolean
// Методы аутентификации
sendSmsCode: (phone: string) => Promise<any>
verifySmsCode: (phone: string, code: string) => Promise<any>
registerOrganization: (input: any) => Promise<any>
checkAuth: () => Promise<void>
logout: () => void
}
```
### Использование в компонентах
```typescript
import { useAuthContext } from '@/contexts/AuthContext'
export function MyComponent() {
const { user, isAuthenticated, logout } = useAuthContext()
if (!isAuthenticated) {
return <LoginForm />
}
return (
<div>
<p>Добро пожаловать, {user?.organization?.name}!</p>
<button onClick={logout}>Выход</button>
</div>
)
}
```
---
## 🏗️ СТРУКТУРА ПРОВАЙДЕРА
### AuthProvider в app/providers.tsx
```typescript
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ApolloProvider client={apolloClient}>
<AuthProvider>
{children}
</AuthProvider>
</ApolloProvider>
)
}
```
**🔑 Ключевая особенность:** AuthProvider оборачивает все приложение, обеспечивая единое состояние аутентификации.
---
## 📋 ТИПЫ ПОЛЬЗОВАТЕЛЕЙ И ОРГАНИЗАЦИЙ
### User Interface
```typescript
interface User {
id: string
phone: string
avatar?: string
managerName?: string
createdAt?: string
organization?: {
id: string
inn: string
kpp?: string
name?: string
fullName?: string
address?: string
type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
referralPoints?: number
apiKeys?: ApiKey[]
}
}
```
### Типы организаций
- **FULFILLMENT** - фулфилмент центры
- **SELLER** - продавцы на маркетплейсах
- **LOGIST** - логистические компании
- **WHOLESALE** - оптовые поставщики
---
## 🔄 ПРОЦЕСС АУТЕНТИФИКАЦИИ
### 1. SMS Авторизация
```typescript
// Отправка SMS кода
const result = await sendSmsCode(phone)
// Подтверждение кода
const verification = await verifySmsCode(phone, code)
```
### 2. Регистрация организации
```typescript
const registrationResult = await registerOrganization({
organizationData: {
inn: '1234567890',
phone: '+7900123456',
type: 'SELLER',
wbApiKey: 'wb_token',
ozonApiKey: 'ozon_token'
}
})
```
### 3. Проверка авторизации
```typescript
// Автоматически вызывается при инициализации
await checkAuth()
// Проверяет токен и загружает данные пользователя
// Если токен невалиден - выполняет logout
```
---
## 🚀 МАРШРУТИЗАЦИЯ ПО РОЛЯМ
### Главная страница (app/page.tsx)
```typescript
export default function Home() {
const { user, isLoading, isAuthenticated } = useAuthContext()
useEffect(() => {
if (isLoading) return // Ждем загрузки
if (user?.organization) {
router.replace('/dashboard') // Авторизованный пользователь
} else if (isAuthenticated && user && !user.organization) {
router.replace('/register') // Продолжить регистрацию
} else {
router.replace('/login') // Неавторизованный
}
}, [user, isLoading, isAuthenticated])
}
```
### Защищенные маршруты (AuthGuard)
```typescript
export function AuthGuard({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuthContext()
if (isLoading) return <LoadingScreen />
if (!isAuthenticated) return <LoginPage />
return <>{children}</>
}
```
---
## 🔧 ИНТЕГРАЦИЯ С GRAPHQL
### Apollo Client
AuthContext автоматически:
- Устанавливает токены в Apollo Client
- Обрабатывает UNAUTHENTICATED ошибки
- Синхронизирует состояние с сервером
```typescript
// При логине
setAuthToken(token)
setUserData(userData)
// При logout
removeAuthToken()
apolloClient.resetStore()
```
---
## 📊 СОСТОЯНИЕ ЗАГРУЗКИ
### Флаги состояния
- **isLoading** - идет проверка аутентификации
- **isAuthenticated** - пользователь авторизован
- **user** - данные пользователя (null если не авторизован)
### Паттерн использования
```typescript
function Component() {
const { user, isLoading, isAuthenticated } = useAuthContext()
if (isLoading) {
return <Spinner />
}
if (!isAuthenticated) {
return <LoginPrompt />
}
return <AuthenticatedContent user={user} />
}
```
---
## 🔄 МИГРАЦИЯ useAuth → AuthContext
**✅ ЗАВЕРШЕНА:** 19 сентября 2025
### Что изменилось
| Аспект | useAuth (старое) | AuthContext (новое) |
|--------|------------------|---------------------|
| **Архитектура** | Изолированные хуки | Централизованный контекст |
| **Состояние** | Дублируется в каждом компоненте | Единое состояние для всего приложения |
| **Race conditions** | Присутствовали | Исправлены |
| **SSR** | Проблемы с Next.js | Полная совместимость |
| **Импорт** | `useAuth()` | `useAuthContext()` |
### Статистика миграции
- **64 файла** мигрированы
- **0 остатков** старого кода
- **9 backup файлов** удалены после тестирования
- **Все тесты** пройдены успешно
---
## 🎯 ЛУЧШИЕ ПРАКТИКИ
### ✅ Правильные паттерны
```typescript
// Корректная проверка аутентификации
const { user, isAuthenticated, isLoading } = useAuthContext()
if (isLoading) {
return <LoadingState />
}
if (!isAuthenticated) {
return <UnauthenticatedState />
}
// Теперь user гарантированно не null
return <AuthenticatedContent user={user} />
```
### ❌ Избегайте
```typescript
// Неправильно - не проверяем isLoading
const { user } = useAuthContext()
if (user) { // может быть false positive во время загрузки
// ...
}
// Неправильно - прямое обращение к токену
const token = getAuthToken() // используйте isAuthenticated
```
---
## 🔐 БЕЗОПАСНОСТЬ
### Токены
- **Автоматическое удаление** при logout
- **Проверка валидности** при каждом запросе
- **Безопасное хранение** в localStorage
### API Keys
- **Шифрование** в БД
- **Валидация** при сохранении
- **Ротация** через UI
---
**🎉 Архитектура аутентификации SFERA готова к production использованию!**

View File

@ -75,7 +75,7 @@ src/components/dashboard/
// Структура dashboard компонента:
export function FulfillmentDashboard() {
const { organization } = useAuth()
const { user } = useAuthContext()
// Условная маршрутизация по функциям
if (activeTab === 'supplies') return <FulfillmentSuppliesTab />

View File

@ -260,7 +260,7 @@ export const logistNavigation: LogistNavigationItem[] = [
```typescript
export function LogistSidebar() {
const { user, logout } = useAuth()
const { user, logout } = useAuthContext()
const router = useRouter()
const pathname = usePathname()
const { isCollapsed, toggleSidebar } = useSidebar()
@ -324,7 +324,7 @@ export function LogistSidebar() {
```typescript
export function Sidebar({ isRootInstance = false }: { isRootInstance?: boolean } = {}) {
const { user } = useAuth()
const { user } = useAuthContext()
// Защита от дубликатов
if (typeof window !== 'undefined' && !isRootInstance && window.__SIDEBAR_ROOT_MOUNTED__) {

View File

@ -188,7 +188,7 @@ import { BaseSidebar } from './BaseSidebar'
import { NavigationItem } from './types'
export function LogistSidebar() {
const { user } = useAuth()
const { user } = useAuthContext()
const pathname = usePathname()
const { isCollapsed, toggleSidebar } = useSidebar()
@ -284,7 +284,7 @@ import { WholesaleSidebar } from './WholesaleSidebar'
import { LogistSidebar } from './LogistSidebar'
export function Sidebar() {
const { user } = useAuth()
const { user } = useAuthContext()
if (!user?.organization?.type) {
return (