From bfc0c13ed73da97ff7110dd6b42e0e711b4cbcd7 Mon Sep 17 00:00:00 2001 From: "Planoetscher Daniel (Student Com20)" <daniel.planoetscher@stud-inf.unibz.it> Date: Sun, 18 Apr 2021 22:13:58 +0200 Subject: [PATCH] login form, basic styling for auth forms --- .../src/components/forms/LoginForm/index.tsx | 40 +++++++++++ .../forms/LoginForm/login-form.scss | 9 +++ .../components/forms/RegisterForm/index.tsx | 25 +++++-- .../forms/RegisterForm/register-form.scss | 9 +++ client/src/components/ui/TextInput/index.tsx | 15 ++-- .../components/ui/TextInput/text-input.scss | 6 +- client/src/index.scss | 70 +++++++++++++++++-- client/src/pages/Home/home.scss | 36 ---------- client/src/pages/Login/index.tsx | 31 ++++++-- client/src/pages/Login/login.scss | 30 ++++++++ client/src/pages/Register/index.tsx | 20 ++++-- client/src/pages/Register/register.scss | 29 ++++++++ client/src/styles/settings.scss | 2 +- 13 files changed, 254 insertions(+), 68 deletions(-) create mode 100644 client/src/components/forms/LoginForm/index.tsx create mode 100644 client/src/components/forms/LoginForm/login-form.scss diff --git a/client/src/components/forms/LoginForm/index.tsx b/client/src/components/forms/LoginForm/index.tsx new file mode 100644 index 0000000..960208a --- /dev/null +++ b/client/src/components/forms/LoginForm/index.tsx @@ -0,0 +1,40 @@ +import { FormEvent, useCallback, useState } from "react"; +import TextInput from 'components/ui/TextInput'; +import Button from 'components/ui/Button'; +import './login-form.scss'; + +interface Props { + onSubmit?: (username: string, password: string) => void +} + +export default function RegisterForm({ onSubmit }: Props) { + const [username, setUsername] = useState<string>(''); + const [password, setPassword] = useState<string>(''); + + const handleSubmit = useCallback(async (e: FormEvent) => { + e.preventDefault(); + onSubmit?.(username, password); + }, [onSubmit, password, username]); + + return ( + <form className="login-form" onSubmit={handleSubmit}> + <TextInput + label="Username" + name="username" + color="dark" + onChange={setUsername} + /> + <TextInput + label="Password" + name="password" + color="dark" + type="password" + onChange={setPassword} + /> + <Button type="submit"> + Login + </Button> + </form> + ); +} + diff --git a/client/src/components/forms/LoginForm/login-form.scss b/client/src/components/forms/LoginForm/login-form.scss new file mode 100644 index 0000000..591a64e --- /dev/null +++ b/client/src/components/forms/LoginForm/login-form.scss @@ -0,0 +1,9 @@ +.login-form { + .button { + width: 100%; + max-width: 300px; + display: block; + margin: 0 auto; + margin-top: 40px; + } +} diff --git a/client/src/components/forms/RegisterForm/index.tsx b/client/src/components/forms/RegisterForm/index.tsx index 0d8b646..7dcd61c 100644 --- a/client/src/components/forms/RegisterForm/index.tsx +++ b/client/src/components/forms/RegisterForm/index.tsx @@ -18,13 +18,20 @@ async function validateUsername(username: string) { } function validatePassword(password: string) { - if (password?.length < 3) { + if (password?.length < 6) { return 'Password has to be at least 6 characters long'; } else { return null; } } +function validateRepeatPassword(password: string, password2: string) { + if (password !== password2) { + return 'The passwords are not the same.'; + } else { + return null; + } +} interface Props { onSubmit?: (username: string, password: string) => void } @@ -32,16 +39,17 @@ interface Props { export default function RegisterForm({ onSubmit }: Props) { const [username, setUsername] = useState<string>(''); const [password, setPassword] = useState<string>(''); + const [repeatedPassword, setRepeatedPassword] = useState<string>(''); const handleSubmit = useCallback(async (e: FormEvent) => { e.preventDefault(); - if (await validateUsername(username) === null && validatePassword(password) === null) { + if (await validateUsername(username) === null && validatePassword(password) === null && validateRepeatPassword(repeatedPassword, password) === null) { onSubmit?.(username, password); } - }, [ onSubmit, password, username ]); + }, [onSubmit, password, username, repeatedPassword]); return ( - <form onSubmit={handleSubmit}> + <form className="register-form" onSubmit={handleSubmit}> <TextInput label="Username" name="username" @@ -57,6 +65,15 @@ export default function RegisterForm({ onSubmit }: Props) { onChange={setPassword} validation={validatePassword} /> + <TextInput + label="Repeat password" + name="repeat-password" + color="dark" + type="password" + onChange={setRepeatedPassword} + compareValue={password} + validation={validateRepeatPassword} + /> <Button type="submit"> Register now </Button> diff --git a/client/src/components/forms/RegisterForm/register-form.scss b/client/src/components/forms/RegisterForm/register-form.scss index 8b13789..16eed79 100644 --- a/client/src/components/forms/RegisterForm/register-form.scss +++ b/client/src/components/forms/RegisterForm/register-form.scss @@ -1 +1,10 @@ +.register-form { + .button { + width: 100%; + max-width: 300px; + display: block; + margin: 0 auto; + margin-top: 40px; + } +} diff --git a/client/src/components/ui/TextInput/index.tsx b/client/src/components/ui/TextInput/index.tsx index cfc0351..37f8a88 100644 --- a/client/src/components/ui/TextInput/index.tsx +++ b/client/src/components/ui/TextInput/index.tsx @@ -8,21 +8,22 @@ interface Props { name: string, color?: 'dark' type?: 'password' | 'textarea' | 'text', + compareValue?: string, onChange: Dispatch<string>, - validation: (text: string) => Promise<string | null> | string | null; + validation?: ((text: string) => Promise<string | null> | string | null) | ((value1: string, value2: string) => Promise<string | null> | string | null); } -export default function TextInput({ label, name, type, color, onChange, validation }: Props) { +export default function TextInput({ label, name, type, color, onChange, validation, compareValue }: Props) { const [error, setError] = useState(''); const handleChange = useCallback((e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { onChange(e.target.value); - }, [ onChange ]); + }, [onChange]); const handleBlur = useCallback(async (e: FocusEvent<HTMLTextAreaElement | HTMLInputElement>) => { - let error = await validation?.(e.target.value); + let error = await validation?.(e.target.value, compareValue ?? ''); setError(error ?? ''); - }, [ validation ]); + }, [validation, compareValue]); return ( <div className={'input-element' + (type === 'textarea' ? ' textarea' : '')}> @@ -31,10 +32,10 @@ export default function TextInput({ label, name, type, color, onChange, validati { type === 'textarea' ? (<textarea onChange={handleChange} name={name} id={name} onBlur={handleBlur} />) - : (<input onChange={handleChange} type={type} name={name} id={name} onBlur={handleBlur} autoComplete="off"/>) + : (<input onChange={handleChange} type={type} name={name} id={name} onBlur={handleBlur} autoComplete="off" />) } </div > - <div className="error">{error}</div> + {error && (<div className="error">{error}</div>)} </div> ); } diff --git a/client/src/components/ui/TextInput/text-input.scss b/client/src/components/ui/TextInput/text-input.scss index d14feaa..cf4af97 100644 --- a/client/src/components/ui/TextInput/text-input.scss +++ b/client/src/components/ui/TextInput/text-input.scss @@ -7,9 +7,9 @@ .error { color: s.$error-color; - margin-top: 10px; + margin: 10px 0 20px 0; padding-left: 15px; - height: 1rem; + line-height: 1; } .input-field { @@ -25,7 +25,7 @@ } &:before { - background: rgba(0, 0, 0, 0.05); + display: none; } } diff --git a/client/src/index.scss b/client/src/index.scss index fe2c1e8..9b5732c 100644 --- a/client/src/index.scss +++ b/client/src/index.scss @@ -1,6 +1,5 @@ - @use 'styles/settings'as s; -@use 'styles/mixins' as mx; +@use 'styles/mixins'as mx; @use 'styles/functions'as fn; * { @@ -42,7 +41,12 @@ a { padding: 30px; } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { font-weight: s.$weight-bold; } @@ -50,9 +54,31 @@ h1 { font-size: fn.toRem(36); margin-bottom: fn.toRem(20); + &.underlined { + position: relative; + display: inline-block; + + &:after { + content: ' '; + position: absolute; + right: -10px; + bottom: 10px; + width: 90px; + background: rgba(s.$accent, .5); + height: 16px; + z-index: -1; + + @include mx.breakpoint(large) { + height: 24px; + width: 120px; + } + + } + } + @include mx.breakpoint(large) { font-size: fn.toRem(48); - margin-bottom: fn.toRem(40); + margin-bottom: fn.toRem(28); } } @@ -93,3 +119,39 @@ h4 { } } + +.background-container { + position: absolute; + height: 100%; + width: 100%; + left: 0; + top: 0; + z-index: -1; + overflow: hidden; + + .bubble { + width: 400px; + height: 400px; + position: absolute; + filter: blur(200px); + opacity: 0.75; + + @include mx.breakpoint(large) { + width: 500px; + height: 500px; + filter: blur(200px); + } + + &.secondary { + background: s.$secondary; + } + + &.primary { + background: s.$primary; + } + + &.accent { + background: s.$accent; + } + } +} \ No newline at end of file diff --git a/client/src/pages/Home/home.scss b/client/src/pages/Home/home.scss index a6d32bc..26b55f9 100644 --- a/client/src/pages/Home/home.scss +++ b/client/src/pages/Home/home.scss @@ -443,39 +443,3 @@ footer { } } } - -.background-container { - position: absolute; - height: 100%; - width: 100%; - left: 0; - top: 0; - z-index: -1; - overflow: hidden; - - .bubble { - width: 400px; - height: 400px; - position: absolute; - filter: blur(200px); - opacity: 0.75; - - @include mx.breakpoint(large) { - width: 500px; - height: 500px; - filter: blur(200px); - } - - &.secondary { - background: s.$secondary; - } - - &.primary { - background: s.$primary; - } - - &.accent { - background: s.$accent; - } - } -} \ No newline at end of file diff --git a/client/src/pages/Login/index.tsx b/client/src/pages/Login/index.tsx index 2b29634..22ce322 100644 --- a/client/src/pages/Login/index.tsx +++ b/client/src/pages/Login/index.tsx @@ -1,15 +1,34 @@ import Page from 'components/ui/Page'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; +import LoginForm from 'components/forms/LoginForm'; import './login.scss'; +import { useCallback } from 'react'; +import { login } from 'adapters/auth'; export default function Login() { + const history = useHistory(); + const handleSubmit = useCallback(async (username: string, password: string) => { + try { + if (await login(username, password)) { + history.push('/tasks'); + } + } catch (e) { } + }, [history]); + return ( - <Page header={false}> - <div className="content-container"> - <h1>Login</h1> - <Link to="/register">You don't have an account?</Link> + <div className="login-page-container"> + <Page className="login-page" header={false}> + <div className="content-container"> + <h1 className="underlined">Login</h1> + <LoginForm onSubmit={handleSubmit} /> + <Link to="/register" className="link">You don't have an account?</Link> + </div> + </Page> + <div className="background-container"> + <div className="bubble primary" style={{ top: '0', right: '0' }}></div> + <div className="bubble accent" style={{ bottom: '-20%', left: '-20%' }}></div> </div> - </Page> + </div> ); } diff --git a/client/src/pages/Login/login.scss b/client/src/pages/Login/login.scss index e69de29..d83f594 100644 --- a/client/src/pages/Login/login.scss +++ b/client/src/pages/Login/login.scss @@ -0,0 +1,30 @@ +@use 'styles/settings.scss'as s; +@use 'styles/mixins.scss'as mx; + +.login-page-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + + .login-page { + max-width: 600px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + @include mx.breakpoint(medium) { + min-height: auto; + padding: 60px; + border-radius: 25px; + } + + .link { + display: block; + text-decoration: underline; + margin-top: 20px; + text-align: center; + } + } +} diff --git a/client/src/pages/Register/index.tsx b/client/src/pages/Register/index.tsx index 6dc9aaa..711536a 100644 --- a/client/src/pages/Register/index.tsx +++ b/client/src/pages/Register/index.tsx @@ -17,16 +17,22 @@ export default function Register() { history.push('/tasks'); } } catch (e) { } - }, [ history ]); + }, [history]); return ( - <Page header={false}> - <div className="content-container"> - <h1>Register</h1> - <RegisterForm onSubmit={handleSubmit} /> - <Link to="/login">You already have an account?</Link> + <div className="register-page-container"> + <Page className="register-page" header={false}> + <div className="content-container"> + <h1 className="underlined">Register</h1> + <RegisterForm onSubmit={handleSubmit} /> + <Link className="link" to="/login">You already have an account?</Link> + </div> + </Page> + <div className="background-container"> + <div className="bubble primary" style={{ top: '-10%', right: '-20%' }}></div> + <div className="bubble accent" style={{ bottom: '-20%', left: '-20%' }}></div> </div> - </Page> + </div> ); } diff --git a/client/src/pages/Register/register.scss b/client/src/pages/Register/register.scss index 8b13789..7894312 100644 --- a/client/src/pages/Register/register.scss +++ b/client/src/pages/Register/register.scss @@ -1 +1,30 @@ +@use 'styles/settings.scss'as s; +@use 'styles/mixins.scss'as mx; +.register-page-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + + .register-page { + max-width: 700px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + @include mx.breakpoint(medium) { + min-height: auto; + padding: 60px; + border-radius: 25px; + } + + .link { + display: block; + text-decoration: underline; + margin-top: 20px; + text-align: center; + } + } +} diff --git a/client/src/styles/settings.scss b/client/src/styles/settings.scss index d95c9c1..f12549a 100644 --- a/client/src/styles/settings.scss +++ b/client/src/styles/settings.scss @@ -9,7 +9,7 @@ $dark: #1f1f1f; $white: #fff; $black: #000; -$error-color: #ff0000; +$error-color: $secondary; $colors: ( 'primary': $primary, -- GitLab