-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from theforbiddenpool/login-signup-pages
Login & Sign Up pages
- Loading branch information
Showing
17 changed files
with
1,572 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { render, screen, waitFor } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import LoginForm from './LoginForm'; | ||
|
||
describe('Login Form', () => { | ||
test('renders and submits', async () => { | ||
const handleSubmit = jest.fn(); | ||
render(<LoginForm onSubmit={handleSubmit} />); | ||
const user = userEvent.setup(); | ||
|
||
await user.type(screen.getByLabelText(/username/i), 'john'); | ||
await user.type(screen.getByLabelText(/password/i), '$Password123'); | ||
|
||
await user.click(screen.getByRole('button', { name: /log in/i })); | ||
|
||
await waitFor(() => expect(handleSubmit).toHaveBeenCalledWith({ | ||
username: 'john', | ||
password: '$Password123', | ||
})); | ||
}); | ||
|
||
test('validation schema is correct', async () => { | ||
const handleSubmit = jest.fn(); | ||
render(<LoginForm onSubmit={handleSubmit} />); | ||
const user = userEvent.setup(); | ||
|
||
await user.click(screen.getByRole('button', { name: /log in/i })); | ||
|
||
await waitFor(() => { | ||
expect(handleSubmit).not.toHaveBeenCalled(); | ||
|
||
// required fields | ||
expect(screen.getByText(/username is required/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/password is required/i)).toBeInTheDocument(); | ||
}); | ||
|
||
// no password strength verification | ||
await user.type(screen.getByLabelText(/password/i), 'password'); | ||
expect(screen.queryByText(/password is too weak/i)).not.toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { Formik, FormikValues } from 'formik'; | ||
import { Button } from '@chakra-ui/react'; | ||
import { MdLogin } from 'react-icons/md'; | ||
import { loginSchema } from '../../utils/validation/schemas'; | ||
import { FormikInput } from '../layout'; | ||
|
||
interface LoginFormProps { | ||
onSubmit?: (values: FormikValues) => void | ||
} | ||
|
||
function LoginForm({ onSubmit }: LoginFormProps) { | ||
return ( | ||
<Formik | ||
initialValues={{ | ||
username: '', password: '', | ||
}} | ||
validationSchema={loginSchema} | ||
onSubmit={(values, { setSubmitting }) => { | ||
onSubmit?.(values); | ||
console.log('submitted', values); | ||
setTimeout(() => { setSubmitting(false); }, 400); | ||
}} | ||
> | ||
{ (formik) => ( | ||
<form onSubmit={formik.handleSubmit}> | ||
<FormikInput | ||
id="username" | ||
name="username" | ||
label="Username" | ||
type="text" | ||
isRequired | ||
/> | ||
<FormikInput | ||
id="password" | ||
name="password" | ||
label="Password" | ||
type="password" | ||
isRequired | ||
/> | ||
|
||
<Button | ||
type="submit" | ||
variant="solid" | ||
leftIcon={<MdLogin />} | ||
isLoading={formik.isSubmitting} | ||
> | ||
Log In | ||
</Button> | ||
|
||
</form> | ||
)} | ||
|
||
</Formik> | ||
); | ||
} | ||
|
||
export default LoginForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { render, screen, waitFor } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import SignUpForm from './SignUpForm'; | ||
|
||
describe('Sign Up Form', () => { | ||
test('renders and submits', async () => { | ||
const handleSubmit = jest.fn(); | ||
render(<SignUpForm onSubmit={handleSubmit} />); | ||
const user = userEvent.setup(); | ||
|
||
await user.type(screen.getByLabelText(/^name/i), 'John'); | ||
await user.type(screen.getByLabelText(/username/i), 'john'); | ||
await user.type(screen.getByLabelText(/email/i), 'john@example.com'); | ||
await user.type(screen.getByLabelText(/password/i), '$Password123'); | ||
|
||
await user.click(screen.getByRole('button', { name: /sign up/i })); | ||
|
||
await waitFor(() => expect(handleSubmit).toHaveBeenCalledWith({ | ||
name: 'John', | ||
username: 'john', | ||
email: 'john@example.com', | ||
password: '$Password123', | ||
})); | ||
}); | ||
|
||
test('validation schema is correct', async () => { | ||
const handleSubmit = jest.fn(); | ||
render(<SignUpForm onSubmit={handleSubmit} />); | ||
const user = userEvent.setup(); | ||
|
||
// submit the form | ||
await user.click(screen.getByRole('button', { name: /sign up/i })); | ||
|
||
await waitFor(() => { | ||
expect(handleSubmit).not.toHaveBeenCalled(); | ||
|
||
// required fields | ||
expect(screen.queryByText(/^name is required/i)).toBeInTheDocument(); | ||
expect(screen.queryByText(/username is required/i)).toBeInTheDocument(); | ||
expect(screen.queryByText(/email is required/i)).toBeInTheDocument(); | ||
expect(screen.queryByText(/password is required/i)).toBeInTheDocument(); | ||
}); | ||
|
||
// valid email | ||
const emailInput = screen.getByLabelText(/email/i); | ||
await user.type(emailInput, 'jj'); | ||
expect(screen.queryByText(/email must be valid/i)).toBeInTheDocument(); | ||
await user.type(emailInput, 'ohn@email.com'); | ||
expect(screen.queryByText(/email must be valid/i)).not.toBeInTheDocument(); | ||
|
||
// valid password | ||
const passwordInput = screen.getByLabelText(/password/i); | ||
await user.type(passwordInput, 'pass'); | ||
expect(screen.queryByText(/password must contain at least 8 characters/i)).toBeInTheDocument(); | ||
await user.type(passwordInput, 'word'); | ||
expect(screen.queryByText(/password is too weak/i)).toBeInTheDocument(); | ||
await user.type(passwordInput, 'D123$'); | ||
expect(screen.queryByText(/password is too weak/i)).not.toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { Formik, FormikValues } from 'formik'; | ||
import { Button } from '@chakra-ui/react'; | ||
import { MdLogin } from 'react-icons/md'; | ||
import { signUpSchema } from '../../utils/validation/schemas'; | ||
import { FormikInput } from '../layout'; | ||
|
||
interface SignUpFormProps { | ||
onSubmit?: (values: FormikValues) => void | ||
} | ||
|
||
function SignUpForm({ onSubmit }: SignUpFormProps) { | ||
return ( | ||
<Formik | ||
initialValues={{ | ||
name: '', username: '', email: '', password: '', | ||
}} | ||
validationSchema={signUpSchema} | ||
onSubmit={(values, { setSubmitting }) => { | ||
onSubmit?.(values); | ||
console.log('submitted', values); | ||
setTimeout(() => { setSubmitting(false); }, 400); | ||
}} | ||
> | ||
{ (formik) => ( | ||
<form onSubmit={formik.handleSubmit}> | ||
<FormikInput | ||
id="name" | ||
name="name" | ||
label="Name" | ||
type="text" | ||
isRequired | ||
/> | ||
<FormikInput | ||
id="username" | ||
name="username" | ||
label="Username" | ||
type="text" | ||
isRequired | ||
/> | ||
<FormikInput | ||
id="email" | ||
name="email" | ||
label="Email" | ||
type="email" | ||
isRequired | ||
/> | ||
<FormikInput | ||
id="password" | ||
name="password" | ||
label="Password" | ||
type="password" | ||
isRequired | ||
/> | ||
|
||
<Button | ||
type="submit" | ||
leftIcon={<MdLogin />} | ||
isLoading={formik.isSubmitting} | ||
> | ||
Sign Up | ||
</Button> | ||
|
||
</form> | ||
)} | ||
|
||
</Formik> | ||
); | ||
} | ||
|
||
export default SignUpForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
/* eslint-disable import/prefer-default-export */ | ||
import Main from './Main/Main'; | ||
import SignUpForm from './SignUpForm/SignUpForm'; | ||
import LoginForm from './LoginForm/LoginForm'; | ||
|
||
export { Main }; | ||
export { Main, SignUpForm, LoginForm }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { FieldHookConfig, useField } from 'formik'; | ||
import { | ||
FormControl, FormErrorMessage, FormLabel, Input, InputProps, | ||
} from '@chakra-ui/react'; | ||
|
||
interface FormControlProps { | ||
isDisabled?: boolean; | ||
isReadOnly?: boolean; | ||
isRequired?: boolean; | ||
} | ||
|
||
type FormikInputProps = | ||
FieldHookConfig<string> & FormControlProps & InputProps & { | ||
label?: string | ||
}; | ||
|
||
function FormikInput({ label, id, ...props }: FormikInputProps) { | ||
const [field, meta] = useField(props); | ||
|
||
const fmp: FormControlProps = {}; | ||
let inputProps: InputProps = {}; | ||
|
||
({ | ||
isDisabled: fmp.isDisabled, | ||
isReadOnly: fmp.isReadOnly, | ||
isRequired: fmp.isRequired, | ||
...inputProps | ||
} = props); | ||
|
||
return ( | ||
<FormControl isInvalid={meta.touched && Boolean(meta.error)} {...fmp}> | ||
{ label && <FormLabel htmlFor={id}>{label}</FormLabel> } | ||
<Input id={id} {...field} {...inputProps} /> | ||
<FormErrorMessage>{meta.error}</FormErrorMessage> | ||
</FormControl> | ||
); | ||
} | ||
|
||
export default FormikInput; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import React from 'react'; | ||
import NextLink, { LinkProps as NextLinkProps } from 'next/link'; | ||
import { | ||
Button as ChakraButton, | ||
ButtonProps as ChakraButtonProps, | ||
Link as ChakraLink, | ||
LinkProps as ChakraLinkProps, | ||
} from '@chakra-ui/react'; | ||
import { FiExternalLink } from 'react-icons/fi'; | ||
|
||
type ButtonLink = | ||
Exclude<ChakraButtonProps, React.ButtonHTMLAttributes<HTMLButtonElement>> | ||
& React.AnchorHTMLAttributes<HTMLAnchorElement>; | ||
|
||
export type LinkProps = | ||
Omit<NextLinkProps, 'as' | 'passHref'> | ||
& ((Omit<ChakraLinkProps, 'href'> & { type?: 'link' }) | (ButtonLink & { type: 'button' })); | ||
|
||
/** | ||
* There are two @ts-ignore because the ChakraButton, even though having the 'as' property, it | ||
* expects to receive certain props as HTMLButtonElement. I cannot be arsed to try to fix this, | ||
* nor I know how to. | ||
*/ | ||
function Link({ type = 'link', children, ...props }: LinkProps) { | ||
const { | ||
href, prefetch, replace, scroll, shallow, locale, role, ...chakraProps | ||
} = props; | ||
|
||
const nextjsProps = { | ||
href, prefetch, replace, scroll, shallow, locale, | ||
}; | ||
|
||
const isExternal = typeof href === 'string' && (href.startsWith('http') || href.startsWith('mailto:')); | ||
|
||
switch (type) { | ||
case 'button': | ||
if (isExternal) { | ||
return ( | ||
// @ts-ignore | ||
<ChakraButton | ||
as="a" | ||
href={href} | ||
target="_blank" | ||
rel="noopener" | ||
{...chakraProps} | ||
> | ||
{children} | ||
{' '} | ||
<FiExternalLink style={{ marginLeft: '0.33em' }} /> | ||
</ChakraButton> | ||
); | ||
} | ||
|
||
return ( | ||
<NextLink {...nextjsProps} passHref> | ||
{ /** @ts-ignore */} | ||
<ChakraButton | ||
as="a" | ||
{...chakraProps} | ||
> | ||
{children} | ||
</ChakraButton> | ||
</NextLink> | ||
); | ||
case 'link': | ||
if (isExternal) { | ||
return ( | ||
<ChakraLink | ||
href={href} | ||
isExternal | ||
{...chakraProps} | ||
> | ||
{children} | ||
{' '} | ||
<FiExternalLink style={{ display: 'inline', verticalAlign: 'middle' }} /> | ||
</ChakraLink> | ||
); | ||
} | ||
|
||
return ( | ||
<NextLink {...nextjsProps} passHref> | ||
<ChakraLink {...chakraProps}>{children}</ChakraLink> | ||
</NextLink> | ||
|
||
); | ||
default: | ||
return null; | ||
} | ||
} | ||
|
||
export default Link; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
/* eslint-disable import/prefer-default-export */ | ||
import HeadTitle from './HeadTitle/HeadTitle'; | ||
import Link from './Link/Link'; | ||
import FormikInput from './FormikInput/FormikInput'; | ||
|
||
export { HeadTitle }; | ||
export { HeadTitle, Link, FormikInput }; |
Oops, something went wrong.