useAuth.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. 'use client'
  2. import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react'
  3. import type { User } from '@/types'
  4. import { api, setAccessToken } from '@/lib/api'
  5. /**
  6. * Состояние аутентификации, доступное через контекст
  7. * @property user - Текущий пользователь или null
  8. * @property isLoading - Флаг загрузки сессии
  9. * @property login - Функция входа (email, пароль)
  10. * @property register - Функция регистрации (email, пароль, роль)
  11. * @property logout - Функция выхода
  12. * @property refreshSession - Функция обновления сессии
  13. */
  14. interface AuthState {
  15. user: User | null
  16. isLoading: boolean
  17. login: (email: string, password: string) => Promise<void>
  18. register: (email: string, password: string, role: string) => Promise<void>
  19. logout: () => Promise<void>
  20. refreshSession: () => Promise<void>
  21. }
  22. const AuthContext = createContext<AuthState | null>(null)
  23. /**
  24. * Провайдер аутентификации. Оборачивает приложение в контекст AuthState.
  25. * При монтировании пытается восстановить сессию через /auth/me.
  26. * Предоставляет дочерним компонентам методы login, register, logout, refreshSession.
  27. */
  28. export function AuthProvider({ children }: { children: ReactNode }) {
  29. const [user, setUser] = useState<User | null>(null)
  30. const [isLoading, setIsLoading] = useState(true)
  31. const refreshSession = useCallback(async () => {
  32. try {
  33. const res = await api.get<User>('/auth/me')
  34. setUser(res)
  35. } catch {
  36. setAccessToken(null)
  37. setUser(null)
  38. }
  39. }, [])
  40. useEffect(() => {
  41. refreshSession().finally(() => setIsLoading(false))
  42. }, [refreshSession])
  43. const login = useCallback(async (email: string, password: string) => {
  44. const res = await api.post<{ user: User; access_token: string }>('/auth/login', { email, password })
  45. setAccessToken(res.access_token)
  46. setUser(res.user)
  47. }, [])
  48. const register = useCallback(async (email: string, password: string, role: string) => {
  49. const res = await api.post<{ user: User; access_token: string }>('/auth/register', { email, password, role })
  50. setAccessToken(res.access_token)
  51. setUser(res.user)
  52. }, [])
  53. const logout = useCallback(async () => {
  54. try {
  55. await api.post('/auth/logout', {})
  56. } catch {}
  57. setAccessToken(null)
  58. setUser(null)
  59. }, [])
  60. return (
  61. <AuthContext.Provider value={{ user, isLoading, login, register, logout, refreshSession }}>
  62. {children}
  63. </AuthContext.Provider>
  64. )
  65. }
  66. /**
  67. * Хук для доступа к контексту аутентификации.
  68. * Должен использоваться внутри компонента AuthProvider.
  69. * @returns Объект AuthState с пользователем, состоянием загрузки и методами auth
  70. * @throws Ошибка, если хук вызван вне AuthProvider
  71. */
  72. export function useAuth() {
  73. const ctx = useContext(AuthContext)
  74. if (!ctx) throw new Error('useAuth must be used within AuthProvider')
  75. return ctx
  76. }