Implemented page system + signup screen
This commit is contained in:
@@ -1,148 +1,14 @@
|
||||
import { Button, Checkbox, Link } from "@nextui-org/react";
|
||||
import { Formik, Form, Field, ErrorMessage } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import axios from "axios";
|
||||
import config from "./config";
|
||||
import NextUIFormikInput from "./components/NextUIFormikInput";
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
firstName: Yup.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.required("First Name is required"),
|
||||
lastName: Yup.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.required("Last Name is required"),
|
||||
email: Yup.string()
|
||||
.trim()
|
||||
.lowercase()
|
||||
.min(5)
|
||||
.max(69)
|
||||
.email("Invalid email format")
|
||||
.required("Email is required"),
|
||||
phoneNumber: Yup.string()
|
||||
.trim()
|
||||
.matches(/^[0-9]+$/, "Phone number must contain only numerical characters")
|
||||
.length(8, "Phone number must be 8 digits")
|
||||
.required("Phone number is required"),
|
||||
password: Yup.string()
|
||||
.trim()
|
||||
.min(8, "Password must be at least 8 characters")
|
||||
.max(69, "Password must be at most 69 characters")
|
||||
.matches(
|
||||
/^(?=.*[a-zA-Z])(?=.*[0-9]).{8,}$/,
|
||||
"Password needs to contain both letters and numbers"
|
||||
)
|
||||
.required("Password is required"),
|
||||
terms: Yup.boolean().oneOf(
|
||||
[true],
|
||||
"You must accept the terms and conditions"
|
||||
),
|
||||
});
|
||||
|
||||
export default function App() {
|
||||
const initialValues = {
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
password: "",
|
||||
terms: false,
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: any) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
config.serverAddress + "/users/register",
|
||||
values
|
||||
);
|
||||
console.log("User created successfully:", response.data);
|
||||
} catch (error) {
|
||||
console.error("Error creating user:", error);
|
||||
}
|
||||
};
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import HomePage from "./pages/HomePage";
|
||||
import SignUpPage from "./pages/SignUpPage";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col p-4 gap-8 relative *:w-max">
|
||||
<h1 className="text-3xl font-bold ">ecoconnect</h1>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ isValid, dirty }) => (
|
||||
<Form className="flex flex-col gap-4">
|
||||
<NextUIFormikInput
|
||||
label="First Name"
|
||||
name="firstName"
|
||||
type="text"
|
||||
placeholder="John"
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
label="Last Name"
|
||||
name="lastName"
|
||||
type="text"
|
||||
placeholder="Doe"
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
label="Phone number"
|
||||
name="phoneNumber"
|
||||
type="text"
|
||||
placeholder="XXXXXXXX"
|
||||
labelPlacement="outside"
|
||||
startContent={
|
||||
<p className="text-sm pr-2 border-r-2 border-neutral-700">
|
||||
+65
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
label="New Password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder=" "
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<div>
|
||||
<Field
|
||||
name="terms"
|
||||
type="checkbox"
|
||||
as={Checkbox}
|
||||
aria-label="Terms and services agreement checkbox"
|
||||
>
|
||||
I have read and agreed to the{" "}
|
||||
<Link href="#">Terms and Services</Link>
|
||||
</Field>
|
||||
<ErrorMessage
|
||||
name="terms"
|
||||
component="div"
|
||||
className="text-red-500"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
isDisabled={!isValid || !dirty}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route element={<HomePage />} path="/" />
|
||||
<Route element={<SignUpPage />} path="/signup" />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
145
client/src/components/SignUpModule.tsx
Normal file
145
client/src/components/SignUpModule.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import { Button, Checkbox, Link } from "@nextui-org/react";
|
||||
import { Formik, Form, Field, ErrorMessage } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import axios from "axios";
|
||||
import config from "../config";
|
||||
import NextUIFormikInput from "./NextUIFormikInput";
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
firstName: Yup.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.required("First Name is required"),
|
||||
lastName: Yup.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(100)
|
||||
.required("Last Name is required"),
|
||||
email: Yup.string()
|
||||
.trim()
|
||||
.lowercase()
|
||||
.min(5)
|
||||
.max(69)
|
||||
.email("Invalid email format")
|
||||
.required("Email is required"),
|
||||
phoneNumber: Yup.string()
|
||||
.trim()
|
||||
.matches(/^[0-9]+$/, "Phone number must contain only numerical characters")
|
||||
.length(8, "Phone number must be 8 digits")
|
||||
.required("Phone number is required"),
|
||||
password: Yup.string()
|
||||
.trim()
|
||||
.min(8, "Password must be at least 8 characters")
|
||||
.max(69, "Password must be at most 69 characters")
|
||||
.matches(
|
||||
/^(?=.*[a-zA-Z])(?=.*[0-9]).{8,}$/,
|
||||
"Password needs to contain both letters and numbers"
|
||||
)
|
||||
.required("Password is required"),
|
||||
terms: Yup.boolean().oneOf(
|
||||
[true],
|
||||
"You must accept the terms and conditions"
|
||||
),
|
||||
});
|
||||
|
||||
export default function SignUpModule() {
|
||||
const initialValues = {
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
password: "",
|
||||
terms: false,
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: any) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
config.serverAddress + "/users/register",
|
||||
values
|
||||
);
|
||||
console.log("User created successfully:", response.data);
|
||||
} catch (error) {
|
||||
console.error("Error creating user:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ isValid, dirty }) => (
|
||||
<Form className="flex flex-col gap-4">
|
||||
<NextUIFormikInput
|
||||
label="First Name"
|
||||
name="firstName"
|
||||
type="text"
|
||||
placeholder="John"
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
label="Last Name"
|
||||
name="lastName"
|
||||
type="text"
|
||||
placeholder="Doe"
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
label="Phone number"
|
||||
name="phoneNumber"
|
||||
type="text"
|
||||
placeholder="XXXXXXXX"
|
||||
labelPlacement="outside"
|
||||
startContent={
|
||||
<p className="text-sm pr-2 border-r-2 border-neutral-300 dark:border-neutral-700">
|
||||
+65
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<NextUIFormikInput
|
||||
label="New Password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder=" "
|
||||
labelPlacement="outside"
|
||||
/>
|
||||
<div>
|
||||
<Field
|
||||
name="terms"
|
||||
type="checkbox"
|
||||
as={Checkbox}
|
||||
aria-label="Terms and services agreement checkbox"
|
||||
>
|
||||
I have read and agreed to the{" "}
|
||||
<Link href="#">Terms and Services</Link>
|
||||
</Field>
|
||||
<ErrorMessage
|
||||
name="terms"
|
||||
component="div"
|
||||
className="text-red-500"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
isDisabled={!isValid || !dirty}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
client/src/components/SingaporeAgencyStrip.tsx
Normal file
13
client/src/components/SingaporeAgencyStrip.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Link } from "@nextui-org/react";
|
||||
|
||||
export default function SingaporeAgencyStrip() {
|
||||
return (
|
||||
<div className="h-8 bg-neutral-200 text-black relative">
|
||||
<div className="flex flex-row gap-2 *:my-auto pl-16 h-full text-sm">
|
||||
<img src="../assets/Merlion.svg" alt="Merlion Icon" className="w-6" />
|
||||
<p>A Singapore Government Agency Website</p>
|
||||
<Link size="sm">How to identify</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { NextUIProvider } from "@nextui-org/react";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<NextUIProvider>
|
||||
<App />
|
||||
</NextUIProvider>
|
||||
<BrowserRouter>
|
||||
<NextUIProvider>
|
||||
<App />
|
||||
</NextUIProvider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
18
client/src/pages/HomePage.tsx
Normal file
18
client/src/pages/HomePage.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Button } from "@nextui-org/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function HomePage() {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div>
|
||||
<p>Home</p>
|
||||
<Button
|
||||
onPress={() => {
|
||||
navigate("/signup");
|
||||
}}
|
||||
>
|
||||
Sign up!
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
37
client/src/pages/SignUpPage.tsx
Normal file
37
client/src/pages/SignUpPage.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import SignUpModule from "../components/SignUpModule";
|
||||
import SingaporeAgencyStrip from "../components/SingaporeAgencyStrip";
|
||||
|
||||
export default function SignUpPage() {
|
||||
return (
|
||||
<div className="absolute inset-0">
|
||||
<div className="flex flex-col h-full">
|
||||
<SingaporeAgencyStrip></SingaporeAgencyStrip>
|
||||
<div className="flex flex-row h-full">
|
||||
<div className="w-3/5 relative">
|
||||
<div className="absolute inset-0">
|
||||
<div className="w-full h-full relative">
|
||||
<img
|
||||
src="../assets/SignupScreenBG.png"
|
||||
alt="HDB flat"
|
||||
className="w-full h-full object-cover -z-10"
|
||||
/>
|
||||
<div className="absolute inset-0 z-10 flex flex-col justify-center">
|
||||
<div className="w-full text-right text-white flex flex-col gap-6 p-16">
|
||||
<p className="text-7xl font-semibold">Welcome!</p>
|
||||
<p className="text-3xl">
|
||||
Register a new account to access all of HDB Residence
|
||||
services.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-2/5 p-8">
|
||||
<SignUpModule></SignUpModule>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user