import AsyncStorage from '@react-native-async-storage/async-storage'; import CryptoJS from 'crypto-js'; import { createContext, useContext, useEffect, useState } from 'react'; import { API_CONFIG, API_ENDPOINTS, buildApiUrl } from '../config/api'; import { clearDownloadedDocuments } from '../services/documentSync'; import { clearUserSessionCache } from '../services/offlineStorage'; import { AuthContextType, AuthProviderProps, LoginResponse, User, UserData } from '../types'; // Contexto const AuthContext = createContext(undefined); // Chaves para AsyncStorage const STORAGE_KEYS = { TOKEN: '@cruiseLovers:token', USER: '@cruiseLovers:user', } as const; // Provider export function AuthProvider({ children }: AuthProviderProps) { const [user, setUser] = useState(null); const [token, setToken] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [userData, setUserData] = useState(null); // Carregar dados salvos ao iniciar useEffect(() => { loadStoredAuth(); }, []); const loadStoredAuth = async () => { try { const [storedToken, storedUser] = await Promise.all([ AsyncStorage.getItem(STORAGE_KEYS.TOKEN), AsyncStorage.getItem(STORAGE_KEYS.USER), ]); if (storedToken && storedUser) { setToken(storedToken); setUser(JSON.parse(storedUser)); } } catch (err) { console.error('Erro ao carregar autenticação:', err); } finally { setIsLoading(false); } }; const login = async (email: string, password: string) => { try { setIsLoading(true); setError(null); const url = buildApiUrl(API_ENDPOINTS.USER_LOGIN); // Encriptar password com MD5 const encryptedPassword = CryptoJS.MD5(password).toString(); // Criar FormData com os dados de login const formData = new FormData(); formData.append('email', email); formData.append('password', encryptedPassword); const response = await fetch(url, { method: 'POST', headers: { 'Accept': 'application/json', // Não definir Content-Type - o React Native define automaticamente para multipart/form-data }, body: formData, }); const data: LoginResponse = await response.json(); if (!response.ok || data.status !== 200) { // A API retorna status 401 com mensagem quando não existe utilizador throw new Error(data.message || 'Erro ao fazer login'); } // Limpar dados do utilizador anterior antes de guardar a nova sessão await clearUserSessionCache(); await clearDownloadedDocuments(); // Se a resposta for bem-sucedida, Guardar token e user if (data.token) { setToken(data.token); await AsyncStorage.setItem(STORAGE_KEYS.TOKEN, data.token); } if (data.user) { setUser(data.user); await AsyncStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(data.user)); } else { // Se não vier user na resposta, criar um objeto básico com email setUser(userData); await AsyncStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(userData)); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Erro desconhecido ao fazer login'; setError(errorMessage); throw err; } finally { setIsLoading(false); } }; const updateProfile = async (nome?: string, apelido?: string, oldPassword?: string, newPassword?: string) => { try { setIsLoading(true); setError(null); if (!token) { throw new Error('Não autenticado'); } const url = buildApiUrl(API_ENDPOINTS.UPDATE_PROFILE); // Criar FormData com os dados de atualização const formData = new FormData(); formData.append('token', token); if (nome) { formData.append('nome', nome); } if (apelido) { formData.append('apelido', apelido); } if (newPassword) { const encryptedNewPassword = CryptoJS.MD5(newPassword).toString(); formData.append('password', encryptedNewPassword); if (oldPassword) { const encryptedOldPassword = CryptoJS.MD5(oldPassword).toString(); formData.append('old_password', encryptedOldPassword); } } const response = await fetch(url, { method: 'POST', headers: { 'Accept': 'application/json', }, body: formData, }); const data: LoginResponse = await response.json(); if (!response.ok || data.status !== 200) { throw new Error(data.message || 'Erro ao atualizar perfil'); } // Atualizar dados do utilizador se vierem na resposta if (data.user) { setUser(data.user); await AsyncStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(data.user)); } else if (nome || apelido) { const updatedUser = { ...user, nome: nome ?? user?.nome, apelido: apelido ?? user?.apelido }; setUser(updatedUser as User); await AsyncStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(updatedUser)); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Erro desconhecido ao atualizar perfil'; setError(errorMessage); throw err; } finally { setIsLoading(false); } }; const logout = async () => { try { setIsLoading(true); // Opcional: chamar endpoint de logout na API if (token) { try { const url = buildApiUrl(API_ENDPOINTS.USER_LOGOUT); await fetch(url, { method: 'POST', headers: { ...API_CONFIG.DEFAULT_HEADERS, Authorization: `Bearer ${token}`, }, }); } catch (err) { console.error('Erro ao fazer logout na API:', err); // Continuar mesmo se falhar } } // Limpar estado local setUser(null); setToken(null); setError(null); await clearUserSessionCache(); await clearDownloadedDocuments(); // Limpar credenciais de sessão await Promise.all([ AsyncStorage.removeItem(STORAGE_KEYS.TOKEN), AsyncStorage.removeItem(STORAGE_KEYS.USER), ]); } catch (err) { console.error('Erro ao fazer logout:', err); } finally { setIsLoading(false); } }; const value: AuthContextType = { user, token, isAuthenticated: !!user && !!token, isLoading, login, logout, updateProfile, error, }; return {children}; } // Hook para usar o contexto export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth deve ser usado dentro de um AuthProvider'); } return context; }