Skip to content

Commit

Permalink
feat(auth): add LoginForm component
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jul 21, 2021
1 parent e8c257f commit dbdb875
Show file tree
Hide file tree
Showing 20 changed files with 346 additions and 43 deletions.
15 changes: 15 additions & 0 deletions src/components/Link/Link.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@use '../../styles/variables';
@use '../../styles/theme';

.link {
display: inline-block;
color: var(--primary-color);
font-family: theme.$body-font-family;
font-weight: 700;
text-decoration: none;

&:hover,
&:focus {
text-decoration: underline;
}
}
12 changes: 12 additions & 0 deletions src/components/Link/Link.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { render } from '@testing-library/react';

import Link from './Link';

describe('<Link>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<Link />);

expect(container).toMatchSnapshot();
});
});
25 changes: 25 additions & 0 deletions src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { Link as RouterLink } from 'react-router-dom';
import classNames from 'classnames';

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

type Props = {
className?: string;
href?: string;
to?: string;
target?: string;
children?: React.ReactNode;
};

const Link: React.FC<Props> = ({ to, href, children, className, ...rest }: Props) => {
const linkClassName = classNames(styles.link, className);

if (to) {
return <RouterLink to={to} className={linkClassName}>{children}</RouterLink>;
}

return <a href={href} className={linkClassName} {...rest}>{children}</a>;
};

export default Link;
9 changes: 9 additions & 0 deletions src/components/Link/__snapshots__/Link.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Link> renders and matches snapshot 1`] = `
<div>
<a
class="link"
/>
</div>
`;
13 changes: 13 additions & 0 deletions src/components/LoginForm/LoginForm.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use '../../styles/variables';
@use '../../styles/theme';

.title {
margin-bottom: 24px;
font-family: theme.$body-alt-font-family;
font-weight: 700;
font-size: 24px;
}

.link {
margin-bottom: 24px;
}
13 changes: 13 additions & 0 deletions src/components/LoginForm/LoginForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

import { render } from '../../testUtils';

import LoginForm from './LoginForm';

describe('<LoginForm>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<LoginForm />);

expect(container).toMatchSnapshot();
});
});
78 changes: 78 additions & 0 deletions src/components/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useState } from 'react';
import { useHistory } from 'react-router';
import { useTranslation } from 'react-i18next';

import useToggle from '../../hooks/useToggle';
import { addQueryParam } from '../../utils/history';
import TextField from '../TextField/TextField';
import Button from '../Button/Button';
import Link from '../Link/Link';
import IconButton from '../IconButton/IconButton';
import Visibility from '../../icons/Visibility';
import VisibilityOff from '../../icons/VisibilityOff';

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

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

type Props = {
onSubmit?: (event: React.FormEvent<HTMLFormElement>, formData: LoginFormData) => void;
};

const LoginForm: React.FC<Props> = ({ onSubmit }: 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>
<h2 className={styles.title}>{t('login.sign_in')}</h2>
<TextField
value={formData.email}
onChange={inputChangeHandler}
label={t('login.email')}
placeholder={t('login.email')}
name="email"
type="email"
/>
<TextField
value={formData.password}
onChange={inputChangeHandler}
label={t('login.password')}
placeholder={t('login.password')}
name="password"
type={viewPassword ? 'text' : 'password'}
rightControl={
<IconButton aria-label={viewPassword ? t('login.hide_password') : t('login.view_password')} onClick={() => toggleViewPassword()}>
{viewPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
}
/>
<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 />
</form>
);
};

export default LoginForm;
96 changes: 96 additions & 0 deletions src/components/LoginForm/__snapshots__/LoginForm.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<LoginForm> renders and matches snapshot 1`] = `
<div>
<form
novalidate=""
>
<h2
class="title"
>
login.sign_in
</h2>
<div
class="textField"
>
<label
class="label"
for="text-field_1235_email"
>
login.email
</label>
<div
class="container"
>
<input
class="input"
id="text-field_1235_email"
name="email"
placeholder="login.email"
type="email"
value=""
/>
</div>
</div>
<div
class="textField rightControl"
>
<label
class="label"
for="text-field_1235_password"
>
login.password
</label>
<div
class="container"
>
<input
class="input"
id="text-field_1235_password"
name="password"
placeholder="login.password"
type="password"
value=""
/>
<div
class="control"
>
<div
aria-label="login.view_password"
class="iconButton"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
class="icon"
focusable="false"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<path
d="M11.83,9L15,12.16C15,12.11 15,12.05 15,12A3,3 0 0,0 12,9C11.94,9 11.89,9 11.83,9M7.53,9.8L9.08,11.35C9.03,11.56 9,11.77 9,12A3,3 0 0,0 12,15C12.22,15 12.44,14.97 12.65,14.92L14.2,16.47C13.53,16.8 12.79,17 12,17A5,5 0 0,1 7,12C7,11.21 7.2,10.47 7.53,9.8M2,4.27L4.28,6.55L4.73,7C3.08,8.3 1.78,10 1,12C2.73,16.39 7,19.5 12,19.5C13.55,19.5 15.03,19.2 16.38,18.66L16.81,19.08L19.73,22L21,20.73L3.27,3M12,7A5,5 0 0,1 17,12C17,12.64 16.87,13.26 16.64,13.82L19.57,16.75C21.07,15.5 22.27,13.86 23,12C21.27,7.61 17,4.5 12,4.5C10.6,4.5 9.26,4.75 8,5.2L10.17,7.35C10.74,7.13 11.35,7 12,7Z"
/>
</g>
</svg>
</div>
</div>
</div>
</div>
<a
class="link link"
href="/?u=forgot-password"
>
login.forgot_password
</a>
<button
class="button primary fullWidth large"
>
<span>
login.sign_in
</span>
</button>
</form>
</div>
`;
2 changes: 2 additions & 0 deletions src/components/TextField/TextField.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
.label {
display: block;
margin-bottom: 4px;
font-family: theme.$body-font-family;
font-weight: 700;
text-align: left;
}

Expand Down
7 changes: 0 additions & 7 deletions src/containers/AccountModal/AccountModal.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,3 @@
max-width: 50%;
}
}

.title {
margin-bottom: 24px;
font-family: theme.$body-alt-font-family;
font-weight: 700;
font-size: 24px;
}
6 changes: 2 additions & 4 deletions src/containers/AccountModal/AccountModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useContext } from 'react';
import { useHistory } from 'react-router';

import Button from '../../components/Button/Button';
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';

Expand All @@ -24,9 +24,7 @@ const AccountModal = () => {
return (
<Dialog open={!!view} onClose={closeHandler}>
<div className={styles.banner}>{banner ? <img src={banner} alt="" /> : null}</div>
<h2 className={styles.title}>Login!</h2>
<form>form</form>
<Button label="Sign in" variant="contained" color="primary" fullWidth />
<LoginForm onSubmit={(event, formData) => console.info(event, formData)} />
</Dialog>
);
};
Expand Down
11 changes: 11 additions & 0 deletions src/hooks/useToggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useState } from 'react';

const useToggle = (initialState: boolean = false): [boolean, (forceValue?: boolean) => void] => {
const [state, setState] = useState(initialState);

const toggle = (forceValue?: boolean) => setState(current => typeof forceValue !== 'undefined' ? forceValue : !current);

return [state, toggle];
};

export default useToggle;
3 changes: 2 additions & 1 deletion src/i18n/locales/en_US.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// This file is generated, do not modify manually.
// Run `$ yarn i18next:scan` to update this file

export { default as account } from './en_US/account.json';
export { default as common } from './en_US/common.json';
export { default as error } from './en_US/error.json';
export { default as menu } from './en_US/menu.json';
export { default as search } from './en_US/search.json';
export { default as video } from './en_US/video.json';
export { default as user } from './en_US/user.json';
export { default as video } from './en_US/video.json';
10 changes: 10 additions & 0 deletions src/i18n/locales/en_US/account.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"login": {
"email": "Email",
"forgot_password": "Forgot password?",
"hide_password": "Hide password",
"password": "Password",
"sign_in": "Sign in",
"view_password": "View password"
}
}
28 changes: 13 additions & 15 deletions src/i18n/locales/en_US/user.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
{
"account": {
"email": "Email",
"about_you": "About you",
"edit_account": "Edit account",
"security": "Security",
"password": "Password",
"edit_information": "Edit information",
"edit_password": "Edit password",
"about_you": "About you",
"email": "Email",
"firstname": "First name",
"lastname": "Last name",
"edit_information": "Edit information",
"terms_and_tracking": "Terms & tracking",
"password": "Password",
"security": "Security",
"update_consents": "Update consents"
},
"payment": {
"subscription_details": "Subscription details",
"card_number": "Card number",
"cvc_cvv": "CVC / CVV",
"edit_subscription": "Edit subscription",
"expiry_date": "Expiry date",
"month": "month",
"monthly_subscription": "Monthly subscription",
"more_transactions": "{{ amount }} more transactions",
"next_billing_date_on": "Next billing date is on",
"month": "month",
"edit_subscription": "Edit subscription",
"payment_method": "Payment method",
"card_number": "Card number",
"expiry_date": "Expiry date",
"cvc_cvv": "CVC / CVV",
"transactions": "Transactions",
"price_payed_with_card": "Price payed with card",
"more_transactions": "{{ amount }} more transactions",
"show_all": "Show all"
"subscription_details": "Subscription details",
"transactions": "Transactions"
}
}
Loading

0 comments on commit dbdb875

Please sign in to comment.