Form components. New login page.

This commit is contained in:
Mike Cao 2020-08-06 22:03:02 -07:00
parent 9d09d89aef
commit a09867f28c
14 changed files with 165 additions and 45 deletions

1
assets/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 428 389.11"><defs><style>.cls-1{fill:#fff;stroke:#000;stroke-miterlimit:10;stroke-width:20px;}</style></defs><title>Asset 2</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_3" data-name="Layer 3"><circle class="cls-1" cx="214.15" cy="181" r="171"/><path d="M0,175.11c0,118.19,95.81,214,214,214s214-95.81,214-214a215.65,215.65,0,0,0-3-36H3A215.65,215.65,0,0,0,0,175.11Z"/><rect x="0.29" y="134.11" width="427.71" height="60" rx="15"/></g></g></svg>

After

Width:  |  Height:  |  Size: 507 B

20
components/FormLayout.js Normal file
View File

@ -0,0 +1,20 @@
import React from 'react';
import classNames from 'classnames';
import { ErrorMessage } from 'formik';
import styles from './FormLayout.module.css';
export default function FormLayout({ className, children }) {
return <div className={classNames(styles.form, className)}>{children}</div>;
}
export const FormButtons = ({ className, children }) => (
<div className={classNames(styles.buttons, className)}>{children}</div>
);
export const FormError = ({ name }) => (
<ErrorMessage name={name}>{msg => <div className={styles.error}>{msg}</div>}</ErrorMessage>
);
export const FormRow = ({ children }) => <div className={styles.row}>{children}</div>;
export const FormMessage = ({ children }) => <div className={styles.message}>{children}</div>;

View File

@ -0,0 +1,48 @@
.form {
display: flex;
flex-direction: column;
justify-content: center;
}
.form label {
display: inline-block;
min-width: 100px;
}
.row {
display: flex;
align-items: center;
position: relative;
margin-bottom: 20px;
line-height: 1.8;
}
.buttons {
display: flex;
justify-content: center;
}
.error {
color: var(--gray50);
background: var(--color-error);
font-size: var(--font-size-small);
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 0;
left: 100%;
bottom: 0;
margin-left: 10px;
padding: 4px 8px;
border-radius: 4px;
}
.message {
text-align: center;
margin: 20px 0;
padding: 4px 8px;
border-radius: 4px;
color: var(--gray50);
background: var(--gray800);
}

View File

@ -3,6 +3,8 @@ import { useSelector } from 'react-redux';
import classNames from 'classnames'; import classNames from 'classnames';
import Link from 'components/Link'; import Link from 'components/Link';
import UserButton from './UserButton'; import UserButton from './UserButton';
import Icon from './Icon';
import Logo from 'assets/logo.svg';
import styles from './Header.module.css'; import styles from './Header.module.css';
export default function Header() { export default function Header() {
@ -12,7 +14,10 @@ export default function Header() {
<header className={classNames(styles.header, 'container')}> <header className={classNames(styles.header, 'container')}>
<div className="row align-items-center"> <div className="row align-items-center">
<div className="col"> <div className="col">
<div className={styles.title}>{user ? <Link href="/">umami</Link> : 'umami'}</div> <div className={styles.title}>
<Icon icon={<Logo />} size="L" className={styles.logo} />
{user ? <Link href="/">umami</Link> : 'umami'}
</div>
</div> </div>
{user && ( {user && (
<div className="col"> <div className="col">

View File

@ -23,3 +23,7 @@
font-weight: 600; font-weight: 600;
margin-left: 40px; margin-left: 40px;
} }
.logo {
margin-right: 12px;
}

View File

@ -6,6 +6,7 @@ export default function Icon({ icon, className, size = 'M' }) {
return ( return (
<div <div
className={classNames(styles.icon, className, { className={classNames(styles.icon, className, {
[styles.xl]: size === 'XL',
[styles.large]: size === 'L', [styles.large]: size === 'L',
[styles.medium]: size === 'M', [styles.medium]: size === 'M',
[styles.small]: size === 'S', [styles.small]: size === 'S',

View File

@ -9,6 +9,11 @@
fill: currentColor; fill: currentColor;
} }
.xl > svg {
width: 48px;
height: 48px;
}
.large > svg { .large > svg {
width: 24px; width: 24px;
height: 24px; height: 24px;

View File

@ -1,9 +1,18 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import Head from 'next/head'; import Head from 'next/head';
import Header from 'components/Header'; import Header from 'components/Header';
import Footer from 'components/Footer'; import Footer from 'components/Footer';
import styles from './Layout.module.css';
export default function Layout({ title, children }) { export default function Layout({
title,
children,
header = true,
footer = true,
center = false,
middle = false,
}) {
return ( return (
<> <>
<Head> <Head>
@ -14,9 +23,16 @@ export default function Layout({ title, children }) {
rel="stylesheet" rel="stylesheet"
/> />
</Head> </Head>
<Header /> {header && <Header />}
<main className="container">{children}</main> <main
<Footer /> className={classNames(styles.layout, 'container', {
[styles.center]: center,
[styles.middle]: middle,
})}
>
{children}
</main>
{footer && <Footer />}
</> </>
); );
} }

View File

@ -0,0 +1,12 @@
.layout {
display: flex;
flex-direction: column;
}
.center {
align-items: center;
}
.middle {
justify-content: center;
}

View File

@ -1,8 +1,9 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik'; import { Formik, Form, Field } from 'formik';
import Router from 'next/router'; import Router from 'next/router';
import { post } from 'lib/web'; import { post } from 'lib/web';
import Button from './Button'; import Button from './Button';
import FormLayout, { FormButtons, FormError, FormMessage, FormRow } from './FormLayout';
import styles from './Login.module.css'; import styles from './Login.module.css';
const validate = ({ username, password }) => { const validate = ({ username, password }) => {
@ -32,30 +33,35 @@ export default function Login() {
}; };
return ( return (
<Formik <FormLayout>
initialValues={{ <Formik
username: '', initialValues={{
password: '', username: '',
}} password: '',
validate={validate} }}
onSubmit={handleSubmit} validate={validate}
> onSubmit={handleSubmit}
{() => ( >
<Form className={styles.form}> {() => (
<h3>{message}</h3> <Form>
<div> <h1 className={styles.title}>umami</h1>
<label htmlFor="username">Username</label> <FormRow>
<Field name="username" type="text" /> <label htmlFor="username">Username</label>
<ErrorMessage name="username" /> <Field name="username" type="text" />
</div> <FormError name="username" />
<div> </FormRow>
<label htmlFor="password">Password</label> <FormRow>
<Field name="password" type="password" /> <label htmlFor="password">Password</label>
<ErrorMessage name="password" /> <Field name="password" type="password" />
</div> <FormError name="password" />
<Button type="submit">Submit</Button> </FormRow>
</Form> <FormButtons>
)} <Button type="submit">Login</Button>
</Formik> </FormButtons>
<FormMessage>{message}</FormMessage>
</Form>
)}
</Formik>
</FormLayout>
); );
} }

View File

@ -1,5 +1,4 @@
.form { .title {
position: absolute; font-size: var(--font-size-xlarge);
left: 50%; text-align: center;
transform: translateX(-50%);
} }

View File

@ -1,10 +1,13 @@
import React from 'react'; import React from 'react';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import Login from 'components/Login'; import Login from 'components/Login';
import Icon from 'components/Icon';
import Logo from 'assets/logo.svg';
export default function LoginPage() { export default function LoginPage() {
return ( return (
<Layout title="login"> <Layout title="login" header={false} footer={false} center middle>
<Icon icon={<Logo />} size="XL" />
<Login /> <Login />
</Layout> </Layout>
); );

View File

@ -19,7 +19,12 @@ body {
box-sizing: inherit; box-sizing: inherit;
} }
h2 { h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 400; font-weight: 400;
} }
@ -42,16 +47,9 @@ a:visited {
color: var(--primary400); color: var(--primary400);
} }
form label {
display: inline-block;
min-width: 100px;
}
input, input,
textarea { textarea {
padding: 4px 8px; padding: 4px 8px;
margin-right: 10px;
margin-bottom: 20px;
font-size: var(--font-size-normal); font-size: var(--font-size-normal);
line-height: 1.8; line-height: 1.8;
border: 1px solid var(--gray500); border: 1px solid var(--gray500);

View File

@ -7,7 +7,7 @@
--gray400: #cacaca; --gray400: #cacaca;
--gray500: #b3b3b3; --gray500: #b3b3b3;
--gray600: #8e8e8e; --gray600: #8e8e8e;
--gray6700: #6e6e6e; --gray700: #6e6e6e;
--gray800: #4b4b4b; --gray800: #4b4b4b;
--gray900: #2c2c2c; --gray900: #2c2c2c;
@ -26,4 +26,6 @@
--grid-size-medium: 768px; --grid-size-medium: 768px;
--grid-size-large: 992px; --grid-size-large: 992px;
--grid-size-xlarge: 1140px; --grid-size-xlarge: 1140px;
--color-error: #e34850;
} }