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