223 lines
6.5 KiB
TypeScript
223 lines
6.5 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 { 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');
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Limpar AsyncStorage
|
|
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;
|
|
}
|