Skip to content

Commit

Permalink
Merge pull request #502 from ITPNYU/main
Browse files Browse the repository at this point in the history
Staging Release 11.18
  • Loading branch information
rlho authored Nov 18, 2024
2 parents 088c676 + dd4f286 commit d388198
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 66 deletions.
39 changes: 9 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,33 @@
# 370J Media Commons Room Reservation App
# Booking App

## About

This application is designed for reserving rooms at the 370J Media Commons Room.

## Technologies Used

- **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.

## Deployment

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

## Preparation

Before setting up the project, ensure you have the following:

- `.clasprc.json` file from another developer.
- `deploymentId` for Google Apps Script.

## Setup Instructions

When developing locally, please follow the flow below.

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**:

2. **Install Packages**:
```bash
npm run start
npm install
```

Please continue running this and execute the following command.
3. **Make sure that you have placed the `.env.local` file in the root directory**

5. **Create a New Version of Google App Script**:
Deploy using clasp, targeting your `deploymentId`:
4. **Run the dev local server**
```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`.
npm run dev
```
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<UserApiData | undefined>(
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 (
<Grid container>
<Grid width={330} />
Expand Down
Original file line number Diff line number Diff line change
@@ -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%;
Expand All @@ -28,6 +27,53 @@ const Container = styled(Box)(({ theme }) => ({
border: `1px solid ${theme.palette.divider}`,
}));

const roleMappings: Record<Role, string[]> = {
[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, string[]> = {
[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;
Expand All @@ -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();

Expand All @@ -50,21 +96,36 @@ export default function UserRolePage({
formState: { errors },
} = useForm<Inputs>({
defaultValues: {
...formData, // restore answers if navigating between form pages
...formData,
},
mode: "onBlur",
});

const watchedFields = watch();
const prevWatchedFieldsRef = useRef<Inputs>();

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 (
Expand Down
23 changes: 23 additions & 0 deletions booking-app/components/src/client/routes/components/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
RoomSetting,
SafetyTraining,
Settings,
UserApiData,
} from "../../../types";

import { useAuth } from "@/components/src/client/routes/components/AuthProvider";
Expand All @@ -34,6 +35,7 @@ export interface DatabaseContextType {
settings: Settings;
userEmail: string | undefined;
netId: string | undefined;
userApiData: UserApiData | undefined;
reloadAdminUsers: () => Promise<void>;
reloadApproverUsers: () => Promise<void>;
reloadBannedUsers: () => Promise<void>;
Expand All @@ -60,6 +62,7 @@ export const DatabaseContext = createContext<DatabaseContextType>({
settings: { bookingTypes: [] },
userEmail: undefined,
netId: undefined,
userApiData: undefined,
reloadAdminUsers: async () => {},
reloadApproverUsers: async () => {},
reloadBannedUsers: async () => {},
Expand Down Expand Up @@ -92,8 +95,27 @@ export const DatabaseProvider = ({
>([]);
const [settings, setSettings] = useState<Settings>({ bookingTypes: [] });
const [userEmail, setUserEmail] = useState<string | undefined>();
const [userApiData, setUserApiData] = useState<UserApiData | undefined>(
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<PagePermission>(() => {
Expand Down Expand Up @@ -401,6 +423,7 @@ export const DatabaseProvider = ({
userEmail,
netId,
bookingsLoading,
userApiData,
reloadAdminUsers: fetchAdminUsers,
reloadApproverUsers: fetchApproverUsers,
reloadBannedUsers: fetchBannedUsers,
Expand Down
1 change: 1 addition & 0 deletions booking-app/components/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit d388198

Please sign in to comment.