Standardize auth forms layouts

This commit is contained in:
Nicolas Meienberger 2022-05-03 22:30:18 +02:00
parent 4d7206f889
commit 1719a14ec5
6 changed files with 215 additions and 181 deletions

View file

@ -0,0 +1,28 @@
import { Container, Flex, SlideFade, Text } from '@chakra-ui/react';
import React from 'react';
interface IProps {
title: string;
description: string;
}
const AuthFormLayout: React.FC<IProps> = ({ children, title, description }) => {
return (
<Container maxW="1250px">
<Flex flex={1} height="100vh" overflowY="hidden">
<SlideFade in className="flex flex-1 flex-col justify-center items-center" offsetY="20px">
<img className="self-center mb-5 logo" src="/tipi.png" width={512} height={512} />
<Text className="text-xl md:text-2xl lg:text-5xl font-bold" size="3xl">
{title}
</Text>
<Text className="md:text-lg lg:text-2xl text-center" color="gray.500">
{description}
</Text>
{children}
</SlideFade>
</Flex>
</Container>
);
};
export default AuthFormLayout;

View file

@ -0,0 +1,57 @@
import { Button } from '@chakra-ui/react';
import React from 'react';
import { Field, Form } from 'react-final-form';
import validator from 'validator';
import FormInput from '../../../components/Form/FormInput';
type FormValues = { email: string; password: string };
interface IProps {
onSubmit: (values: FormValues) => void;
loading: boolean;
}
const LoginForm: React.FC<IProps> = ({ onSubmit, loading }) => {
const validateFields = (values: FormValues) => {
const errors: Record<string, string> = {};
if (!validator.isEmail(values.email || '')) {
errors.email = 'Invalid email';
}
if (!values.password) {
errors.password = 'Required';
}
return errors;
};
return (
<Form<FormValues>
onSubmit={onSubmit}
validateOnBlur={true}
validate={(values) => validateFields(values)}
render={({ handleSubmit, validating, submitting }) => (
<form className="flex flex-col" onSubmit={handleSubmit}>
<Field
name="email"
render={({ input, meta }) => (
<FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Email" {...input} />
)}
/>
<Field
name="password"
render={({ input, meta }) => (
<FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Password" type="password" {...input} />
)}
/>
<Button isLoading={validating || submitting || loading} className="mt-2" colorScheme="green" type="submit">
Login
</Button>
</form>
)}
/>
);
};
export default LoginForm;

View file

@ -0,0 +1,75 @@
import { Button } from '@chakra-ui/react';
import React from 'react';
import { Field, Form } from 'react-final-form';
import validator from 'validator';
import FormInput from '../../../components/Form/FormInput';
interface IProps {
onSubmit: (values: FormValues) => void;
loading: boolean;
}
type FormValues = { email: string; password: string; passwordConfirm: string };
const RegisterForm: React.FC<IProps> = ({ onSubmit, loading }) => {
const validateFields = (values: FormValues) => {
const errors: Record<string, string> = {};
if (!validator.isEmail(values.email || '')) {
errors.email = 'Invalid email';
}
if (!values.password) {
errors.password = 'Required';
}
if (values.password !== values.passwordConfirm) {
errors.passwordConfirm = 'Passwords do not match';
}
return errors;
};
return (
<Form<FormValues>
onSubmit={onSubmit}
validateOnBlur={true}
validate={(values) => validateFields(values)}
render={({ handleSubmit, validating, submitting }) => (
<form className="flex flex-col" onSubmit={handleSubmit}>
<Field
name="email"
render={({ input, meta }) => (
<FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Email" {...input} />
)}
/>
<Field
name="password"
render={({ input, meta }) => (
<FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Password" type="password" {...input} />
)}
/>
<Field
name="passwordConfirm"
render={({ input, meta }) => (
<FormInput
size="lg"
className="mt-3 w-full"
error={meta.error}
isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)}
placeholder="Repeat password"
type="password"
{...input}
/>
)}
/>
<Button isLoading={validating || submitting || loading} className="mt-2" colorScheme="green" type="submit">
Enter
</Button>
</form>
)}
/>
);
};
export default RegisterForm;

View file

@ -1,4 +1,3 @@
import { useToast } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import LoadingScreen from '../../../components/LoadingScreen';
import { useAuthStore } from '../../../state/authStore';
@ -7,8 +6,7 @@ import Onboarding from './Onboarding';
const AuthWrapper: React.FC = ({ children }) => {
const [initialLoad, setInitialLoad] = useState(true);
const { configured, loading, user, login, me, fetchConfigured, register } = useAuthStore();
const toast = useToast();
const { configured, user, me, fetchConfigured } = useAuthStore();
useEffect(() => {
const fetchUser = async () => {
@ -20,36 +18,6 @@ const AuthWrapper: React.FC = ({ children }) => {
if (!user) fetchUser();
}, [fetchConfigured, me, user]);
const handleError = (error: unknown) => {
if (error instanceof Error) {
toast({
title: 'Error',
description: error.message,
status: 'error',
position: 'top',
isClosable: true,
});
}
};
const handleLogin = async (values: { email: string; password: string }) => {
try {
await login(values.email, values.password);
await me();
} catch (error) {
handleError(error);
}
};
const handleRegister = async (values: { email: string; password: string }) => {
try {
await register(values.email, values.password);
await me();
} catch (error) {
handleError(error);
}
};
if (initialLoad && !user) {
return <LoadingScreen />;
}
@ -59,10 +27,10 @@ const AuthWrapper: React.FC = ({ children }) => {
}
if (!configured) {
return <Onboarding loading={loading} onSubmit={handleRegister} />;
return <Onboarding />;
}
return <Login loading={loading} onSubmit={handleLogin} />;
return <Login />;
};
export default AuthWrapper;

View file

@ -1,77 +1,40 @@
import { Button, Container, Flex, SlideFade, Text } from '@chakra-ui/react';
import { useToast } from '@chakra-ui/react';
import React from 'react';
import { Field, Form } from 'react-final-form';
import validator from 'validator';
import FormInput from '../../../components/Form/FormInput';
import { useAuthStore } from '../../../state/authStore';
import AuthFormLayout from '../components/AuthFormLayout';
import LoginForm from '../components/LoginForm';
type FormValues = { email: string; password: string };
interface IProps {
onSubmit: (values: FormValues) => void;
loading: boolean;
}
const Login: React.FC = () => {
const { me, login, loading } = useAuthStore();
const toast = useToast();
const Login: React.FC<IProps> = ({ onSubmit, loading }) => {
const validateFields = (values: FormValues) => {
const errors: Record<string, string> = {};
if (!validator.isEmail(values.email || '')) {
errors.email = 'Invalid email';
const handleError = (error: unknown) => {
if (error instanceof Error) {
toast({
title: 'Error',
description: error.message,
status: 'error',
position: 'top',
isClosable: true,
});
}
};
if (!values.password) {
errors.password = 'Required';
const handleLogin = async (values: FormValues) => {
try {
await login(values.email, values.password);
await me();
} catch (error) {
handleError(error);
}
return errors;
};
return (
<Container maxW="1250px">
<Flex flex={1} height="100vh" overflowY="hidden">
<SlideFade in className="flex flex-1 flex-col justify-center items-center" offsetY="20px">
<img className="self-center mb-5 logo" src="/tipi.png" width={512} height={512} />
<Text className="text-xl md:text-2xl lg:text-5xl font-bold" size="3xl">
Welcome back
</Text>
<Text className="md:text-lg lg:text-2xl text-center" color="gray.500">
Enter your credentials to login to your Tipi
</Text>
<Form<FormValues>
onSubmit={onSubmit}
validateOnBlur={true}
validate={(values) => validateFields(values)}
render={({ handleSubmit, validating, submitting }) => (
<form className="flex flex-col" onSubmit={handleSubmit}>
<Field
name="email"
render={({ input, meta }) => (
<FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Email" {...input} />
)}
/>
<Field
name="password"
render={({ input, meta }) => (
<FormInput
size="lg"
className="mt-3 w-full"
error={meta.error}
isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)}
placeholder="Password"
type="password"
{...input}
/>
)}
/>
<Button isLoading={validating || submitting || loading} className="mt-2" colorScheme="green" type="submit">
Login
</Button>
</form>
)}
/>
</SlideFade>
</Flex>
</Container>
<AuthFormLayout title="Welcome back" description="Enter your credentials to login to your Tipi">
<LoginForm onSubmit={handleLogin} loading={loading} />
</AuthFormLayout>
);
};

View file

@ -1,95 +1,38 @@
import { Button, Container, Flex, SlideFade, Text } from '@chakra-ui/react';
import { useToast } from '@chakra-ui/react';
import React from 'react';
import { Field, Form } from 'react-final-form';
import validator from 'validator';
import FormInput from '../../../components/Form/FormInput';
import { useAuthStore } from '../../../state/authStore';
import AuthFormLayout from '../components/AuthFormLayout';
import RegisterForm from '../components/RegisterForm';
type FormValues = { email: string; password: string; passwordConfirm: string };
const Onboarding: React.FC = () => {
const toast = useToast();
const { me, register, loading } = useAuthStore();
interface IProps {
onSubmit: (values: FormValues) => void;
loading: boolean;
}
const Onboarding: React.FC<IProps> = ({ onSubmit, loading }) => {
const validateFields = (values: FormValues) => {
const errors: Record<string, string> = {};
if (!validator.isEmail(values.email || '')) {
errors.email = 'Invalid email';
const handleError = (error: unknown) => {
if (error instanceof Error) {
toast({
title: 'Error',
description: error.message,
status: 'error',
position: 'top',
isClosable: true,
});
}
};
if (!values.password) {
errors.password = 'Required';
const handleRegister = async (values: { email: string; password: string }) => {
try {
await register(values.email, values.password);
await me();
} catch (error) {
handleError(error);
}
if (values.password !== values.passwordConfirm) {
errors.passwordConfirm = 'Passwords do not match';
}
return errors;
};
return (
<Container maxW="1250px">
<Flex flex={1} height="100vh" overflowY="hidden">
<SlideFade in className="flex flex-1 flex-col justify-center items-center" offsetY="20px">
<img className="self-center mb-5 logo" src="/tipi.png" width={512} height={512} />
<Text className="text-xl md:text-2xl lg:text-5xl font-bold" size="3xl">
Welcome to your Tipi
</Text>
<Text className="md:text-lg lg:text-2xl text-center" color="gray.500">
Register your account to get started
</Text>
<Form<FormValues>
onSubmit={onSubmit}
validateOnBlur={true}
validate={(values) => validateFields(values)}
render={({ handleSubmit, validating, submitting }) => (
<form className="flex flex-col" onSubmit={handleSubmit}>
<Field
name="email"
render={({ input, meta }) => (
<FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Email" {...input} />
)}
/>
<Field
name="password"
render={({ input, meta }) => (
<FormInput
size="lg"
className="mt-3 w-full"
error={meta.error}
isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)}
placeholder="Password"
type="password"
{...input}
/>
)}
/>
<Field
name="passwordConfirm"
render={({ input, meta }) => (
<FormInput
size="lg"
className="mt-3 w-full"
error={meta.error}
isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)}
placeholder="Repeat password"
type="password"
{...input}
/>
)}
/>
<Button isLoading={validating || submitting || loading} className="mt-2" colorScheme="green" type="submit">
Enter
</Button>
</form>
)}
/>
</SlideFade>
</Flex>
</Container>
<AuthFormLayout title="Welcome to your Tipi" description="Register your account to get started">
<RegisterForm onSubmit={handleRegister} loading={loading} />
</AuthFormLayout>
);
};