From daef60cdd3672c6bcb93c2f15acc76593f31292e Mon Sep 17 00:00:00 2001 From: iibarbari <45620986+iibarbari@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:02:18 +0300 Subject: [PATCH] add sign up page --- .../ActivityOverlay.module.css | 2 + frontend/src/components/App/index.tsx | 2 + .../AuthenticationHeader.module.css | 40 ++++ .../components/AuthenticationHeader/index.tsx | 32 +++ .../components/Bootstrap/Bootstrap.module.css | 36 +--- frontend/src/components/Bootstrap/index.tsx | 110 +++++----- .../src/components/Header/Header.module.css | 2 +- frontend/src/components/Header/index.tsx | 4 +- frontend/src/components/Home/index.tsx | 2 +- .../src/components/Layout/Layout.module.css | 2 +- frontend/src/components/Layout/index.tsx | 1 + .../src/components/SignIn/SignIn.module.css | 45 ++--- frontend/src/components/SignIn/index.tsx | 77 ++++--- .../src/components/SignUp/SignUp.module.css | 11 + frontend/src/components/SignUp/index.tsx | 188 ++++++++++++++++++ frontend/src/styles/components.css | 81 +++++++- 16 files changed, 475 insertions(+), 160 deletions(-) create mode 100644 frontend/src/components/AuthenticationHeader/AuthenticationHeader.module.css create mode 100644 frontend/src/components/AuthenticationHeader/index.tsx create mode 100644 frontend/src/components/SignUp/SignUp.module.css create mode 100644 frontend/src/components/SignUp/index.tsx diff --git a/frontend/src/components/ActivityOverlay/ActivityOverlay.module.css b/frontend/src/components/ActivityOverlay/ActivityOverlay.module.css index 9339a607..da9b4391 100644 --- a/frontend/src/components/ActivityOverlay/ActivityOverlay.module.css +++ b/frontend/src/components/ActivityOverlay/ActivityOverlay.module.css @@ -16,6 +16,7 @@ justify-content: center; overflow: hidden; position: absolute; + z-index: 10; } .spinner { @@ -24,6 +25,7 @@ position: absolute; top: 50%; transform: translate(-50%, -50%); + z-index: 11; } } } diff --git a/frontend/src/components/App/index.tsx b/frontend/src/components/App/index.tsx index f9debdcc..99ea3f79 100644 --- a/frontend/src/components/App/index.tsx +++ b/frontend/src/components/App/index.tsx @@ -5,6 +5,7 @@ import Error from "~/components/Error"; import Header from "~/components/Header"; import Home from "~/components/Home"; import SignIn from "~/components/SignIn"; +import SignUp from "~/components/SignUp"; import Sites from "~/components/Sites"; export default function App() { @@ -13,6 +14,7 @@ export default function App() {
+ diff --git a/frontend/src/components/AuthenticationHeader/AuthenticationHeader.module.css b/frontend/src/components/AuthenticationHeader/AuthenticationHeader.module.css new file mode 100644 index 00000000..179a4475 --- /dev/null +++ b/frontend/src/components/AuthenticationHeader/AuthenticationHeader.module.css @@ -0,0 +1,40 @@ +@import "~/styles/media.css"; + +.authentication-header { + text-align: center; + + .action-group { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + justify-content: center; + margin-top: 2.5rem; + } + + .heading { + font-size: 2rem; + font-weight: 700; + line-height: 3.5rem; + margin-bottom: 0.5rem; + transition: font-size 0.2s ease; + } + + .summary { + color: rgba(var(--color-blue-700-rgb)); + font-weight: 700; + margin-bottom: 0.25rem; + } + + @media (prefers-color-scheme: dark) { + .summary { + color: rgba(var(--color-blue-400-rgb)); + } + } + + @media (--min-sm) { + .heading { + font-size: 3rem; + } + } +} diff --git a/frontend/src/components/AuthenticationHeader/index.tsx b/frontend/src/components/AuthenticationHeader/index.tsx new file mode 100644 index 00000000..66be241c --- /dev/null +++ b/frontend/src/components/AuthenticationHeader/index.tsx @@ -0,0 +1,32 @@ +import clsx from "clsx"; +import { PropsWithoutRef, ReactNode, JSX } from "react"; +import styles from "./AuthenticationHeader.module.css"; + +type AuthenticationHeaderProps = Overwrite, "children">, { + actions?: ReactNode, + description: ReactNode, + summary: ReactNode, + title: ReactNode +}> + +export default function AuthenticationHeader({ actions, className, description, summary, title, ...props }: AuthenticationHeaderProps) { + return ( +
+

{summary}

+ +

+ {title} +

+ +

+ {description} +

+ + {actions ? ( +
+ {actions} +
+ ) : null} +
+ ); +} diff --git a/frontend/src/components/Bootstrap/Bootstrap.module.css b/frontend/src/components/Bootstrap/Bootstrap.module.css index 8c65c63b..e946cb84 100644 --- a/frontend/src/components/Bootstrap/Bootstrap.module.css +++ b/frontend/src/components/Bootstrap/Bootstrap.module.css @@ -1,35 +1,15 @@ -.title { - margin-bottom: 2rem; - text-align: center; - - .summary { - color: rgba(var(--color-blue-700-rgb)); - font-weight: 700; - - @media (prefers-color-scheme: dark) { - color: rgba(var(--color-blue-400-rgb)); - } - } - - .heading { - font-size: 3rem; - font-weight: 700; - line-height: 3.5rem; - margin-bottom: 1rem; - } - - .button-group { - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 1rem; - justify-content: center; - margin-block: 2rem; - } +.button-group { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + justify-content: center; + margin-block: 2rem; } .card { margin-inline: auto; + margin-top: 2rem; max-width: 32rem; } diff --git a/frontend/src/components/Bootstrap/index.tsx b/frontend/src/components/Bootstrap/index.tsx index 7176fb87..9848c0ba 100644 --- a/frontend/src/components/Bootstrap/index.tsx +++ b/frontend/src/components/Bootstrap/index.tsx @@ -4,6 +4,7 @@ import { useErrorBoundary } from "react-error-boundary"; import { useForm } from "react-hook-form"; import { Link } from "wouter"; import ActivityOverlay from "~/components/ActivityOverlay"; +import AuthenticationHeader from "~/components/AuthenticationHeader"; import Layout from "~/components/Layout"; import Title from "~/components/Title"; import { api } from "~/lib/api"; @@ -52,7 +53,7 @@ export default function Bootstrap() { if (accessTokenResponse.ok) { localStorage.setItem("accessToken", accessTokenResponseJson.accessToken); } else { - /* TODO: Handle error */ + showBoundary(accessTokenResponseJson); } } else { setErrors(setError, responseJson); @@ -99,65 +100,53 @@ export default function Bootstrap() { ) : isAlreadyBootstrapped ? (
-
- Bootstrap - -

- Already bootstrapped! -

- -

- It looks like PoeticMetric has already been initialized. -

- -
- - Return home - - - - Contact support - -
-
+ + + Return home + + + + Contact support + + + )} + description="It looks like PoeticMetric has already been initialized." + summary="Bootstrap" + title="Already bootstrapped!" + />
) : isBootstrapped ? (
-
- Bootstrap - -

- You are all set! -

- -

- PoeticMetric has been successfully initialized. -

- -
+ Sign up to create dashboard -
-
+ )} + description="PoeticMetric has been successfully initialized." + summary="Bootstrap" + title="You are all set!" + />
) : (
-
- Bootstrap - -

- Welcome to -
- PoeticMetric! -

- -

Complete PoeticMetric installation to continue.

-
+ + Welcome to +
+ PoeticMetric! + + )} + />
@@ -166,7 +155,12 @@ export default function Bootstrap() {
- + {!!errors.userName ? (
{errors.userName.message}
) : null}
@@ -188,7 +182,12 @@ export default function Bootstrap() {
- + {!!errors.userPassword ? (
{errors.userPassword.message}
) : null}
@@ -209,14 +208,23 @@ export default function Bootstrap() {
- + {!!errors.organizationName ? (
{errors.organizationName.message}
) : null}
- +
diff --git a/frontend/src/components/Header/Header.module.css b/frontend/src/components/Header/Header.module.css index 1b248716..8b839317 100644 --- a/frontend/src/components/Header/Header.module.css +++ b/frontend/src/components/Header/Header.module.css @@ -55,7 +55,7 @@ align-items: center; display: flex; flex-direction: row; - gap: 0.5rem 0.25rem; + gap: 0.5rem; justify-content: flex-start; } diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index 06c5a914..9efdd579 100644 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -38,11 +38,11 @@ export default function Header() {
- + Sign in - + Sign up
diff --git a/frontend/src/components/Home/index.tsx b/frontend/src/components/Home/index.tsx index 2e77318c..0e1304b1 100644 --- a/frontend/src/components/Home/index.tsx +++ b/frontend/src/components/Home/index.tsx @@ -6,7 +6,7 @@ export default function Home() { <> Homepage - +
Something beautiful is about to happen here <3 diff --git a/frontend/src/components/Layout/Layout.module.css b/frontend/src/components/Layout/Layout.module.css index 6a922e33..89aa9dc7 100644 --- a/frontend/src/components/Layout/Layout.module.css +++ b/frontend/src/components/Layout/Layout.module.css @@ -1,5 +1,5 @@ .layout { - margin-block: 2rem; + padding-block: 2rem; &.vertically-center { align-items: center; diff --git a/frontend/src/components/Layout/index.tsx b/frontend/src/components/Layout/index.tsx index e861734e..c2c1e704 100644 --- a/frontend/src/components/Layout/index.tsx +++ b/frontend/src/components/Layout/index.tsx @@ -7,6 +7,7 @@ type LayoutProps = Overwrite, { }> export default function Layout({ children, className, verticallyCenter = false, ...props }: LayoutProps) { + return (
a { + font-size: 0.875rem; } } -.card { - margin-inline: auto; - max-width: 32rem; +.sign-up-link { + font-size: 0.875rem; + text-align: center; } + diff --git a/frontend/src/components/SignIn/index.tsx b/frontend/src/components/SignIn/index.tsx index 21646bf4..4a85f65d 100644 --- a/frontend/src/components/SignIn/index.tsx +++ b/frontend/src/components/SignIn/index.tsx @@ -4,8 +4,9 @@ import { useErrorBoundary } from "react-error-boundary"; import { useForm } from "react-hook-form"; import { Link } from "wouter"; import ActivityOverlay from "~/components/ActivityOverlay"; +import AuthenticationHeader from "~/components/AuthenticationHeader"; import Layout from "~/components/Layout"; -import styles from "~/components/SignIn/SignIn.module.css"; +import styles from "./SignIn.module.css"; import Title from "~/components/Title"; import { api } from "~/lib/api"; import { setErrors } from "~/lib/form"; @@ -22,7 +23,7 @@ type State = { export default function SignIn() { const { showBoundary } = useErrorBoundary(); const [isAlreadySignedIn] = useState(false); - const [signedIn, setSignedIn] = useState(true); + const [signedIn, setSignedIn] = useState(false); const [state, setState] = useState({ isInProgress: false }); const { formState: { errors }, handleSubmit, register, setError } = useForm
({}); @@ -54,61 +55,41 @@ export default function SignIn() { {isAlreadySignedIn ? (
-
- Sign in - -

- Signed in! -

- -

- It looks like you're already signed in. -

- -
+ Go to dashboard -
-
+ )} + description="It looks like you're already signed in." + summary="Sign in" + title="Signed in!" + />
) : signedIn ? (
-
- Sign in - -

- Signed in! -

- -

- You're successfully signed in. -

- -
+ Go to dashboard -
-
+ )} + description="You're successfully signed in." + summary="Sign in" + title="Signed in!" + />
) : (
-
- Sign in - -

- Welcome back! -

- -

- Sign in to view your analytics dashboard. -

-
+
@@ -131,7 +112,11 @@ export default function SignIn() {
- +
+ + + Forgot password? +
Sign in + +
+

+ {"Don't have an account?"} + {" "} + Sign up +

+
diff --git a/frontend/src/components/SignUp/SignUp.module.css b/frontend/src/components/SignUp/SignUp.module.css new file mode 100644 index 00000000..baa042ab --- /dev/null +++ b/frontend/src/components/SignUp/SignUp.module.css @@ -0,0 +1,11 @@ +.card { + margin-inline: auto; + margin-top: 2rem; + max-width: 28rem; +} + +.sign-in-link { + font-size: 0.875rem; + text-align: center; +} + diff --git a/frontend/src/components/SignUp/index.tsx b/frontend/src/components/SignUp/index.tsx new file mode 100644 index 00000000..97c4c4b9 --- /dev/null +++ b/frontend/src/components/SignUp/index.tsx @@ -0,0 +1,188 @@ +import clsx from "clsx"; +import { useState } from "react"; +import { useErrorBoundary } from "react-error-boundary"; +import { useForm } from "react-hook-form"; +import { Link } from "wouter"; +import ActivityOverlay from "~/components/ActivityOverlay"; +import AuthenticationHeader from "~/components/AuthenticationHeader"; +import Layout from "~/components/Layout"; +import Title from "~/components/Title"; +import styles from "./SignUp.module.css"; +import { api } from "~/lib/api"; +import { setErrors } from "~/lib/form"; + +type Form = { + organizationName: string; + userEmail: string; + userName: string; + userPassword: string; +}; + +type State = { + isInProgress: boolean; +}; + +export default function SignUp() { + const { showBoundary } = useErrorBoundary(); + const [isAlreadySignedIn] = useState(false); + const [signedUp, setSignedUp] = useState(false); + const [state, setState] = useState({ isInProgress: false }); + const { formState: { errors }, handleSubmit, register, setError } = useForm
({}); + + async function submit(data: Form) { + try { + setState((s) => ({ ...s, isInProgress: true })); + + /* TODO: Change the endpoint */ + const response = await api.post("", data); + const responseJson = await response.json(); + + if (response.ok) { + setSignedUp(true); + } else { + setErrors(setError, responseJson); + } + } catch (error) { + showBoundary(error); + } finally { + setState((s) => ({ ...s, isInProgress: false })); + } + } + + return ( + <> + Sign up + + {isAlreadySignedIn ? ( + +
+ + + Go to dashboard + + + )} + description="It looks like you're already signed in." + summary="Sign up" + title="You are in!" + /> +
+
+ ) : signedUp ? ( + +
+ + + Go to dashboard + + + )} + description="You're successfully signed up." + summary="Sign up" + title="Signed up!" + /> +
+
+ ) : ( + +
+ + Welcome to +
+ PoeticMetric! + + )} + /> + +
+ + +
+ +
+ + + + + {!!errors.userName ? ( +
{errors.userName.message}
+ ) : null} +
+ +
+ + + + + {!!errors.userEmail ? ( +
{errors.userEmail.message}
+ ) : null} +
+ +
+ + + + + {!!errors.userPassword ? ( +
{errors.userPassword.message}
+ ) : null} +
+ +
+ + + + + {!!errors.organizationName ? ( +
{errors.organizationName.message}
+ ) : null} +
+ + +
+ + +
+

+ {"Have an account?"} + {" "} + Sign in +

+
+
+
+
+
+ )} + + ); +} diff --git a/frontend/src/styles/components.css b/frontend/src/styles/components.css index 222b1c3c..ec2d2d57 100644 --- a/frontend/src/styles/components.css +++ b/frontend/src/styles/components.css @@ -105,11 +105,7 @@ .fieldset { display: flex; flex-direction: column; - gap: 1.5em; -} - -.form-error { - color: rgb(var(--color-red-600-rgb)); + gap: 1.25rem; } .form-group { @@ -128,6 +124,10 @@ } } +.form-error { + color: rgb(var(--color-red-600-rgb)); +} + .input { background-color: transparent; border: 1px solid var(--color-border); @@ -153,7 +153,14 @@ border-radius: var(--border-radius); .card-body { - padding: 1em; + padding: 1rem; + } + + .card-footer { + background-color: rgba(var(--color-foreground-rgb) / 0.03); + border-radius: 0 0 var(--border-radius) var(--border-radius); + border-top: 1px solid var(--color-border); + padding: 0.5rem 1rem; } } @@ -206,3 +213,65 @@ transform: translate(-50%, -50%) rotate(360deg); } } + +.link { + color: rgb(var(--color-blue-600-rgb)); + font-weight: 500; + text-decoration: none; + transition: color var(--transition-duration) ease-in-out; + + &:focus-visible, &:hover { + color: rgb(var(--color-blue-700-rgb)); + } + + &:active { + color: rgb(var(--color-blue-800-rgb)); + } + + &.link-animate { + position: relative; + + &:before { + background: rgb(var(--color-blue-700-rgb)); + bottom: -2px; + content: ""; + height: 2px; + left: 0; + position: absolute; + transition: all 0.2s ease-in-out; + visibility: hidden; + width: 0; + } + + &:hover { + transition: all 0.15s ease-out; + + &:before { + visibility: visible; + width: 100%; + } + } + } + + &.link-muted { + font-weight: 400; + } + + @media (prefers-color-scheme: dark) { + color: rgb(var(--color-blue-400-rgb)); + + &:focus-visible, &:hover { + color: rgb(var(--color-blue-500-rgb)); + } + + &:active { + color: rgb(var(--color-blue-600-rgb)); + } + + &.link-animate { + &:before { + background: rgb(var(--color-blue-500-rgb)); + } + } + } +}