Archived
✨ Improve Login
♻ Extract FormErrors and FormInput into own components
This commit is contained in:
1 parent
e39adb3695
commit
f558d783e9
6 files changed
+141
-158
No files matched your search
@@ -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;
|
||||
@@ -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
@@ -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,
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user