diff --git a/booking-app/app/api/bookings/route.ts b/booking-app/app/api/bookings/route.ts index e1e75be5..d7179e42 100644 --- a/booking-app/app/api/bookings/route.ts +++ b/booking-app/app/api/bookings/route.ts @@ -1,15 +1,7 @@ import { - BookingFormDetails, - BookingStatusLabel, - RoomSetting, -} from "@/components/src/types"; -import { NextRequest, NextResponse } from "next/server"; -import { - approvalUrl, - declineUrl, - getBookingToolDeployUrl, -} from "@/components/src/server/ui"; -import { deleteEvent, insertEvent } from "@/components/src/server/calendars"; + serverFormatDate, + toFirebaseTimestampFromString, +} from "@/components/src/client/utils/serverDate"; import { firstApproverEmails, serverApproveInstantBooking, @@ -17,19 +9,24 @@ import { serverDeleteFieldsByCalendarEventId, serverUpdateDataByCalendarEventId, } from "@/components/src/server/admin"; +import { deleteEvent, insertEvent } from "@/components/src/server/calendars"; +import { getBookingToolDeployUrl } from "@/components/src/server/ui"; import { - serverFormatDate, - toFirebaseTimestampFromString, -} from "@/components/src/client/utils/serverDate"; + ApproverType, + BookingFormDetails, + BookingStatusLabel, + RoomSetting, +} from "@/components/src/types"; import { serverGetNextSequentialId, serverSaveDataToFirestore, } from "@/lib/firebase/server/adminDb"; +import { NextRequest, NextResponse } from "next/server"; -import { DateSelectArg } from "fullcalendar"; +import { sendHTMLEmail } from "@/app/lib/sendHTMLEmail"; import { TableNames } from "@/components/src/policy"; import { Timestamp } from "firebase-admin/firestore"; -import { sendHTMLEmail } from "@/app/lib/sendHTMLEmail"; +import { DateSelectArg } from "fullcalendar"; async function createBookingCalendarEvent( selectedRooms: RoomSetting[], @@ -88,7 +85,7 @@ async function handleBookingApprovalEmails( ); const emailPromises = recipients.map(recipient => sendHTMLEmail({ - templateName: "approval_email", + templateName: "booking_detail", contents: { ...otherContentsStrings, roomId: selectedRoomIds, @@ -101,6 +98,7 @@ async function handleBookingApprovalEmails( eventTitle: contents.title, requestNumber: contents.requestNumber ?? sequentialId, body: "", + approverType: ApproverType.LIAISON, }), ); await Promise.all(emailPromises); @@ -117,8 +115,6 @@ async function handleBookingApprovalEmails( email, startDate: bookingCalendarInfo?.startStr, endDate: bookingCalendarInfo?.endStr, - approvalUrl: approvalUrl(calendarEventId), - declineUrl: declineUrl(calendarEventId), bookingToolUrl: getBookingToolDeployUrl(), headerMessage: "This is a request email for first approval.", requestNumber: sequentialId, diff --git a/booking-app/app/api/sendEmail/route.ts b/booking-app/app/api/sendEmail/route.ts index c9da616c..107465e1 100644 --- a/booking-app/app/api/sendEmail/route.ts +++ b/booking-app/app/api/sendEmail/route.ts @@ -11,6 +11,7 @@ export async function POST(req: NextRequest) { eventTitle, requestNumber, bodyMessage, + approverType, } = await req.json(); // if (!templateName || !contents || !targetEmail || !status || !eventTitle) { @@ -29,6 +30,7 @@ export async function POST(req: NextRequest) { eventTitle, requestNumber, body: bodyMessage || "", + approverType, }); return NextResponse.json( { message: "Email sent successfully" }, diff --git a/booking-app/app/liaison/page.tsx b/booking-app/app/liaison/page.tsx index 59799d7f..598d8307 100644 --- a/booking-app/app/liaison/page.tsx +++ b/booking-app/app/liaison/page.tsx @@ -3,7 +3,22 @@ "use client"; import Liaison from "@/components/src/client/routes/liaison/Liaison"; +import { useSearchParams } from "next/navigation"; +import { Suspense } from "react"; -const LiaisonPage: React.FC = () => ; +const LiaisonWithParams: React.FC = () => { + const searchParams = useSearchParams(); + const calendarEventId = searchParams.get("calendarEventId"); + + return ; +}; + +const LiaisonPage: React.FC = () => { + return ( + Loading...}> + + + ); +}; export default LiaisonPage; diff --git a/booking-app/app/lib/sendHTMLEmail.ts b/booking-app/app/lib/sendHTMLEmail.ts index 8f4ecd09..9086d050 100644 --- a/booking-app/app/lib/sendHTMLEmail.ts +++ b/booking-app/app/lib/sendHTMLEmail.ts @@ -1,10 +1,9 @@ -import { approvalUrl, declineUrl } from "@/components/src/server/ui"; - -import fs from "fs"; +import { serverFormatDate } from "@/components/src/client/utils/serverDate"; import { getEmailBranchTag } from "@/components/src/server/emails"; +import { ApproverType } from "@/components/src/types"; import { getGmailClient } from "@/lib/googleClient"; +import fs from "fs"; import path from "path"; -import { serverFormatDate } from "@/components/src/client/utils/serverDate"; let Handlebars; @@ -24,6 +23,7 @@ interface SendHTMLEmailParams { eventTitle: string; requestNumber: number; body: string; + approverType?: ApproverType; } export const sendHTMLEmail = async (params: SendHTMLEmailParams) => { @@ -35,10 +35,30 @@ export const sendHTMLEmail = async (params: SendHTMLEmailParams) => { eventTitle, requestNumber, body, + approverType, } = params; const subj = `${getEmailBranchTag()}${status} - Media Commons request #${requestNumber}: "${eventTitle}"`; + const getUrlPathByApproverType = ( + calendarEventId, + approverType?: ApproverType, + ): string => { + let path: string; + switch (approverType) { + case ApproverType.LIAISON: + path = "/liaison"; + break; + case ApproverType.FINAL_APPROVER: + path = "/admin"; + break; + default: + path = "/"; + } + + return `${process.env.NEXT_PUBLIC_BASE_URL}${path}?calendarEventId=${calendarEventId}`; + }; + const templatePath = path.join( process.cwd(), "app/templates", @@ -46,6 +66,10 @@ export const sendHTMLEmail = async (params: SendHTMLEmailParams) => { ); const templateSource = fs.readFileSync(templatePath, "utf8"); const template = Handlebars.compile(templateSource); + const approvalUrl = getUrlPathByApproverType( + contents.calendarEventId, + approverType, + ); const htmlBody = template({ eventTitle, @@ -54,8 +78,7 @@ export const sendHTMLEmail = async (params: SendHTMLEmailParams) => { contents, startDate: serverFormatDate(contents.startDate), endDate: serverFormatDate(contents.endDate), - approvalUrl: approvalUrl(contents.calendarEventId), - declineUrl: declineUrl(contents.calendarEventId), + approvalUrl, }); const messageParts = [ diff --git a/booking-app/app/templates/approval_email.html b/booking-app/app/templates/approval_email.html deleted file mode 100644 index 7bccef41..00000000 --- a/booking-app/app/templates/approval_email.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - -
-
-

{{ contents.headerMessage }}

-
-
-

Room Reservation Request

-

Room Number: {{ contents.roomId }}

- -

First name: {{ contents.firstName }}

-

Last name: {{ contents.lastName }}

- -

Email: {{ contents.email }}

-

Start date: {{ startDate }}

-

End date: {{ endDate }}

-

Secondary Point of Contact: {{ contents.secondaryName }}

-

NYU N-number: {{ contents.nNumber }}

-

Net Id: {{ contents.netId }}

-

Phone Number: {{ contents.phoneNumber }}

-

Department: {{ contents.department }}

-

Requestor's Role: {{ contents.role}}

-

Sponsor First Name: {{ contents.sponsorFirstName }}

-

Sponsor Last Name: {{ contents.sponsorLastName }}

-

Sponsor Email: {{ contents.sponsorEmail }}

-

Title: {{ contents.title }}

-

Description: {{ contents.description }}

-

Booking Type: {{ contents.bookingType }}

-

Expected Attendance: {{ contents.expectedAttendance }}

-

Attendee Affiliations: {{ contents.attendeeAffiliation }}

-

- Room setup needed?: {{ contents.roomSetup }} {{ contents.setupDetails }} -

-

Chartfield for Room setup?: {{ contents.chartFieldForRoomSetup }}

-

- Media Services: {{ contents.mediaServices }} {{ - contents.mediaServicesDetails }} -

-

Catering: {{ contents.catering }}

-

Catering Details: {{ contents.cateringService }}

-

Chartfield for Catering?: {{ contents.chartFieldForCatering }}

-

Hire Security: {{ contents.hireSecurity }}

-

- Chartfield for Hiring Security?: {{ contents.chartFieldForSecurity }} -

- - Approve - Decline - Open Booking Tool -
- - diff --git a/booking-app/app/templates/booking_detail.html b/booking-app/app/templates/booking_detail.html index a0fdf777..22e8f22e 100644 --- a/booking-app/app/templates/booking_detail.html +++ b/booking-app/app/templates/booking_detail.html @@ -1,105 +1,192 @@ + + -
-
-
+

{{ contents.headerMessage }}

-

Room Number: {{ contents.roomId }}

-

First name: {{ contents.firstName }}

-

Last name: {{ contents.lastName }}

-

Email: {{ contents.email }}

-

Start date: {{ startDate }}

-

End date: {{ endDate }}

-

Secondary Point of Contact: {{ contents.secondaryName }}

-

NYU N-number: {{ contents.nNumber }}

-

Net Id: {{ contents.netId }}

-

Phone Number: {{ contents.phoneNumber }}

-

Department: {{ contents.department }}

-

Requestor's Role: {{ contents.role}}

-

Sponsor First Name: {{ contents.sponsorFirstName }}

-

Sponsor Last Name: {{ contents.sponsorLastName }}

-

Sponsor Email: {{ contents.sponsorEmail }}

-

Title: {{ contents.title }}

-

Description: {{ contents.description }}

-

Booking Type: {{ contents.bookingType }}

-

Expected Attendance: {{ contents.expectedAttendance }}

-

Attendee Affiliations: {{ contents.attendeeAffiliation }}

-

- Room setup needed?: {{ contents.roomSetup }} {{ contents.setupDetails }} -

-

Chartfield for Room setup?: {{ contents.chartFieldForRoomSetup }}

-

- Media Services: {{ contents.mediaServices }} {{ - contents.mediaServicesDetails }} -

-

Catering: {{ contents.catering }}

-

Catering Details: {{ contents.cateringService }}

-

Chartfield for Catering?: {{ contents.chartFieldForCatering }}

-

Hire Security: {{ contents.hireSecurity }}

-

- Chartfield for Hiring Security?: {{ contents.chartFieldForSecurity }} -

+
Requestor
+ + + + + + + + + + + + + + + + + + + + + +
NetID / Name +
+ {{ contents.netId }}
{{ contents.firstName }} {{ + contents.lastName }} +
+
Contact Info +
+ {{ contents.email }}
+ {{ contents.phoneNumber }} +
+
N-Number{{ contents.nNumber }}
Secondary Contact{{ contents.secondaryName }}
Sponsor + {{ contents.sponsorEmail }}
+ {{ contents.sponsorFirstName }} {{ contents.sponsorLastName }} +
+
+ +
+
Details
+ + + + + + + + + + + + + + + + + + + + + +
Title{{ contents.title }}
Description{{ contents.description }}
Booking Type{{ contents.bookingType }}
Expected Attendance{{ contents.expectedAttendance }}
Attendee Affiliation{{ contents.attendeeAffiliation }}
+
+ +
+
Services
+ + + + + + + + + + + + + + + + + +
Room Setup +
+ {{ contents.roomSetup }} {{#if contents.setupDetails}}
{{ + contents.setupDetails }}{{/if}} {{#if + contents.chartFieldForRoomSetup}}
{{ + contents.chartFieldForRoomSetup }}{{/if}} +
+
Media Services +
+ {{contents.mediaServices}} {{#if + contents.mediaServicesDetails}}
{{contents.mediaServicesDetails}}{{/if}} +
+
Catering +
+ {{contents.cateringService}} {{#if + contents.chartFieldForCatering}}
+ {{contents.chartFieldForCatering}}{{/if}} +
+
Security +
+ {{contents.hireSecurity}} {{#if + contents.chartFieldForSecurity}}
{{contents.chartFieldForSecurity}}{{/if}} +
+
+ Open Booking Tool + to approve or decline this request.
diff --git a/booking-app/components/src/client/routes/approval.html b/booking-app/components/src/client/routes/approval.html deleted file mode 100644 index 5f730014..00000000 --- a/booking-app/components/src/client/routes/approval.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - Approved! - - diff --git a/booking-app/components/src/client/routes/booking/approval_email.html b/booking-app/components/src/client/routes/booking/approval_email.html deleted file mode 100644 index 49680548..00000000 --- a/booking-app/components/src/client/routes/booking/approval_email.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - -
-
-

-
-
-

Room Reservation Request

-

Room Number:

- -

First name:

-

Last name:

- -

Email:

-

Start date:

-

End date:

-

Secondary Point of Contact:

-

NYU N-number:

-

Net Id:

-

Phone Number:

-

Department:

-

Requestor's Role:

-

Sponsor First Name:

-

Sponsor Last Name:

-

Sponsor Email:

-

Title:

-

Description:

-

Booking Type:

-

Expected Attendance:

-

Attendee Affiliations:

-

Room setup needed?:

-

Chartfield for Room setup?:

-

Media Services:

-

Catering:

-

Catering Details:

-

Chartfield for Catering?:

-

Hire Security:

-

Chartfield for Hiring Security?:

- - Approve - Decline - Open Booking Tool -
- - diff --git a/booking-app/components/src/client/routes/booking/booking_detail.html b/booking-app/components/src/client/routes/booking/booking_detail.html deleted file mode 100644 index 635bd7bd..00000000 --- a/booking-app/components/src/client/routes/booking/booking_detail.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - -
-
-
-

-

Room Number:

- -

First name:

-

Last name:

-

Email:

-

Start date:

-

End date:

-

Secondary Point of Contact:

-

NYU N-number:

-

Net Id:

-

Phone Number:

-

Department:

-

Requestor's Role:

-

Sponsor First Name:

-

Sponsor Last Name:

-

Sponsor Email:

-

Title:

-

Description:

-

Booking Type:

-

Expected Attendance:

-

Attendee Affiliations:

-

Room setup needed?:

-

Chartfield for Room setup?:

-

Media Services:

-

Catering:

-

Catering Details:

-

Chartfield for Catering?:

-

Hire Security:

-

Chartfield for Hiring Security?:

-
- - diff --git a/booking-app/components/src/client/routes/components/bookingTable/Bookings.tsx b/booking-app/components/src/client/routes/components/bookingTable/Bookings.tsx index 95b6e5cb..2ffecb0b 100644 --- a/booking-app/components/src/client/routes/components/bookingTable/Bookings.tsx +++ b/booking-app/components/src/client/routes/components/bookingTable/Bookings.tsx @@ -1,4 +1,3 @@ -import { Booking, BookingRow, PageContextLevel } from "../../../../types"; import { Box, TableCell } from "@mui/material"; import React, { useCallback, @@ -7,25 +6,30 @@ import React, { useMemo, useState, } from "react"; +import { Booking, BookingRow, PageContextLevel } from "../../../../types"; import Table, { TableEmpty } from "../Table"; +import Loading from "../Loading"; +import { DatabaseContext } from "../Provider"; import BookMoreButton from "./BookMoreButton"; import BookingTableFilters from "./BookingTableFilters"; import BookingTableRow from "./BookingTableRow"; -import { ColumnSortOrder } from "./hooks/getColumnComparator"; -import { DatabaseContext } from "../Provider"; -import { DateRangeFilter } from "./hooks/getDateFilter"; -import Loading from "../Loading"; import MoreInfoModal from "./MoreInfoModal"; import SortableTableCell from "./SortableTableCell"; +import { ColumnSortOrder } from "./hooks/getColumnComparator"; +import { DateRangeFilter } from "./hooks/getDateFilter"; import useAllowedStatuses from "./hooks/useAllowedStatuses"; import { useBookingFilters } from "./hooks/useBookingFilters"; interface BookingsProps { pageContext: PageContextLevel; + calendarEventId?: string; } -export const Bookings: React.FC = ({ pageContext }) => { +export const Bookings: React.FC = ({ + pageContext, + calendarEventId, +}) => { const { bookings, bookingsLoading, reloadBookings } = useContext(DatabaseContext); const allowedStatuses = useAllowedStatuses(pageContext); diff --git a/booking-app/components/src/client/routes/liaison/Liaison.tsx b/booking-app/components/src/client/routes/liaison/Liaison.tsx index e808e304..19279f01 100644 --- a/booking-app/components/src/client/routes/liaison/Liaison.tsx +++ b/booking-app/components/src/client/routes/liaison/Liaison.tsx @@ -1,14 +1,15 @@ import { Box, Tab, Tabs } from "@mui/material"; +import { useContext, useMemo, useState } from "react"; import { PageContextLevel, PagePermission } from "../../../types"; -import React, { useContext, useMemo, useState } from "react"; import { Bookings } from "../components/bookingTable/Bookings"; import { CenterLoading } from "../components/Loading"; import { DatabaseContext } from "../components/Provider"; -const Liaison = () => { +const Liaison = ({ calendarEventId }) => { const { liaisonUsers, pagePermission, userEmail } = useContext(DatabaseContext); + const [tab, setTab] = useState("bookings"); const liaisonEmails = useMemo( @@ -39,7 +40,10 @@ const Liaison = () => { {tab === "bookings" && ( - + )}
)} diff --git a/booking-app/components/src/client/routes/reject.html b/booking-app/components/src/client/routes/reject.html deleted file mode 100644 index a0d042ec..00000000 --- a/booking-app/components/src/client/routes/reject.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - Decline the request - - diff --git a/booking-app/components/src/server/admin.ts b/booking-app/components/src/server/admin.ts index a123b89c..c410bd3f 100644 --- a/booking-app/components/src/server/admin.ts +++ b/booking-app/components/src/server/admin.ts @@ -1,9 +1,3 @@ -import { - BookingFormDetails, - BookingStatus, - BookingStatusLabel, - RoomSetting, -} from "../types"; import { Constraint, serverDeleteData, @@ -14,7 +8,13 @@ import { serverUpdateInFirestore, } from "@/lib/firebase/server/adminDb"; import { TableNames, getApprovalCcEmail } from "../policy"; -import { approvalUrl, declineUrl, getBookingToolDeployUrl } from "./ui"; +import { + ApproverType, + BookingFormDetails, + BookingStatus, + BookingStatusLabel, + RoomSetting, +} from "../types"; import { Timestamp } from "firebase-admin/firestore"; @@ -23,9 +23,6 @@ export const serverBookingContents = (id: string) => { .then((bookingObj) => { const updatedBookingObj = Object.assign({}, bookingObj, { headerMessage: "This is a request email for final approval.", - approvalUrl: approvalUrl(id), - bookingToolUrl: getBookingToolDeployUrl(), - declineUrl: declineUrl(id), }); return updatedBookingObj as unknown as BookingFormDetails; @@ -155,13 +152,14 @@ export const serverApproveBooking = async (id: string, email: string) => { }; const recipient = await serverGetFinalApproverEmail(); const formData = { - templateName: "approval_email", + templateName: "booking_detail", contents: emailContents, targetEmail: recipient, status: BookingStatusLabel.PENDING, eventTitle: contents.title || "", requestNumber: contents.requestNumber, bodyMessage: "", + approverType: ApproverType.FINAL_APPROVER, }; const res = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/sendEmail`, @@ -188,7 +186,8 @@ export const serverSendBookingDetailEmail = async ( calendarEventId: string, email: string, headerMessage: string, - status: BookingStatusLabel + status: BookingStatusLabel, + approverType?: ApproverType ) => { const contents = await serverBookingContents(calendarEventId); contents.headerMessage = headerMessage; @@ -200,6 +199,7 @@ export const serverSendBookingDetailEmail = async ( eventTitle: contents.title, requestNumber: contents.requestNumber ?? "--", bodyMessage: "", + approverType: approverType, }; const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/sendEmail`, { method: "POST", diff --git a/booking-app/components/src/server/db.ts b/booking-app/components/src/server/db.ts index ba619a38..f15895b7 100644 --- a/booking-app/components/src/server/db.ts +++ b/booking-app/components/src/server/db.ts @@ -1,22 +1,17 @@ import { - Approver, - BookingFormDetails, - BookingStatusLabel, - PolicySettings, -} from "../types"; + clientFetchAllDataFromCollection, + clientGetDataByCalendarEventId, + clientUpdateDataInFirestore, +} from "@/lib/firebase/firebase"; +import { Timestamp, where } from "@firebase/firestore"; import { ApproverLevel, TableNames, clientGetFinalApproverEmail, getCancelCcEmail, } from "../policy"; -import { Timestamp, where } from "@firebase/firestore"; -import { approvalUrl, declineUrl, getBookingToolDeployUrl } from "./ui"; -import { - clientFetchAllDataFromCollection, - clientGetDataByCalendarEventId, - clientUpdateDataInFirestore, -} from "@/lib/firebase/firebase"; +import { Approver, BookingFormDetails, BookingStatusLabel } from "../types"; +import { getBookingToolDeployUrl } from "./ui"; import { clientUpdateDataByCalendarEventId } from "@/lib/firebase/client/clientDb"; import { roundTimeUp } from "../client/utils/date"; @@ -271,9 +266,7 @@ export const clientBookingContents = (id: string) => { .then((bookingObj) => { const updatedBookingObj = Object.assign({}, bookingObj, { headerMessage: "This is a request email for final approval.", - approvalUrl: approvalUrl(id), bookingToolUrl: getBookingToolDeployUrl(), - declineUrl: declineUrl(id), }); return updatedBookingObj as unknown as BookingFormDetails; diff --git a/booking-app/components/src/server/ui.ts b/booking-app/components/src/server/ui.ts index 294f31ec..b32622e8 100644 --- a/booking-app/components/src/server/ui.ts +++ b/booking-app/components/src/server/ui.ts @@ -1,7 +1,7 @@ import { DevBranch } from "../types"; export const getBookingToolDeployUrl = () => { - switch (process.env.BRANCH_NAME as DevBranch) { + switch (process.env.NEXT_PUBLIC_BRANCH_NAME as DevBranch) { case "development": return "https://development-dot-flowing-mantis-389917.uc.r.appspot.com/"; case "staging": @@ -10,9 +10,3 @@ export const getBookingToolDeployUrl = () => { return "https://flowing-mantis-389917.uc.r.appspot.com/"; } }; - -export const approvalUrl = (calendarEventId: string) => - `${process.env.NEXT_PUBLIC_BASE_URL}/approve?calendarEventId=${calendarEventId}`; - -export const declineUrl = (calendarEventId: string) => - `${process.env.NEXT_PUBLIC_BASE_URL}/decline?calendarEventId=${calendarEventId}`; diff --git a/booking-app/components/src/types.ts b/booking-app/components/src/types.ts index 00a6267f..217b0a62 100644 --- a/booking-app/components/src/types.ts +++ b/booking-app/components/src/types.ts @@ -40,9 +40,6 @@ export type BookingRow = Booking & { }; export type BookingFormDetails = Booking & { - approvalUrl: string; - bookingToolUrl: string; - declinedUrl: string; headerMessage?: string; }; @@ -111,6 +108,11 @@ export enum Department { } export type DevBranch = "development" | "staging" | "production" | ""; +export enum ApproverType { + LIAISON = "liaison", + FINAL_APPROVER = "admin", +} + // what context are we entering the form in? export enum FormContextLevel { EDIT = "/edit", diff --git a/reject.html b/reject.html deleted file mode 100644 index db9ac5dd..00000000 --- a/reject.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - Reject the request - -