Skip to content

Commit

Permalink
Add temporary unstyled Login and Guest Login forms (#98)
Browse files Browse the repository at this point in the history
- Temporarily include unstyled versions of the Login and Guest Login
  forms from last year's site to test the login functionality and flow
- Include basic `Portal` showing user identity, similar to previous demo
  • Loading branch information
taesungh authored Dec 16, 2023
1 parent 9dd09da commit e5dc2e9
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 0 deletions.
15 changes: 15 additions & 0 deletions apps/site/src/app/guest-login/GuestLogin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import VerificationForm from "./components/VerificationForm";

function GuestLogin() {
return (
<div className="min-h-screen flex flex-col justify-center bg-white text-black">
<h1>Log In</h1>
<p>
A login passphrase was sent to your email. Please enter the passphrase.
</p>
<VerificationForm />
</div>
);
}

export default GuestLogin;
39 changes: 39 additions & 0 deletions apps/site/src/app/guest-login/components/VerificationForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import { useSearchParams } from "next/navigation";

import ValidatingForm from "@/lib/components/ValidatingForm/ValidatingForm";

const VERIFICATION_PATH = "/api/guest/verify";
const PASSPHRASE_REGEX = /\w+-\w+-\w+-\w+/;

function VerificationForm() {
const searchParams = useSearchParams();
const email = searchParams.get("email");

if (!email) {
return <p>Error: email was not provided</p>;
}

return (
<ValidatingForm method="post" action={VERIFICATION_PATH}>
<input type="email" name="email" value={email} readOnly hidden />
<div>
<label>Passphrase</label>
<input
type="text"
pattern={PASSPHRASE_REGEX.source}
required
name="passphrase"
placeholder="Enter passphrase"
/>
<p className="feedback invalid">
Sorry, that passphrase is invalid.
</p>
</div>
<button type="submit">Continue</button>
</ValidatingForm>
);
}

export default VerificationForm;
6 changes: 6 additions & 0 deletions apps/site/src/app/guest-login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Guest Login | IrvineHacks 2024",
};

export { default as default } from "./GuestLogin";
14 changes: 14 additions & 0 deletions apps/site/src/app/login/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import LoginForm from "./components/LoginForm";

function Login() {
// TODO: check if user is already authenticated

return (
<div className="min-h-screen flex flex-col justify-center bg-white text-black">
<h1>Log In</h1>
<LoginForm />
</div>
);
}

export default Login;
32 changes: 32 additions & 0 deletions apps/site/src/app/login/components/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import ValidatingForm from "@/lib/components/ValidatingForm/ValidatingForm";

const EMAIL_REGEX = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/;
const LOGIN_PATH = "/api/user/login";

function LoginForm() {
return (
<ValidatingForm method="post" action={LOGIN_PATH}>
<div>
<label>Email address</label>
<input
type="email"
pattern={EMAIL_REGEX.source}
required
name="email"
placeholder="Enter email"
aria-describedby="email-description"
/>
<div className="feedback invalid">
Sorry, that email address is invalid.
</div>
<p id="email-description" className="muted">
UCI students will log in with UCI SSO. Please make sure to
use your school email address if you have one.
</p>
</div>
<button type="submit">Continue</button>
</ValidatingForm>
);
}

export default LoginForm;
7 changes: 7 additions & 0 deletions apps/site/src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Log In | IrvineHacks 2024",
};

export { default as default } from "./Login";
22 changes: 22 additions & 0 deletions apps/site/src/app/portal/Portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import api from "@/lib/utils/api";

async function getIdentity(): Promise<string> {
const res = await api.get<string>("/user/me");
return res.data;
}

async function Portal() {
const identity = await getIdentity();
return (
<div className="min-h-screen flex flex-col justify-center bg-white text-black">
<h1>Hello</h1>
{Object.entries(identity).map(([key, value]) => (
<p key={key}>
{key} - {value}
</p>
))}
</div>
);
}

export default Portal;
1 change: 1 addition & 0 deletions apps/site/src/app/portal/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as default } from "./Portal";
33 changes: 33 additions & 0 deletions apps/site/src/lib/components/ValidatingForm/ValidatingForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";

import { FormEvent, PropsWithChildren, useState } from "react";

interface FormProps {
action?: string;
method?: string;
}

function ValidatingForm(props: PropsWithChildren<FormProps>) {
const [validated, setValidated] = useState<boolean>(false);

const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
const form = event.currentTarget;
if (!form.checkValidity()) {
// prevent submission to display validation feedback
event.preventDefault();
}
setValidated(true);
};

return (
<form
onSubmit={handleSubmit}
noValidate // use custom validation feedback
className={validated ? "validated" : ""}
// validated={validated}
{...props}
/>
);
}

export default ValidatingForm;

0 comments on commit e5dc2e9

Please sign in to comment.