From 3f8ef8f9bf6dd34b37e9c6220703aed42856eb2f Mon Sep 17 00:00:00 2001 From: Dev Singh Date: Wed, 5 Nov 2025 10:12:16 -0600 Subject: [PATCH 1/3] Fix crash when clicking back on room requests --- .../roomRequest/RoomRequestLanding.page.tsx | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/ui/pages/roomRequest/RoomRequestLanding.page.tsx b/src/ui/pages/roomRequest/RoomRequestLanding.page.tsx index 83d0dbe2..17c58f4b 100644 --- a/src/ui/pages/roomRequest/RoomRequestLanding.page.tsx +++ b/src/ui/pages/roomRequest/RoomRequestLanding.page.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { Container, Title, Tabs, Select, Loader } from "@mantine/core"; +import { Container, Title, Tabs, Select } from "@mantine/core"; import { AuthGuard } from "@ui/components/AuthGuard"; import { AppRoles } from "@common/roles"; import { useApi } from "@ui/util/api"; @@ -19,16 +19,17 @@ import { useSearchParams } from "react-router-dom"; export const ManageRoomRequestsPage: React.FC = () => { const api = useApi("core"); const [semester, setSemesterState] = useState(null); - const [isLoading, setIsLoading] = useState(false); const nextSemesters = getSemesters(); const semesterOptions = [...getPreviousSemesters(), ...nextSemesters]; const [searchParams, setSearchParams] = useSearchParams(); - const setSemester = (semester: string | null) => { - setSemesterState(semester); - if (semester) { - setSearchParams({ semester }); + + const setSemester = (newSemester: string | null) => { + if (newSemester && newSemester !== semester) { + setSemesterState(newSemester); + setSearchParams({ semester: newSemester }); } }; + const createRoomRequest = async ( payload: RoomRequestFormValues, ): Promise => { @@ -53,16 +54,23 @@ export const ManageRoomRequestsPage: React.FC = () => { }; useEffect(() => { - const semeseterFromUrl = searchParams.get("semester") as string | null; + const semesterFromUrl = searchParams.get("semester"); if ( - semeseterFromUrl && - semesterOptions.map((x) => x.value).includes(semeseterFromUrl) + semesterFromUrl && + semesterOptions.map((x) => x.value).includes(semesterFromUrl) ) { - setSemester(semeseterFromUrl); + if (semesterFromUrl !== semester) { + setSemesterState(semesterFromUrl); + } } else { - setSemester(nextSemesters[0].value); + const defaultSemester = nextSemesters[0].value; + if (defaultSemester !== semester) { + setSemesterState(defaultSemester); + setSearchParams({ semester: defaultSemester }); + } } - }, [searchParams, semesterOptions, nextSemesters]); + }, [searchParams, semester, semesterOptions, nextSemesters, setSearchParams]); + return ( { New Request - {isLoading ? ( - - ) : ( - - + {semester && ( + - {semester && ( - - )} - - )} + )} +
From bffb9da89dfe8d1f159a3ed8797c7a7767ae2115 Mon Sep 17 00:00:00 2001 From: Dev Singh Date: Wed, 5 Nov 2025 10:20:22 -0600 Subject: [PATCH 2/3] Add an error boundary for react errors --- src/ui/Router.tsx | 280 +++++++++++++++++------------------- src/ui/pages/Error.page.tsx | 35 +++++ 2 files changed, 164 insertions(+), 151 deletions(-) create mode 100644 src/ui/pages/Error.page.tsx diff --git a/src/ui/Router.tsx b/src/ui/Router.tsx index fad72141..134fa913 100644 --- a/src/ui/Router.tsx +++ b/src/ui/Router.tsx @@ -1,15 +1,14 @@ -import React, { useState, useEffect, ReactNode } from "react"; +import React from "react"; import { createBrowserRouter, Navigate, RouterProvider, useLocation, } from "react-router-dom"; -import { AcmAppShell } from "./components/AppShell"; import { useAuth } from "./components/AuthContext"; import AuthCallback from "./components/AuthContext/AuthCallbackHandler.page"; import { Error404Page } from "./pages/Error404.page"; -import { Error500Page } from "./pages/Error500.page"; +import { ErrorPage } from "./pages/Error.page"; import { HomePage } from "./pages/Home.page"; import { LoginPage } from "./pages/Login.page"; import { LogoutPage } from "./pages/Logout.page"; @@ -96,165 +95,148 @@ const commonRoutes = [ ]; const profileRouter = createBrowserRouter([ - ...commonRoutes, { - path: "/profile", - element: , - }, - { - path: "*", - element: , + path: "/", + errorElement: , + children: [ + ...commonRoutes, + { + path: "/profile", + element: , + }, + { + path: "*", + element: , + }, + ], }, ]); const unauthenticatedRouter = createBrowserRouter([ - ...commonRoutes, { path: "/", - element: , - }, - { - path: "/login", - element: , - }, - { - path: "*", - element: , + errorElement: , + children: [ + ...commonRoutes, + { + path: "/", + element: , + }, + { + path: "/login", + element: , + }, + { + path: "*", + element: , + }, + ], }, ]); const authenticatedRouter = createBrowserRouter([ - ...commonRoutes, { path: "/", - element: , - }, - { - path: "/login", - element: , - }, - { - path: "/logout", - element: , - }, - { - path: "/profile", - element: , - }, - { - path: "/home", - element: , - }, - { - path: "/events/add", - element: , - }, - { - path: "/events/edit/:eventId", - element: , - }, - { - path: "/events/manage", - element: , - }, - { - path: "/linkry", - element: , - }, - { - path: "/linkry/add", - element: , - }, - { - path: "/linkry/edit/:slug", - element: , - }, - { - path: "/tickets/scan", - element: , - }, - { - path: "/tickets", - element: , - }, - { - path: "/iam", - element: , - }, - { - path: "/membershipLists", - element: , - }, - { - path: "/tickets/manage/:eventId", - element: , - }, - { - path: "/stripe", - element: , - }, - { - path: "/roomRequests", - element: , - }, - { - path: "/roomRequests/:semesterId/:requestId", - element: , - }, - { - path: "/logs", - element: , - }, - { - path: "/apiKeys", - element: , - }, - { - path: "/orgInfo", - element: , - }, - // Catch-all route for authenticated users shows 404 page - { - path: "*", - element: , + errorElement: , + children: [ + ...commonRoutes, + { + path: "/", + element: , + }, + { + path: "/login", + element: , + }, + { + path: "/logout", + element: , + }, + { + path: "/profile", + element: , + }, + { + path: "/home", + element: , + }, + { + path: "/events/add", + element: , + }, + { + path: "/events/edit/:eventId", + element: , + }, + { + path: "/events/manage", + element: , + }, + { + path: "/linkry", + element: , + }, + { + path: "/linkry/add", + element: , + }, + { + path: "/linkry/edit/:slug", + element: , + }, + { + path: "/tickets/scan", + element: , + }, + { + path: "/tickets", + element: , + }, + { + path: "/iam", + element: , + }, + { + path: "/membershipLists", + element: , + }, + { + path: "/tickets/manage/:eventId", + element: , + }, + { + path: "/stripe", + element: , + }, + { + path: "/roomRequests", + element: , + }, + { + path: "/roomRequests/:semesterId/:requestId", + element: , + }, + { + path: "/logs", + element: , + }, + { + path: "/apiKeys", + element: , + }, + { + path: "/orgInfo", + element: , + }, + // Catch-all route for authenticated users shows 404 page + { + path: "*", + element: , + }, + ], }, ]); -interface ErrorBoundaryProps { - children: ReactNode; -} - -const ErrorBoundary: React.FC = ({ children }) => { - const [hasError, setHasError] = useState(false); - const [error, setError] = useState(null); - const { isLoggedIn } = useAuth(); - - const onError = (errorObj: Error) => { - setHasError(true); - setError(errorObj); - }; - - useEffect(() => { - const errorHandler = (event: ErrorEvent) => { - onError(event.error); - }; - - window.addEventListener("error", errorHandler); - return () => { - window.removeEventListener("error", errorHandler); - }; - }, []); - - if (hasError && error) { - if (error.message === "404") { - return isLoggedIn ? : ; - } - return ; - } - - return <>{children}; -}; - export const Router: React.FC = () => { const { isLoggedIn } = useAuth(); const router = isLoggedIn @@ -263,9 +245,5 @@ export const Router: React.FC = () => { ? profileRouter : unauthenticatedRouter; - return ( - - - - ); + return ; }; diff --git a/src/ui/pages/Error.page.tsx b/src/ui/pages/Error.page.tsx new file mode 100644 index 00000000..776a7675 --- /dev/null +++ b/src/ui/pages/Error.page.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { useRouteError } from "react-router-dom"; +import { Container, Title, Text, Card, Accordion } from "@mantine/core"; +import { AcmAppShell } from "@ui/components/AppShell"; + +export const ErrorPage: React.FC = () => { + const error = useRouteError() as Error; + + return ( + + + + Oops! Something went wrong. + + We're sorry, but an unexpected error has occurred. Please contact + support if this error persists. + + + + + Error Details + + + +
{error.stack || error.message}
+
+
+
+
+
+
+
+
+ ); +}; From 55237aa07ae880b1f51252508f31114c29ab10e2 Mon Sep 17 00:00:00 2001 From: Dev Singh Date: Wed, 5 Nov 2025 10:22:18 -0600 Subject: [PATCH 3/3] Memoize semester options --- src/ui/pages/roomRequest/RoomRequestLanding.page.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ui/pages/roomRequest/RoomRequestLanding.page.tsx b/src/ui/pages/roomRequest/RoomRequestLanding.page.tsx index 17c58f4b..e4de0d76 100644 --- a/src/ui/pages/roomRequest/RoomRequestLanding.page.tsx +++ b/src/ui/pages/roomRequest/RoomRequestLanding.page.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { Container, Title, Tabs, Select } from "@mantine/core"; import { AuthGuard } from "@ui/components/AuthGuard"; import { AppRoles } from "@common/roles"; @@ -19,8 +19,11 @@ import { useSearchParams } from "react-router-dom"; export const ManageRoomRequestsPage: React.FC = () => { const api = useApi("core"); const [semester, setSemesterState] = useState(null); - const nextSemesters = getSemesters(); - const semesterOptions = [...getPreviousSemesters(), ...nextSemesters]; + const nextSemesters = useMemo(() => getSemesters(), []); + const semesterOptions = useMemo( + () => [...getPreviousSemesters(), ...nextSemesters], + [nextSemesters], + ); const [searchParams, setSearchParams] = useSearchParams(); const setSemester = (newSemester: string | null) => {