diff --git a/backend/app/app.py b/backend/app/app.py index ef3605ab..fc72f7a9 100644 --- a/backend/app/app.py +++ b/backend/app/app.py @@ -173,7 +173,7 @@ def get_all_reservations(): try: cursor = connection.cursor(dictionary=True) cursor.execute(""" - SELECT Reservations.ReservationID, Users.Username, Reservations.StartTime, Reservations.EndTime, Reservations.Seat + SELECT Reservations.ReservationID, Users.Username, Reservations.StartTime, Reservations.EndTime, Reservations.Seat, Reservations.TableFee, Reservations.ResDate FROM Reservations JOIN Users ON Reservations.UserID = Users.UserID """) @@ -239,16 +239,27 @@ def create_reservation(user_id, reservation_data): try: cursor = connection.cursor() query = """ - INSERT INTO Reservations (UserID, StartTime, EndTime, Seat) - VALUES (%s, %s, %s, %s) + INSERT INTO Reservations (UserID, StartTime, EndTime, Seat, TableFee, ResDate) + VALUES (%s, %s, %s, %s, %s) """ - values = (user_id, reservation_data['starttime'], reservation_data['endtime'], reservation_data.get('seat')) + values = ( + user_id, + reservation_data['starttime'], + reservation_data['endtime'], + reservation_data['seat'], + reservation_data['tablefee'], + reservation_data['resdate'] + ) cursor.execute(query, values) connection.commit() cursor.close() connection.close() + return {'message': 'Reservation created successfully'} except mysql.connector.Error as err: print(f"Error creating reservation: {err}") + connection.rollback() + return {'error': f"Error creating reservation: {err}"} + @app.route('/api/create-reservation', methods=['POST']) def create_reservation_route(): diff --git a/backend/db/init.sql b/backend/db/init.sql index 1ece8042..743a7d75 100644 --- a/backend/db/init.sql +++ b/backend/db/init.sql @@ -22,9 +22,11 @@ CREATE TABLE Users ( CREATE TABLE Reservations ( ReservationID INT AUTO_INCREMENT PRIMARY KEY, UserID INT, + ResDate VARCHAR(225), StartTime VARCHAR(255) NOT NULL, EndTime VARCHAR(225) NOT NULL, Seat VARCHAR(50), + TableFee DECIMAL(10,2) DEFAULT 0.00, FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE ); @@ -49,8 +51,8 @@ VALUES INSERT INTO Reservations (UserID, StartTime, EndTime, Seat) VALUES -(1, '2023-01-01 08:00:00', '2023-01-01 12:00:00', 'A1'), -(2, '2023-01-02 09:00:00', '2023-01-02 13:00:00', 'B2'), +(1, '08:00:00', '12:00:00', 'chair1'), +(2, '09:00:00', '3:00:00', 'chair10'), -(3, '2023-01-03 10:00:00', '2023-01-03 14:00:00', 'C3'), -(4, '2023-01-04 11:00:00', '2023-01-04 15:00:00', 'D4'); +(3, '2023-01-03 10:00:00', '2023-01-03 14:00:00', 'chair4'), +(4, '2023-01-04 11:00:00', '2023-01-04 15:00:00', 'chair5'); diff --git a/frontend/app/account/page.tsx b/frontend/app/account/page.tsx index 8d221e95..9128d37a 100644 --- a/frontend/app/account/page.tsx +++ b/frontend/app/account/page.tsx @@ -19,6 +19,14 @@ function page() { document.title = "Account"; }, []); + const handleLogout = () => { + // Clear user key from local storage + localStorage.removeItem("user"); + + // Redirect to home page + window.location.href = "/"; + }; + const parseJwt = (token: string) => { try { return JSON.parse(atob(token.split(".")[1])); @@ -103,7 +111,7 @@ function page() {
- + ); } diff --git a/frontend/app/admin_accounts/[userId]/page.tsx b/frontend/app/admin_accounts/[userId]/page.tsx index b67f6524..f1ee8e3f 100644 --- a/frontend/app/admin_accounts/[userId]/page.tsx +++ b/frontend/app/admin_accounts/[userId]/page.tsx @@ -8,15 +8,17 @@ import TextInput from "@/app/components/text_input"; import Drop from "@/app/components/dropdown_button"; import Butt from "@/app/components/button"; -import { useRouter } from "next/router"; -function Page() { - const router = useRouter(); +function Page() { + const handleBackButtonClick = () => { - router.back(); - }; - const userId = 1; + console.log("back button clicked") + } + + + const userId = localStorage.getItem('user_id') + console.log(userId) const [formData, setFormData] = useState({ userName: "", @@ -34,12 +36,15 @@ function Page() { const response = await fetch(`http://localhost:5000/api/get-user/${userId}`); if (response.ok) { const userData = await response.json(); + const datas = userData.user + console.log("User Data:", userData); // Log the received user data + console.log("User Data1:", datas); // Log the received user data setFormData({ - userName: userData.Username, - email: userData.Email, - phoneNumber: userData.PhoneNumber, - gender: userData.Gender, - occupation: userData.Occupation, + userName: datas.Username, + email: datas.Email, + phoneNumber: datas.PhoneNumber, + gender: datas.Gender, + occupation: datas.Occupation, }); } else { console.error("Error fetching user data:", await response.json()); diff --git a/frontend/app/admin_areamap/page.tsx b/frontend/app/admin_areamap/page.tsx index 375937da..f105043a 100644 --- a/frontend/app/admin_areamap/page.tsx +++ b/frontend/app/admin_areamap/page.tsx @@ -20,8 +20,23 @@ function Page() { document.title = "Admin Area Map"; }, []); + const [isModalOpen, setModalOpen] = React.useState(false); + //now we need a function to handle the opening of the modal per chair... the funciton must contain a paramete that will be the chair id and the state of the chair if open or close + + const handleChairClick = (chairId: string, isChairOpen: boolean) => { + console.log("chairId", chairId); + setModalOpen(!isModalOpen); + console.log("isChairOpen", isChairOpen); + // Toggle the modal state + if (isChairOpen) { + setModalOpen(true); + } else { + setModalOpen(false); + } + } + const handleModalOpen = () => { setModalOpen(true); }; @@ -29,7 +44,6 @@ function Page() { const handleModalClose = () => { setModalOpen(false); }; - const refreshPage = () => { location.reload(); }; @@ -93,91 +107,96 @@ function Page() { border: "2px solid #DC9D94", }} > + + {/* now we need to refractor this modaladmin t only open if the parameter in the onlcick of the button is set to true */} + - - - - - - - - - - - - - - - - - - - - - - - + + handleChairClick("chair1", true)} +/> + + handleChairClick("chair2", true)} +/> + + handleChairClick("chair3", true)} +/> + + handleChairClick("chair4", true)} +/> + + handleChairClick("chair5", true)} +/> + + handleChairClick("chair6", true)} +/> + + handleChairClick("chair7", true)} +/> + + handleChairClick("chair8", true)} +/> + + handleChairClick("chair9", true)} +/> + + handleChairClick("chair10", true)} +/> + + handleChairClick("chair11", true)} +/> + + handleChairClick("chair12", true)} +/> + diff --git a/frontend/app/components/data_grid.tsx b/frontend/app/components/data_grid.tsx index fc7c5d8a..6f82d569 100644 --- a/frontend/app/components/data_grid.tsx +++ b/frontend/app/components/data_grid.tsx @@ -9,6 +9,8 @@ interface Reservation { StartTime: string; EndTime: string; Username: string; + Price: number; + ResDate: string; } const columns: GridColDef[] = [ @@ -17,6 +19,8 @@ const columns: GridColDef[] = [ { field: "Username", headerName: "Username", width: 150 }, { field: "StartTime", headerName: "Start Time", width: 200 }, { field: "EndTime", headerName: "End Time", width: 200 }, + { field: "ResDate", headerName: "Date", width: 150}, + { field: "Price", headerName: "Price", width: 150 }, ]; export default function DataGridDemo() { diff --git a/frontend/app/components/date_picker.tsx b/frontend/app/components/date_picker.tsx index 503af4ce..a1465cf6 100644 --- a/frontend/app/components/date_picker.tsx +++ b/frontend/app/components/date_picker.tsx @@ -1,5 +1,8 @@ +// date_picker.tsx import React, { useState } from "react"; import { DemoContainer } from "@mui/x-date-pickers/internals/demo"; +import dayjs from "dayjs"; +import utc from "dayjs/plugin/utc"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; @@ -7,26 +10,18 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; type PickProps = { text?: string; labelFontSize?: string; - onInputChange: (value: string) => string | any; + onDateChange: (value: Date | null) => void; }; -function DatePick({ text, labelFontSize = "14px", onInputChange }: PickProps) { - const PickStyle = { - text: text || "Date", - }; +dayjs.extend(utc); - const inputLabelProps = { - style: { - fontSize: labelFontSize, - }, +function DatePick({ text, labelFontSize = "14px", onDateChange }: PickProps) { + const pickStyle = { + text: text || "Date", }; - const [inputValue, setInputValue] = useState(""); - - const handleInputChange = (event: React.ChangeEvent) => { - const newValue = event.target.value; - setInputValue(newValue); - onInputChange(newValue); + const handleDateChange = (date: Date | null) => { + onDateChange(date); }; return ( @@ -34,8 +29,9 @@ function DatePick({ text, labelFontSize = "14px", onInputChange }: PickProps) {
diff --git a/frontend/app/components/info_table.tsx b/frontend/app/components/info_table.tsx index b4df63dc..32eca7a1 100644 --- a/frontend/app/components/info_table.tsx +++ b/frontend/app/components/info_table.tsx @@ -17,6 +17,7 @@ function InfoTable({ data }: InfoProps) { const handleEditClick = (userId: number) => { // Navigate to the [id] folder + localStorage.setItem("user_id", String(userId)); router.push(`/admin_accounts/${userId}`); }; return ( diff --git a/frontend/app/components/modal.tsx b/frontend/app/components/modal.tsx index c6788e1f..9b759cd8 100644 --- a/frontend/app/components/modal.tsx +++ b/frontend/app/components/modal.tsx @@ -1,11 +1,18 @@ -"use client"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import Box from "@mui/material/Box"; import Modal from "@mui/material/Modal"; import Butt from "./button"; import DatePick from "./date_picker"; import TimePick from "./time_picker"; import { useRouter } from "next/navigation"; +import moment from "moment"; +import middleware from "@/middleware"; + +interface BasicModalProps { + isOpen: boolean; + onClose: () => void; + chairId: string; +} const style = { position: "absolute" as "absolute", @@ -20,48 +27,52 @@ const style = { p: 4, }; -interface BasicModalProps { - isOpen: boolean; - onClose: () => void; -} - -function BasicModal({ isOpen, onClose }: BasicModalProps) { - const [open, setOpen] = React.useState(false); - const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); - const router = useRouter(); - - const [formData, setFormData] = useState<{ - Date: string | any; - StartTime: string | any; - EndTime: string | any; - }>({ - Date: "", - StartTime: "", - EndTime: "", +const BasicModal: React.FC = ({ isOpen, onClose, chairId }) => { + const [formData, setFormData] = useState({ + Date: null as Date | null, + StartTime: null as string | null, + EndTime: null as string | null, }); + const router = useRouter(); - const handleInputChange = (field: string, value: string) => { - setFormData({ - ...formData, + const handleInputChange = (field: keyof typeof formData, value: Date | string | null) => { + setFormData((prevFormData) => ({ + ...prevFormData, [field]: value, - }); + })); }; - const redirectUrl = "http://localhost:3000/qr_success_reservation"; //change this to a page after ng payment so magamit yung handleCreateAccount function. Dun pa dapat ma-ce-create yung reservation - const getName = "Gian"; //change get the name of user from session or local storage kung san man naka store - const tableFee = 140; //change den sa calculation - const handleCreateAccount = async () => { + const redirectUrl = "http://localhost:3000/qr_success_reservation"; + const baseTableFee = 50; // Base price per hour + + const handleCreateReservation = async () => { try { + const storedUserData = localStorage.getItem('user'); + const parsedUserData = storedUserData ? JSON.parse(storedUserData) : null; + const initialFormData = parsedUserData?.updated_user || null; + let userID = initialFormData ? initialFormData.UserID : ""; + if (userID == undefined || userID == null || userID === "") { + userID = parsedUserData ? parsedUserData.UserID : ""; + } + + const startMoment = moment(formData.StartTime, "HH:mm"); + const endMoment = moment(formData.EndTime, "HH:mm"); + const durationInHours = moment.duration(endMoment.diff(startMoment)).asHours(); + const tableFee = Math.ceil(durationInHours) * baseTableFee; + const apiData = { - chair_id: "", // Set a default value if not applicable - date: formData.Date, + seat: chairId, + resdate: formData.Date ? moment(formData.Date).format("YYYY-MM-DD") : null, starttime: formData.StartTime, endtime: formData.EndTime, + user_id: userID, + tablefee: tableFee, }; - router.push( - `https://payment-gateway-weld.vercel.app/gcash/login?amountDue=${tableFee}&merchant=Brew and Brains&redirectUrl=${redirectUrl}` - ); + console.log(apiData) + console.log(tableFee) + // router.push( + // `https://payment-gateway-weld.vercel.app/gcash/login?amountDue=${tableFee}&merchant=Brew and Brains&redirectUrl=${redirectUrl}` + // ); const response = await fetch( "http://localhost:5000/api/create-reservation", @@ -75,11 +86,8 @@ function BasicModal({ isOpen, onClose }: BasicModalProps) { ); if (response.ok) { - // Successfully created account console.log("Reserved successfully!"); - // Optionally, you can redirect the user to a login page or another page } else { - // Handle error cases console.error("Error Reservation", await response.json()); } } catch (error) { @@ -88,52 +96,50 @@ function BasicModal({ isOpen, onClose }: BasicModalProps) { }; return ( -
- - -
-

Arrange Reservation

+ + +
+

Arrange Reservation

+
+ +
+
+ handleInputChange("Date", value)} + /> +
+ +
+ handleInputChange("StartTime", value)} + />
-
-
- handleInputChange("date", value)} - > -
- -
- handleInputChange("starttime", value)} - > -
- -
- handleInputChange("endtime", value)} - > -
+
+ handleInputChange("EndTime", value)} + />
+
- - - -
+ +
+
); -} +}; export default BasicModal; diff --git a/frontend/app/components/time_picker.tsx b/frontend/app/components/time_picker.tsx index 289ae551..64199627 100644 --- a/frontend/app/components/time_picker.tsx +++ b/frontend/app/components/time_picker.tsx @@ -1,32 +1,27 @@ +// time_picker.tsx import React, { useState } from "react"; import { DemoContainer } from "@mui/x-date-pickers/internals/demo"; +import dayjs from "dayjs"; +import utc from "dayjs/plugin/utc"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { TimePicker } from "@mui/x-date-pickers/TimePicker"; -type PickProps = { +interface TimePickProps { text?: string; labelFontSize?: string; - onInputChange: (value: string) => string | any; -}; - -function TimePick({ text, labelFontSize = "14px", onInputChange }: PickProps) { - const PickStyle = { - text: text || "Time", - }; + onInputChange: (value: string) => void; +} - const inputLabelProps = { - style: { - fontSize: labelFontSize, - }, - }; +dayjs.extend(utc); - const [inputValue, setInputValue] = useState(""); +function TimePick({ text = "Time", labelFontSize = "14px", onInputChange }: TimePickProps) { + const [inputValue, setInputValue] = useState(null); - const handleInputChange = (event: React.ChangeEvent) => { - const newValue = event.target.value; - setInputValue(newValue); - onInputChange(newValue); + const handleInputChange = (value: any) => { + const selectedTime = value ? dayjs.utc(value).format("HH:mm") : ""; + setInputValue(value); + onInputChange(selectedTime); }; return ( @@ -34,8 +29,10 @@ function TimePick({ text, labelFontSize = "14px", onInputChange }: PickProps) {
diff --git a/frontend/app/edit_profile/page.tsx b/frontend/app/edit_profile/page.tsx index 15d476d3..7a4e442a 100644 --- a/frontend/app/edit_profile/page.tsx +++ b/frontend/app/edit_profile/page.tsx @@ -1,173 +1,173 @@ -// "use client"; - -// import React, { useState, useEffect } from "react"; -// import Teste from "../components/account"; -// import CloseIcon from "@mui/icons-material/Close"; -// import Image from "../components/account_image"; -// import CircleIcon from "@mui/icons-material/Circle"; -// import TextInput from "../components/text_input"; -// import Drop from "../components/dropdown_button"; -// import Butt from "../components/button"; - -// import { useRouter } from "next/navigation"; -// function Page() { -// const router = useRouter(); - -// const handleBackButtonClick = () => { -// router.back(); -// }; - -// useEffect(() => { -// document.title = "Edit Profile"; -// }, []); - -// const options = ["Male", "Female", "Others"]; -// const options1 = ["Student", "Worker"]; - -// // Fetch user data from local storage -// const storedUserData = localStorage.getItem('user'); -// const initialFormData = storedUserData ? JSON.parse(storedUserData) : null; - -// const [formData, setFormData] = useState<{ -// userName: string; -// email: string; -// phoneNumber: string; -// gender: string; -// occupation: string; -// }>({ - -// userName: initialFormData ? initialFormData.Username : "", -// email: initialFormData ? initialFormData.Email : "", -// phoneNumber: initialFormData ? initialFormData.PhoneNumber : "", -// gender: initialFormData ? initialFormData.Gender : options[0], -// occupation: initialFormData ? initialFormData.Occupation : options1[0] - -// }); -// const userId = initialFormData ? initialFormData.UserID : null; // Adjust this line based on your actual property name -// console.log(userId) -// const handleInputChange = (field: string, value: string) => { -// setFormData({ -// ...formData, -// [field]: value, -// }); -// }; -// console.log(formData) -// const handleUpdateProfile = async () => { -// try { -// const response = await fetch(`http://localhost:5000/api/update-account/${userId}`, { -// method: "POST", -// headers: { -// "Content-Type": "application/json", -// }, -// body: JSON.stringify(formData), -// }); - -// if (response.ok) { -// const updatedUserData = await response.json(); -// localStorage.setItem('user', JSON.stringify(updatedUserData)); -// console.log("Profile updated successfully:", updatedUserData); -// // Optionally, you can update the local state or perform other actions -// } else { -// console.error("Error updating profile:", await response.json()); -// } -// } catch (error) { -// console.error("Error updating profile:", error); -// } -// }; - -// return ( -//
-//
-// } -// onBackButtonClick={handleBackButtonClick} -// title="" -// subTitle1="" -// /> -//

Edit Profile

-//
- -//
-// } -// > -//
- -//
-// -//
- -//
-// handleInputChange("userName", value)} - -// /> -// handleInputChange("email", value)} - -// /> -// handleInputChange("phoneNumber", value)} - -// /> - -//
-//

Gender

-//

Occupation

-//
- -//
-// handleInputChange("gender", value)} -// /> -// handleInputChange("occupation", value)} -// /> -//
-// {/* handleInputChange("school", value)} -// /> */} -//
- -//
- -// -//
-// ); -// } - -// export default Page; +"use client"; + +import React, { useState, useEffect } from "react"; +import Teste from "../components/account"; +import CloseIcon from "@mui/icons-material/Close"; +import Image from "../components/account_image"; +import CircleIcon from "@mui/icons-material/Circle"; +import TextInput from "../components/text_input"; +import Drop from "../components/dropdown_button"; +import Butt from "../components/button"; + +import { useRouter } from "next/navigation"; +function Page() { + const router = useRouter(); + + const handleBackButtonClick = () => { + router.back(); + }; + + useEffect(() => { + document.title = "Edit Profile"; + }, []); + + const options = ["Male", "Female", "Others"]; + const options1 = ["Student", "Worker"]; + + // Fetch user data from local storage + const storedUserData = localStorage.getItem('user'); + const initialFormData = storedUserData ? JSON.parse(storedUserData) : null; + + const [formData, setFormData] = useState<{ + userName: string; + email: string; + phoneNumber: string; + gender: string; + occupation: string; + }>({ + + userName: initialFormData ? initialFormData.Username : "", + email: initialFormData ? initialFormData.Email : "", + phoneNumber: initialFormData ? initialFormData.PhoneNumber : "", + gender: initialFormData ? initialFormData.Gender : options[0], + occupation: initialFormData ? initialFormData.Occupation : options1[0] + + }); + const userId = initialFormData ? initialFormData.UserID : null; // Adjust this line based on your actual property name + console.log(userId) + const handleInputChange = (field: string, value: string) => { + setFormData({ + ...formData, + [field]: value, + }); + }; + console.log(formData) + const handleUpdateProfile = async () => { + try { + const response = await fetch(`http://localhost:5000/api/update-account/${userId}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + const updatedUserData = await response.json(); + localStorage.setItem('user', JSON.stringify(updatedUserData)); + console.log("Profile updated successfully:", updatedUserData); + // Optionally, you can update the local state or perform other actions + } else { + console.error("Error updating profile:", await response.json()); + } + } catch (error) { + console.error("Error updating profile:", error); + } + }; + + return ( +
+
+ } + onBackButtonClick={handleBackButtonClick} + title="" + subTitle1="" + /> +

Edit Profile

+
+ +
+ } + > +
+ +
+ +
+ +
+ handleInputChange("userName", value)} + + /> + handleInputChange("email", value)} + + /> + handleInputChange("phoneNumber", value)} + + /> + +
+

Gender

+

Occupation

+
+ +
+ handleInputChange("gender", value)} + /> + handleInputChange("occupation", value)} + /> +
+ {/* handleInputChange("school", value)} + /> */} +
+ +
+ + +
+ ); +} + +export default Page; diff --git a/frontend/app/reservation/page.tsx b/frontend/app/reservation/page.tsx index 3f556834..b829197f 100644 --- a/frontend/app/reservation/page.tsx +++ b/frontend/app/reservation/page.tsx @@ -36,7 +36,22 @@ function Page() { */ const [isModalOpen, setModalOpen] = React.useState(false); - + const [ chairId, setChairId ] = React.useState(""); + + //now we need a function to handle the opening of the modal per chair... the funciton must contain a paramete that will be the chair id and the state of the chair if open or close + + const handleChairClick = (chairId: string, isChairOpen: boolean) => { + console.log("chairId", chairId); + setChairId(chairId); + console.log("isChairOpen", isChairOpen); + setModalOpen(!isModalOpen); + if(isChairOpen == true){ + setModalOpen(true); + }else[ + setModalOpen(false) + ] + } + const handleModalOpen = () => { setModalOpen(true); }; @@ -73,90 +88,90 @@ function Page() { border: "2px solid #DC9D94", }} > - + handleChairClick("chair1", true)} /> handleChairClick("chair2", true)} /> handleChairClick("chair3", true)} /> handleChairClick("chair4", true)} /> handleChairClick("chair5", true)} /> handleChairClick("chair6", true)} /> handleChairClick("chair7", true)} /> handleChairClick("chair8", true)} /> handleChairClick("chair9", true)} /> handleChairClick("chair10", true)} /> handleChairClick("chair11", true)} /> handleChairClick("chair12", true)} />
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5bc80f2a..c4d4993e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,6 +28,7 @@ "date-fns": "^2.30.0", "framer-motion": "^10.16.16", "isomorphic-unfetch": "^4.0.2", + "moment": "^2.29.4", "next": "^14.0.1", "next-auth": "^4.24.5", "react": "^18", @@ -9474,6 +9475,14 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index afc458ea..69a78c02 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,6 +29,7 @@ "date-fns": "^2.30.0", "framer-motion": "^10.16.16", "isomorphic-unfetch": "^4.0.2", + "moment": "^2.29.4", "next": "^14.0.1", "next-auth": "^4.24.5", "react": "^18",