Files
cruiseLovers/assets/contexts/useAuth.tsx

232 lines
6.9 KiB
TypeScript

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<AuthContextType | undefined>(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<User | null>(null);
const [token, setToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [userData, setUserData] = useState<UserData | null>(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 <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
// 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;
}