From 9f4983d8598f3ab6530d58803162cfe48e8c1a9a Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Tue, 25 Jun 2024 14:46:31 +0800 Subject: [PATCH] Update information page --- client/src/App.tsx | 5 + client/src/components/UpdateAccountModule.tsx | 173 ++++++++++++++++++ client/src/pages/ManageUserAccountPage.tsx | 43 +++++ client/src/pages/SpringboardPage.tsx | 45 ++--- client/src/security/users.ts | 31 ++++ server/routes/users.js | 15 +- 6 files changed, 278 insertions(+), 34 deletions(-) create mode 100644 client/src/components/UpdateAccountModule.tsx create mode 100644 client/src/pages/ManageUserAccountPage.tsx create mode 100644 client/src/security/users.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index f180478..d897151 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,6 +3,7 @@ import HomePage from "./pages/HomePage"; import SignUpPage from "./pages/SignUpPage"; import SignInPage from "./pages/SignInPage"; import SpringboardPage from "./pages/SpringboardPage"; +import ManageUserAccountPage from "./pages/ManageUserAccountPage"; function App() { return ( @@ -11,6 +12,10 @@ function App() { } path="/signup" /> } path="/signin" /> } path="/springboard/:accessToken" /> + } + path="/manage-account/:accessToken" + /> ); } diff --git a/client/src/components/UpdateAccountModule.tsx b/client/src/components/UpdateAccountModule.tsx new file mode 100644 index 0000000..4f8eb0a --- /dev/null +++ b/client/src/components/UpdateAccountModule.tsx @@ -0,0 +1,173 @@ +import axios from "axios"; +import * as Yup from "yup"; +import config from "../config"; +import { useEffect, useState } from "react"; +import { retrieveUserInformation } from "../security/users"; +import { Button } from "@nextui-org/react"; +import { Form, Formik } from "formik"; +import NextUIFormikInput from "./NextUIFormikInput"; +import { PencilSquareIcon } from "../icons"; +import { useNavigate } from "react-router-dom"; + +export default function UpdateAccountModule({ + accessToken, +}: { + accessToken: string; +}) { + const navigate = useNavigate(); + let [userInformation, setUserInformation] = useState(); + + useEffect(() => { + retrieveUserInformation(accessToken!, setUserInformation); + }, [accessToken]); + + 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"), + }); + + const handleSubmit = async (values: any) => { + try { + const response = await axios.put( + `${config.serverAddress}/users/individual/${userInformation.id}`, + values, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + } + ); + console.log("User updated successfully:", response.data); + navigate("/springboard/" + accessToken); + } catch (error) { + console.error("Error updating user:", error); + } + }; + + const initialValues = userInformation + ? { + id: userInformation.id || "", + firstName: userInformation.firstName || "", + lastName: userInformation.lastName || "", + email: userInformation.email || "", + phoneNumber: userInformation.phoneNumber || "", + } + : { + id: "", + firstName: "", + lastName: "", + email: "", + phoneNumber: "", + }; + + return ( +
+ {userInformation && ( +
+ + {({ isValid, dirty }) => ( +
+
+

Update your information

+
+ + +
+
+
+
+
+ + +
+
+ + + +65 +

+ } + /> +
+
+
+
+
+ +
+
+
+
+
+ )} +
+
+ )} +
+ ); +} diff --git a/client/src/pages/ManageUserAccountPage.tsx b/client/src/pages/ManageUserAccountPage.tsx new file mode 100644 index 0000000..4405c85 --- /dev/null +++ b/client/src/pages/ManageUserAccountPage.tsx @@ -0,0 +1,43 @@ +import { useParams } from "react-router-dom"; +import DefaultLayout from "../layouts/default"; +import UpdateAccountModule from "../components/UpdateAccountModule"; +import { Accordion, AccordionItem, Button } from "@nextui-org/react"; + +export default function ManageUserAccountPage() { + let { accessToken } = useParams(); // TODO: Replace AT from props with AT from localstorage + + return ( + +
+
+ + + +
+
+

Danger zone

+

+ These actions may be destructive. Proceed with caution. +

+
+
+ + +
+
+
+
+
+
+
+ ); +} diff --git a/client/src/pages/SpringboardPage.tsx b/client/src/pages/SpringboardPage.tsx index 8417737..3dce4f2 100644 --- a/client/src/pages/SpringboardPage.tsx +++ b/client/src/pages/SpringboardPage.tsx @@ -1,18 +1,19 @@ -import { useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import DefaultLayout from "../layouts/default"; import { useEffect, useState } from "react"; -import axios from "axios"; -import config from "../config"; import { Button, Link } from "@nextui-org/react"; import { PencilSquareIcon } from "../icons"; import SpringboardButton from "../components/SpringboardButton"; import { getTimeOfDay } from "../utilities"; +import { retrieveUserInformation } from "../security/users"; export default function SpringboardPage() { - let { accessToken } = useParams(); // TODO: Replace AT from props with AT from localstorage + let { accessToken } = useParams(); // TODO: Replace AT from props with AT from localstorage let [userInformation, setUserInformation] = useState(); let timeOfDay = getTimeOfDay(); + const navigate = useNavigate(); + let greeting = ""; if (timeOfDay === 0) { greeting = "Good morning"; @@ -22,35 +23,10 @@ export default function SpringboardPage() { greeting = "Good evening"; } - const retrieveUserInformation = () => { - axios - .get(`${config.serverAddress}/users/auth`, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) - .then((response) => { - axios - .get(`${config.serverAddress}/users/individual/${response.data.id}`, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) - .then((response) => { - setUserInformation(response.data); - }) - .catch((error) => { - console.error("Error retrieving user information:", error); - }); - }) - .catch((error) => { - console.error("Error retrieving user ID:", error); - }); - }; - - useEffect(() => { - retrieveUserInformation(); - }, []); + useEffect( + () => retrieveUserInformation(accessToken!, setUserInformation), + [] + ); return ( @@ -74,6 +50,9 @@ export default function SpringboardPage() { } + onPress={() => { + navigate("/manage-account/" + accessToken); + }} > Manage your account diff --git a/client/src/security/users.ts b/client/src/security/users.ts new file mode 100644 index 0000000..5e35f62 --- /dev/null +++ b/client/src/security/users.ts @@ -0,0 +1,31 @@ +import axios from "axios"; +import config from "../config"; + +export function retrieveUserInformation( + accessToken: string, + andThen: React.Dispatch +) { + axios + .get(`${config.serverAddress}/users/auth`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + .then((response) => { + axios + .get(`${config.serverAddress}/users/individual/${response.data.id}`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + .then((response) => { + andThen(response.data); + }) + .catch((error) => { + console.error("Error retrieving user information:", error); + }); + }) + .catch((error) => { + console.error("Error retrieving user ID:", error); + }); +} diff --git a/server/routes/users.js b/server/routes/users.js index d53a6a5..20e0a87 100644 --- a/server/routes/users.js +++ b/server/routes/users.js @@ -24,6 +24,19 @@ let validationSchema = yup.object({ password: yup.string().trim().max(100).required(), }); +let optionalValidationSchema = yup.object({ + id: yup.string().trim().min(36).max(36).required(), + firstName: yup.string().trim().min(1).max(100), + lastName: yup.string().trim().min(1).max(100), + email: yup.string().trim().min(5).max(69).email(), + phoneNumber: yup + .string() + .trim() + .matches(/^[0-9]+$/) + .length(8), + password: yup.string().trim().max(100), +}); + router.post("/register", async (req, res) => { let data = req.body; let user = await User.findOne({ @@ -89,7 +102,7 @@ router.put("/individual/:id", validateToken, async (req, res) => { let data = req.body; try { - data = await validationSchema.validate(data, { abortEarly: false }); + data = await optionalValidationSchema.validate(data, { abortEarly: false }); let num = await User.update(data, { where: { id: id },