@fadyshawky/react-native-magic 1.0.5 → 1.0.7

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/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.0.7] - 2024-12-27
6
+
7
+ ### Changed
8
+ - Enhanced UI components for better user experience:
9
+ - Improved form input styling
10
+ - Updated button states for better feedback
11
+ - Refined error message displays
12
+ - Added loading spinners for async actions
13
+ - Standardized form layouts across authentication flows
14
+
15
+ ### Added
16
+ - Responsive design improvements for mobile devices
17
+ - Visual feedback for form validation states
18
+ - Transition animations for state changes
19
+ - Consistent error message styling
20
+
21
+ ## [1.0.6] - 2024-12-25
22
+
23
+ ### Added
24
+ - Added missing password reset handlers (`resetPasswordErrorHandler` and `resetPasswordLoadingHandler`) to user slice
25
+ - Added password reset flow with the following states:
26
+ - Loading state during password reset request
27
+ - Error handling for failed password reset attempts
28
+ - Success handling for completed password reset
29
+ - Added complete user registration flow:
30
+ - User input validation
31
+ - Registration request handling
32
+ - Success state with automatic login
33
+ - Error handling for failed registration attempts
34
+ - Loading states during registration process
35
+
36
+ ### Fixed
37
+ - Fixed password reset error handling in user slice
38
+ - Resolved undefined handler errors in password reset flow
39
+ - Improved registration flow error handling
40
+
5
41
  ## [1.0.5] - 2024-12-23
6
42
 
7
43
  ### 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.7",
4
4
  "description": "react native template with ready components, hooks, react navigation, redux, typescript, etc.",
5
5
  "keywords": [
6
6
  "react-native-magic",
@@ -16,7 +16,7 @@ import {
16
16
  IIconPlatformProps,
17
17
  TouchablePlatformProps,
18
18
  } from '../../../types';
19
- import {Colors} from '../../core/theme/colors';
19
+ import {Colors, NewColors} from '../../core/theme/colors';
20
20
  import {CommonSizes} from '../../core/theme/commonSizes';
21
21
  import {CommonStyles} from '../../core/theme/commonStyles';
22
22
  import {IconPlatform} from './IconPlatform';
@@ -203,7 +203,7 @@ const commonIcon: ImageStyle = {
203
203
  const solidStyles = StyleSheet.create({
204
204
  button: {
205
205
  ...commonButtonStyle,
206
- backgroundColor: Colors.primary100,
206
+ backgroundColor: NewColors.blueNormalActive,
207
207
  } as ViewStyle,
208
208
  label: {
209
209
  ...commonLabelStyle,
@@ -217,12 +217,12 @@ const solidStyles = StyleSheet.create({
217
217
  const outlineStyles = StyleSheet.create({
218
218
  button: {
219
219
  ...commonButtonStyle,
220
- borderColor: Colors.primary100,
220
+ borderColor: NewColors.blueNormalActive,
221
221
  borderWidth: 2,
222
222
  } as ViewStyle,
223
223
  label: {
224
224
  ...commonLabelStyle,
225
- color: Colors.primary100,
225
+ color: NewColors.blueNormalActive,
226
226
  } as TextStyle,
227
227
  icon: {
228
228
  ...commonIcon,
@@ -255,7 +255,7 @@ const borderlessStyles = StyleSheet.create({
255
255
  } as ViewStyle,
256
256
  label: {
257
257
  ...commonLabelStyle,
258
- color: Colors.primary100,
258
+ color: NewColors.blueNormalActive,
259
259
  textDecorationLine: 'underline',
260
260
  } as TextStyle,
261
261
  icon: {
@@ -278,7 +278,7 @@ const roundedButtonStyle: ViewStyle = {
278
278
  const smallSolidStyles = StyleSheet.create({
279
279
  button: {
280
280
  ...roundedButtonStyle,
281
- backgroundColor: Colors.primary100,
281
+ backgroundColor: NewColors.blueNormalActive,
282
282
  } as ViewStyle,
283
283
  label: {
284
284
  ...CommonStyles.normalText,
@@ -21,7 +21,7 @@ import {
21
21
  View,
22
22
  ViewStyle,
23
23
  } from 'react-native';
24
- import {Colors} from '../../core/theme/colors';
24
+ import {Colors, NewColors} from '../../core/theme/colors';
25
25
  import {isIos} from '../../core/theme/commonConsts';
26
26
  import {CommonSizes} from '../../core/theme/commonSizes';
27
27
  import {CommonStyles} from '../../core/theme/commonStyles';
@@ -192,7 +192,7 @@ function getInputContainerStyle(
192
192
  }
193
193
  }
194
194
 
195
- const selectionColor = Colors.primary100;
195
+ const selectionColor = NewColors.blueNormalActive;
196
196
 
197
197
  const commonInputContainer: TextStyle = {
198
198
  flexDirection: 'row',
@@ -1,6 +1,6 @@
1
1
  import React, {FC, memo, useMemo} from 'react';
2
2
  import {StyleSheet, View, ViewStyle} from 'react-native';
3
- import {Colors} from '../../core/theme/colors';
3
+ import {Colors, NewColors} from '../../core/theme/colors';
4
4
 
5
5
  interface IProps {
6
6
  isSelected: boolean;
@@ -42,14 +42,14 @@ const commonInnerCircle: ViewStyle = {
42
42
  const styles = StyleSheet.create({
43
43
  outerCircle: {
44
44
  ...commonOuterCircle,
45
- borderColor: Colors.primary100,
45
+ borderColor: NewColors.blueNormalActive,
46
46
  } as ViewStyle,
47
47
  outerCircleSelected: {
48
48
  ...commonOuterCircle,
49
- borderColor: Colors.primary100,
49
+ borderColor: NewColors.blueNormalActive,
50
50
  } as ViewStyle,
51
51
  innerCircle: {
52
52
  ...commonInnerCircle,
53
- backgroundColor: Colors.primary100,
53
+ backgroundColor: NewColors.blueNormalActive,
54
54
  } as ViewStyle,
55
55
  });
@@ -6,7 +6,7 @@ import {
6
6
  TouchableOpacity,
7
7
  View,
8
8
  } from 'react-native';
9
- import {Colors} from '../../core/theme/colors';
9
+ import {Colors, NewColors} from '../../core/theme/colors';
10
10
  import {CommonSizes} from '../../core/theme/commonSizes';
11
11
  import {CommonStyles} from '../../core/theme/commonStyles';
12
12
  import {localization} from '../localization/localization';
@@ -42,7 +42,7 @@ const styles = StyleSheet.create({
42
42
  } as TextStyle,
43
43
  description: {
44
44
  ...CommonStyles.normalText,
45
- color: Colors.primary100,
45
+ color: NewColors.blueNormalActive,
46
46
  textAlign: 'center',
47
47
  textDecorationLine: 'underline',
48
48
  } as TextStyle,
@@ -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() {
@@ -27,24 +27,7 @@ function AppNavigator() {
27
27
  border: '#000',
28
28
  notification: '#ff0000',
29
29
  },
30
- fonts: {
31
- regular: {
32
- fontFamily: Fonts.regular,
33
- fontWeight: 'normal',
34
- },
35
- medium: {
36
- fontFamily: Fonts.medium,
37
- fontWeight: '500',
38
- },
39
- bold: {
40
- fontFamily: Fonts.bold,
41
- fontWeight: '700',
42
- },
43
- heavy: {
44
- fontFamily: Fonts.bold,
45
- fontWeight: '900',
46
- },
47
- },
30
+ fonts: DefaultTheme.fonts,
48
31
  }}
49
32
  onReady={() => {
50
33
  routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name;
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import {View, Text, TouchableOpacity, Image, StyleSheet} from 'react-native';
3
3
  import {CommonStyles} from '../core/theme/commonStyles';
4
- import {Colors} from '../core/theme/colors';
4
+ import {Colors, NewColors} from '../core/theme/colors';
5
5
  import {
6
6
  BottomTabBarProps,
7
7
  BottomTabNavigationOptions,
@@ -89,6 +89,6 @@ const styles = StyleSheet.create({
89
89
  color: Colors.gray,
90
90
  },
91
91
  labelFocused: {
92
- color: Colors.primary100,
92
+ color: NewColors.blueNormalActive,
93
93
  },
94
94
  });
@@ -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
- import {Colors} from '../../core/theme/colors';
22
+ import {Colors, NewColors} 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: NewColors.blueNormalActive,
155
+ textAlign: 'right',
156
+ marginTop: 8,
157
+ marginBottom: 24,
158
+ },
159
+ registerLink: {
160
+ ...CommonStyles.normalText,
161
+ color: NewColors.blueNormalActive,
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, NewColors} 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: NewColors.blueNormalActive,
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, NewColors} 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: NewColors.blueNormalActive,
126
+ textAlign: 'center',
127
+ marginTop: 16,
128
+ },
129
+ });