Skip to content

Commit

Permalink
[md][feat][frontend][sign-in] Created sign in page (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gutts-n authored Aug 27, 2024
1 parent 3339ff6 commit dad1e16
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 46 deletions.
221 changes: 175 additions & 46 deletions frontend/src/pages/auth/signin.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { ErrorAlert } from "@components/_shared/Alerts";
import Spinner from "@components/_shared/Spinner";
import { Button } from "@components/ui/button";
import type { GetServerSidePropsContext } from "next";
import { getCsrfToken, signIn } from "next-auth/react";
import { NextSeo } from "next-seo";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { match } from "ts-pattern";

Expand All @@ -18,36 +19,121 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
}

export default function LoginPage({ csrfToken }: { csrfToken: string }) {
const REMEMBER_LOGIN_DATA_KEY = "REMEMBER_LOGIN_DATA";
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [loggingIn, setLogin] = useState(false);
const router = useRouter();
const { register, handleSubmit } = useForm<{
const { register, handleSubmit, formState, setValue } = useForm<{
username: string;
password: string;
remember?: boolean;
}>();

const { errors } = formState;

useEffect(() => {
const parsedData = JSON.parse(
localStorage.getItem(REMEMBER_LOGIN_DATA_KEY) ?? "{}"
);

if (parsedData.username) {
setValue("username", parsedData.username);
}

if (parsedData.remember) {
setValue("remember", parsedData.remember);
}
}, []);

return (
<>
<NextSeo title="Sign in" />
<div className="flex min-h-full flex-1 items-center justify-center bg-background px-4 py-12 text-primary dark:bg-background-dark dark:text-primary-dark sm:px-6 lg:px-8">
<div className="w-full max-w-sm space-y-10">
<div className="dark:bg-background-dark dark:text-primary-dark flex h-full flex-1 items-center justify-center bg-white text-primary">
<div className="w-[50%] px-28">
<h2 className="text-xl font-bold text-[#111928]">Welcome back</h2>
<div>
<a
aria-label="Home page"
className="mx-auto flex justify-center text-3xl font-extrabold"
href="https://portaljs.com"
<div className="mt-6 grid grid-cols-2 gap-4">
<a
href="#"
className="flex w-full items-center justify-center gap-3 rounded-md bg-white px-3 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent"
>
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-5 w-5">
<path
d="M12.0003 4.75C13.7703 4.75 15.3553 5.36002 16.6053 6.54998L20.0303 3.125C17.9502 1.19 15.2353 0 12.0003 0C7.31028 0 3.25527 2.69 1.28027 6.60998L5.27028 9.70498C6.21525 6.86002 8.87028 4.75 12.0003 4.75Z"
fill="#EA4335"
/>
<path
d="M23.49 12.275C23.49 11.49 23.415 10.73 23.3 10H12V14.51H18.47C18.18 15.99 17.34 17.25 16.08 18.1L19.945 21.1C22.2 19.01 23.49 15.92 23.49 12.275Z"
fill="#4285F4"
/>
<path
d="M5.26498 14.2949C5.02498 13.5699 4.88501 12.7999 4.88501 11.9999C4.88501 11.1999 5.01998 10.4299 5.26498 9.7049L1.275 6.60986C0.46 8.22986 0 10.0599 0 11.9999C0 13.9399 0.46 15.7699 1.28 17.3899L5.26498 14.2949Z"
fill="#FBBC05"
/>
<path
d="M12.0004 24.0001C15.2404 24.0001 17.9654 22.935 19.9454 21.095L16.0804 18.095C15.0054 18.82 13.6204 19.245 12.0004 19.245C8.8704 19.245 6.21537 17.135 5.2654 14.29L1.27539 17.385C3.25539 21.31 7.3104 24.0001 12.0004 24.0001Z"
fill="#34A853"
/>
</svg>
<span className="text-sm font-semibold leading-6">
Sign up with Google
</span>
</a>

<a
href="#"
className="flex w-full items-center justify-center gap-3 rounded-md bg-white px-3 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent"
>
<svg
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
className="h-5 w-5 fill-[#24292F]"
>
<path
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
fillRule="evenodd"
/>
</svg>
<span className="text-sm font-semibold leading-6">
Sign up with GitHub
</span>
</a>
</div>
</div>
<div className="relative my-5">
<div
aria-hidden="true"
className="absolute inset-0 flex items-center"
>
<span className="text-primary dark:text-white">🌀 PortalJS</span>
</a>
<h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-primary dark:text-white">
Sign in to your account
</h2>
<div className="w-full border-t border-gray-200" />
</div>
<div className="relative flex justify-center text-sm font-medium leading-6">
<span className="bg-white px-6 text-[#6B7280]">or</span>
</div>
</div>
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<div>
<form
className="space-y-6"
className="space-y-5"
onSubmit={(event) =>
void handleSubmit(async (data) => {
setLogin(true);
if (data.remember) {
const copiedData: {
username: string;
password?: string;
remember?: boolean;
} = { ...data };
delete copiedData.password;
localStorage.setItem(
REMEMBER_LOGIN_DATA_KEY,
JSON.stringify(copiedData)
);
} else {
localStorage.removeItem(REMEMBER_LOGIN_DATA_KEY);
}

const signInStatus = await signIn("credentials", {
callbackUrl: "/dashboard/datasets",
redirect: false,
Expand All @@ -72,79 +158,122 @@ export default function LoginPage({ csrfToken }: { csrfToken: string }) {
<div>
<label
htmlFor="username"
className="block text-sm font-medium leading-6 text-primary dark:text-white"
className="block text-sm font-medium leading-6 text-[#111928]"
>
Username
Email
</label>
<div className="mt-2">
<input
id="username"
{...register("username")}
className="block w-full rounded-md border-0 py-1.5 text-primary shadow-sm ring-1 ring-inset ring-white/10 focus:ring-2 focus:ring-inset focus:ring-secondary dark:bg-slate-800 dark:text-white sm:text-sm sm:leading-6"
placeholder="[email protected]"
{...register("username", {
required: true,
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Invalid email address",
},
})}
className="block w-full rounded-md border-0 py-3.5 text-[#111928] shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-[#006064] sm:text-sm sm:leading-6"
/>
</div>
{errors && errors.username && (
<div className="mt-2 text-sm text-red-700">
<p>{errors.username.message}</p>
</div>
)}
</div>

<div>
<div className="flex items-center justify-between">
<label
htmlFor="password"
className="block text-sm font-medium leading-6 text-primary dark:text-white"
className="block text-sm font-medium leading-6 text-[#111928]"
>
Password
</label>
<div className="text-sm">
<a
href="#"
className="font-semibold text-secondary-foreground hover:text-secondary-hover"
>
Forgot password?
</a>
</div>
</div>
<div className="mt-2">
<input
id="password"
placeholder="••••••••••"
type="password"
{...register("password")}
autoComplete="current-password"
className="block w-full rounded-md border-0 py-1.5 text-primary shadow-sm ring-1 ring-inset ring-white/10 focus:ring-2 focus:ring-inset focus:ring-secondary dark:bg-slate-800 dark:text-white sm:text-sm sm:leading-6"
className="block w-full rounded-md border-0 py-3.5 text-[#111928] shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-[#006064] sm:text-sm sm:leading-6"
/>
</div>
</div>
<div className="flex justify-between">
<div className="flex items-center gap-2">
<input
type="checkbox"
className="rounded border-gray-300 text-[#006064] focus:ring-[#006064]"
id="remember"
{...register("remember")}
/>
<div className="pb-1 text-[#6B7280]">
<label htmlFor="remember">Remember me</label>
</div>
</div>
<div className="text-sm">
<a
href="#"
className="font-semibold text-[#00ACC1] hover:text-[#008E9D]"
>
Forgot password?
</a>
</div>
</div>
<div>
<div className="col-span-full">
{match(loggingIn)
.with(false, () => (
<button
<Button
disabled={!!errors.password || !!errors.username}
type="submit"
className="flex w-full justify-center rounded-md bg-secondary px-3 py-1.5 text-sm font-semibold leading-6 text-primary shadow-sm hover:bg-secondary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary dark:text-white"
className={
"flex w-full justify-center rounded-md px-3 py-3 text-sm font-semibold leading-6 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary" +
(!!errors.password || !!errors.username
? " cursor-not-allowed"
: "")
}
>
Sign in
</button>
Log in
</Button>
))
.otherwise(() => (
<button
<Button
disabled
className="flex w-full justify-center rounded-md bg-secondary px-3 py-1.5 text-sm font-semibold leading-6 text-primary shadow-sm hover:bg-secondary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary dark:text-white"
className="flex w-full justify-center rounded-md px-3 py-3 text-sm font-semibold leading-6 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary"
>
<Spinner />
</button>
</Button>
))}
</div>
</div>
<p className="text-sm leading-6 text-[#6B7280]">
Don't have an account yet?{" "}
<Link
href="/auth/signup"
className="font-semibold text-[#00ACC1] hover:text-[#008E9D]"
>
Sign up
</Link>
</p>
{errorMessage && <ErrorAlert text={errorMessage} />}
</form>
</div>
{errorMessage && <ErrorAlert text={errorMessage} />}

<p className="text-center text-sm leading-6 text-gray-500">
Not a member?{" "}
<Link
href="/auth/signup"
className="font-semibold text-secondary-foreground hover:text-secondary-hover"
>
sign up here
</Link>
</div>
<div className="flex h-[100vh] w-[50%] flex-col justify-center bg-[#DFF64D] px-20">
<h2 className="pb-7 text-2xl font-semibold text-[#006064]">
Transport Data Commons
</h2>
<h1 className="inline-size-88 pb-3 text-4xl font-extrabold text-[#006064]">
Unlock the Power of Transportation Data
</h1>
<p className="text-[#006064]">
Transport Data Commons aims to improve access, sharing, and
analysing transportation data for a more sustainable future.
</p>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ body {
padding-bottom: min(7vh, 10rem);
}

.inline-size-88 {
inline-size: 88%;
}

.tag-checkbox:checked + label {
background-color: #00bbc2;
color: white;
Expand Down

0 comments on commit dad1e16

Please sign in to comment.