@fadyshawky/react-native-magic 1.0.4 → 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 +23 -0
- package/README.md +2 -4
- package/local.properties +1 -0
- package/package.json +1 -3
- package/template/.env.development +3 -0
- package/template/.env.production +4 -1
- package/template/.env.staging +4 -1
- package/template/App.tsx +0 -5
- package/template/android/app/build.gradle +71 -6
- package/template/android/app/src/main/AndroidManifest.xml +2 -0
- package/template/android/gradle.properties +0 -1
- package/template/babel.config.js +1 -0
- package/template/index.js +1 -1
- package/template/ios/Podfile +37 -6
- package/template/ios/Podfile.lock +2 -2
- package/template/ios/reactnativemagic/Info.plist +4 -0
- package/template/ios/reactnativemagic.xcodeproj/project.pbxproj +74 -74
- package/template/ios/tmp.xcconfig +2 -0
- package/template/metro.config.js +6 -1
- package/template/package-lock.json +4 -3
- package/template/package.json +7 -6
- package/template/src/common/components/Background.tsx +0 -1
- package/template/src/common/components/ImageCropPickerButton.tsx +4 -5
- package/template/src/common/components/LoadingComponent.tsx +1 -6
- package/template/src/common/localization/translations/loginLocalization.ts +18 -2
- package/template/src/core/store/user/userActions.ts +47 -3
- package/template/src/core/store/user/userSlice.ts +59 -2
- package/template/src/core/theme/fonts.ts +1 -7
- package/template/src/navigation/AuthStack.tsx +16 -0
- package/template/src/navigation/HeaderComponents.tsx +3 -5
- package/template/src/navigation/MainStack.tsx +0 -1
- package/template/src/navigation/types.ts +7 -1
- package/template/src/screens/Login/Login.tsx +54 -3
- package/template/src/screens/registration/RegistrationScreen.tsx +198 -0
- package/template/src/screens/resetPassword/ForgotPasswordScreen.tsx +129 -0
- package/template/src/types/react-native-config.d.ts +8 -0
|
@@ -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,10 +1,8 @@
|
|
|
1
|
+
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {CommonStyles, screenWidth} from '../core/theme/commonStyles';
|
|
4
|
-
import {BottomTabHeaderProps} from '@react-navigation/bottom-tabs';
|
|
5
|
-
import {ImageResources} from '../common/ImageResources.g';
|
|
3
|
+
import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
|
|
6
4
|
import {CommonSizes} from '../core/theme/commonSizes';
|
|
7
|
-
import {
|
|
5
|
+
import {CommonStyles, screenWidth} from '../core/theme/commonStyles';
|
|
8
6
|
import {RootStackParamList} from './types';
|
|
9
7
|
|
|
10
8
|
export function Header({title}: {title: string}) {
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
NativeStackNavigationProp,
|
|
6
6
|
} from '@react-navigation/native-stack';
|
|
7
7
|
import React from 'react';
|
|
8
|
-
import {ImageResources} from '../common/ImageResources.g';
|
|
9
8
|
import {localization} from '../common/localization/localization';
|
|
10
9
|
import {Main} from '../screens/main/Main';
|
|
11
10
|
import {Profile} from '../screens/profile/Profile';
|
|
@@ -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,31 +1,54 @@
|
|
|
1
1
|
import {useNavigation} from '@react-navigation/native';
|
|
2
|
+
import type {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
|
2
3
|
import {toLower} from 'lodash';
|
|
3
4
|
import React, {useRef, useState} from 'react';
|
|
4
5
|
import {
|
|
6
|
+
findNodeHandle,
|
|
5
7
|
NativeSyntheticEvent,
|
|
6
8
|
StyleSheet,
|
|
7
9
|
Text,
|
|
8
10
|
TextInputFocusEventData,
|
|
9
|
-
findNodeHandle,
|
|
10
11
|
} from 'react-native';
|
|
11
12
|
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
|
12
13
|
import {ButtonType} from '../../../types';
|
|
13
14
|
import {PrimaryButton} from '../../common/components/PrimaryButton';
|
|
14
15
|
import {PrimaryTextInput} from '../../common/components/PrimaryTextInput';
|
|
15
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';
|
|
16
20
|
import {useAppDispatch} from '../../core/store/reduxHelpers';
|
|
17
21
|
import {userLogin} from '../../core/store/user/userActions';
|
|
18
22
|
import {Colors} from '../../core/theme/colors';
|
|
19
23
|
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
20
24
|
import {CommonStyles} from '../../core/theme/commonStyles';
|
|
25
|
+
import type {RootStackParamList} from '../../navigation/types';
|
|
21
26
|
|
|
22
27
|
export function Login(): JSX.Element {
|
|
23
28
|
const dispatch = useAppDispatch();
|
|
24
29
|
const [email, setEmail] = useState('');
|
|
25
30
|
const [password, setPassword] = useState('');
|
|
26
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
|
+
);
|
|
27
43
|
|
|
28
44
|
async function loginUser() {
|
|
45
|
+
const emailValid = recheckEmail() === null;
|
|
46
|
+
const passwordValid = recheckPassword() === null;
|
|
47
|
+
|
|
48
|
+
if (!emailValid || !passwordValid) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
setLoading(true);
|
|
30
53
|
await dispatch(
|
|
31
54
|
userLogin({
|
|
@@ -33,7 +56,6 @@ export function Login(): JSX.Element {
|
|
|
33
56
|
password,
|
|
34
57
|
}),
|
|
35
58
|
);
|
|
36
|
-
|
|
37
59
|
setLoading(false);
|
|
38
60
|
}
|
|
39
61
|
const scroll = useRef<KeyboardAwareScrollView>(null);
|
|
@@ -44,6 +66,15 @@ export function Login(): JSX.Element {
|
|
|
44
66
|
scroll.current?.scrollToFocusedInput(reactNode);
|
|
45
67
|
// }, 500);
|
|
46
68
|
}
|
|
69
|
+
|
|
70
|
+
const goToRegistration = () => {
|
|
71
|
+
navigation.navigate('Registration');
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const goToForgotPassword = () => {
|
|
75
|
+
navigation.navigate('ForgotPassword');
|
|
76
|
+
};
|
|
77
|
+
|
|
47
78
|
return (
|
|
48
79
|
<KeyboardAwareScrollView
|
|
49
80
|
ref={scroll}
|
|
@@ -65,6 +96,7 @@ export function Login(): JSX.Element {
|
|
|
65
96
|
containerStyle={CommonStyles.textInputContainer}
|
|
66
97
|
label={localization.login.Email}
|
|
67
98
|
placeholder={localization.login.EnterEmail}
|
|
99
|
+
error={emailError}
|
|
68
100
|
/>
|
|
69
101
|
<PrimaryTextInput
|
|
70
102
|
onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
|
@@ -77,11 +109,12 @@ export function Login(): JSX.Element {
|
|
|
77
109
|
containerStyle={CommonStyles.textInputContainer}
|
|
78
110
|
label={localization.login.Password}
|
|
79
111
|
placeholder={localization.login.EnterPassword}
|
|
112
|
+
error={passwordError}
|
|
80
113
|
/>
|
|
81
114
|
<PrimaryButton
|
|
115
|
+
onPress={goToForgotPassword}
|
|
82
116
|
label={localization.login.forgetPassword}
|
|
83
117
|
type={ButtonType.borderless}
|
|
84
|
-
style={{alignSelf: 'flex-end'}}
|
|
85
118
|
/>
|
|
86
119
|
<PrimaryButton
|
|
87
120
|
isLoading={loading}
|
|
@@ -89,6 +122,11 @@ export function Login(): JSX.Element {
|
|
|
89
122
|
label={localization.login.continue}
|
|
90
123
|
type={ButtonType.solid}
|
|
91
124
|
/>
|
|
125
|
+
<PrimaryButton
|
|
126
|
+
onPress={goToRegistration}
|
|
127
|
+
label={localization.login.notMember}
|
|
128
|
+
type={ButtonType.borderless}
|
|
129
|
+
/>
|
|
92
130
|
</KeyboardAwareScrollView>
|
|
93
131
|
);
|
|
94
132
|
}
|
|
@@ -111,4 +149,17 @@ const styles = StyleSheet.create({
|
|
|
111
149
|
paddingVertical: 26,
|
|
112
150
|
gap: 16,
|
|
113
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
|
+
},
|
|
114
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
|
+
});
|