Skip to content

Commit

Permalink
feat(auth): connect login form with MediaStore API
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jul 21, 2021
1 parent 5a02575 commit 8dc53e9
Show file tree
Hide file tree
Showing 17 changed files with 231 additions and 64 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"test": "jest",
"test-watch": "jest --watch",
"test-coverage": "jest --coverage",
"i18next": "i18next src/{components,containers,screens}/**/*.tsx && node ./scripts/i18next/generate.js",
"i18next": "i18next src/{components,containers,screens}/**/{**/,/}*.tsx && node ./scripts/i18next/generate.js",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
"lint:styles": "stylelint \"src/**/*.scss\"",
Expand Down
60 changes: 37 additions & 23 deletions src/components/Button/Button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,27 @@ $large-button-height: 40px;
transition: background-color 0.1s ease, transform 0.1s ease;

@media (hover: hover) and (pointer: fine) {
&:hover,
&:focus {
transform: scale(1.1);
}
&:focus:not(:focus-visible):not(:hover) {
transform: scale(1);
}
&:focus-visible {
transform: scale(1.1);
&:not(.disabled) {
&:hover,
&:focus {
transform: scale(1.1);
}

&:focus:not(:focus-visible):not(:hover) {
transform: scale(1);
}

&:focus-visible {
transform: scale(1.1);
}
}
}

&.disabled {
cursor: default;
opacity: 0.7;
}

&.large {
height: $large-button-height;
}
Expand All @@ -55,26 +64,31 @@ $large-button-height: 40px;
&.outlined {
border: 1px solid rgba(255, 255, 255, 0.3);

&.active,
&:focus {
color: var(--highlight-contrast-color, theme.$btn-primary-color);
background-color: var(--highlight-color, theme.$btn-primary-bg);
border-color: var(--highlight-color, theme.$btn-primary-bg);
&:not(.disabled) {
&.active,
&:focus {
color: var(--highlight-contrast-color, theme.$btn-primary-color);
background-color: var(--highlight-color, theme.$btn-primary-bg);
border-color: var(--highlight-color, theme.$btn-primary-bg);
}
}
}

&.text {
background: none;
opacity: 0.7;

&.active,
&:focus {
opacity: 1;
}
&:hover {
z-index: 1;
background: theme.$btn-default-bg;
opacity: 1;
&:not(.disabled) {
&.active,
&:focus {
opacity: 1;
}

&:hover {
z-index: 1;
background: theme.$btn-default-bg;
opacity: 1;
}
}
}

Expand All @@ -83,7 +97,7 @@ $large-button-height: 40px;
width: 100%;

@media (hover: hover) and (pointer: fine) {
&:hover {
&:hover:not(.disabled) {
transform: scale(1.04);
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type Props = {
to?: string;
role?: string;
className?: string;
type?: 'button' | 'submit' | 'reset';
disabled?: boolean;
} & React.AriaAttributes;

const Button: React.FC<Props> = ({
Expand All @@ -33,6 +35,8 @@ const Button: React.FC<Props> = ({
active = false,
variant = 'outlined',
size = 'medium',
disabled,
type,
to,
onClick,
className,
Expand All @@ -42,6 +46,7 @@ const Button: React.FC<Props> = ({
[styles.active]: active,
[styles.fullWidth]: fullWidth,
[styles.large]: size === 'large',
[styles.disabled]: disabled,
});

const icon = startIcon ? <div className={styles.startIcon}>{startIcon}</div> : null;
Expand All @@ -54,7 +59,7 @@ const Button: React.FC<Props> = ({
{children}
</NavLink>
) : (
<button className={combinedClassNames} onClick={onClick} {...rest}>
<button className={combinedClassNames} onClick={onClick} type={type} disabled={disabled} aria-disabled={disabled} {...rest}>
{icon}
{span}
{children}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Props = {
const Form = ({ initialValues, editing = true, children, onSubmit }: Props): JSX.Element => {
const [values, setValues] = useState<FormValues>(initialValues);

const handleChange = (event: React.FormEvent<HTMLInputElement>) => {
const handleChange = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (!event.currentTarget) return;
setValues({ ...values, [event.currentTarget.name]: event.currentTarget.value });
};
Expand All @@ -30,7 +30,7 @@ const Form = ({ initialValues, editing = true, children, onSubmit }: Props): JSX
return children({ values });
}

return <form onSubmit={handleSubmit}>{children({ values, handleChange, handleSubmit })}</form>;
return <form onSubmit={handleSubmit} noValidate>{children({ values, handleChange, handleSubmit })}</form>;
};

export default Form;
4 changes: 3 additions & 1 deletion src/components/Form/__snapshots__/Form.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

exports[`<Form> renders Form 1`] = `
<div>
<form>
<form
novalidate=""
>
<input
name="test"
value="Testing"
Expand Down
10 changes: 10 additions & 0 deletions src/components/LoginForm/LoginForm.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
font-size: 24px;
}

.error {
margin-bottom: 24px;
padding: 16px;
color: variables.$white;
font-family: theme.$body-font-family;
font-size: 18px;
background-color: theme.$form-error-bg-color;
border-radius: 4px;
}

.link {
margin-bottom: 24px;
}
2 changes: 1 addition & 1 deletion src/components/LoginForm/LoginForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import LoginForm from './LoginForm';

describe('<LoginForm>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<LoginForm />);
const { container } = render(<LoginForm onSubmit={jest.fn()} onChange={jest.fn()} values={{ email: '', password: ''}} errors={{}} />);

expect(container).toMatchSnapshot();
});
Expand Down
51 changes: 21 additions & 30 deletions src/components/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import React from 'react';
import { useHistory } from 'react-router';
import { useTranslation } from 'react-i18next';
import type { LoginFormData } from 'types/account';

import useToggle from '../../hooks/useToggle';
import { addQueryParam } from '../../utils/history';
Expand All @@ -10,55 +11,45 @@ import Link from '../Link/Link';
import IconButton from '../IconButton/IconButton';
import Visibility from '../../icons/Visibility';
import VisibilityOff from '../../icons/VisibilityOff';
import type { FormErrors } from '../../hooks/useForm';

import styles from './LoginForm.module.scss';

export type LoginFormData = {
email: string;
password: string;
};

type Props = {
onSubmit?: (event: React.FormEvent<HTMLFormElement>, formData: LoginFormData) => void;
onSubmit: React.FormEventHandler<HTMLFormElement>;
onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
error?: string;
errors: FormErrors<LoginFormData>;
values: LoginFormData;
submitting: boolean;
};

const LoginForm: React.FC<Props> = ({ onSubmit }: Props) => {
const LoginForm: React.FC<Props> = ({ onSubmit, onChange, values, errors, submitting }: Props) => {
const [viewPassword, toggleViewPassword] = useToggle();
const [formData, setFormData] = useState<LoginFormData>({
email: '',
password: '',
});
const { t } = useTranslation('account');
const history = useHistory();

const formSubmitHandler = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

if (onSubmit) {
onSubmit(event, formData);
}
};

const inputChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
setFormData((data) => ({ ...data, [event.target.name]: event.target.value }));
};

return (
<form onSubmit={formSubmitHandler} noValidate>
<form onSubmit={onSubmit} noValidate>
<h2 className={styles.title}>{t('login.sign_in')}</h2>
{errors.form ? <div className={styles.error}>{errors.form}</div> : null}
<TextField
value={formData.email}
onChange={inputChangeHandler}
value={values.email}
onChange={onChange}
label={t('login.email')}
placeholder={t('login.email')}
error={!!errors.email || !!errors.form}
helperText={errors.email}
name="email"
type="email"
/>
<TextField
value={formData.password}
onChange={inputChangeHandler}
value={values.password}
onChange={onChange}
label={t('login.password')}
placeholder={t('login.password')}
error={!!errors.password || !!errors.form}
helperText={errors.password}
name="password"
type={viewPassword ? 'text' : 'password'}
rightControl={
Expand All @@ -70,7 +61,7 @@ const LoginForm: React.FC<Props> = ({ onSubmit }: Props) => {
<Link className={styles.link} to={addQueryParam(history, 'u', 'forgot-password')}>
{t('login.forgot_password')}
</Link>
<Button label={t('login.sign_in')} variant="contained" color="primary" size="large" fullWidth />
<Button type="submit" label={t('login.sign_in')} variant="contained" color="primary" size="large" disabled={submitting} fullWidth />
</form>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ exports[`<LoginForm> renders and matches snapshot 1`] = `
</a>
<button
class="button primary fullWidth large"
type="submit"
>
<span>
login.sign_in
Expand Down
2 changes: 1 addition & 1 deletion src/components/TextField/TextField.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
margin-bottom: 8px;

&.error {
.label,
.helperText {
color: theme.$text-field-error-color;
}
Expand Down Expand Up @@ -85,6 +84,7 @@

.helperText {
margin-top: 4px;
font-family: theme.$body-font-family;
font-size: 12px;
text-align: left;
}
5 changes: 3 additions & 2 deletions src/containers/AccountModal/AccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { ConfigContext } from '../../providers/ConfigProvider';
import Dialog from '../../components/Dialog/Dialog';
import useQueryParam from '../../hooks/useQueryParam';
import { removeQueryParam } from '../../utils/history';
import LoginForm from '../../components/LoginForm/LoginForm';

import styles from './AccountModal.module.scss';
import Login from './forms/Login';

const AccountModal = () => {
const history = useHistory();
Expand All @@ -24,7 +24,8 @@ const AccountModal = () => {
return (
<Dialog open={!!view} onClose={closeHandler}>
<div className={styles.banner}>{banner ? <img src={banner} alt="" /> : null}</div>
<LoginForm onSubmit={(event, formData) => console.info(event, formData)} />
<Login />

</Dialog>
);
};
Expand Down
45 changes: 45 additions & 0 deletions src/containers/AccountModal/forms/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { object, string, SchemaOf } from 'yup';
import type { LoginFormData } from 'types/account';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router';

import LoginForm from '../../../components/LoginForm/LoginForm';
import { login } from '../../../stores/AccountStore';
import useForm, { UseFormOnSubmitHandler } from '../../../hooks/useForm';
import { removeQueryParam } from '../../../utils/history';

const Login = () => {
const history = useHistory();
const { t } = useTranslation('account');
const loginSubmitHandler: UseFormOnSubmitHandler<LoginFormData> = async (formData, { setErrors, setSubmitting, setValue }) => {
try {
await login(formData.email, formData.password);

// close modal
history.push(removeQueryParam(history, 'u'));
} catch (error: unknown) {
if (error instanceof Error) {
if (error.message.toLowerCase().includes('invalid param email')) {
setErrors({ email: t('login.wrong_email') });
} else {
setErrors({ form: t('login.wrong_combination') });
}
setValue('password', '');
}
}

setSubmitting(false);
};

const validationSchema: SchemaOf<LoginFormData> = object().shape({
email: string().email(t('login.field_is_not_valid_email')).required(t('login.field_required')),
password: string().required(t('login.field_required')),
});
const initialValues: LoginFormData = { email: '', password: '' };
const { handleSubmit, handleChange, values, errors, submitting } = useForm(initialValues, loginSubmitHandler, validationSchema);

return <LoginForm onSubmit={handleSubmit} onChange={handleChange} values={values} errors={errors} submitting={submitting} />;
};

export default Login;
Loading

0 comments on commit 8dc53e9

Please sign in to comment.