✨ Improve Login

♻ Extract FormErrors and FormInput into own components
This commit is contained in:
nikolark committed 2021-10-12 19:51:38 +02:00
1 parent e39adb3695
commit f558d783e9
6 files changed
+141 -158

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
import React from 'react';
import { Alert, HStack, Text } from 'native-base';
const FormErrors = ({ errors }) => {
if (!Object.keys(errors).length || !['non_field_errors', 'detail'].some((errorField) => errorField in errors)) {
return null;
}
let errorFormatted;
if ('non_field_errors' in errors) {
errorFormatted = errors.non_field_errors?.[0];
} else if ('detail' in errors) {
errorFormatted = errors.detail;
}
if (!errorFormatted) errorFormatted = 'Kunne ikke sende inn skjemaet, prøv igjen...';
return (
<Alert w="100%" colorScheme="error" status="error">
<HStack space={2} flexShrink={1} w="100%">
<Alert.Icon mt="1" />
<Text>{errorFormatted}</Text>
</HStack>
</Alert>
);
};
export default FormErrors;
+44
View File
@@ -0,0 +1,44 @@
import React from 'react';
import { Controller } from 'react-hook-form';
import { Input, Text, WarningOutlineIcon, FormControl } from 'native-base';
const FormInput = ({
control,
errors,
name,
label,
rules = {
required: 'kan ikke være tomt.',
},
returnKeyType = 'next',
...rest
}) => {
return (
<Controller
control={control}
rules={rules}
render={({ field: { onChange, onBlur, value, ref } }) => (
<FormControl isInvalid={Boolean(errors[name])}>
<FormControl.Label>{label}</FormControl.Label>
<Input
ref={ref}
returnKeyType={returnKeyType}
onChangeText={onChange}
onBlur={onBlur}
value={value}
{...rest}
/>
{errors[name] && (
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
<Text>{errors[name].message}</Text>
</FormControl.ErrorMessage>
)}
</FormControl>
)}
name={name}
defaultValue=""
/>
);
};
export default FormInput;
+47 -79
View File
@@ -1,102 +1,70 @@
import React, { useRef } from 'react';
import React from 'react';
import { Linking, StyleSheet, View } from 'react-native';
import { Container, Input, Spinner, Button, Text } from 'native-base';
import { ScrollView, Spinner, Button, Text } from 'native-base';
import Config from 'react-native-config';
import theme from '../../theme';
import useLogin from './useLogin';
const FormErrors = ({ loginError }) => {
if (!loginError) {
return <View style={styles.errorBox} />;
}
const err = loginError.non_field_errors;
const errorFormatted = err ? err[0] : 'Feil brukernavn eller passord, prøv igjen...';
// TODO: Format these and highlight error fields
return (
<View style={styles.errorBox}>
<Text style={styles.errorMessage}>{errorFormatted}</Text>
</View>
);
};
import FormInput from '../../components/FormInput';
import FormErrors from '../../components/FormErrors';
const Login = ({ onLogin }) => {
const passwordInputRef = useRef();
const { password, setPassword, email, setEmail, loginError, onLoginPress, isLoggingIn } = useLogin(onLogin);
// FIXME: highlight fields with errors
// const usernameHasError = Boolean(loginError?.username);
// const passwordHasError = Boolean(loginError?.password);
const { isLoggingIn, errors, control, setFocus, onSubmit } = useLogin(onLogin);
return (
<Container style={styles.container}>
<FormErrors loginError={loginError} />
<Text>E-post / Brukernavn</Text>
<Input
keyboardType="email-address"
autoFocus={true}
autoCapitalize="none"
autoCorrect={false}
returnKeyType="next"
onChangeText={setEmail}
value={email}
onSubmitEditing={() => {
passwordInputRef.current.focus();
}}
/>
<Text>Passord</Text>
<Input
ref={passwordInputRef}
secureTextEntry={true}
autoCapitalize="none"
autoCorrect={false}
onChangeText={setPassword}
value={password}
onSubmitEditing={onLoginPress}
/>
<Button full onPress={onLoginPress} style={styles.loginButton}>
<Text>Logg inn</Text>
</Button>
<Button
small
light
onPress={() => {
Linking.openURL(Config.DUSKEN_FORGOT_PASSWORD_URL);
}}
style={styles.forgotPasswordButton}
>
<Text>Glemt passord?</Text>
</Button>
{isLoggingIn && <Spinner color="#f58220" />}
</Container>
<ScrollView>
<View style={styles.card}>
<FormErrors errors={errors} />
<FormInput
name="email"
label="E-post / Brukernavn"
keyboardType="email-address"
control={control}
errors={errors}
autoFocus
autoCapitalize="none"
autoCorrect={false}
onSubmitEditing={() => setFocus('password')}
/>
<FormInput
name="password"
label="Passord"
secureTextEntry
control={control}
errors={errors}
autoCapitalize="none"
autoCorrect={false}
onSubmitEditing={onSubmit}
returnKeyType="send"
/>
<Button full onPress={onSubmit} style={styles.loginButton}>
<Text>Logg inn</Text>
</Button>
<Button
small
light
onPress={() => {
Linking.openURL(Config.DUSKEN_FORGOT_PASSWORD_URL);
}}
style={styles.forgotPasswordButton}
>
<Text>Glemt passord?</Text>
</Button>
{isLoggingIn && <Spinner color="#f58220" />}
</View>
</ScrollView>
);
};
export default Login;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
input: {
width: 320,
fontSize: 20,
height: 52,
},
loginButton: {
marginBottom: 16,
marginTop: 16,
},
forgotPasswordButton: {
alignSelf: 'center',
marginTop: 32,
marginBottom: 16,
},
errorBox: {
marginVertical: 10,
},
errorMessage: {
textAlign: 'center',
color: 'red',
},
card: theme.card,
});
+14 -8
View File
@@ -1,25 +1,31 @@
import { useNavigation } from '@react-navigation/native';
import { useState, useEffect } from 'react';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useForm } from 'react-hook-form';
import { requestLogin } from '../../api';
const useLogin = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const navigation = useNavigation();
const dispatch = useDispatch();
const onLoginPress = () => {
dispatch(requestLogin(email, password));
};
const { isAuthenticated, isLoggingIn, loginError } = useSelector((store) => ({
isAuthenticated: store.isAuthenticated,
isLoggingIn: store.isLoggingIn,
loginError: store.loginError,
}));
const {
control,
handleSubmit,
formState: { errors },
setFocus,
} = useForm();
const onSubmit = handleSubmit(({ email, password }) => {
dispatch(requestLogin(email, password));
});
useEffect(() => {
if (isAuthenticated) {
// navigation.goBack onLogin works since LoginScreen is allways navigated to from MembershipScreen
@@ -27,7 +33,7 @@ const useLogin = () => {
}
}, [isAuthenticated]);
return { email, setEmail, password, setPassword, onLoginPress, isLoggingIn, loginError };
return { onSubmit, isLoggingIn, control, setFocus, errors: { ...errors, ...loginError } };
};
export default useLogin;
+9 -70
View File
@@ -1,77 +1,12 @@
import React from 'react';
import { Input, Spinner, Button, Text, WarningOutlineIcon, FormControl, ScrollView } from 'native-base';
import { Spinner, Button, Text, ScrollView } from 'native-base';
import { StyleSheet, View } from 'react-native';
import * as EmailValidator from 'email-validator';
import { Controller } from 'react-hook-form';
import theme from '../../theme';
import useUserRegister from './useUserRegister';
const FormInput = ({
control,
errors,
name,
label,
rules = {
required: 'kan ikke være tomt.',
},
returnKeyType = 'next',
...rest
}) => {
return (
<Controller
control={control}
rules={rules}
render={({ field: { onChange, onBlur, value, ref } }) => (
<FormControl isInvalid={Boolean(errors[name])}>
<FormControl.Label>{label}</FormControl.Label>
<Input
ref={ref}
returnKeyType={returnKeyType}
onChangeText={onChange}
onBlur={onBlur}
value={value}
{...rest}
/>
{errors[name] && (
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
<Text>{errors[name].message}</Text>
</FormControl.ErrorMessage>
)}
</FormControl>
)}
name={name}
defaultValue=""
/>
);
};
const Registerbutton = ({ isDisabled, onPress }) => (
<Button full disabled={isDisabled} onPress={onPress} style={styles.registerButton}>
<Text>Registrer meg</Text>
</Button>
);
const NonFieldErrors = ({ errors }) => {
if (!Object.keys(errors).length || !['non_field_errors', 'detail'].some((errorField) => errorField in errors)) {
return null;
}
let errorFormatted;
if ('non_field_errors' in errors) {
errorFormatted = errors.non_field_errors?.[0];
} else if ('detail' in errors) {
errorFormatted = errors.detail;
}
if (!errorFormatted) errorFormatted = 'Kunne ikke registrere bruker, prøv igjen...';
return (
<View style={styles.errorBox}>
<FormControl.ErrorMessage leftIcon={<WarningOutlineIcon size="xs" />}>
<Text>{errorFormatted}</Text>
</FormControl.ErrorMessage>
</View>
);
};
import FormInput from '../../components/FormInput';
import FormErrors from '../../components/FormErrors';
const UserRegister = () => {
const { isRegisteringUser, onSubmit, control, isValid, errors, setFocus } = useUserRegister();
@@ -130,8 +65,12 @@ const UserRegister = () => {
returnKeyType="send"
onSubmitEditing={onSubmit}
/>
<NonFieldErrors errors={errors} />
<Registerbutton isDisabled={isDisabled} onPress={onSubmit} />
<View style={styles.errorBox}>
<FormErrors errors={errors} />
</View>
<Button full disabled={isDisabled} onPress={onSubmit} style={styles.registerButton}>
<Text>Registrer meg</Text>
</Button>
{isRegisteringUser && <Spinner color="#f58220" />}
</View>
</ScrollView>
+1 -1
View File
@@ -8,7 +8,7 @@ import rootReducer from './reducers';
const persistConfig = {
key: 'root',
storage: AsyncStorage,
blacklist: ['isLoggingIn', 'isFetchingUserData', 'isRegisteringUser', 'isChargingMembership'],
blacklist: ['isLoggingIn', 'isFetchingUserData', 'isRegisteringUser', 'isChargingMembership', 'loginError'],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);