@fadyshawky/react-native-magic 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +7 -0
- package/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/template/src/common/localization/translations/loginLocalization.ts +18 -2
- package/template/src/core/store/user/userActions.ts +47 -0
- package/template/src/core/store/user/userSlice.ts +59 -2
- package/template/src/navigation/AuthStack.tsx +16 -0
- package/template/src/navigation/types.ts +7 -1
- package/template/src/screens/Login/Login.tsx +59 -2
- package/template/src/screens/registration/RegistrationScreen.tsx +198 -0
- package/template/src/screens/resetPassword/ForgotPasswordScreen.tsx +129 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.6] - 2024-12-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added missing password reset handlers (`resetPasswordErrorHandler` and `resetPasswordLoadingHandler`) to user slice
|
|
9
|
+
- Added password reset flow with the following states:
|
|
10
|
+
- Loading state during password reset request
|
|
11
|
+
- Error handling for failed password reset attempts
|
|
12
|
+
- Success handling for completed password reset
|
|
13
|
+
- Added complete user registration flow:
|
|
14
|
+
- User input validation
|
|
15
|
+
- Registration request handling
|
|
16
|
+
- Success state with automatic login
|
|
17
|
+
- Error handling for failed registration attempts
|
|
18
|
+
- Loading states during registration process
|
|
19
|
+
|
|
5
20
|
## [1.0.5] - 2024-12-23
|
|
6
21
|
|
|
7
22
|
### Fixed
|
package/package.json
CHANGED
|
@@ -6,9 +6,25 @@ export const loginLocalization = {
|
|
|
6
6
|
EnterEmail: 'Enter email',
|
|
7
7
|
Password: 'Password',
|
|
8
8
|
EnterPassword: 'Enter password',
|
|
9
|
-
forgetPassword: '
|
|
9
|
+
forgetPassword: 'Forgot Password?',
|
|
10
10
|
continue: 'continue',
|
|
11
|
-
notMember: '
|
|
11
|
+
notMember: 'Not a member? Register now',
|
|
12
12
|
findNursery: 'Find nursery',
|
|
13
|
+
registration: {
|
|
14
|
+
title: 'Create Account',
|
|
15
|
+
fullName: 'Full Name',
|
|
16
|
+
email: 'Email',
|
|
17
|
+
password: 'Password',
|
|
18
|
+
confirmPassword: 'Confirm Password',
|
|
19
|
+
register: 'Register',
|
|
20
|
+
alreadyHaveAccount: 'Already have an account? Login',
|
|
21
|
+
},
|
|
22
|
+
forgotPassword: {
|
|
23
|
+
title: 'Forgot Password',
|
|
24
|
+
description:
|
|
25
|
+
"Enter your email address and we'll send you instructions to reset your password.",
|
|
26
|
+
resetPassword: 'Reset Password',
|
|
27
|
+
backToLogin: 'Back to Login',
|
|
28
|
+
},
|
|
13
29
|
},
|
|
14
30
|
};
|
|
@@ -25,3 +25,50 @@ export const userLogin = createAsyncThunk(
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
);
|
|
28
|
+
|
|
29
|
+
export const userRegister = createAsyncThunk(
|
|
30
|
+
'user/register',
|
|
31
|
+
async (
|
|
32
|
+
{
|
|
33
|
+
fullName,
|
|
34
|
+
email,
|
|
35
|
+
password,
|
|
36
|
+
}: {
|
|
37
|
+
fullName: string;
|
|
38
|
+
email: string;
|
|
39
|
+
password: string;
|
|
40
|
+
},
|
|
41
|
+
{rejectWithValue},
|
|
42
|
+
) => {
|
|
43
|
+
try {
|
|
44
|
+
const response = await post({
|
|
45
|
+
url: '/register',
|
|
46
|
+
data: {
|
|
47
|
+
fullName,
|
|
48
|
+
email,
|
|
49
|
+
password,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return handleFetchJsonResponse(response);
|
|
54
|
+
} catch (e: any) {
|
|
55
|
+
return rejectWithValue(e?.response);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const resetPassword = createAsyncThunk(
|
|
61
|
+
'user/resetPassword',
|
|
62
|
+
async ({email}: {email: string}, {rejectWithValue}) => {
|
|
63
|
+
try {
|
|
64
|
+
const response = await post({
|
|
65
|
+
url: '/reset-password',
|
|
66
|
+
data: {email},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return handleFetchJsonResponse(response);
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
return rejectWithValue(e?.response);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
);
|
|
@@ -2,7 +2,7 @@ import {createSlice} from '@reduxjs/toolkit';
|
|
|
2
2
|
import {LoadState} from '../../../../types';
|
|
3
3
|
import {newState} from '../../../common/utils/newState';
|
|
4
4
|
import {handleErrorResponse} from '../../api/responseHandlers';
|
|
5
|
-
import {userLogin} from './userActions';
|
|
5
|
+
import {resetPassword, userLogin, userRegister} from './userActions';
|
|
6
6
|
import {UserInitialState, UserPayload, UserState} from './userState';
|
|
7
7
|
|
|
8
8
|
function loginHandler(state: UserState, payload: {payload: UserPayload}) {
|
|
@@ -32,6 +32,47 @@ function logoutHandler(state: UserState) {
|
|
|
32
32
|
return newState(state, UserInitialState);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function resetPasswordHandler(
|
|
36
|
+
state: UserState,
|
|
37
|
+
payload: {payload: UserPayload},
|
|
38
|
+
) {
|
|
39
|
+
return newState(state, {
|
|
40
|
+
user: payload.payload.user,
|
|
41
|
+
accessToken: payload.payload.token,
|
|
42
|
+
loginLoading: LoadState['allIsLoaded'],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resetPasswordLoadingHandler(state: UserState) {
|
|
47
|
+
return newState(state, {
|
|
48
|
+
loginLoading: LoadState['pullToRefresh'],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function registerHandler(state: UserState, payload: {payload: UserPayload}) {
|
|
53
|
+
return newState(state, {
|
|
54
|
+
user: payload.payload.user,
|
|
55
|
+
accessToken: payload.payload.token,
|
|
56
|
+
loginLoading: LoadState['allIsLoaded'],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function registerLoadingHandler(state: UserState) {
|
|
61
|
+
return newState(state, {
|
|
62
|
+
loginLoading: LoadState['pullToRefresh'],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function registerErrorHandler(
|
|
67
|
+
state: UserState,
|
|
68
|
+
payload: {payload: string | unknown},
|
|
69
|
+
) {
|
|
70
|
+
handleErrorResponse((payload.payload as string) || 'Register failed');
|
|
71
|
+
return newState(state, {
|
|
72
|
+
loginLoading: LoadState['error'],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
35
76
|
function updateHandler(state: UserState, payload: any) {
|
|
36
77
|
return newState(state, {
|
|
37
78
|
user: {
|
|
@@ -41,6 +82,16 @@ function updateHandler(state: UserState, payload: any) {
|
|
|
41
82
|
});
|
|
42
83
|
}
|
|
43
84
|
|
|
85
|
+
function resetPasswordErrorHandler(
|
|
86
|
+
state: UserState,
|
|
87
|
+
payload: {payload: string | unknown},
|
|
88
|
+
) {
|
|
89
|
+
handleErrorResponse((payload.payload as string) || 'Password reset failed');
|
|
90
|
+
return newState(state, {
|
|
91
|
+
loginLoading: LoadState['error'],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
44
95
|
export const {reducer: UserReducer, actions} = createSlice({
|
|
45
96
|
name: 'user',
|
|
46
97
|
initialState: UserInitialState,
|
|
@@ -55,7 +106,13 @@ export const {reducer: UserReducer, actions} = createSlice({
|
|
|
55
106
|
builder
|
|
56
107
|
.addCase(userLogin.fulfilled, loginHandler)
|
|
57
108
|
.addCase(userLogin.rejected, loginErrorHandler)
|
|
58
|
-
.addCase(userLogin.pending, loginLoadingHandler)
|
|
109
|
+
.addCase(userLogin.pending, loginLoadingHandler)
|
|
110
|
+
.addCase(resetPassword.fulfilled, resetPasswordHandler)
|
|
111
|
+
.addCase(resetPassword.rejected, resetPasswordErrorHandler)
|
|
112
|
+
.addCase(resetPassword.pending, resetPasswordLoadingHandler)
|
|
113
|
+
.addCase(userRegister.fulfilled, registerHandler)
|
|
114
|
+
.addCase(userRegister.rejected, registerErrorHandler)
|
|
115
|
+
.addCase(userRegister.pending, registerLoadingHandler);
|
|
59
116
|
},
|
|
60
117
|
});
|
|
61
118
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {createNativeStackNavigator} from '@react-navigation/native-stack';
|
|
2
2
|
import {Login} from '../screens/Login/Login';
|
|
3
3
|
import {Splash} from '../screens/splash/Splash';
|
|
4
|
+
import RegistrationScreen from '../screens/registration/RegistrationScreen';
|
|
5
|
+
import ForgotPasswordScreen from '../screens/resetPassword/ForgotPasswordScreen';
|
|
4
6
|
|
|
5
7
|
const Stack = createNativeStackNavigator();
|
|
6
8
|
|
|
@@ -19,6 +21,20 @@ const AuthScreens = [
|
|
|
19
21
|
headerShown: false,
|
|
20
22
|
},
|
|
21
23
|
},
|
|
24
|
+
{
|
|
25
|
+
id: 'Registration',
|
|
26
|
+
component: RegistrationScreen,
|
|
27
|
+
options: {
|
|
28
|
+
headerShown: false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'ForgotPassword',
|
|
33
|
+
component: ForgotPasswordScreen,
|
|
34
|
+
options: {
|
|
35
|
+
headerShown: false,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
22
38
|
];
|
|
23
39
|
|
|
24
40
|
export function AuthStack() {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export type RootStackParamList = {
|
|
2
2
|
Home: undefined;
|
|
3
3
|
Details: {id: string};
|
|
4
|
-
|
|
4
|
+
Login: undefined;
|
|
5
|
+
Registration: undefined;
|
|
6
|
+
ForgotPassword: undefined;
|
|
7
|
+
Main: undefined;
|
|
8
|
+
Profile: undefined;
|
|
9
|
+
Settings: undefined;
|
|
10
|
+
Splash: undefined;
|
|
5
11
|
};
|
|
@@ -1,30 +1,54 @@
|
|
|
1
|
+
import {useNavigation} from '@react-navigation/native';
|
|
2
|
+
import type {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
|
1
3
|
import {toLower} from 'lodash';
|
|
2
4
|
import React, {useRef, useState} from 'react';
|
|
3
5
|
import {
|
|
6
|
+
findNodeHandle,
|
|
4
7
|
NativeSyntheticEvent,
|
|
5
8
|
StyleSheet,
|
|
6
9
|
Text,
|
|
7
10
|
TextInputFocusEventData,
|
|
8
|
-
findNodeHandle,
|
|
9
11
|
} from 'react-native';
|
|
10
12
|
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
|
11
13
|
import {ButtonType} from '../../../types';
|
|
12
14
|
import {PrimaryButton} from '../../common/components/PrimaryButton';
|
|
13
15
|
import {PrimaryTextInput} from '../../common/components/PrimaryTextInput';
|
|
14
16
|
import {localization} from '../../common/localization/localization';
|
|
17
|
+
import {emptyValidation} from '../../common/validations/commonValidations';
|
|
18
|
+
import {useInputError} from '../../common/validations/hooks/useInputError';
|
|
19
|
+
import {emailValidations} from '../../common/validations/profileValidations';
|
|
15
20
|
import {useAppDispatch} from '../../core/store/reduxHelpers';
|
|
16
21
|
import {userLogin} from '../../core/store/user/userActions';
|
|
17
22
|
import {Colors} from '../../core/theme/colors';
|
|
18
23
|
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
19
24
|
import {CommonStyles} from '../../core/theme/commonStyles';
|
|
25
|
+
import type {RootStackParamList} from '../../navigation/types';
|
|
20
26
|
|
|
21
27
|
export function Login(): JSX.Element {
|
|
22
28
|
const dispatch = useAppDispatch();
|
|
23
29
|
const [email, setEmail] = useState('');
|
|
24
30
|
const [password, setPassword] = useState('');
|
|
25
31
|
const [loading, setLoading] = useState(false);
|
|
32
|
+
const navigation =
|
|
33
|
+
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
|
34
|
+
|
|
35
|
+
const {error: emailError, recheckValue: recheckEmail} = useInputError(
|
|
36
|
+
email,
|
|
37
|
+
emailValidations,
|
|
38
|
+
);
|
|
39
|
+
const {error: passwordError, recheckValue: recheckPassword} = useInputError(
|
|
40
|
+
password,
|
|
41
|
+
emptyValidation,
|
|
42
|
+
);
|
|
26
43
|
|
|
27
44
|
async function loginUser() {
|
|
45
|
+
const emailValid = recheckEmail() === null;
|
|
46
|
+
const passwordValid = recheckPassword() === null;
|
|
47
|
+
|
|
48
|
+
if (!emailValid || !passwordValid) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
setLoading(true);
|
|
29
53
|
await dispatch(
|
|
30
54
|
userLogin({
|
|
@@ -32,7 +56,6 @@ export function Login(): JSX.Element {
|
|
|
32
56
|
password,
|
|
33
57
|
}),
|
|
34
58
|
);
|
|
35
|
-
|
|
36
59
|
setLoading(false);
|
|
37
60
|
}
|
|
38
61
|
const scroll = useRef<KeyboardAwareScrollView>(null);
|
|
@@ -43,6 +66,15 @@ export function Login(): JSX.Element {
|
|
|
43
66
|
scroll.current?.scrollToFocusedInput(reactNode);
|
|
44
67
|
// }, 500);
|
|
45
68
|
}
|
|
69
|
+
|
|
70
|
+
const goToRegistration = () => {
|
|
71
|
+
navigation.navigate('Registration');
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const goToForgotPassword = () => {
|
|
75
|
+
navigation.navigate('ForgotPassword');
|
|
76
|
+
};
|
|
77
|
+
|
|
46
78
|
return (
|
|
47
79
|
<KeyboardAwareScrollView
|
|
48
80
|
ref={scroll}
|
|
@@ -64,6 +96,7 @@ export function Login(): JSX.Element {
|
|
|
64
96
|
containerStyle={CommonStyles.textInputContainer}
|
|
65
97
|
label={localization.login.Email}
|
|
66
98
|
placeholder={localization.login.EnterEmail}
|
|
99
|
+
error={emailError}
|
|
67
100
|
/>
|
|
68
101
|
<PrimaryTextInput
|
|
69
102
|
onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
@@ -76,6 +109,12 @@ export function Login(): JSX.Element {
|
|
|
76
109
|
containerStyle={CommonStyles.textInputContainer}
|
|
77
110
|
label={localization.login.Password}
|
|
78
111
|
placeholder={localization.login.EnterPassword}
|
|
112
|
+
error={passwordError}
|
|
113
|
+
/>
|
|
114
|
+
<PrimaryButton
|
|
115
|
+
onPress={goToForgotPassword}
|
|
116
|
+
label={localization.login.forgetPassword}
|
|
117
|
+
type={ButtonType.borderless}
|
|
79
118
|
/>
|
|
80
119
|
<PrimaryButton
|
|
81
120
|
isLoading={loading}
|
|
@@ -83,6 +122,11 @@ export function Login(): JSX.Element {
|
|
|
83
122
|
label={localization.login.continue}
|
|
84
123
|
type={ButtonType.solid}
|
|
85
124
|
/>
|
|
125
|
+
<PrimaryButton
|
|
126
|
+
onPress={goToRegistration}
|
|
127
|
+
label={localization.login.notMember}
|
|
128
|
+
type={ButtonType.borderless}
|
|
129
|
+
/>
|
|
86
130
|
</KeyboardAwareScrollView>
|
|
87
131
|
);
|
|
88
132
|
}
|
|
@@ -105,4 +149,17 @@ const styles = StyleSheet.create({
|
|
|
105
149
|
paddingVertical: 26,
|
|
106
150
|
gap: 16,
|
|
107
151
|
},
|
|
152
|
+
forgotPassword: {
|
|
153
|
+
...CommonStyles.normalText,
|
|
154
|
+
color: Colors.primary100,
|
|
155
|
+
textAlign: 'right',
|
|
156
|
+
marginTop: 8,
|
|
157
|
+
marginBottom: 24,
|
|
158
|
+
},
|
|
159
|
+
registerLink: {
|
|
160
|
+
...CommonStyles.normalText,
|
|
161
|
+
color: Colors.primary100,
|
|
162
|
+
textAlign: 'center',
|
|
163
|
+
marginTop: 16,
|
|
164
|
+
},
|
|
108
165
|
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {useNavigation} from '@react-navigation/native';
|
|
2
|
+
import type {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
|
3
|
+
import React, {useRef, useState} from 'react';
|
|
4
|
+
import {
|
|
5
|
+
NativeSyntheticEvent,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
TextInputFocusEventData,
|
|
9
|
+
findNodeHandle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
|
12
|
+
import {ButtonType} from '../../../types';
|
|
13
|
+
import {PrimaryButton} from '../../common/components/PrimaryButton';
|
|
14
|
+
import {PrimaryTextInput} from '../../common/components/PrimaryTextInput';
|
|
15
|
+
import {localization} from '../../common/localization/localization';
|
|
16
|
+
import {emptyValidation} from '../../common/validations/commonValidations';
|
|
17
|
+
import {useInputError} from '../../common/validations/hooks/useInputError';
|
|
18
|
+
import {
|
|
19
|
+
emailValidations,
|
|
20
|
+
fullNameValidations,
|
|
21
|
+
} from '../../common/validations/profileValidations';
|
|
22
|
+
import {useAppDispatch} from '../../core/store/reduxHelpers';
|
|
23
|
+
import {userRegister} from '../../core/store/user/userActions';
|
|
24
|
+
import {Colors} from '../../core/theme/colors';
|
|
25
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
26
|
+
import {CommonStyles} from '../../core/theme/commonStyles';
|
|
27
|
+
import type {RootStackParamList} from '../../navigation/types';
|
|
28
|
+
|
|
29
|
+
export default function RegistrationScreen(): JSX.Element {
|
|
30
|
+
const dispatch = useAppDispatch();
|
|
31
|
+
const navigation =
|
|
32
|
+
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
|
33
|
+
const [loading, setLoading] = useState(false);
|
|
34
|
+
const [formData, setFormData] = useState({
|
|
35
|
+
fullName: '',
|
|
36
|
+
email: '',
|
|
37
|
+
password: '',
|
|
38
|
+
confirmPassword: '',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const scroll = useRef<KeyboardAwareScrollView>(null);
|
|
42
|
+
|
|
43
|
+
// Add validation hooks
|
|
44
|
+
const {error: fullNameError, recheckValue: recheckFullName} = useInputError(
|
|
45
|
+
formData.fullName,
|
|
46
|
+
fullNameValidations,
|
|
47
|
+
);
|
|
48
|
+
const {error: emailError, recheckValue: recheckEmail} = useInputError(
|
|
49
|
+
formData.email,
|
|
50
|
+
emailValidations,
|
|
51
|
+
);
|
|
52
|
+
const {error: passwordError, recheckValue: recheckPassword} = useInputError(
|
|
53
|
+
formData.password,
|
|
54
|
+
emptyValidation,
|
|
55
|
+
);
|
|
56
|
+
const {error: confirmPasswordError, recheckValue: recheckConfirmPassword} =
|
|
57
|
+
useInputError(formData.confirmPassword, emptyValidation);
|
|
58
|
+
|
|
59
|
+
function scrollToInput(reactNode: any) {
|
|
60
|
+
scroll.current?.scrollToFocusedInput(reactNode);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function handleRegistration() {
|
|
64
|
+
// Validate all fields
|
|
65
|
+
const isFullNameValid = recheckFullName() === null;
|
|
66
|
+
const isEmailValid = recheckEmail() === null;
|
|
67
|
+
const isPasswordValid = recheckPassword() === null;
|
|
68
|
+
const isConfirmPasswordValid = recheckConfirmPassword() === null;
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
!isFullNameValid ||
|
|
72
|
+
!isEmailValid ||
|
|
73
|
+
!isPasswordValid ||
|
|
74
|
+
!isConfirmPasswordValid
|
|
75
|
+
) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (formData.password !== formData.confirmPassword) {
|
|
80
|
+
// Add error handling for password mismatch
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
setLoading(true);
|
|
85
|
+
await dispatch(
|
|
86
|
+
userRegister({
|
|
87
|
+
fullName: formData.fullName,
|
|
88
|
+
email: formData.email.toLowerCase(),
|
|
89
|
+
password: formData.password,
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
setLoading(false);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const goToLogin = () => {
|
|
96
|
+
navigation.navigate('Login');
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<KeyboardAwareScrollView
|
|
101
|
+
ref={scroll}
|
|
102
|
+
resetScrollToCoords={{x: 0, y: 0}}
|
|
103
|
+
scrollEnabled={true}
|
|
104
|
+
enableOnAndroid={true}
|
|
105
|
+
contentContainerStyle={styles.contentContainer}
|
|
106
|
+
contentInsetAdjustmentBehavior={'automatic'}
|
|
107
|
+
style={styles.container}>
|
|
108
|
+
<Text style={CommonStyles.h1_semiBold}>
|
|
109
|
+
{localization.login.registration.title}
|
|
110
|
+
</Text>
|
|
111
|
+
|
|
112
|
+
<PrimaryTextInput
|
|
113
|
+
onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
114
|
+
scrollToInput(findNodeHandle(event.target));
|
|
115
|
+
}}
|
|
116
|
+
value={formData.fullName}
|
|
117
|
+
onChangeText={text => setFormData({...formData, fullName: text})}
|
|
118
|
+
containerStyle={CommonStyles.textInputContainer}
|
|
119
|
+
label={localization.login.registration.fullName}
|
|
120
|
+
placeholder={localization.login.registration.fullName}
|
|
121
|
+
error={fullNameError}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
<PrimaryTextInput
|
|
125
|
+
onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
126
|
+
scrollToInput(findNodeHandle(event.target));
|
|
127
|
+
}}
|
|
128
|
+
autoCapitalize="none"
|
|
129
|
+
value={formData.email}
|
|
130
|
+
onChangeText={text => setFormData({...formData, email: text})}
|
|
131
|
+
containerStyle={CommonStyles.textInputContainer}
|
|
132
|
+
label={localization.login.registration.email}
|
|
133
|
+
placeholder={localization.login.EnterEmail}
|
|
134
|
+
error={emailError}
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
<PrimaryTextInput
|
|
138
|
+
onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
139
|
+
scrollToInput(findNodeHandle(event.target));
|
|
140
|
+
}}
|
|
141
|
+
secureTextEntry
|
|
142
|
+
value={formData.password}
|
|
143
|
+
onChangeText={text => setFormData({...formData, password: text})}
|
|
144
|
+
containerStyle={CommonStyles.textInputContainer}
|
|
145
|
+
label={localization.login.registration.password}
|
|
146
|
+
placeholder={localization.login.EnterPassword}
|
|
147
|
+
error={passwordError}
|
|
148
|
+
/>
|
|
149
|
+
|
|
150
|
+
<PrimaryTextInput
|
|
151
|
+
onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
152
|
+
scrollToInput(findNodeHandle(event.target));
|
|
153
|
+
}}
|
|
154
|
+
secureTextEntry
|
|
155
|
+
value={formData.confirmPassword}
|
|
156
|
+
onChangeText={text => setFormData({...formData, confirmPassword: text})}
|
|
157
|
+
containerStyle={CommonStyles.textInputContainer}
|
|
158
|
+
label={localization.login.registration.confirmPassword}
|
|
159
|
+
placeholder={localization.login.registration.confirmPassword}
|
|
160
|
+
error={confirmPasswordError}
|
|
161
|
+
/>
|
|
162
|
+
|
|
163
|
+
<PrimaryButton
|
|
164
|
+
isLoading={loading}
|
|
165
|
+
onPress={handleRegistration}
|
|
166
|
+
label={localization.login.registration.register}
|
|
167
|
+
type={ButtonType.solid}
|
|
168
|
+
/>
|
|
169
|
+
<PrimaryButton
|
|
170
|
+
onPress={goToLogin}
|
|
171
|
+
label={localization.login.registration.alreadyHaveAccount}
|
|
172
|
+
type={ButtonType.borderless}
|
|
173
|
+
/>
|
|
174
|
+
</KeyboardAwareScrollView>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const styles = StyleSheet.create({
|
|
179
|
+
container: {
|
|
180
|
+
flexGrow: 1,
|
|
181
|
+
backgroundColor: Colors.white,
|
|
182
|
+
borderTopRightRadius: CommonSizes.spacing.large,
|
|
183
|
+
borderTopLeftRadius: CommonSizes.spacing.large,
|
|
184
|
+
},
|
|
185
|
+
contentContainer: {
|
|
186
|
+
justifyContent: 'center',
|
|
187
|
+
alignItems: 'center',
|
|
188
|
+
paddingHorizontal: CommonSizes.spacing.large,
|
|
189
|
+
paddingVertical: 26,
|
|
190
|
+
gap: 16,
|
|
191
|
+
},
|
|
192
|
+
loginLink: {
|
|
193
|
+
...CommonStyles.normalText,
|
|
194
|
+
color: Colors.primary100,
|
|
195
|
+
textAlign: 'center',
|
|
196
|
+
marginTop: 16,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {useNavigation} from '@react-navigation/native';
|
|
2
|
+
import type {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
|
3
|
+
import React, {useRef, useState} from 'react';
|
|
4
|
+
import {
|
|
5
|
+
NativeSyntheticEvent,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
TextInputFocusEventData,
|
|
9
|
+
findNodeHandle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
|
12
|
+
import {ButtonType} from '../../../types';
|
|
13
|
+
import {PrimaryButton} from '../../common/components/PrimaryButton';
|
|
14
|
+
import {PrimaryTextInput} from '../../common/components/PrimaryTextInput';
|
|
15
|
+
import {localization} from '../../common/localization/localization';
|
|
16
|
+
import {useInputError} from '../../common/validations/hooks/useInputError';
|
|
17
|
+
import {emailValidations} from '../../common/validations/profileValidations';
|
|
18
|
+
import {useAppDispatch} from '../../core/store/reduxHelpers';
|
|
19
|
+
import {resetPassword} from '../../core/store/user/userActions';
|
|
20
|
+
import {Colors} from '../../core/theme/colors';
|
|
21
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
22
|
+
import {CommonStyles} from '../../core/theme/commonStyles';
|
|
23
|
+
import type {RootStackParamList} from '../../navigation/types';
|
|
24
|
+
|
|
25
|
+
export default function ForgotPasswordScreen(): JSX.Element {
|
|
26
|
+
const dispatch = useAppDispatch();
|
|
27
|
+
const navigation =
|
|
28
|
+
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
|
29
|
+
const [email, setEmail] = useState('');
|
|
30
|
+
const [loading, setLoading] = useState(false);
|
|
31
|
+
|
|
32
|
+
const scroll = useRef<KeyboardAwareScrollView>(null);
|
|
33
|
+
|
|
34
|
+
const {error: emailError, recheckValue: recheckEmail} = useInputError(
|
|
35
|
+
email,
|
|
36
|
+
emailValidations,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
function scrollToInput(reactNode: any) {
|
|
40
|
+
scroll.current?.scrollToFocusedInput(reactNode);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function handleResetPassword() {
|
|
44
|
+
const isEmailValid = recheckEmail() === null;
|
|
45
|
+
if (!isEmailValid) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setLoading(true);
|
|
50
|
+
await dispatch(resetPassword({email: email.toLowerCase()}));
|
|
51
|
+
setLoading(false);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const goToLogin = () => {
|
|
55
|
+
navigation.navigate('Login');
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<KeyboardAwareScrollView
|
|
60
|
+
ref={scroll}
|
|
61
|
+
resetScrollToCoords={{x: 0, y: 0}}
|
|
62
|
+
scrollEnabled={true}
|
|
63
|
+
enableOnAndroid={true}
|
|
64
|
+
contentContainerStyle={styles.contentContainer}
|
|
65
|
+
contentInsetAdjustmentBehavior={'automatic'}
|
|
66
|
+
style={styles.container}>
|
|
67
|
+
<Text style={CommonStyles.h1_semiBold}>
|
|
68
|
+
{localization.login.forgotPassword.title}
|
|
69
|
+
</Text>
|
|
70
|
+
|
|
71
|
+
<Text style={styles.description}>
|
|
72
|
+
{localization.login.forgotPassword.description}
|
|
73
|
+
</Text>
|
|
74
|
+
|
|
75
|
+
<PrimaryTextInput
|
|
76
|
+
onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
77
|
+
scrollToInput(findNodeHandle(event.target));
|
|
78
|
+
}}
|
|
79
|
+
autoCapitalize="none"
|
|
80
|
+
value={email}
|
|
81
|
+
onChangeText={setEmail}
|
|
82
|
+
containerStyle={CommonStyles.textInputContainer}
|
|
83
|
+
label={localization.login.Email}
|
|
84
|
+
placeholder={localization.login.EnterEmail}
|
|
85
|
+
error={emailError}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<PrimaryButton
|
|
89
|
+
isLoading={loading}
|
|
90
|
+
onPress={handleResetPassword}
|
|
91
|
+
label={localization.login.forgotPassword.resetPassword}
|
|
92
|
+
type={ButtonType.solid}
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
<PrimaryButton
|
|
96
|
+
onPress={goToLogin}
|
|
97
|
+
label={localization.login.forgotPassword.backToLogin}
|
|
98
|
+
type={ButtonType.borderless}
|
|
99
|
+
/>
|
|
100
|
+
</KeyboardAwareScrollView>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const styles = StyleSheet.create({
|
|
105
|
+
container: {
|
|
106
|
+
flexGrow: 1,
|
|
107
|
+
backgroundColor: Colors.white,
|
|
108
|
+
borderTopRightRadius: CommonSizes.spacing.large,
|
|
109
|
+
borderTopLeftRadius: CommonSizes.spacing.large,
|
|
110
|
+
},
|
|
111
|
+
contentContainer: {
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
paddingHorizontal: CommonSizes.spacing.large,
|
|
115
|
+
paddingVertical: 26,
|
|
116
|
+
gap: 16,
|
|
117
|
+
},
|
|
118
|
+
description: {
|
|
119
|
+
...CommonStyles.normalText,
|
|
120
|
+
textAlign: 'center',
|
|
121
|
+
marginBottom: 8,
|
|
122
|
+
},
|
|
123
|
+
loginLink: {
|
|
124
|
+
...CommonStyles.normalText,
|
|
125
|
+
color: Colors.primary100,
|
|
126
|
+
textAlign: 'center',
|
|
127
|
+
marginTop: 16,
|
|
128
|
+
},
|
|
129
|
+
});
|