eletrotupi / tcc/ frontend/context/AuthContext.tsx9897f0373a757f09dbe54e9fae5842f71c99703d
4.2 KB | 172 lines Raw
1
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
2
import { apiClient, User } from "@/lib/api";
3
import { queryClient, storage } from "@/lib/queryClient";
4

5
interface AuthProviderProps {
6
  children: React.ReactNode;
7
}
8

9
interface AuthContextType {
10
  user: User | null;
11
  isLoading: boolean;
12
  isAuthenticated: boolean;
13
  updateAuthUser: (user: User) => void;
14
  login: (email: string, password: string) => Promise<{ user: User } | null>;
15
  signup: (userData: {
16
    firstName: string;
17
    lastName?: string;
18
    email: string;
19
    password: string;
20
  }) => Promise<void>;
21
  logout: () => Promise<void>;
22
  forgotPassword: (
23
    email: string,
24
  ) => Promise<{ message: string; token?: string }>;
25
  resetPassword: (token: string, password: string) => Promise<void>;
26
  activate: (code: string) => Promise<void>;
27
  requestActivateCode: () => Promise<void>;
28
}
29

30
export function sanitizeUser(data: User): User {
31
  return {
32
    id: data.id,
33
    email: data.email,
34
    firstName: data.firstName,
35
    lastName: data.lastName,
36
    updatedAt: data.updatedAt,
37
    avatarKey: data.avatarKey,
38
    avatarURL: data.avatarURL,
39
    active: data.active,
40
    notificationsEnabled: data.notificationsEnabled,
41
    dailyReminderTime: data.dailyReminderTime,
42
  };
43
}
44

45
const AuthContext = createContext<AuthContextType | null>(null);
46

47
export function useAuth() {
48
  const context = useContext(AuthContext);
49
  if (!context) {
50
    throw new Error("useAuth must be used within an AuthProvider");
51
  }
52

53
  return context;
54
}
55

56
export const AuthProvider = ({ children }: AuthProviderProps) => {
57
  const [user, setUser] = useState<User | null>(null);
58
  const [isLoading, setIsLoading] = useState(true);
59
  const isAuthenticated = !!user;
60

61
  useEffect(() => {
62
    checkAuthState();
63
  }, []);
64

65
  useEffect(() => {
66
    apiClient.registerUnauthorizedHandler(() => {
67
      logout();
68
    });
69
  }, []);
70

71
  const checkAuthState = async () => {
72
    const doAuthCheck = async () => {
73
      const { token } = await apiClient.getStoredAuthData();
74
      if (token) {
75
        await apiClient.verifyToken();
76
        const { user } = await apiClient.getMe();
77
        setUser(user);
78
      }
79
    };
80

81
    const timeout = new Promise<never>((_, reject) =>
82
      setTimeout(() => reject(new Error("Auth check timed out")), 10_000),
83
    );
84

85
    try {
86
      await Promise.race([doAuthCheck(), timeout]);
87
    } catch (error) {
88
      console.error("Auth check failed:", error);
89
      await apiClient.logout();
90
    } finally {
91
      setIsLoading(false);
92
    }
93
  };
94

95
  const login = async (
96
    email: string,
97
    password: string,
98
  ): Promise<{ user: User } | null> => {
99
    const response = await apiClient.login(email, password);
100

101
    if (response.user) {
102
      setUser(response.user);
103
    }
104

105
    return response;
106
  };
107

108
  const signup = async (userData: {
109
    firstName: string;
110
    lastName?: string;
111
    email: string;
112
    password: string;
113
  }) => {
114
    const response = await apiClient.signup(userData);
115
    setUser(response.user);
116
  };
117

118
  const logout = async () => {
119
    setIsLoading(true);
120
    try {
121
      await apiClient.logout();
122

123
      setUser(null);
124
      // Clear both the in-memory cache and the persisted MMKV cache
125
      queryClient.clear();
126
      storage.clearAll();
127
    } finally {
128
      setIsLoading(false);
129
    }
130
  };
131

132
  const forgotPassword = async (email: string) => {
133
    return apiClient.forgotPassword(email);
134
  };
135

136
  const resetPassword = async (token: string, password: string) => {
137
    await apiClient.resetPassword(token, password);
138
  };
139

140
  const activate = async (code: string) => {
141
    const response = await apiClient.activate(code);
142
    setUser(response.user);
143
  };
144

145
  const requestActivateCode = async () => {
146
    await apiClient.requestActivateCode();
147
  };
148

149
  const updateAuthUser = (user: User) => {
150
    setUser(user);
151
  };
152

153
  const value = useMemo<AuthContextType>(
154
    () => ({
155
      user,
156
      isLoading,
157
      isAuthenticated,
158
      login,
159
      signup,
160
      logout,
161
      forgotPassword,
162
      resetPassword,
163
      updateAuthUser,
164
      activate,
165
      requestActivateCode,
166
    }),
167
    // eslint-disable-next-line react-hooks/exhaustive-deps
168
    [user, isLoading, isAuthenticated],
169
  );
170

171
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
172
};