recover password feature
This commit is contained in:
52
assets/components/auth/RecoverScreenLayout.tsx
Normal file
52
assets/components/auth/RecoverScreenLayout.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import styles from '@/styles/screens/auth/recover.styles';
|
||||
import { ReactNode } from 'react';
|
||||
import {
|
||||
Image,
|
||||
ImageBackground,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function RecoverScreenLayout({ title, subtitle, children }: Props) {
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
bounces={false}>
|
||||
<ImageBackground
|
||||
source={require('@/assets/images/banner-login.png')}
|
||||
style={styles.hero}
|
||||
imageStyle={styles.heroImage}>
|
||||
<View style={styles.overlay} />
|
||||
<View style={styles.heroContent}>
|
||||
<Image
|
||||
source={require('@/assets/icons/logotipo-branco.png')}
|
||||
style={styles.logo}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<Text style={styles.subtitle}>{subtitle}</Text>
|
||||
</View>
|
||||
</ImageBackground>
|
||||
|
||||
<View style={styles.formCard}>{children}</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
export const API_BASE_URL = "https://apmtests.webclientes.com/pt/app";
|
||||
export const API_BASE_URL_DEV = "https://apmtests.webclientes.com/pt/app";
|
||||
|
||||
export const API_URL_RECUPERAR_PALAVRA_PASSE = "https://gurudasviagens.pt/pt/areareservada/recuperarPassword/";
|
||||
/**
|
||||
* Endpoints da aplicação
|
||||
* Adicione novos endpoints aqui conforme necessário
|
||||
@@ -19,7 +18,10 @@ export const API_ENDPOINTS = {
|
||||
USER_LOGIN: "/UserLogin",
|
||||
USER_LOGOUT: "/UserLogout",
|
||||
UPDATE_PROFILE: "/UserEditProfile",
|
||||
|
||||
RECOVER_PASSWORD: "/recoverPassword",
|
||||
CONFIRM_TOKEN: "/confirmToken",
|
||||
RESET_PASSWORD: "/resetPassword",
|
||||
|
||||
// Utilizador
|
||||
USER_RESERVAS: "/getUserReservas",
|
||||
USER_INFO: "/UserInfo",
|
||||
|
||||
@@ -2,6 +2,8 @@ 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);
|
||||
@@ -74,6 +76,10 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
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);
|
||||
@@ -186,7 +192,10 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
setToken(null);
|
||||
setError(null);
|
||||
|
||||
// Limpar AsyncStorage
|
||||
await clearUserSessionCache();
|
||||
await clearDownloadedDocuments();
|
||||
|
||||
// Limpar credenciais de sessão
|
||||
await Promise.all([
|
||||
AsyncStorage.removeItem(STORAGE_KEYS.TOKEN),
|
||||
AsyncStorage.removeItem(STORAGE_KEYS.USER),
|
||||
|
||||
@@ -128,6 +128,23 @@ export const verificarDocumentosParaDownload = async (
|
||||
return para;
|
||||
};
|
||||
|
||||
/** Remove documentos descarregados e checksums (ex.: no logout). */
|
||||
export const clearDownloadedDocuments = async (): Promise<void> => {
|
||||
try {
|
||||
const keys = await AsyncStorage.getAllKeys();
|
||||
const docKeys = keys.filter((key) => key.startsWith("@cruiseLovers:documento:"));
|
||||
if (docKeys.length > 0) {
|
||||
await AsyncStorage.multiRemove(docKeys);
|
||||
}
|
||||
const info = await FileSystem.getInfoAsync(DOCUMENTOS_DIR);
|
||||
if (info.exists) {
|
||||
await FileSystem.deleteAsync(DOCUMENTOS_DIR, { idempotent: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao limpar documentos locais:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Descarrega múltiplos documentos
|
||||
export const downloadDocumentos = async (
|
||||
documentos: DocumentoChecksum[],
|
||||
|
||||
@@ -166,9 +166,11 @@ export const clearCache = async (): Promise<void> => {
|
||||
export const clearReservasCache = async (): Promise<void> => {
|
||||
try {
|
||||
const keys = await AsyncStorage.getAllKeys();
|
||||
const reservaKeys = keys.filter(key =>
|
||||
key === STORAGE_KEYS.RESERVAS ||
|
||||
key.startsWith(STORAGE_KEYS.RESERVA_DETAIL)
|
||||
const reservaKeys = keys.filter(
|
||||
(key) =>
|
||||
key === STORAGE_KEYS.RESERVAS ||
|
||||
key.startsWith(STORAGE_KEYS.RESERVA_DETAIL) ||
|
||||
key.startsWith(STORAGE_KEYS.RESERVA_FULL),
|
||||
);
|
||||
await AsyncStorage.multiRemove(reservaKeys);
|
||||
} catch (error) {
|
||||
@@ -176,6 +178,22 @@ export const clearReservasCache = async (): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
/** Dados de sessão do utilizador — mantém contactos da agência (partilhados). */
|
||||
export const clearUserSessionCache = async (): Promise<void> => {
|
||||
try {
|
||||
const keys = await AsyncStorage.getAllKeys();
|
||||
const sessionKeys = keys.filter(
|
||||
(key) =>
|
||||
key.startsWith("@cruiseLovers:") &&
|
||||
key !== STORAGE_KEYS.CONTACTS &&
|
||||
key !== STORAGE_KEYS.SOCIALS,
|
||||
);
|
||||
await AsyncStorage.multiRemove(sessionKeys);
|
||||
} catch (error) {
|
||||
console.error("Erro ao limpar cache da sessão:", error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Armazena informações do perfil no cache
|
||||
*/
|
||||
|
||||
50
assets/services/passwordRecovery.ts
Normal file
50
assets/services/passwordRecovery.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { API_ENDPOINTS, buildApiUrl } from '@/assets/config/api';
|
||||
import { ApiMessageResponse } from '@/assets/types';
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
async function postToEndpoint(
|
||||
endpoint: string,
|
||||
fields: Record<string, string>,
|
||||
): Promise<ApiMessageResponse> {
|
||||
const formData = new FormData();
|
||||
Object.entries(fields).forEach(([key, value]) => formData.append(key, value));
|
||||
|
||||
const response = await fetch(buildApiUrl(endpoint), {
|
||||
method: 'POST',
|
||||
headers: { Accept: 'application/json' },
|
||||
body: formData,
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function recoverPassword(email: string): Promise<ApiMessageResponse> {
|
||||
return postToEndpoint(API_ENDPOINTS.RECOVER_PASSWORD, { email: email.trim() });
|
||||
}
|
||||
|
||||
export async function confirmRecoveryToken(
|
||||
email: string,
|
||||
token: string,
|
||||
): Promise<ApiMessageResponse> {
|
||||
return postToEndpoint(API_ENDPOINTS.CONFIRM_TOKEN, {
|
||||
email: email.trim(),
|
||||
token: token.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
export async function resetPassword(
|
||||
email: string,
|
||||
token: string,
|
||||
password: string,
|
||||
confirmPassword: string,
|
||||
): Promise<ApiMessageResponse> {
|
||||
const encryptedPassword = CryptoJS.MD5(password).toString();
|
||||
const encryptedConfirm = CryptoJS.MD5(confirmPassword).toString();
|
||||
|
||||
return postToEndpoint(API_ENDPOINTS.RESET_PASSWORD, {
|
||||
email: email.trim(),
|
||||
token: token.trim(),
|
||||
password: encryptedPassword,
|
||||
confirmPassword: encryptedConfirm,
|
||||
});
|
||||
}
|
||||
@@ -34,6 +34,11 @@ export interface LoginResponse {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ApiMessageResponse {
|
||||
status: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface UserData {
|
||||
email: string;
|
||||
nome: string;
|
||||
|
||||
Reference in New Issue
Block a user