@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 +36 -0
- package/package.json +1 -1
- package/template/src/common/components/PrimaryButton.tsx +6 -6
- package/template/src/common/components/PrimaryTextInput.tsx +2 -2
- package/template/src/common/components/RadioIcon.tsx +4 -4
- package/template/src/common/components/TryAgain.tsx +2 -2
- 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/MainNavigation.tsx +1 -18
- package/template/src/navigation/TabBar.tsx +2 -2
- package/template/src/navigation/types.ts +7 -1
- package/template/src/screens/Login/Login.tsx +60 -3
- 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,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
|
@@ -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:
|
|
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:
|
|
220
|
+
borderColor: NewColors.blueNormalActive,
|
|
221
221
|
borderWidth: 2,
|
|
222
222
|
} as ViewStyle,
|
|
223
223
|
label: {
|
|
224
224
|
...commonLabelStyle,
|
|
225
|
-
color:
|
|
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:
|
|
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:
|
|
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 =
|
|
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:
|
|
45
|
+
borderColor: NewColors.blueNormalActive,
|
|
46
46
|
} as ViewStyle,
|
|
47
47
|
outerCircleSelected: {
|
|
48
48
|
...commonOuterCircle,
|
|
49
|
-
borderColor:
|
|
49
|
+
borderColor: NewColors.blueNormalActive,
|
|
50
50
|
} as ViewStyle,
|
|
51
51
|
innerCircle: {
|
|
52
52
|
...commonInnerCircle,
|
|
53
|
-
backgroundColor:
|
|
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:
|
|
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: '
|
|
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() {
|
|
@@ -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:
|
|
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
|
-
|
|
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
|
+
});
|