diff --git a/frontend/src/common/pages/UniversalUnexpected.scss b/frontend/src/common/pages/UniversalUnexpected.scss index f9c54e9d0..a8be7acb2 100644 --- a/frontend/src/common/pages/UniversalUnexpected.scss +++ b/frontend/src/common/pages/UniversalUnexpected.scss @@ -2,4 +2,9 @@ &__link { margin-left: 0.25rem; } + + &__correlation-id { + font-weight: bold; + margin-bottom: 0px; + } } diff --git a/frontend/src/common/pages/UniversalUnexpected.tsx b/frontend/src/common/pages/UniversalUnexpected.tsx index 5f946136b..6d5115447 100644 --- a/frontend/src/common/pages/UniversalUnexpected.tsx +++ b/frontend/src/common/pages/UniversalUnexpected.tsx @@ -2,23 +2,49 @@ import "./UniversalUnexpected.scss"; import { ONROUTE_WEBPAGE_LINKS } from "../../routes/constants"; import { ErrorPage } from "../components/error/ErrorPage"; import { CustomExternalLink } from "../components/links/CustomExternalLink"; +import { useLocation } from "react-router-dom"; export const UniversalUnexpected = () => { + const { state } = useLocation(); + if (!state?.correlationId) { + return ( + + Please return to + + + onRouteBC + + + } + /> + ); + } return ( - Please return to + If this problem persists, please email - - - onRouteBC +

+ + the error reference number below to + + + PPCPermit@gov.bc.ca + +

+ {/** Display just the first segment of the correlation id */} + {state.correlationId.split("-")[0].toUpperCase()} +

} /> diff --git a/frontend/src/features/idir/company/IDIRCreateCompany.tsx b/frontend/src/features/idir/company/IDIRCreateCompany.tsx index 302dede49..672df3dd0 100644 --- a/frontend/src/features/idir/company/IDIRCreateCompany.tsx +++ b/frontend/src/features/idir/company/IDIRCreateCompany.tsx @@ -20,6 +20,7 @@ import { CreateCompanyRequest, CompanyProfile, } from "../../manageProfile/types/manageProfile"; +import { AxiosError } from "axios"; /** * The form for a staff user to create a company. @@ -117,8 +118,10 @@ export const IDIRCreateCompany = React.memo(() => { setClientNumber(() => clientNumber); } }, - onError: () => { - navigate(ERROR_ROUTES.UNEXPECTED); + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: error.response?.headers["x-correlation-id"] }, + }); }, }); diff --git a/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx b/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx index 370c1c2d2..1a58c9213 100644 --- a/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx +++ b/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx @@ -162,7 +162,9 @@ export const IDIRPermitSearchRowActions = ({ alertType: "success", }); } else { - navigate(routes.ERROR_ROUTES.UNEXPECTED); + navigate(routes.ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: response.headers["x-correlation-id"] }, + }); } }; diff --git a/frontend/src/features/manageProfile/apiManager/hooks.ts b/frontend/src/features/manageProfile/apiManager/hooks.ts index 25f874dc3..8bbd84e57 100644 --- a/frontend/src/features/manageProfile/apiManager/hooks.ts +++ b/frontend/src/features/manageProfile/apiManager/hooks.ts @@ -2,7 +2,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { useAuth } from "react-oidc-context"; import { useContext, useEffect } from "react"; import { useNavigate } from "react-router"; -import { AxiosResponse } from "axios"; +import { AxiosError, AxiosResponse } from "axios"; import { IDPS } from "../../../common/types/idp"; import { Nullable } from "../../../common/types/common"; @@ -54,9 +54,7 @@ export const useCompanyInfoQuery = () => { * @param companyId Id of the company to get the info for * @returns Query object containing company info */ -export const useCompanyInfoDetailsQuery = ( - companyId: number, -) => { +export const useCompanyInfoDetailsQuery = (companyId: number) => { return useQuery({ queryKey: ["companyInfo"], queryFn: () => getCompanyInfoById(companyId), @@ -268,12 +266,19 @@ export const useDeleteCompanyActiveUsers = () => { const navigate = useNavigate(); return useMutation({ mutationFn: deleteCompanyActiveUsers, - onError: () => navigate(ERROR_ROUTES.UNEXPECTED), + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: error.response?.headers["x-correlation-id"] }, + }); + }, onSuccess: (response: AxiosResponse) => { - const { data: companyUserResponse } = response; + const { data: companyUserResponse, headers } = response; + const { failure } = companyUserResponse as DeleteResponse; if (failure?.length > 0) { - navigate(ERROR_ROUTES.UNEXPECTED); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: headers["x-correlation-id"] }, + }); } }, }); @@ -287,12 +292,18 @@ export const useDeleteCompanyPendingUsers = () => { const navigate = useNavigate(); return useMutation({ mutationFn: deleteCompanyPendingUsers, - onError: () => navigate(ERROR_ROUTES.UNEXPECTED), + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: error.response?.headers["x-correlation-id"] }, + }); + }, onSuccess: (response: AxiosResponse) => { - const { data: companyUserResponse } = response; + const { data: companyUserResponse, headers } = response; const { failure } = companyUserResponse as DeleteResponse; if (failure?.length > 0) { - navigate(ERROR_ROUTES.UNEXPECTED); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: headers["x-correlation-id"] }, + }); } }, }); diff --git a/frontend/src/features/manageProfile/components/forms/myInfo/MyInfoForm.tsx b/frontend/src/features/manageProfile/components/forms/myInfo/MyInfoForm.tsx index 3c76210cc..58da10b15 100644 --- a/frontend/src/features/manageProfile/components/forms/myInfo/MyInfoForm.tsx +++ b/frontend/src/features/manageProfile/components/forms/myInfo/MyInfoForm.tsx @@ -19,6 +19,9 @@ import { BCeIDUserRoleType, BCeID_USER_ROLE, } from "../../../../../common/authentication/types"; +import { AxiosError } from "axios"; +import { useNavigate } from "react-router-dom"; +import { ERROR_ROUTES } from "../../../../../routes/constants"; export const MyInfoForm = memo( ({ @@ -29,6 +32,7 @@ export const MyInfoForm = memo( setIsEditing: React.Dispatch>; }) => { const queryClient = useQueryClient(); + const navigate = useNavigate(); const formMethods = useForm({ defaultValues: { firstName: getDefaultRequiredVal("", myInfo?.firstName), @@ -61,6 +65,11 @@ export const MyInfoForm = memo( setIsEditing(false); } }, + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: error.response?.headers["x-correlation-id"] }, + }); + }, }); const onUpdateMyInfo = (data: UserInfoRequest) => { diff --git a/frontend/src/features/manageProfile/components/forms/userManagement/EditUser.tsx b/frontend/src/features/manageProfile/components/forms/userManagement/EditUser.tsx index 8cf39a807..a7c56c340 100644 --- a/frontend/src/features/manageProfile/components/forms/userManagement/EditUser.tsx +++ b/frontend/src/features/manageProfile/components/forms/userManagement/EditUser.tsx @@ -25,6 +25,7 @@ import { getDefaultRequiredVal, } from "../../../../../common/helpers/util"; import { BCeID_USER_ROLE } from "../../../../../common/authentication/types"; +import { AxiosError } from "axios"; /** * Edit User form for User Management. @@ -76,8 +77,10 @@ export const EditUserForm = memo( */ const updateUserInfoMutation = useMutation({ mutationFn: updateUserInfo, - onError: () => { - navigate(ERROR_ROUTES.UNEXPECTED); + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: error.response?.headers["x-correlation-id"] }, + }); }, onSuccess: (response) => { if (response.status === 200) { @@ -93,6 +96,10 @@ export const EditUserForm = memo( selectedTab: BCEID_PROFILE_TABS.USER_MANAGEMENT, }, }); + } else { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: response.headers["x-correlation-id"] }, + }); } }, }); diff --git a/frontend/src/features/manageVehicles/components/list/List.tsx b/frontend/src/features/manageVehicles/components/list/List.tsx index 2bc3520b0..c076c6b76 100644 --- a/frontend/src/features/manageVehicles/components/list/List.tsx +++ b/frontend/src/features/manageVehicles/components/list/List.tsx @@ -159,9 +159,13 @@ export const List = memo( setIsDeleteDialogOpen(() => true); }, []); - const handleError = () => { + const handleError = (correlationId?: string) => { setIsDeleteDialogOpen(() => false); - navigate(ERROR_ROUTES.UNEXPECTED); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { + correlationId, + }, + }); }; const onConfirmDelete = async () => { @@ -177,7 +181,7 @@ export const List = memo( const responseBody = response.data; if (responseBody.failure.length > 0) { // Delete action for some vehicles failed - handleError(); + handleError(response.headers["x-correlation-id"]); } else { setIsDeleteDialogOpen(() => false); snackBar.setSnackBar({ @@ -193,7 +197,7 @@ export const List = memo( query.refetch(); } } else { - handleError(); + handleError(response.headers["x-correlation-id"]); } } catch { handleError(); diff --git a/frontend/src/features/permits/hooks/cart.ts b/frontend/src/features/permits/hooks/cart.ts index 5299c7724..4ee9ba31a 100644 --- a/frontend/src/features/permits/hooks/cart.ts +++ b/frontend/src/features/permits/hooks/cart.ts @@ -9,6 +9,9 @@ import { getCartCount, removeFromCart, } from "../apiManager/cart"; +import { AxiosError } from "axios"; +import { ERROR_ROUTES } from "../../../routes/constants"; +import { useNavigate } from "react-router-dom"; const CART_KEY = "cart"; const CART_COUNT_KEY = "cart-count"; @@ -19,6 +22,7 @@ const CART_ITEM = "cart-item"; * @returns Mutation object for adding items to cart */ export const useAddToCart = () => { + const navigate = useNavigate(); return useMutation({ mutationFn: ({ companyId, @@ -27,6 +31,13 @@ export const useAddToCart = () => { companyId: number; applicationIds: string[]; }) => addToCart(companyId, applicationIds), + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { + correlationId: error?.response?.headers["x-correlation-id"], + }, + }); + }, }); }; diff --git a/frontend/src/features/permits/hooks/hooks.ts b/frontend/src/features/permits/hooks/hooks.ts index d3a39aeb2..541d1a037 100644 --- a/frontend/src/features/permits/hooks/hooks.ts +++ b/frontend/src/features/permits/hooks/hooks.ts @@ -11,7 +11,11 @@ import { import { Application, ApplicationFormData } from "../types/application"; import { IssuePermitsResponse } from "../types/permit"; import { StartTransactionResponseData } from "../types/payment"; -import { APPLICATION_STEPS, ApplicationStep } from "../../../routes/constants"; +import { + APPLICATION_STEPS, + ApplicationStep, + ERROR_ROUTES, +} from "../../../routes/constants"; import { isPermitTypeValid } from "../types/PermitType"; import { isPermitIdNumeric } from "../helpers/permitState"; import { deserializeApplicationResponse } from "../helpers/deserializeApplication"; @@ -36,6 +40,7 @@ import { getPendingPermits, } from "../apiManager/permitsAPI"; import { getDefaultRequiredVal } from "../../../common/helpers/util"; +import { useNavigate } from "react-router-dom"; const QUERY_KEYS = { PERMIT_DETAIL: ( @@ -58,6 +63,7 @@ const QUERY_KEYS = { */ export const useSaveApplicationMutation = () => { const queryClient = useQueryClient(); + const navigate = useNavigate(); return useMutation({ mutationFn: async ({ data, @@ -86,6 +92,14 @@ export const useSaveApplicationMutation = () => { }; } }, + onError: (error: AxiosError) => { + console.error(error); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { + correlationId: error?.response?.headers["x-correlation-id"], + }, + }); + }, }); }; @@ -196,10 +210,7 @@ export const useApplicationDetailsQuery = ({ * @param permitId permit id for the permit * @returns Query object containing the permit details */ -export const usePermitDetailsQuery = ( - companyId: number, - permitId: string, -) => { +export const usePermitDetailsQuery = (companyId: number, permitId: string) => { return useQuery({ queryKey: QUERY_KEYS.PERMIT_DETAIL(permitId, companyId), queryFn: async () => { @@ -221,7 +232,7 @@ export const useStartTransaction = () => { const [transaction, setTransaction] = useState>(undefined); const queryClient = useQueryClient(); - + const navigate = useNavigate(); const mutation = useMutation({ mutationFn: startTransaction, retry: false, @@ -232,9 +243,14 @@ export const useStartTransaction = () => { queryClient.setQueryData(["transaction"], transactionData); setTransaction(transactionData); }, - onError: (err: unknown) => { - console.error(err); + onError: (error: AxiosError) => { + console.error(error); setTransaction(undefined); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { + correlationId: error?.response?.headers["x-correlation-id"], + }, + }); }, }); @@ -335,12 +351,9 @@ export const useIssuePermits = () => { useState>(undefined); const queryClient = useQueryClient(); - + const navigate = useNavigate(); const mutation = useMutation({ - mutationFn: (data: { - companyId: number; - applicationIds: string[]; - }) => + mutationFn: (data: { companyId: number; applicationIds: string[] }) => issuePermits(data.companyId, data.applicationIds), retry: false, onSuccess: (issueResponseData) => { @@ -349,9 +362,14 @@ export const useIssuePermits = () => { }); setIssueResults(issueResponseData); }, - onError: (err: unknown) => { - console.error(err); + onError: (error: AxiosError) => { + console.error(error); setIssueResults(null); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { + correlationId: error?.response?.headers["x-correlation-id"], + }, + }); }, }); @@ -368,6 +386,7 @@ export const useIssuePermits = () => { */ export const useAmendPermit = (companyId: number) => { const queryClient = useQueryClient(); + const navigate = useNavigate(); return useMutation({ mutationFn: async (data: AmendPermitFormData) => { const amendResult = await amendPermit(data, companyId); @@ -382,10 +401,7 @@ export const useAmendPermit = (companyId: number) => { ), }); queryClient.invalidateQueries({ - queryKey: QUERY_KEYS.PERMIT_HISTORY( - data.originalPermitId, - companyId, - ), + queryKey: QUERY_KEYS.PERMIT_HISTORY(data.originalPermitId, companyId), }); return { @@ -398,11 +414,20 @@ export const useAmendPermit = (companyId: number) => { status: amendResult.status, }; }, + onError: (error: AxiosError) => { + console.error(error); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { + correlationId: error?.response?.headers["x-correlation-id"], + }, + }); + }, }); }; export const useModifyAmendmentApplication = () => { const queryClient = useQueryClient(); + const navigate = useNavigate(); return useMutation({ mutationFn: async (data: { application: AmendPermitFormData; @@ -435,6 +460,14 @@ export const useModifyAmendmentApplication = () => { status: amendResult.status, }; }, + onError: (error: AxiosError) => { + console.error(error); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { + correlationId: error?.response?.headers["x-correlation-id"], + }, + }); + }, }); }; @@ -481,14 +514,11 @@ export const useApplicationsInProgressQuery = (companyId: number) => { sorting, ], queryFn: () => - getApplicationsInProgress( - companyId, - { - page: pagination.pageIndex, - take: pagination.pageSize, - orderBy, - }, - ), + getApplicationsInProgress(companyId, { + page: pagination.pageIndex, + take: pagination.pageSize, + orderBy, + }), refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground refetchOnMount: "always", placeholderData: keepPreviousData, @@ -518,13 +548,10 @@ export const usePendingPermitsQuery = (companyId: number) => { const { data: pendingPermits } = useQuery({ queryKey: ["pendingPermits", pagination.pageIndex, pagination.pageSize], queryFn: () => - getPendingPermits( - companyId, - { - page: pagination.pageIndex, - take: pagination.pageSize, - }, - ), + getPendingPermits(companyId, { + page: pagination.pageIndex, + take: pagination.pageSize, + }), refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground refetchOnMount: "always", placeholderData: keepPreviousData, diff --git a/frontend/src/features/queue/hooks/hooks.ts b/frontend/src/features/queue/hooks/hooks.ts index f80cfa8db..776ddc585 100644 --- a/frontend/src/features/queue/hooks/hooks.ts +++ b/frontend/src/features/queue/hooks/hooks.ts @@ -180,7 +180,9 @@ export const useUpdateApplicationInQueueStatus = () => { if (err.response?.status === 422) { return err; } else { - navigate(ERROR_ROUTES.UNEXPECTED); + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: err.response?.headers["x-correlation-id"] }, + }); } }, }); diff --git a/frontend/src/features/settings/hooks/creditAccount.ts b/frontend/src/features/settings/hooks/creditAccount.ts index 17df4a56d..e0a78c4b3 100644 --- a/frontend/src/features/settings/hooks/creditAccount.ts +++ b/frontend/src/features/settings/hooks/creditAccount.ts @@ -22,12 +22,16 @@ import { UpdateStatusData, } from "../types/creditAccount"; import { CompanyProfile } from "../../manageProfile/types/manageProfile"; +import { AxiosError } from "axios"; /** * Hook to fetch the company credit account details for the active user. * @returns Query result of the company credit account details */ -export const useGetCreditAccountMetadataQuery = (companyId: number, enabled?: boolean ) => { +export const useGetCreditAccountMetadataQuery = ( + companyId: number, + enabled?: boolean, +) => { return useQuery({ queryKey: ["credit-account", { companyId }, "metadata"], queryFn: () => getCreditAccountMetadata(companyId), @@ -163,7 +167,11 @@ export const useCreateCreditAccountMutation = () => { queryKey: ["credit-account", { companyId }], }); }, - onError: () => navigate(ERROR_ROUTES.UNEXPECTED), + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: error?.response?.headers["x-correlation-id"] }, + }); + }, }); }; @@ -203,7 +211,11 @@ export const useAddCreditAccountUserMutation = () => { queryKey: ["credit-account", { companyId }], }); }, - onError: () => navigate(ERROR_ROUTES.UNEXPECTED), + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: error.response?.headers["x-correlation-id"] }, + }); + }, }); }; @@ -229,8 +241,10 @@ export const useRemoveCreditAccountUsersMutation = () => { alertType: "info", }); }, - onError: () => { - navigate(ERROR_ROUTES.UNEXPECTED); + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: error.response?.headers["x-correlation-id"] }, + }); }, }); }; @@ -276,8 +290,12 @@ export const useUpdateCreditAccountStatusMutation = () => { ...getResultingSnackbarOptionsFromAction(updateStatusAction), }); }, - onError: () => { - navigate(ERROR_ROUTES.UNEXPECTED); + onError: (error: AxiosError) => { + navigate(ERROR_ROUTES.UNEXPECTED, { + state: { + correlationId: error?.response?.headers["x-correlation-id"], + }, + }); }, }); }; diff --git a/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx b/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx index b2205fd39..6dc6e73ae 100644 --- a/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx +++ b/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx @@ -57,8 +57,10 @@ export const LOASteps = ({ const [activeStep, setActiveStep] = useState(LOA_STEPS.BASIC); - const showPrevBtn = activeStep === LOA_STEPS.VEHICLES || activeStep === LOA_STEPS.REVIEW; - const showNextBtn = activeStep === LOA_STEPS.BASIC || activeStep === LOA_STEPS.VEHICLES; + const showPrevBtn = + activeStep === LOA_STEPS.VEHICLES || activeStep === LOA_STEPS.REVIEW; + const showNextBtn = + activeStep === LOA_STEPS.BASIC || activeStep === LOA_STEPS.VEHICLES; const showFinishBtn = activeStep === LOA_STEPS.REVIEW; const handlePrev = () => { @@ -80,14 +82,16 @@ export const LOASteps = ({ const handleFinish = async () => { // Handle submitting LOA const isLOACreation = !loaId; - const res = isLOACreation ? await createLOAMutation.mutateAsync({ - companyId, - data: getValues(), - }) : await updateLOAMutation.mutateAsync({ - companyId, - loaId, - data: getValues(), - }); + const res = isLOACreation + ? await createLOAMutation.mutateAsync({ + companyId, + data: getValues(), + }) + : await updateLOAMutation.mutateAsync({ + companyId, + loaId, + data: getValues(), + }); if (res.status === 200 || res.status === 201) { setSnackBar({ @@ -98,7 +102,8 @@ export const LOASteps = ({ }); onExit(); } else { - navigate(ERROR_ROUTES.UNEXPECTED); + const correlationId = res.headers["x-correlation-id"]; + navigate(ERROR_ROUTES.UNEXPECTED, { state: { correlationId } }); } }; @@ -125,14 +130,10 @@ export const LOASteps = ({ case LOA_STEPS.REVIEW: return ; default: - return ( - - ); + return ; } }, [activeStep]); - + return (
@@ -166,16 +167,16 @@ export const LOASteps = ({ text: "step__step-number", active: "step__icon--active", completed: "step__icon--completed", - } + }, }} - >{label} + > + {label} + ))} -
- {stepComponent} -
+
{stepComponent}