From 26dca0cb2f1dfd8cc05773e64d65648cc6cf6f2b Mon Sep 17 00:00:00 2001 From: "riho.takagi" Date: Fri, 15 Nov 2024 13:47:41 -0500 Subject: [PATCH 1/5] Save userApiData on context --- .../formPages/BookingFormDetailsPage.tsx | 27 +++---------------- .../src/client/routes/components/Provider.tsx | 23 ++++++++++++++++ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/booking-app/components/src/client/routes/booking/formPages/BookingFormDetailsPage.tsx b/booking-app/components/src/client/routes/booking/formPages/BookingFormDetailsPage.tsx index 0e6214b..a91a25c 100644 --- a/booking-app/components/src/client/routes/booking/formPages/BookingFormDetailsPage.tsx +++ b/booking-app/components/src/client/routes/booking/formPages/BookingFormDetailsPage.tsx @@ -1,8 +1,8 @@ "use client"; -import { FormContextLevel, UserApiData } from "@/components/src/types"; +import { FormContextLevel } from "@/components/src/types"; import Grid from "@mui/material/Unstable_Grid2"; -import { useContext, useEffect, useState } from "react"; +import { useContext } from "react"; import { DatabaseContext } from "../../components/Provider"; import FormInput from "../components/FormInput"; import useCheckFormMissingData from "../hooks/useCheckFormMissingData"; @@ -16,29 +16,8 @@ export default function BookingFormDetailsPage({ calendarEventId, formContext = FormContextLevel.FULL_FORM, }: Props) { - const { netId } = useContext(DatabaseContext); + const { userApiData } = useContext(DatabaseContext); useCheckFormMissingData(); - const [userApiData, setUserApiData] = useState( - undefined - ); - useEffect(() => { - const fetchUserData = async () => { - if (!netId) return; - - try { - const response = await fetch(`/api/nyu/identity/${netId}`); - - if (response.ok) { - const data = await response.json(); - setUserApiData(data); - } - } catch (err) { - console.error("Failed to fetch user data:", err); - } - }; - - fetchUserData(); - }, [netId]); return ( diff --git a/booking-app/components/src/client/routes/components/Provider.tsx b/booking-app/components/src/client/routes/components/Provider.tsx index 2a02456..eee9c75 100644 --- a/booking-app/components/src/client/routes/components/Provider.tsx +++ b/booking-app/components/src/client/routes/components/Provider.tsx @@ -13,6 +13,7 @@ import { RoomSetting, SafetyTraining, Settings, + UserApiData, } from "../../../types"; import { useAuth } from "@/components/src/client/routes/components/AuthProvider"; @@ -34,6 +35,7 @@ export interface DatabaseContextType { settings: Settings; userEmail: string | undefined; netId: string | undefined; + userApiData: UserApiData | undefined; reloadAdminUsers: () => Promise; reloadApproverUsers: () => Promise; reloadBannedUsers: () => Promise; @@ -60,6 +62,7 @@ export const DatabaseContext = createContext({ settings: { bookingTypes: [] }, userEmail: undefined, netId: undefined, + userApiData: undefined, reloadAdminUsers: async () => {}, reloadApproverUsers: async () => {}, reloadBannedUsers: async () => {}, @@ -92,8 +95,27 @@ export const DatabaseProvider = ({ >([]); const [settings, setSettings] = useState({ bookingTypes: [] }); const [userEmail, setUserEmail] = useState(); + const [userApiData, setUserApiData] = useState( + undefined + ); + const { user } = useAuth(); const netId = useMemo(() => userEmail?.split("@")[0], [userEmail]); + useEffect(() => { + const fetchUserApiData = async () => { + if (!netId) return; + try { + const response = await fetch(`/api/nyu/identity/${netId}`); + if (response.ok) { + const data = await response.json(); + setUserApiData(data); + } + } catch (err) { + console.error("Failed to fetch user data:", err); + } + }; + fetchUserApiData(); + }, [netId]); // page permission updates with respect to user email, admin list, PA list const pagePermission = useMemo(() => { @@ -401,6 +423,7 @@ export const DatabaseProvider = ({ userEmail, netId, bookingsLoading, + userApiData, reloadAdminUsers: fetchAdminUsers, reloadApproverUsers: fetchApproverUsers, reloadBannedUsers: fetchBannedUsers, From d0624190669766b89181e8e122c5e38409553a55 Mon Sep 17 00:00:00 2001 From: "riho.takagi" Date: Fri, 15 Nov 2024 13:47:53 -0500 Subject: [PATCH 2/5] Pre-enter roles and departments using userApiData --- .../routes/booking/formPages/UserRolePage.tsx | 85 ++++++++++++++++--- 1 file changed, 73 insertions(+), 12 deletions(-) diff --git a/booking-app/components/src/client/routes/booking/formPages/UserRolePage.tsx b/booking-app/components/src/client/routes/booking/formPages/UserRolePage.tsx index 93d34c9..70bfcac 100644 --- a/booking-app/components/src/client/routes/booking/formPages/UserRolePage.tsx +++ b/booking-app/components/src/client/routes/booking/formPages/UserRolePage.tsx @@ -1,16 +1,15 @@ "use client"; - -import { Box, Button, TextField, Typography } from "@mui/material"; +import { Box, Button, Typography } from "@mui/material"; +import { styled } from "@mui/material/styles"; +import { useRouter } from "next/navigation"; +import { useContext, useEffect, useRef } from "react"; +import { useForm } from "react-hook-form"; import { Department, FormContextLevel, Inputs, Role } from "../../../../types"; -import React, { useContext, useEffect, useRef, useState } from "react"; - +import { useAuth } from "../../components/AuthProvider"; +import { DatabaseContext } from "../../components/Provider"; import { BookingContext } from "../bookingProvider"; import { BookingFormTextField } from "../components/BookingFormInputs"; import Dropdown from "../components/Dropdown"; -import { styled } from "@mui/material/styles"; -import { useAuth } from "../../components/AuthProvider"; -import { useForm } from "react-hook-form"; -import { useRouter } from "next/navigation"; const Center = styled(Box)` width: 100%; @@ -28,6 +27,53 @@ const Container = styled(Box)(({ theme }) => ({ border: `1px solid ${theme.palette.divider}`, })); +const roleMappings: Record = { + [Role.STUDENT]: ["STUDENT", "DEGREE", "UNDERGRADUATE", "GRADUATE"], + [Role.RESIDENT_FELLOW]: ["FELLOW", "RESIDENT", "POSTDOC"], + [Role.FACULTY]: ["FACULTY", "PROFESSOR", "INSTRUCTOR", "LECTURER"], + [Role.ADMIN_STAFF]: ["ADMIN", "STAFF", "EMPLOYEE"], +}; + +const departmentMappings: Record = { + [Department.ITP]: ["ITP", "IMA", "LOWRES"], + [Department.ALT]: ["ALT"], + [Department.CDI]: ["CDI"], + [Department.GAMES]: ["GAMES", "GAMECENTER"], + [Department.IDM]: ["IDM"], + [Department.MARL]: ["MARL"], + [Department.MPAP]: ["MPAP", "PERFORMINGARTS"], + [Department.MUSIC_TECH]: ["MUSICTECH", "MUSTECH"], + [Department.OTHER]: [], +}; + +const mapAffiliationToRole = (affiliation?: string): Role | undefined => { + if (!affiliation) return undefined; + + const normalizedAffiliation = affiliation.toUpperCase(); + + for (const [role, affiliations] of Object.entries(roleMappings)) { + if (affiliations.includes(normalizedAffiliation)) { + return role as Role; + } + } + + return undefined; +}; + +const mapDepartmentCode = (deptCode?: string): Department | undefined => { + if (!deptCode) return undefined; + + const normalizedCode = deptCode.toUpperCase(); + + for (const [dept, codes] of Object.entries(departmentMappings)) { + if (codes.includes(normalizedCode)) { + return dept as Department; + } + } + + return Department.OTHER; +}; + interface Props { calendarEventId?: string; formContext?: FormContextLevel; @@ -39,7 +85,7 @@ export default function UserRolePage({ }: Props) { const { formData, role, department, setDepartment, setRole, setFormData } = useContext(BookingContext); - + const { userApiData } = useContext(DatabaseContext); const router = useRouter(); const { user } = useAuth(); @@ -50,21 +96,36 @@ export default function UserRolePage({ formState: { errors }, } = useForm({ defaultValues: { - ...formData, // restore answers if navigating between form pages + ...formData, }, mode: "onBlur", }); const watchedFields = watch(); const prevWatchedFieldsRef = useRef(); - const showOther = department === Department.OTHER; useEffect(() => { if (!user) { router.push("/signin"); + return; + } + + if (userApiData) { + const mappedRole = mapAffiliationToRole(userApiData.affiliation_sub_type); + const mappedDepartment = mapDepartmentCode( + userApiData.reporting_dept_code + ); + + if (mappedRole && !role) { + setRole(mappedRole); + } + + if (mappedDepartment && !department) { + setDepartment(mappedDepartment); + } } - }, []); + }, [userApiData, user]); useEffect(() => { if ( From e09bfbc536fa85df1f36d42eaf41f38f86c07708 Mon Sep 17 00:00:00 2001 From: nimanns Date: Sat, 16 Nov 2024 15:54:43 -0500 Subject: [PATCH 3/5] update readme. --- README.md | 66 +++++++++++++++++++++++-------------------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index dca281b..fcb5fa7 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,44 @@ -# 370J Media Commons Room Reservation App +# Booking App -## About +This project is a booking management application built with Next.js. -This application is designed for reserving rooms at the 370J Media Commons Room. +## Getting Started -## Technologies Used +Follow these steps to run the application in your local environment. -- **Google App Script**: For backend scripting and integration. -- **React & Google App Script**: Utilizing [React-Google-Apps-Script](https://github.com/enuchi/React-Google-Apps-Script) for a responsive front-end experience. -- **clasp**: For Google Apps Script project management and deployment. +### Prerequisites -## Deployment +- Node.js (version 18 or later) +- npm (usually comes with Node.js) -We employ GitHub workflows for our deployment process. Pushing or merging to different branches will automatically trigger different Google App Script deploys: -- `main` branch: triggers the DEVELOPMENT deploy, which serves from localhost and reads/writes the development calendars -- `staging` branch: triggers the STAGING deploy, which serves from the GAS project and reads/writes the development calendars -- `prod` branch: triggers the PRODUCTION deploy, which serves from the GAS project and reads/writes the **production** calendars +### Installation -The `NODE_ENV` environment variable controls where we serve from, and the `CALENDAR_ENV` environment variable controls which calendars we use. These values are specified in the `package.json` commands triggered by the workflows +1. Clone the repository or download the project files. -## Preparation +2. Navigate to the project directory: + ``` + cd booking-app + ``` +3. Install the dependencies: + `npm install` +4. Obtain the `.env` file from a project administrator and place it in the root directory of the project. -Before setting up the project, ensure you have the following: +### Running the Application -- `.clasprc.json` file from another developer. -- `deploymentId` for Google Apps Script. +To start the development server: +`npm run dev` +The application should now be running on [http://localhost:3000](http://localhost:3000). -## Setup Instructions +## Environment Variables -When developing locally, please follow the flow below. +This project uses environment variables for configuration. Make sure you have received the `.env` file from a project administrator and placed it in the root directory before running the application. -1. **Clone the Repository**: -2. **Configure `.clasprc.json`**: - Set up the `.clasprc.json` file with the necessary credentials. -3. **Install Packages**: - ```bash - npm install - ``` -4. **Upload Local Code to Google App Script**: +## Deployment - ```bash - npm run start - ``` +This project uses automated deployment pipelines: - Please continue running this and execute the following command. +- Pushing to the `main` branch automatically deploys to the development environment. +- Pushing to the `staging` branch automatically deploys to the staging environment. +- Pushing to the `production` branch automatically deploys to the production environment. -5. **Create a New Version of Google App Script**: - Deploy using clasp, targeting your `deploymentId`: - ```bash - clasp deploy --deploymentId ${deploymentID} -d DEV_YOURNAME - ``` -6. **Access the Application**: - You can now access the app at `https://script.google.com/a/macros/nyu.edu/s/${deploymentId}/exec`. +Please ensure you push your changes to the appropriate branch based on the intended deployment environment. From 991cf2341517bb796da3c4cf69359413bd6f0e28 Mon Sep 17 00:00:00 2001 From: nimanns Date: Sat, 16 Nov 2024 16:08:42 -0500 Subject: [PATCH 4/5] update readme. --- README.md | 53 +++++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index fcb5fa7..b6ba6b7 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,33 @@ # Booking App -This project is a booking management application built with Next.js. +## About -## Getting Started +This application is designed for reserving rooms at the 370J Media Commons Room. -Follow these steps to run the application in your local environment. +## Deployment -### Prerequisites +We employ GitHub workflows for our deployment process. +Any merges into the main branch automatically trigger a deployment to the production Google App Script. +We employ GitHub workflows for our deployment process. Pushing or merging to different branches will automatically trigger different Google App Script deploys: +- `main` branch: triggers the DEVELOPMENT deploy, which serves from localhost and reads/writes the development calendars +- `staging` branch: triggers the STAGING deploy, which serves from the GAS project and reads/writes the development calendars +- `prod` branch: triggers the PRODUCTION deploy, which serves from the GAS project and reads/writes the **production** calendars +The `NODE_ENV` environment variable controls where we serve from, and the `CALENDAR_ENV` environment variable controls which calendars we use. These values are specified in the `package.json` commands triggered by the workflows -- Node.js (version 18 or later) -- npm (usually comes with Node.js) +## Setup Instructions -### Installation +When developing locally, please follow the flow below. -1. Clone the repository or download the project files. +1. **Clone the Repository**: -2. Navigate to the project directory: - ``` - cd booking-app +2. **Install Packages**: + ```bash + npm install ``` -3. Install the dependencies: - `npm install` -4. Obtain the `.env` file from a project administrator and place it in the root directory of the project. - -### Running the Application - -To start the development server: -`npm run dev` -The application should now be running on [http://localhost:3000](http://localhost:3000). - -## Environment Variables - -This project uses environment variables for configuration. Make sure you have received the `.env` file from a project administrator and placed it in the root directory before running the application. - -## Deployment - -This project uses automated deployment pipelines: -- Pushing to the `main` branch automatically deploys to the development environment. -- Pushing to the `staging` branch automatically deploys to the staging environment. -- Pushing to the `production` branch automatically deploys to the production environment. +3. **Make sure that you have placed the `.env.local` file in the root directory** -Please ensure you push your changes to the appropriate branch based on the intended deployment environment. +4. **Run the dev local server** + ```bash + npm run dev + ``` \ No newline at end of file From dd4f2868c3e97bad890e701d8e45ae9fa6efb44d Mon Sep 17 00:00:00 2001 From: nimanns Date: Sat, 16 Nov 2024 19:58:47 -0500 Subject: [PATCH 5/5] Add 'Chair / Program Director' to affiliation dropdown menu options (issue #480) --- booking-app/components/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/booking-app/components/src/types.ts b/booking-app/components/src/types.ts index fbcac8c..f66c569 100644 --- a/booking-app/components/src/types.ts +++ b/booking-app/components/src/types.ts @@ -13,6 +13,7 @@ export type Approver = { }; export enum AttendeeAffiliation { + CHAIR = "Chair / Program Director", NYU = "NYU Members with an active NYU ID", NON_NYU = "Non-NYU guests", BOTH = "All of the above",