Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Started work on login and register screens #40

Merged
merged 15 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "index.js",
"scripts": {
"start": "NODE_ENV=dev ts-node src/index.ts",
"start_win": "set NODE_ENV=dev && ts-node src/index.ts",
"test": "vitest",
"test:int": "./scripts/run-integration.sh"
},
Expand Down
20 changes: 8 additions & 12 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
CreateSocietyBody,
CreateEventBody,
societyIdBody,
eventIdBody
eventIdBody,
RegisterBody
} from "./requestTypes";
import bcrypt from "bcrypt";
import { LoginErrors, SanitisedUser } from "./interfaces";
Expand Down Expand Up @@ -76,27 +77,22 @@ app.get("/", (req: Request, res: Response) => {

app.post(
"/auth/register",
async (req: TypedRequest<LoginBody>, res: Response) => {
const { username, email, password, userType } = req.body;
async (req: TypedRequest<RegisterBody>, res: Response) => {
const { username, email, password } = req.body;

if (!username || !email || !password || !userType) {
if (!username || !email || !password) {
return res.status(400).json({ error: "Missing required fields" });
}

// check database for existing user with same username
const errorCheck: LoginErrors = {
matchingCredentials: true,
};


const results = await prisma.user.findFirst({
where: {
OR: [{ username: username }, { email: email }],
},
});

if (results) {
errorCheck.matchingCredentials = true;
return res.status(400).json(errorCheck);
return res.status(400).json({ error: "Account with same credentials already exists"});
}

const saltRounds: number = 10;
Expand All @@ -110,7 +106,7 @@ app.post(
email,
password: hashedPassword,
salt,
userType,
userType:"ATTENDEE",
lachlanshoesmith marked this conversation as resolved.
Show resolved Hide resolved
dateJoined: new Date(),
profilePicture: null,
},
Expand Down
6 changes: 4 additions & 2 deletions backend/src/requestTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ export interface TypedResponse<T> extends Express.Response {

export interface LoginBody {
username: string;
email: string;
password: string;
userType: UserType;
}

export interface RegisterBody extends LoginBody {
email: string;
}

export interface UserIdBody {
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import NavBar from "./NavBar/NavBar";
import { BrowserRouter, Route, Routes } from "react-router";
import HomePage from "./HomePage/HomePage";
import AboutPage from "./About/About";
import LoginPage from "./Login/Login";
import RegisterPage from "./Register/Register";

function App() {
return (
Expand All @@ -11,6 +13,8 @@ function App() {
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
</Routes>
</div>
<NavBar profileImage="https://i.redd.it/white-pharaoh-in-school-textbook-v0-fgr8oliazlkd1.jpg?width=225&format=pjpg&auto=webp&s=04dc4c2c8a0170c4e161091673352cd966591475"></NavBar>
Expand Down
26 changes: 25 additions & 1 deletion frontend/src/AuthScreen/AuthScreen.module.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
.container{
width: 500px;
width: 20%;
height: 50%;
background-color: hsl(0, 0%, 100%);
border-radius: 10px;
margin: auto;
padding: 30px;
min-width: 500px;
min-height: 300px;
}

.form{
Expand All @@ -11,3 +15,23 @@
align-items: center;
width: 100%;
}

.headerText{
padding-top: 34px;
padding-bottom: 46px;
}

.footer{
text-align: center;
color: hsl(0, 0%, 62%);
}

.footer div:first-child {
padding-top: 50px;
}

.error{
padding: 10px;
color: hsl(0, 100%, 64%);
font-weight: bold;
}
19 changes: 15 additions & 4 deletions frontend/src/AuthScreen/AuthScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { ReactNode } from "react";
import { ReactNode, FormEvent } from "react";
import classes from "./AuthScreen.module.css";
import Button, { ButtonOptions } from "../Button/Button";
import { AuthError } from "../errorHandler";

type AuthScreenProp = {
heading: string;
text: ReactNode; //this is the text in the p tag of the header
inputs: ReactNode[];
buttonText: string;
footer?: ReactNode;
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
error?: AuthError;
};

export function AuthScreen(props: AuthScreenProp) {
return (
<div className={classes.container}>
<header>
<h1>{props.heading}</h1>
<p>{props.text}</p>
<p className={classes.headerText}>{props.text}</p>
</header>
<main>
<form className={classes.form}>
<form className={classes.form} onSubmit={props.onSubmit}>
{props.inputs.map((input: ReactNode) => input)}
<Button
type="submit"
Expand All @@ -29,7 +32,15 @@ export function AuthScreen(props: AuthScreenProp) {
</Button>
</form>
</main>
{props.footer && <footer>{props.footer}</footer>}

<footer className={classes.footer}>
{props.footer && <div>{props.footer}</div>}{" "}
{props.error && (
<div>
<p className={classes.error}>{props.error.message}</p>
</div>
)}
</footer>
</div>
);
}
11 changes: 11 additions & 0 deletions frontend/src/Login/Login.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.container{
background-color: hsl(0, 0%, 96%);
min-height: 100%;
min-width: 100%;
height: fit-content;
padding: 2%;
}

.link{
color: hsl(208, 100%, 50%);
}
74 changes: 74 additions & 0 deletions frontend/src/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import classes from "./Login.module.css";
import { AuthScreen } from "../AuthScreen/AuthScreen";
import { TextInput, TextOptions } from "../TextInput/TextInput";
import { UserCircleIcon } from "@heroicons/react/24/outline";
import { LockClosedIcon } from "@heroicons/react/24/outline";
import { useState, FormEvent } from "react";
import { Link } from "react-router";
import { errorHandler, AuthError } from "../errorHandler";

export default function LoginPage() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<AuthError | undefined>(undefined);
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const res = await fetch("http://localhost:5180/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username,
password,
}),
});
const json = await res.json();

if (!res.ok) {
setError(errorHandler(json.error));
} else {
setError(undefined);
}
}

return (
<main className={classes.container}>
<div className={classes.upper} />
<AuthScreen
heading="Log in"
text={
<span>
New to Pyramids? {}
<Link className={classes.link} to="/register">
Sign Up
</Link>
</span>
}
inputs={[
<TextInput
placeholder="Username"
name="username"
icon={<UserCircleIcon />}
onChange={setUsername}
type={TextOptions.Text}
error={(error && error.usernameError) || false}
/>,
<TextInput
placeholder="Password"
name="password"
icon={<LockClosedIcon />}
onChange={setPassword}
type={TextOptions.Password}
error={(error && error.passwordError) || false}
/>,
]}
buttonText="Log in"
footer={<p>Forgot Password</p>}
onSubmit={handleSubmit}
error={error}
/>
<div className={classes.lower} />
</main>
);
}
33 changes: 24 additions & 9 deletions frontend/src/NavBar/NavBar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,6 @@
min-width: 300px;
}

.profile {
border-radius: 12px;
height: 32px;
width: 32px;
margin-right: 3%;
margin-left: 5%;
background-size: cover;
}

.container a {
text-decoration: none;
font-weight: bold;
Expand All @@ -52,6 +43,7 @@
.container a:hover {
margin-top: 1.5px;
border-bottom: 2px solid hsl(203, 88%, 47%);
transition: border 0.03s ease-in;
}

.container a.active {
Expand All @@ -63,3 +55,26 @@
margin-top: 0;
border: none;
}

.loginLink {
border-radius: 12px;
height: 32px;
width: 32px;
margin-right: 3%;
margin-left: 5%;
background-size: cover;
}

a.loginLink:hover {
margin-top: 0px;
border: none;
outline: 2px solid hsl(203, 88%, 47%);
transition: outline 0.03s linear;
}

a.loginLink.active {
margin-top: 0px;
border: none;
outline: 2px solid hsl(203, 88%, 27%);
}

12 changes: 9 additions & 3 deletions frontend/src/NavBar/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,18 @@ function NavBar(props: NavBarProps) {
About
</NavLink>
</div>
<div
className={classes.profile}

<NavLink
className={({ isActive }) =>
isActive
? `${classes.loginLink} ${classes.active}`
: classes.loginLink
}
style={{
backgroundImage: `url(${props.profileImage})`,
}}
></div>
to="/login"
></NavLink>
</nav>
);
}
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/Register/Register.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.container{
background-color: hsl(0, 0%, 96%);
min-height: 100%;
min-width: 100%;
height: fit-content;
padding: 2%;
}

.link{
color: hsl(208, 100%, 50%);
}
Loading
Loading