Skip to content

Commit

Permalink
Feature/vs2961/create user page (#45)
Browse files Browse the repository at this point in the history
* Add login page

* Final touch ups

* Add create user page

* Fix colors

* Final touches on first create page

* Add last 2 pages

* Add change handler back

* Fix linting

* Fix login + linting
  • Loading branch information
vs2961 authored Feb 19, 2024
1 parent e2edeb7 commit 308a13a
Show file tree
Hide file tree
Showing 17 changed files with 713 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ src/firebase/ServiceAccountKey.json
.env
node_modules/
.eslintcache
ServiceAccountKey.json

#Compiled JavaScript files
lib/**/*.js
Expand Down
Binary file added frontend/public/admin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/public/sidebar/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/team.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 22 additions & 4 deletions frontend/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,31 @@ type ButtonStyles = {
primary: string;
small: string;
default: string;
selected: string;
big: string;
};

const poppins = Poppins({ weight: "400", style: "normal", subsets: [] });

export type ButtonProps = {
label: string;
label: React.ReactNode | string;

kind?: "primary" | "secondary" | "destructive" | "destructive-secondary";
size?: "default" | "small";
size?: "default" | "small" | "big";
disabled?: boolean;
selected?: boolean;
} & React.ComponentProps<"button">;

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
{ label, kind = "primary", size = "default", disabled = false, className, ...props }: ButtonProps,
{
label,
kind = "primary",
size = "default",
disabled = false,
selected = false,
className,
...props
}: ButtonProps,
ref,
) {
const buttonStyles: ButtonStyles = styles as ButtonStyles;
Expand Down Expand Up @@ -58,19 +70,25 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function
case "small":
buttonClass += ` ${buttonStyles.small}`;
break;
case "big":
buttonClass += ` ${buttonStyles.big}`;
break;
default:
buttonClass += ` ${buttonStyles.default}`;
break;
}

if (selected) {
buttonClass += ` ${buttonStyles.selected}`;
}

// Lets developers apply their own styling
if (className) {
buttonClass += ` ${className}`;
}

// Set font to poppins
buttonClass += ` ${poppins.className}`;

return (
<button ref={ref} className={buttonClass} {...props}>
{label}
Expand Down
83 changes: 83 additions & 0 deletions frontend/src/components/Landing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Navigation Component
*
* Wraps the content of the current page with the navbar
* Uses the constant in `src/constants` to populate the actual sidebar
*/
import { Poppins } from "next/font/google";
import Image from "next/image";
import React, { useMemo } from "react";

import { useWindowSize } from "../hooks/useWindowSize";
import { cn } from "../lib/utils";

const poppins = Poppins({ subsets: ["latin"], weight: "400" });

// the logo and company name component
const Logo = () => {
return (
<div className="flex h-full flex-col items-center justify-center">
<div className="mb-14 flex flex-col items-center">
<h1 className="font-[alternate-gothic] text-6xl text-white max-lg:text-5xl">Welcome to</h1>
<h1 className="font-[alternate-gothic] text-6xl text-white max-lg:text-5xl">
Plant it Again!
</h1>
</div>
<div className="flex h-[50%] flex-col items-center">
<Image alt="company logo" src="/sidebar/logo.png" width={250} height={250} className="" />
</div>
</div>
);
};

// Navigation component that wraps the content of the page
function Landing({ children }: { children: React.ReactNode }) {
const { width } = useWindowSize();
const isMobile = useMemo(() => width <= 640, [width]);

return (
<main
className={cn(
"flex h-full w-full bg-pia_primary_light_green max-sm:relative sm:max-lg:flex-col",
poppins.className,
)}
>
{/* Login left side */}
{!isMobile ? (
<div
className={cn(
"flex h-screen w-full bg-pia_primary_light_green max-sm:relative",
poppins.className,
)}
>
<div
className={cn(
"z-10 flex h-screen w-[50%] flex-col", // Adjust the width based on the isMobile flag
"flex-col gap-12 bg-pia_dark_green pt-16 text-pia_accent_green transition-transform",
)}
>
<Logo />
</div>
<div
className={cn(
"flex h-screen w-[50%] flex-col", // Adjust the width based on the isMobile flag
)}
>
{children}
</div>
</div>
) : (
<div
className={cn(
"flex h-screen w-full bg-pia_primary_light_green max-sm:relative",
poppins.className,
)}
>
{children}
</div>
)}
</main>
);
}

export default Landing;
5 changes: 5 additions & 0 deletions frontend/src/components/Textfield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type BaseProps<T extends FieldValues> = {
label?: string;
type?: string;
placeholder: string;
handleInputChange?: React.ChangeEventHandler<HTMLInputElement>;
defaultValue?: string;
className?: string;
};
Expand All @@ -37,6 +38,9 @@ export function Textfield<T extends FieldValues>({
placeholder,
calendar = false,
className,
handleInputChange = () => {
/* do nothing */
},
type = "text",
defaultValue = "",
}: TextFieldProps<T>) {
Expand Down Expand Up @@ -66,6 +70,7 @@ export function Textfield<T extends FieldValues>({
className="focus-visible:out w-full appearance-none bg-inherit px-2 placeholder-pia_accent outline-none"
id={label + placeholder}
type={type}
onChange={handleInputChange}
placeholder={placeholder}
defaultValue={
calendar && defaultValue
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { AppProps } from "next/app";

import Navigation from "../components/Navigation";
import Landing from "../components/Landing";

import "../styles/global.css";
import "../styles/globals.css";

// import Navigation from "../components/Navigation";

function App({ Component, pageProps }: AppProps) {
return (
<Navigation>
<Landing {...pageProps}>
<Component {...pageProps} />
</Navigation>
</Landing>
// <Navigation>
// <Component {...pageProps} />
// </Navigation>
);
}
export default App;
185 changes: 185 additions & 0 deletions frontend/src/pages/create_user.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { AlertCircle, CheckCircle2 } from "lucide-react";
import Image from "next/image";
import { useRouter } from "next/router";
import { useMemo, useState } from "react";
import { FieldValues, SubmitHandler, useForm } from "react-hook-form";

import { Textfield } from "@/components/Textfield";
import { useWindowSize } from "@/hooks/useWindowSize";
import { cn } from "@/lib/utils";

export default function CreateUser() {
const { register, setValue, handleSubmit } = useForm();
const _setValue = setValue;

const [passwordError, setPasswordError] = useState(true);
const [matchError, setMatchError] = useState(false);
const [emailError, setEmailError] = useState(false);

const [password, setPassword] = useState("");
const [confirm, setConfirm] = useState("");

const router = useRouter();

const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.target.name);
switch (event.target.name) {
case "password":
console.log(event.target.value.length, passwordError);
setPassword(event.target.value);
setPasswordError(event.target.value.length < 6);
setMatchError(event.target.value !== confirm);
break;
case "confirm":
console.log(password, confirm, matchError);
setConfirm(event.target.value);
setMatchError(password !== event.target.value);
break;
}
};

const onSubmit: SubmitHandler<FieldValues> = (data) => {
setEmailError(false); // add email error logic here
console.log(data);

void router.push("/create_user_2");
};
const { width } = useWindowSize();
const isMobile = useMemo(() => width <= 640, [width]);

return (
<main className="flex h-screen w-full items-center justify-center">
<div className="flex h-full w-full items-center justify-center">
<div
className={cn(
"flex h-full flex-col",
isMobile ? "mt-[20%] w-[80%]" : "mb-[8%] w-[65%] justify-center",
)}
>
{isMobile && (
<div className="flex flex-col justify-center">
<div className="flex h-full flex-col justify-center">
<div className="flex flex-col items-center">
<Image
alt="company logo"
src="/sidebar/logo.png"
width={130}
height={130}
className=""
/>
</div>
<div className="mb-5 mt-5 flex flex-col items-center">
<h1 className="font-[alternate-gothic] text-3xl max-lg:text-3xl">Welcome to</h1>
<h1 className="font-[alternate-gothic] text-3xl max-lg:text-3xl">
Plant it Again!
</h1>
</div>
</div>
</div>
)}
{!isMobile && (
<div>
<h1 className="font-[alternate-gothic] text-5xl text-black max-lg:text-5xl">
Create an Account
</h1>
<h1 className="text-1xl max-lg:text-1xl mb-6 text-black text-pia_accent">
Already have an account?{" "}
<a className="text-1xl max-lg:text-1xl text-pia_accent text-pia_dark_green">
Sign in
</a>
</h1>
</div>
)}

<div className="grid gap-5">
<form
onSubmit={handleSubmit(onSubmit)}
className="flex w-full flex-col justify-between gap-5"
>
<div>
<h1 className="text-lg font-light text-black text-pia_accent max-lg:text-lg">
Full Name
</h1>
<Textfield
register={register}
name={"name"}
label={""}
placeholder="Enter your full name"
/>
</div>
<div>
<h1 className="text-lg font-light text-black text-pia_accent max-lg:text-lg">
Email Address
</h1>
<Textfield
register={register}
handleInputChange={onChange}
name={"email"}
label={""}
placeholder="[email protected]"
/>
{emailError && (
<h1 className="mt-1 flex items-center text-sm font-light text-orange-700 text-pia_accent">
<AlertCircle className="mr-1 text-sm" /> Account already exists for this email.
Sign in?
</h1>
)}
</div>
<div>
<h1 className="text-lg font-light text-black text-pia_accent max-lg:text-lg">
Password
</h1>
<Textfield
register={register}
name={"password"}
handleInputChange={onChange}
label={""}
type="password"
placeholder="Enter password"
/>
{passwordError ? (
<h1 className="mt-1 flex items-center text-sm font-light text-orange-700">
<AlertCircle className="mr-1 text-sm" /> At least 6 characters
</h1>
) : (
<h1 className="mt-1 flex items-center text-sm font-light text-green-700">
<CheckCircle2 className="mr-1 text-sm" /> At least 6 characters
</h1>
)}
</div>
<div>
<h1 className="text-lg font-light text-pia_accent max-lg:text-lg">
Confirm Password
</h1>
<Textfield
register={register}
handleInputChange={onChange}
name={"confirm"}
label=""
type="password"
placeholder="Re-enter Password"
/>
{matchError && (
<h1 className="mt-1 flex items-center text-sm font-light text-orange-700">
<AlertCircle className="mr-1 text-sm" /> Passwords do not match.
</h1>
)}
</div>
<button type="submit" className="rounded-md bg-pia_dark_green px-5 py-3 text-white">
Continue
</button>
{isMobile && (
<div className="flex items-center justify-center">
<h1 className={cn("text-sm text-black text-pia_accent max-lg:text-sm")}>
Already have an account?{" "}
<a className="text-sm text-black text-pia_accent max-lg:text-sm">Sign in</a>
</h1>
</div>
)}
</form>
</div>
</div>
</div>
</main>
);
}
Loading

0 comments on commit 308a13a

Please sign in to comment.