@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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "workbench.colorCustomizations": {
3
+ "activityBar.background": "#342D0C",
4
+ "titleBar.activeBackground": "#493F10",
5
+ "titleBar.activeForeground": "#FCFAF0"
6
+ }
7
+ }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fadyshawky/react-native-magic",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "react native template with ready components, hooks, react navigation, redux, typescript, etc.",
5
5
  "keywords": [
6
6
  "react-native-magic",
@@ -6,9 +6,25 @@ export const loginLocalization = {
6
6
  EnterEmail: 'Enter email',
7
7
  Password: 'Password',
8
8
  EnterPassword: 'Enter password',
9
- forgetPassword: 'Forget Password',
9
+ forgetPassword: 'Forgot Password?',
10
10
  continue: 'continue',
11
- notMember: 'not a member yet?',
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
- // ... add other screens as needed
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
+ });