From e7cf68df2e846012b6d208c56b80c1c178da5c5f Mon Sep 17 00:00:00 2001 From: Jared White Date: Fri, 19 Apr 2024 15:30:02 -0700 Subject: [PATCH] feat: uptake of Seeds Toast component (#3962) * Squashed commit of the following: commit d9640567b7d2ae404557726df7a38f717f9a22f9 Author: Jared White Date: Mon Jan 22 15:41:22 2024 -0800 feat: adjust Seeds Toast inset commit 75b1e078c272417ccbd86487ea8cb3d6e68ffa48 Author: Jared White Date: Thu Jan 18 14:29:03 2024 -0800 test: get partners unit tests to pass commit af8987150d14bd3dc012fd811ea87ad6e3e542c1 Author: Jared White Date: Wed Jan 17 22:18:31 2024 -0800 fix: more linting commit fca32118b2114914b2f35924698fcb7e2de27bca Author: Jared White Date: Wed Jan 17 21:50:21 2024 -0800 fix: another lint error commit 22243fefd4903200c5b363e8e5c1b594bb221d79 Author: Jared White Date: Wed Jan 17 20:38:39 2024 -0800 fix: lint issues commit 1168e023b6030f0b9987740cb9d1aac712a3b969 Author: Jared White Date: Wed Jan 17 19:25:19 2024 -0800 feat: switch a number of site alert features to Toast commit 4309bda0c93523b829e8e76746f1741fbeb14fc9 Author: Jared White Date: Mon Jan 15 16:58:06 2024 -0800 feat: toast message context now working as expected commit 083500054878295400d837b56a7cacf1a86fa7ca Author: Jared White Date: Wed Jan 10 20:50:36 2024 -0800 chore: bump Seeds version commit 1feceb8edcf43bd1052661a40e2cde276bfaaf44 Merge: 504746455 dbcd45cf1 Author: Jared White Date: Wed Jan 10 20:37:42 2024 -0800 Merge commit 'dbcd45cf1060dc955e71e4dd11720c75a92efc3d' into 3434/react-context-messaging commit 504746455a05d24fe9444341f57c8f563263153d Author: Morgan Ludtke Date: Tue Oct 17 10:59:54 2023 -0500 fix: email csv fix commit 19cd8b2fdd901100d30c309b8c4e4bf6dc0b9099 Author: Morgan Ludtke Date: Tue Oct 17 10:34:42 2023 -0500 fix: review comments commit fc220a799f0c47b397bb22d78cc6247dc705ef21 Author: Morgan Ludtke Date: Fri Oct 6 15:41:25 2023 -0500 fix: add comment commit 4aa3cfbd5023554bfb5ccf6c59844899a181fdce Author: ColinBuyck Date: Thu Jul 6 21:04:09 2023 -0700 fix: uptake toast component * feat: strip out old SiteAlert code in favor of toasts * fix: lint issues * fix: more linting * test: fix tests due to new Toasts * feat: fix some timing/context issues with Toast * fix: type error * fix: type error redux * fix: update React types to clean up false errors * fix: add back missing toast, use Seeds size tokens --- .eslintrc.js | 2 +- shared-helpers/index.ts | 1 + shared-helpers/package.json | 3 +- shared-helpers/src/auth/AuthContext.ts | 2 +- shared-helpers/src/auth/RequireLogin.tsx | 14 +++-- shared-helpers/src/auth/Timeout.tsx | 15 ++---- .../src/utilities/MessageContext.ts | 51 +++++++++++++++++++ .../forgot-password/FormForgotPassword.tsx | 3 -- .../src/views/sign-in/FormSignInErrorBox.tsx | 3 +- .../__tests__/pages/listings/index.test.tsx | 6 ++- .../__tests__/pages/settings/index.test.tsx | 23 ++++++++- .../__tests__/pages/users/index.test.tsx | 6 ++- .../default/06-admin-user-mangement.spec.ts | 6 +-- ...risdictional-admin-user-management.spec.ts | 4 +- .../default/08-preference-management.spec.ts | 2 +- sites/partners/package.json | 2 +- .../PaperApplicationForm.tsx | 15 ++---- .../listings/ListingFormActions.tsx | 12 +++-- .../listings/PaperListingForm/index.tsx | 9 ++-- .../settings/PreferenceDeleteModal.tsx | 9 ++-- .../components/users/FormSignInAddPhone.tsx | 2 - .../components/users/FormSignInMFACode.tsx | 2 - .../components/users/FormSignInMFAType.tsx | 2 - .../src/components/users/FormUserConfirm.tsx | 6 +-- .../src/components/users/FormUserManage.tsx | 37 +++++--------- sites/partners/src/layouts/index.tsx | 22 +++++--- sites/partners/src/lib/hooks.ts | 30 ++++------- sites/partners/src/pages/_app.tsx | 9 +++- .../src/pages/application/[id]/index.tsx | 3 +- sites/partners/src/pages/forgot-password.tsx | 6 ++- sites/partners/src/pages/index.tsx | 38 ++------------ .../pages/listings/[id]/applications/add.tsx | 3 +- .../listings/[id]/applications/index.tsx | 19 +------ .../src/pages/listings/[id]/index.tsx | 4 +- sites/partners/src/pages/listings/add.tsx | 3 +- sites/partners/src/pages/reset-password.tsx | 17 ++----- sites/partners/src/pages/settings/index.tsx | 20 +++----- sites/partners/src/pages/users/confirm.tsx | 7 +-- sites/partners/src/pages/users/index.tsx | 16 +----- sites/partners/styles/overrides.scss | 4 ++ sites/public/package.json | 2 +- .../components/account/ConfirmationModal.tsx | 23 +++------ .../applications/ApplicationTimeout.ts | 1 - .../src/components/listing/ListingView.tsx | 2 - sites/public/src/layouts/application.tsx | 15 ++++-- sites/public/src/pages/_app.tsx | 7 ++- sites/public/src/pages/account/dashboard.tsx | 3 +- sites/public/src/pages/account/edit.tsx | 3 -- .../src/pages/applications/review/summary.tsx | 8 +-- .../src/pages/applications/review/terms.tsx | 2 +- .../applications/start/choose-language.tsx | 8 +-- sites/public/src/pages/create-account.tsx | 2 - sites/public/src/pages/forgot-password.tsx | 6 ++- sites/public/src/pages/index.tsx | 22 ++------ sites/public/src/pages/reset-password.tsx | 21 ++++---- sites/public/src/pages/sign-in.tsx | 11 ++-- sites/public/src/pages/verify.tsx | 11 ++-- sites/public/styles/overrides.scss | 4 ++ yarn.lock | 24 ++++----- 59 files changed, 286 insertions(+), 327 deletions(-) create mode 100644 shared-helpers/src/utilities/MessageContext.ts diff --git a/.eslintrc.js b/.eslintrc.js index 16d23dbcdd..84a96e3bf1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,7 +24,7 @@ module.exports = { "@typescript-eslint/camelcase": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], "@typescript-eslint/no-var-requires": "off", "react/jsx-uses-vars": "warn", "react/jsx-uses-react": "warn", diff --git a/shared-helpers/index.ts b/shared-helpers/index.ts index d111acb81d..a9124786db 100644 --- a/shared-helpers/index.ts +++ b/shared-helpers/index.ts @@ -30,3 +30,4 @@ export * from "./src/views/sign-in/FormSignInDefault" export * from "./src/views/sign-in/FormSignInErrorBox" export * from "./src/views/sign-in/FormSignInPwdless" export * from "./src/views/sign-in/ResendConfirmationModal" +export * from "./src/utilities/MessageContext" diff --git a/shared-helpers/package.json b/shared-helpers/package.json index 8433b23552..a303feaa3f 100644 --- a/shared-helpers/package.json +++ b/shared-helpers/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@bloom-housing/ui-components": "12.1.0", - "@bloom-housing/ui-seeds": "1.12.1", + "@bloom-housing/ui-seeds": "1.12.2", "axios-cookiejar-support": "4.0.6", "tough-cookie": "4.1.3" }, @@ -26,6 +26,7 @@ "@testing-library/react": "14.0.0", "@types/jest": "^26.0.14", "@types/node-polyglot": "^2.4.1", + "@types/react": "18.0.33", "@types/react-beautiful-dnd": "^13.1.1", "@types/react-dom": "^16.9.5", "@types/react-tabs": "^2.3.2", diff --git a/shared-helpers/src/auth/AuthContext.ts b/shared-helpers/src/auth/AuthContext.ts index f3399c1078..551c8dda68 100644 --- a/shared-helpers/src/auth/AuthContext.ts +++ b/shared-helpers/src/auth/AuthContext.ts @@ -61,7 +61,7 @@ type ContextProps = { password: string, passwordConfirmation: string ) => Promise - signOut: () => void + signOut: () => Promise confirmAccount: (token: string) => Promise forgotPassword: (email: string, listingIdRedirect?: string) => Promise createUser: (user: UserCreate, listingIdRedirect?: string) => Promise diff --git a/shared-helpers/src/auth/RequireLogin.tsx b/shared-helpers/src/auth/RequireLogin.tsx index 046c4e1b36..89b97f0089 100644 --- a/shared-helpers/src/auth/RequireLogin.tsx +++ b/shared-helpers/src/auth/RequireLogin.tsx @@ -1,10 +1,8 @@ import React, { FunctionComponent, useContext, useEffect, useState } from "react" -import { - clearSiteAlertMessage, - setSiteAlertMessage, - NavigationContext, -} from "@bloom-housing/ui-components" +import { NavigationContext } from "@bloom-housing/ui-components" import { AuthContext } from "./AuthContext" +import { MessageContext } from "../utilities/MessageContext" + // See https://github.com/Microsoft/TypeScript/issues/14094 type Without = { [P in Exclude]?: never } type XOR = T | U extends Record @@ -33,6 +31,7 @@ const RequireLogin: FunctionComponent = ({ }) => { const { router } = useContext(NavigationContext) const { profile, initialStateLoaded } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const [hasTerms, setHasTerms] = useState(false) // Parse just the pathname portion of the signInPath (in case we want to pass URL params) @@ -58,10 +57,8 @@ const RequireLogin: FunctionComponent = ({ useEffect(() => { if (loginRequiredForPath && initialStateLoaded && !profile) { - setSiteAlertMessage(signInMessage, "notice") + addToast(signInMessage, { variant: "primary" }) void router.push(signInPath) - } else { - clearSiteAlertMessage("notice") } if (termsPath && profile && !profile?.agreedToTermsOfService && hasTerms) { @@ -76,6 +73,7 @@ const RequireLogin: FunctionComponent = ({ signInMessage, termsPath, hasTerms, + addToast, ]) if ( diff --git a/shared-helpers/src/auth/Timeout.tsx b/shared-helpers/src/auth/Timeout.tsx index 9570f5daae..8e50f67986 100644 --- a/shared-helpers/src/auth/Timeout.tsx +++ b/shared-helpers/src/auth/Timeout.tsx @@ -2,13 +2,8 @@ import React, { createElement, FunctionComponent, useContext, useEffect, useStat import { AuthContext } from "./AuthContext" import { ConfigContext } from "./ConfigContext" import { Button } from "@bloom-housing/ui-seeds" -import { - NavigationContext, - Modal, - setSiteAlertMessage, - AlertTypes, - t, -} from "@bloom-housing/ui-components" +import { NavigationContext, Modal, t } from "@bloom-housing/ui-components" +import { MessageContext } from "../utilities/MessageContext" const PROMPT_TIMEOUT = 60000 const events = ["mousemove", "keypress", "scroll"] @@ -44,7 +39,6 @@ type IdleTimeoutProps = { promptAction: string redirectPath: string alertMessage: string - alertType?: AlertTypes onTimeout: () => unknown } @@ -54,10 +48,10 @@ export const IdleTimeout: FunctionComponent = ({ promptText, redirectPath, alertMessage, - alertType = "alert", onTimeout, }) => { const { idleTimeout } = useContext(ConfigContext) + const { addToast } = useContext(MessageContext) const [promptTimeout, setPromptTimeout] = useState() const { router } = useContext(NavigationContext) @@ -73,8 +67,8 @@ export const IdleTimeout: FunctionComponent = ({ const timeoutAction = async () => { setPromptTimeout(undefined) await onTimeout() - setSiteAlertMessage(alertMessage, alertType) void router.push(redirectPath) + addToast(alertMessage, { variant: "primary", hideTimeout: PROMPT_TIMEOUT }) } void timeoutAction() }, PROMPT_TIMEOUT) as unknown as number @@ -124,7 +118,6 @@ export const LoggedInUserIdleTimeout = ({ onTimeout }: { onTimeout?: () => unkno promptAction: t("authentication.timeout.action"), redirectPath: `/sign-in`, alertMessage: t("authentication.timeout.signOutMessage"), - alertType: "notice", onTimeout: timeoutFxn, }) : null diff --git a/shared-helpers/src/utilities/MessageContext.ts b/shared-helpers/src/utilities/MessageContext.ts new file mode 100644 index 0000000000..9b6af48025 --- /dev/null +++ b/shared-helpers/src/utilities/MessageContext.ts @@ -0,0 +1,51 @@ +import React, { FunctionComponent, createContext, createElement, useState, useRef } from "react" +import { CommonMessageProps } from "@bloom-housing/ui-seeds/src/blocks/shared/CommonMessage" + +// TODO: this should be exportable from seeds directly +interface ToastProps extends Omit { + hideTimeout?: number +} + +const defaultContext = { + /* eslint-disable @typescript-eslint/no-empty-function */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + addToast: (message: string, props: ToastProps): void => {}, + /* eslint-disable @typescript-eslint/no-explicit-any */ + toastMessagesRef: {} as React.MutableRefObject[]>, +} + +export const MessageContext = createContext(defaultContext) + +export const MessageProvider: FunctionComponent = ({ children }) => { + const [_, setToastMessages] = useState[]>([]) + const toastMessagesRef = useRef[]>([]) + + // Toast timeouts default to 5 seconds, unless otherwise specified in props + const addToast = (message: string, props: ToastProps) => { + const newMsg = { message, props: { hideTimeout: 5000, ...props }, timestamp: Date.now() } + const msgs = [...toastMessagesRef.current, newMsg] + + // We need a stable ref so live toast messages can be read outside of this function + toastMessagesRef.current = msgs + setToastMessages(msgs) + + // Clean up messages after they're expired + setTimeout(() => { + toastMessagesRef.current = toastMessagesRef.current.filter( + (msg) => msg.timestamp != newMsg.timestamp + ) + }, newMsg.props.hideTimeout) + } + const contextValues = { + addToast, + toastMessagesRef, + } + return createElement( + MessageContext.Provider, + { + value: contextValues, + }, + + children + ) +} diff --git a/shared-helpers/src/views/forgot-password/FormForgotPassword.tsx b/shared-helpers/src/views/forgot-password/FormForgotPassword.tsx index c6ae0baa7e..e255455145 100644 --- a/shared-helpers/src/views/forgot-password/FormForgotPassword.tsx +++ b/shared-helpers/src/views/forgot-password/FormForgotPassword.tsx @@ -5,7 +5,6 @@ import { Form, t, AlertBox, - SiteAlert, AlertNotice, ErrorMessage, NavigationContext, @@ -70,8 +69,6 @@ const FormForgotPassword = ({ )} - -
)} - ) } diff --git a/sites/partners/__tests__/pages/listings/index.test.tsx b/sites/partners/__tests__/pages/listings/index.test.tsx index 28fcccf7e7..af93c8691c 100644 --- a/sites/partners/__tests__/pages/listings/index.test.tsx +++ b/sites/partners/__tests__/pages/listings/index.test.tsx @@ -1,4 +1,4 @@ -import { AuthProvider, ConfigProvider } from "@bloom-housing/shared-helpers" +import { AuthProvider, ConfigProvider, MessageProvider } from "@bloom-housing/shared-helpers" import { fireEvent, render } from "@testing-library/react" import { rest } from "msw" @@ -148,7 +148,9 @@ describe("listings", () => { const { findByText, getByText } = render( - + + + ) diff --git a/sites/partners/__tests__/pages/settings/index.test.tsx b/sites/partners/__tests__/pages/settings/index.test.tsx index d17eb411bf..4a871bbdaa 100644 --- a/sites/partners/__tests__/pages/settings/index.test.tsx +++ b/sites/partners/__tests__/pages/settings/index.test.tsx @@ -8,6 +8,8 @@ import { multiselectQuestionPreference, } from "@bloom-housing/shared-helpers/__tests__/testHelpers" import { mockNextRouter, render } from "../../testUtils" +import { MessageContext, MessageProvider } from "@bloom-housing/shared-helpers" +import { Toast } from "@bloom-housing/ui-seeds" const server = setupServer() @@ -125,7 +127,26 @@ describe("settings", () => { return res(ctx.json({})) }) ) - const { findByText, getByTestId, findByRole, queryAllByText } = render() + + const ToastProvider = (props) => { + const { toastMessagesRef } = React.useContext(MessageContext) + return ( + + {toastMessagesRef.current?.map((toastMessage) => ( + + {toastMessage.message} + + ))} + {props.children} + + ) + } + + const { findByText, getByTestId, findByRole, queryAllByText } = render( + + + + ) await findByText(multiselectQuestionPreference.text) diff --git a/sites/partners/__tests__/pages/users/index.test.tsx b/sites/partners/__tests__/pages/users/index.test.tsx index 391f70773c..fbff66b27a 100644 --- a/sites/partners/__tests__/pages/users/index.test.tsx +++ b/sites/partners/__tests__/pages/users/index.test.tsx @@ -1,4 +1,4 @@ -import { AuthProvider, ConfigProvider } from "@bloom-housing/shared-helpers" +import { AuthProvider, ConfigProvider, MessageProvider } from "@bloom-housing/shared-helpers" import { fireEvent, render } from "@testing-library/react" import { rest } from "msw" import { setupServer } from "msw/node" @@ -107,7 +107,9 @@ describe("users", () => { const { findByText, getByText } = render( - + + + ) diff --git a/sites/partners/cypress/e2e/default/06-admin-user-mangement.spec.ts b/sites/partners/cypress/e2e/default/06-admin-user-mangement.spec.ts index 10d7cf253e..e6b99da926 100644 --- a/sites/partners/cypress/e2e/default/06-admin-user-mangement.spec.ts +++ b/sites/partners/cypress/e2e/default/06-admin-user-mangement.spec.ts @@ -71,7 +71,7 @@ describe("Admin User Mangement Tests", () => { ) }) cy.getByID("invite-user").click() - cy.getByTestId("alert-box").contains("Invite sent").should("have.text", "Invite sent") + cy.getByTestId("toast-alert").contains("Invite sent").should("have.text", "Invite sent") }) it("as admin user, should be able to create new jurisidictional admin", () => { @@ -110,7 +110,7 @@ describe("Admin User Mangement Tests", () => { ) }) cy.getByID("invite-user").click() - cy.getByTestId("alert-box").contains("Invite sent").should("have.text", "Invite sent") + cy.getByTestId("toast-alert").contains("Invite sent").should("have.text", "Invite sent") }) it("as admin user, should be able to create new partner", () => { @@ -148,6 +148,6 @@ describe("Admin User Mangement Tests", () => { cy.getByTestId("listings_Bloomington").first().click() cy.getByTestId("listings_Bloomington").last().click() cy.getByID("invite-user").click() - cy.getByTestId("alert-box").contains("Invite sent").should("have.text", "Invite sent") + cy.getByTestId("toast-alert").contains("Invite sent").should("have.text", "Invite sent") }) }) diff --git a/sites/partners/cypress/e2e/default/07-jurisdictional-admin-user-management.spec.ts b/sites/partners/cypress/e2e/default/07-jurisdictional-admin-user-management.spec.ts index 865232740c..ece1f04bd0 100644 --- a/sites/partners/cypress/e2e/default/07-jurisdictional-admin-user-management.spec.ts +++ b/sites/partners/cypress/e2e/default/07-jurisdictional-admin-user-management.spec.ts @@ -50,7 +50,7 @@ describe("Jurisdictional Admin User Mangement Tests", () => { ) }) cy.getByID("invite-user").click() - cy.getByTestId("alert-box").contains("Invite sent").should("have.text", "Invite sent") + cy.getByTestId("toast-alert").contains("Invite sent").should("have.text", "Invite sent") }) it("as jurisdictional admin user, should be able to create new partner", () => { @@ -85,6 +85,6 @@ describe("Jurisdictional Admin User Mangement Tests", () => { cy.getByTestId("listings_Bloomington").first().click() cy.getByTestId("listings_Bloomington").last().click() cy.getByID("invite-user").click() - cy.getByTestId("alert-box").contains("Invite sent").should("have.text", "Invite sent") + cy.getByTestId("toast-alert").contains("Invite sent").should("have.text", "Invite sent") }) }) diff --git a/sites/partners/cypress/e2e/default/08-preference-management.spec.ts b/sites/partners/cypress/e2e/default/08-preference-management.spec.ts index 583e548f32..4a05d749d1 100644 --- a/sites/partners/cypress/e2e/default/08-preference-management.spec.ts +++ b/sites/partners/cypress/e2e/default/08-preference-management.spec.ts @@ -45,7 +45,7 @@ describe("Preference Management Tests", () => { cy.getByTestId("preference-opt-out-label").type("Preference Opt Out Label") cy.getByTestId("preference-jurisdiction").select("Bloomington") cy.getByID("preference-save-button").click() - cy.getByTestId("alert-box") + cy.getByTestId("toast-alert") .contains("Preference Created") .should("have.text", "Preference Created") diff --git a/sites/partners/package.json b/sites/partners/package.json index 5b4405b815..647544e6ef 100644 --- a/sites/partners/package.json +++ b/sites/partners/package.json @@ -31,7 +31,7 @@ "dependencies": { "@bloom-housing/shared-helpers": "^7.7.1", "@bloom-housing/ui-components": "12.1.0", - "@bloom-housing/ui-seeds": "1.12.1", + "@bloom-housing/ui-seeds": "1.12.2", "@mapbox/mapbox-sdk": "^0.13.0", "ag-grid-community": "^26.0.0", "ag-grid-react": "^26.0.0", diff --git a/sites/partners/src/components/applications/PaperApplicationForm/PaperApplicationForm.tsx b/sites/partners/src/components/applications/PaperApplicationForm/PaperApplicationForm.tsx index dc758281dd..9d4eeb9411 100644 --- a/sites/partners/src/components/applications/PaperApplicationForm/PaperApplicationForm.tsx +++ b/sites/partners/src/components/applications/PaperApplicationForm/PaperApplicationForm.tsx @@ -1,14 +1,8 @@ import React, { useState, useContext, useEffect } from "react" import { useRouter } from "next/router" -import { - t, - Form, - AlertBox, - setSiteAlertMessage, - LoadingOverlay, -} from "@bloom-housing/ui-components" +import { t, Form, AlertBox, LoadingOverlay } from "@bloom-housing/ui-components" import { Tag } from "@bloom-housing/ui-seeds" -import { AuthContext, listingSectionQuestions } from "@bloom-housing/shared-helpers" +import { AuthContext, MessageContext, listingSectionQuestions } from "@bloom-housing/shared-helpers" import { useForm, FormProvider } from "react-hook-form" import { Application, @@ -75,6 +69,7 @@ const ApplicationForm = ({ listingId, editMode, application }: ApplicationFormPr const router = useRouter() const { applicationsService } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const [alert, setAlert] = useState(null) const [loading, setLoading] = useState(false) @@ -147,11 +142,11 @@ const ApplicationForm = ({ listingId, editMode, application }: ApplicationFormPr setLoading(false) if (result) { - setSiteAlertMessage( + addToast( editMode ? t("application.add.applicationUpdated") : t("application.add.applicationSubmitted"), - "success" + { variant: "success" } ) if (redirect === "details") { diff --git a/sites/partners/src/components/listings/ListingFormActions.tsx b/sites/partners/src/components/listings/ListingFormActions.tsx index 840e1ea7ab..c9679e62ad 100644 --- a/sites/partners/src/components/listings/ListingFormActions.tsx +++ b/sites/partners/src/components/listings/ListingFormActions.tsx @@ -1,9 +1,9 @@ import React, { useContext, useMemo } from "react" import { useRouter } from "next/router" import dayjs from "dayjs" -import { t, StatusMessages, Icon, setSiteAlertMessage } from "@bloom-housing/ui-components" +import { t, StatusMessages, Icon } from "@bloom-housing/ui-components" import { Button, Link, Grid } from "@bloom-housing/ui-seeds" -import { pdfUrlFromListingEvents, AuthContext } from "@bloom-housing/shared-helpers" +import { pdfUrlFromListingEvents, AuthContext, MessageContext } from "@bloom-housing/shared-helpers" import { ListingContext } from "./ListingContext" import { StatusAside } from "../shared/StatusAside" import { @@ -38,6 +38,7 @@ const ListingFormActions = ({ }: ListingFormActionsProps) => { const listing = useContext(ListingContext) const { profile, listingsService } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const router = useRouter() // single jurisdiction check covers jurisAdmin adding a listing (listing is undefined then) @@ -255,15 +256,15 @@ const ListingFormActions = ({ }, }) if (result) { - setSiteAlertMessage(t("listings.approval.listingPublished"), "success") + addToast(t("listings.approval.listingPublished"), { variant: "success" }) await router.push(`/`) } } catch (err) { - setSiteAlertMessage( + addToast( err.response?.data?.message === "email failed" ? "errors.alert.listingsApprovalEmailError" : "errors.somethingWentWrong", - "warn" + { variant: "warn" } ) } } @@ -470,6 +471,7 @@ const ListingFormActions = ({ listingId, listingsService, router, + addToast, showCloseListingModal, showLotteryResultsDrawer, showRequestChangesModal, diff --git a/sites/partners/src/components/listings/PaperListingForm/index.tsx b/sites/partners/src/components/listings/PaperListingForm/index.tsx index 2640300b11..611960423d 100644 --- a/sites/partners/src/components/listings/PaperListingForm/index.tsx +++ b/sites/partners/src/components/listings/PaperListingForm/index.tsx @@ -5,7 +5,6 @@ import { t, Form, AlertBox, - setSiteAlertMessage, LoadingOverlay, Modal, Tabs, @@ -16,7 +15,7 @@ import { Icon, } from "@bloom-housing/ui-components" import { Button } from "@bloom-housing/ui-seeds" -import { AuthContext, listingSectionQuestions } from "@bloom-housing/shared-helpers" +import { AuthContext, MessageContext, listingSectionQuestions } from "@bloom-housing/shared-helpers" import { ListingCreate, ListingEventsTypeEnum, @@ -74,6 +73,7 @@ const ListingForm = ({ listing, editMode }: ListingFormProps) => { const router = useRouter() const { listingsService, profile } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const [tabIndex, setTabIndex] = useState(0) const [alert, setAlert] = useState(null) @@ -212,7 +212,7 @@ const ListingForm = ({ listing, editMode }: ListingFormProps) => { return t("listings.listingUpdated") } - setSiteAlertMessage(getToast(listing?.status, formattedData?.status), "success") + addToast(getToast(listing?.status, formattedData?.status), { variant: "success" }) await router.push(`/listings/${result.id}`) } @@ -248,7 +248,7 @@ const ListingForm = ({ listing, editMode }: ListingFormProps) => { }) setAlert("form") } else if (data?.message === "email failed") { - setSiteAlertMessage(t("errors.alert.listingsApprovalEmailError"), "warn") + addToast(t("errors.alert.listingsApprovalEmailError"), { variant: "warn" }) await router.push(`/listings/${formData.id}/`) } else setAlert("api") } @@ -270,6 +270,7 @@ const ListingForm = ({ listing, editMode }: ListingFormProps) => { reset, setError, profile, + addToast, ] ) diff --git a/sites/partners/src/components/settings/PreferenceDeleteModal.tsx b/sites/partners/src/components/settings/PreferenceDeleteModal.tsx index ce5b9f6033..b24a83524d 100644 --- a/sites/partners/src/components/settings/PreferenceDeleteModal.tsx +++ b/sites/partners/src/components/settings/PreferenceDeleteModal.tsx @@ -2,21 +2,20 @@ import React, { useContext, useMemo } from "react" import { AlertTypes, MinimalTable, Modal, t } from "@bloom-housing/ui-components" import { Button, Link } from "@bloom-housing/ui-seeds" import { useListingsMultiselectQuestionList } from "../../lib/hooks" -import { AuthContext } from "@bloom-housing/shared-helpers" +import { AuthContext, MessageContext } from "@bloom-housing/shared-helpers" import { MultiselectQuestion } from "@bloom-housing/shared-helpers/src/types/backend-swagger" type PreferenceDeleteModalProps = { onClose: () => void multiselectQuestion: MultiselectQuestion - setAlertMessage: ({ message, type }: { message: string; type: AlertTypes }) => void } export const PreferenceDeleteModal = ({ multiselectQuestion, onClose, - setAlertMessage, }: PreferenceDeleteModalProps) => { const { multiselectQuestionsService } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const { data, loading } = useListingsMultiselectQuestionList(multiselectQuestion.id) const listingsTableData = useMemo( @@ -39,11 +38,11 @@ export const PreferenceDeleteModal = ({ body: { id: multiselectQuestion.id }, }) .then(() => { - setAlertMessage({ message: t("settings.preferenceAlertDeleted"), type: "success" }) + addToast(t("settings.preferenceAlertDeleted"), { variant: "success" }) onClose() }) .catch((e) => { - setAlertMessage({ message: t("errors.alert.timeoutPleaseTryAgain"), type: "alert" }) + addToast(t("errors.alert.timeoutPleaseTryAgain"), { variant: "alert" }) console.log(e) }) } diff --git a/sites/partners/src/components/users/FormSignInAddPhone.tsx b/sites/partners/src/components/users/FormSignInAddPhone.tsx index 1f2aa00b6d..7c44a00611 100644 --- a/sites/partners/src/components/users/FormSignInAddPhone.tsx +++ b/sites/partners/src/components/users/FormSignInAddPhone.tsx @@ -4,7 +4,6 @@ import { FormCard, Icon, t, - SiteAlert, PhoneField, FormSignInErrorBox, NetworkStatus, @@ -52,7 +51,6 @@ const FormSignInAddPhone = ({ errorMessageId={"add-phone"} /> -
-
-
{ reset: resetMutation, } = useMutate() const { userService, loadProfile, loading, authService } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const token = router.query?.token as string const password = useRef({}) @@ -79,7 +79,7 @@ const FormUserConfirm = () => { if (response) { loadProfile("/") - setSiteAlertMessage(t(`users.accountConfirmed`), "success") + addToast(t(`users.accountConfirmed`), { variant: "success" }) } } catch (err) { setSubmitting(false) diff --git a/sites/partners/src/components/users/FormUserManage.tsx b/sites/partners/src/components/users/FormUserManage.tsx index 60b65b6978..7248d66d6e 100644 --- a/sites/partners/src/components/users/FormUserManage.tsx +++ b/sites/partners/src/components/users/FormUserManage.tsx @@ -2,7 +2,7 @@ import React, { useMemo, useContext, useState, useCallback } from "react" import { FormProvider, useForm } from "react-hook-form" import { t, Form, Field, Select, useMutate, emailRegex, Modal } from "@bloom-housing/ui-components" import { Button, Card, Grid, Tag } from "@bloom-housing/ui-seeds" -import { RoleOption, roleKeys, AuthContext } from "@bloom-housing/shared-helpers" +import { RoleOption, roleKeys, AuthContext, MessageContext } from "@bloom-housing/shared-helpers" import { Listing, User, UserRole } from "@bloom-housing/shared-helpers/src/types/backend-swagger" import { JurisdictionAndListingSelection } from "./JurisdictionAndListingSelection" import SectionWithGrid from "../shared/SectionWithGrid" @@ -12,12 +12,6 @@ type FormUserManageProps = { user?: User listings: Listing[] onDrawerClose: () => void - setAlertMessage: React.Dispatch< - React.SetStateAction<{ - type: string - message: string - }> - > } type FormUserManageValues = { @@ -39,14 +33,9 @@ const determineUserRole = (roles: UserRole) => { return RoleOption.Partner } -const FormUserManage = ({ - mode, - user, - listings, - onDrawerClose, - setAlertMessage, -}: FormUserManageProps) => { +const FormUserManage = ({ mode, user, listings, onDrawerClose }: FormUserManageProps) => { const { userService, profile } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const jurisdictionList = profile.jurisdictions const [isDeleteModalActive, setDeleteModalActive] = useState(false) @@ -214,13 +203,13 @@ const FormUserManage = ({ body: body, }) .then(() => { - setAlertMessage({ message: t(`users.inviteSent`), type: "success" }) + addToast(t(`users.inviteSent`), { variant: "success" }) }) .catch((e) => { if (e?.response?.status === 409) { - setAlertMessage({ message: t(`errors.alert.emailConflict`), type: "alert" }) + addToast(t(`errors.alert.emailConflict`), { variant: "alert" }) } else { - setAlertMessage({ message: t(`errors.alert.badRequest`), type: "alert" }) + addToast(t(`errors.alert.badRequest`), { variant: "alert" }) } }) .finally(() => { @@ -238,10 +227,10 @@ const FormUserManage = ({ userService .resendPartnerConfirmation({ body }) .then(() => { - setAlertMessage({ message: t(`users.confirmationSent`), type: "success" }) + addToast(t(`users.confirmationSent`), { variant: "success" }) }) .catch(() => { - setAlertMessage({ message: t(`errors.alert.badRequest`), type: "alert" }) + addToast(t(`errors.alert.badRequest`), { variant: "alert" }) }) .finally(() => { onDrawerClose() @@ -264,16 +253,16 @@ const FormUserManage = ({ body: body, }) .then(() => { - setAlertMessage({ message: t(`users.userUpdated`), type: "success" }) + addToast(t(`users.userUpdated`), { variant: "success" }) }) .catch(() => { - setAlertMessage({ message: t(`errors.alert.badRequest`), type: "alert" }) + addToast(t(`errors.alert.badRequest`), { variant: "alert" }) }) .finally(() => { onDrawerClose() }) ) - }, [createUserBody, onDrawerClose, updateUser, userService, user, setAlertMessage]) + }, [createUserBody, onDrawerClose, updateUser, userService, user, addToast]) const onDelete = () => { void deleteUser(() => @@ -284,10 +273,10 @@ const FormUserManage = ({ }, }) .then(() => { - setAlertMessage({ message: t(`users.userDeleted`), type: "success" }) + addToast(t(`users.userDeleted`), { variant: "success" }) }) .catch(() => { - setAlertMessage({ message: t(`errors.alert.badRequest`), type: "alert" }) + addToast(t(`errors.alert.badRequest`), { variant: "alert" }) }) .finally(() => { onDrawerClose() diff --git a/sites/partners/src/layouts/index.tsx b/sites/partners/src/layouts/index.tsx index 569a01c84a..3adb174579 100644 --- a/sites/partners/src/layouts/index.tsx +++ b/sites/partners/src/layouts/index.tsx @@ -8,12 +8,13 @@ import { FooterSection, t, MenuLink, - setSiteAlertMessage, } from "@bloom-housing/ui-components" -import { AuthContext, ExygyFooter } from "@bloom-housing/shared-helpers" +import { AuthContext, ExygyFooter, MessageContext } from "@bloom-housing/shared-helpers" +import { Toast } from "@bloom-housing/ui-seeds" const Layout = (props) => { const { profile, signOut } = useContext(AuthContext) + const { toastMessagesRef, addToast } = useContext(MessageContext) const router = useRouter() const currentYear = new Date().getFullYear() const menuLinks: MenuLink[] = [] @@ -42,19 +43,19 @@ const Layout = (props) => { menuLinks.push({ title: t("nav.signOut"), onClick: async () => { - setSiteAlertMessage(t(`authentication.signOut.success`), "notice") await router.push("/sign-in") - signOut() + await signOut() + addToast(t(`authentication.signOut.success`), { variant: "primary" }) }, }) } + return (
{t("nav.siteTitlePartners")} - { siteHeaderWidth={"wide"} homeURL={"/"} /> - -
{props.children}
- +
+ {toastMessagesRef.current?.map((toastMessage) => ( + + {toastMessage.message} + + ))} + {props.children} +
diff --git a/sites/partners/src/lib/hooks.ts b/sites/partners/src/lib/hooks.ts index e4e9ca2671..78b7530055 100644 --- a/sites/partners/src/lib/hooks.ts +++ b/sites/partners/src/lib/hooks.ts @@ -4,8 +4,8 @@ import qs from "qs" import dayjs from "dayjs" import utc from "dayjs/plugin/utc" import tz from "dayjs/plugin/timezone" -import { AuthContext } from "@bloom-housing/shared-helpers" -import { setSiteAlertMessage, t } from "@bloom-housing/ui-components" +import { AuthContext, MessageContext } from "@bloom-housing/shared-helpers" +import { t } from "@bloom-housing/ui-components" import { ApplicationOrderByKeys, EnumListingFilterParamsComparison, @@ -126,14 +126,11 @@ export function useListingsData({ export const useListingExport = () => { const { listingsService } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const [csvExportLoading, setCsvExportLoading] = useState(false) - const [csvExportError, setCsvExportError] = useState(false) - const [csvExportSuccess, setCsvExportSuccess] = useState(false) const onExport = useCallback(async () => { - setCsvExportError(false) - setCsvExportSuccess(false) setCsvExportLoading(true) try { @@ -151,11 +148,10 @@ export const useListingExport = () => { document.body.appendChild(link) link.click() link.parentNode.removeChild(link) - setCsvExportSuccess(true) - setSiteAlertMessage(t("t.exportSuccess"), "success") + addToast(t("t.exportSuccess"), { variant: "success" }) } catch (err) { console.log(err) - setCsvExportError(true) + addToast(t("account.settings.alerts.genericError"), { variant: "alert" }) } setCsvExportLoading(false) @@ -164,8 +160,6 @@ export const useListingExport = () => { return { onExport, csvExportLoading, - csvExportError, - csvExportSuccess, } } @@ -529,12 +523,9 @@ export const useUsersExport = () => { const useCsvExport = (endpoint: () => Promise, fileName: string) => { const [csvExportLoading, setCsvExportLoading] = useState(false) - const [csvExportError, setCsvExportError] = useState(false) - const [csvExportSuccess, setCsvExportSuccess] = useState(false) + const { addToast } = useContext(MessageContext) const onExport = useCallback(async () => { - setCsvExportError(false) - setCsvExportSuccess(false) setCsvExportLoading(true) try { @@ -544,21 +535,18 @@ const useCsvExport = (endpoint: () => Promise, fileName: string) => { fileLink.setAttribute("download", fileName) fileLink.href = URL.createObjectURL(blob) fileLink.click() - setCsvExportSuccess(true) - setSiteAlertMessage(t("t.exportSuccess"), "success") + addToast(t("t.exportSuccess"), { variant: "success" }) } catch (err) { console.log(err) - setCsvExportError(true) + addToast(t("account.settings.alerts.genericError"), { variant: "alert" }) } setCsvExportLoading(false) - }, [endpoint, fileName]) + }, [endpoint, fileName, addToast]) return { onExport, csvExportLoading, - csvExportError, - csvExportSuccess, } } diff --git a/sites/partners/src/pages/_app.tsx b/sites/partners/src/pages/_app.tsx index cb85cab6f7..43ffb98eaa 100644 --- a/sites/partners/src/pages/_app.tsx +++ b/sites/partners/src/pages/_app.tsx @@ -6,7 +6,12 @@ import "@bloom-housing/ui-components/src/global/css-imports.scss" import "@bloom-housing/ui-components/src/global/app-css.scss" import "@bloom-housing/ui-seeds/src/global/app-css.scss" import { addTranslation, NavigationContext, GenericRouter } from "@bloom-housing/ui-components" -import { AuthProvider, ConfigProvider, RequireLogin } from "@bloom-housing/shared-helpers" +import { + AuthProvider, + ConfigProvider, + MessageProvider, + RequireLogin, +} from "@bloom-housing/shared-helpers" // TODO: Make these not-global import "ag-grid-community/dist/styles/ag-grid.css" @@ -74,7 +79,7 @@ function BloomApp({ Component, router, pageProps }: AppProps) { signInMessage={signInMessage} skipForRoutes={skipLoginRoutes} > - {hasMounted && } + {hasMounted && } diff --git a/sites/partners/src/pages/application/[id]/index.tsx b/sites/partners/src/pages/application/[id]/index.tsx index aad10285fc..be2dcd5302 100644 --- a/sites/partners/src/pages/application/[id]/index.tsx +++ b/sites/partners/src/pages/application/[id]/index.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState, useContext } from "react" import { useRouter } from "next/router" import Head from "next/head" -import { t, AlertBox, SiteAlert, Breadcrumbs, BreadcrumbLink } from "@bloom-housing/ui-components" +import { t, AlertBox, Breadcrumbs, BreadcrumbLink } from "@bloom-housing/ui-components" import { Tag } from "@bloom-housing/ui-seeds" import { useSingleApplicationData, useSingleListingData } from "../../../lib/hooks" import { AuthContext } from "@bloom-housing/shared-helpers" @@ -81,7 +81,6 @@ export default function ApplicationsList() { {t("nav.siteTitlePartners")} - { const router = useRouter() const { forgotPassword } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) /* Form Handler */ // This is causing a linting issue with unbound-method, see open issue as of 10/21/2020: @@ -28,7 +30,7 @@ const ForgotPassword = () => { const { status } = error.response || {} determineNetworkError(status, error) } - setSiteAlertMessage(t(`authentication.forgotPassword.message`), "notice") + addToast(t(`authentication.forgotPassword.message`), { variant: "primary" }) await router.push("/sign-in") } diff --git a/sites/partners/src/pages/index.tsx b/sites/partners/src/pages/index.tsx index ad031a4be9..896304423b 100644 --- a/sites/partners/src/pages/index.tsx +++ b/sites/partners/src/pages/index.tsx @@ -1,15 +1,7 @@ -import React, { useMemo, useContext, useState, useEffect } from "react" +import React, { useMemo, useContext } from "react" import Head from "next/head" import { Button } from "@bloom-housing/ui-seeds" -import { - t, - AgTable, - useAgTable, - AlertBox, - SiteAlert, - Icon, - UniversalIconType, -} from "@bloom-housing/ui-components" +import { t, AgTable, useAgTable, Icon, UniversalIconType } from "@bloom-housing/ui-components" import { AuthContext } from "@bloom-housing/shared-helpers" import dayjs from "dayjs" import { ColDef, ColGroupDef } from "ag-grid-community" @@ -67,13 +59,9 @@ class ListingsLink extends formatLinkCell { export default function ListingsList() { const metaDescription = t("pageDescription.welcome", { regionName: t("region.name") }) - const [errorAlert, setErrorAlert] = useState(false) const { profile } = useContext(AuthContext) const isAdmin = profile?.userRoles?.isAdmin || profile?.userRoles?.isJurisdictionalAdmin || false - const { onExport, csvExportLoading, csvExportError, csvExportSuccess } = useListingExport() - useEffect(() => { - setErrorAlert(csvExportError) - }, [csvExportError]) + const { onExport, csvExportLoading } = useListingExport() const tableOptions = useAgTable() @@ -152,28 +140,10 @@ export default function ListingsList() { {t("nav.siteTitlePartners")} - - - {csvExportSuccess && ( -
- -
- )} -
+
- {errorAlert && ( - setErrorAlert(false)} - closeable - type="alert" - inverted - > - {t("account.settings.alerts.genericError")} - - )} { {t("nav.siteTitlePartners")} - { ) const includeDemographicsPartner = profile?.userRoles?.isPartner && listingJurisdiction?.enablePartnerDemographics - const { onExport, csvExportLoading, csvExportError, csvExportSuccess } = useApplicationsExport( + const { onExport, csvExportLoading } = useApplicationsExport( listingId, (profile?.userRoles?.isAdmin || profile?.userRoles?.isJurisdictionalAdmin || @@ -116,14 +109,6 @@ const ApplicationsList = () => { {t("nav.siteTitlePartners")} - {csvExportSuccess && } - {csvExportError && ( - - )} {t("nav.siteTitlePartners")} - - { image={metaImage} description={metaDescription} /> - { const router = useRouter() const { token } = router.query const { resetPassword } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) /* Form Handler */ // This is causing a linting issue with unbound-method, see open issue as of 10/21/2020: // https://github.com/react-hook-form/react-hook-form/issues/2887 @@ -34,9 +26,9 @@ const ResetPassword = () => { try { const user = await resetPassword(token.toString(), password, passwordConfirmation) - setSiteAlertMessage(t(`authentication.signIn.success`, { name: user.firstName }), "success") await router.push("/") window.scrollTo(0, 0) + addToast(t(`authentication.signIn.success`, { name: user.firstName }), { variant: "success" }) } catch (err) { const { status, data } = err.response || {} if (status === 400) { @@ -60,7 +52,6 @@ const ResetPassword = () => { {requestError} )} -
{ const { mutate } = useSWRConfig() const { profile, multiselectQuestionsService } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const { mutate: updateQuestion, isLoading: isUpdateLoading } = useMutate() const { mutate: createQuestion, isLoading: isCreateLoading } = useMutate() @@ -42,11 +41,6 @@ const Settings = () => { const [copyModalOpen, setCopyModalOpen] = useState(null) const [questionData, setQuestionData] = useState(null) const [updatedIds, setUpdatedIds] = useState([]) - - const [alertMessage, setAlertMessage] = useState({ - type: "alert" as AlertTypes, - message: undefined, - }) const [deleteConfirmModalOpen, setDeleteConfirmModalOpen] = useState( null ) @@ -121,10 +115,10 @@ const Settings = () => { ? updatedIds : [...updatedIds, result.id] ) - setAlertMessage({ message: t(`settings.preferenceAlertUpdated`), type: "success" }) + addToast(t(`settings.preferenceAlertUpdated`), { variant: "success" }) }) .catch((e) => { - setAlertMessage({ message: t(`errors.alert.badRequest`), type: "alert" }) + addToast(t(`errors.alert.badRequest`), { variant: "alert" }) console.log(e) }) .finally(() => { @@ -144,10 +138,10 @@ const Settings = () => { ? updatedIds : [...updatedIds, result.id] ) - setAlertMessage({ message: t(`settings.preferenceAlertCreated`), type: "success" }) + addToast(t(`settings.preferenceAlertCreated`), { variant: "success" }) }) .catch((e) => { - setAlertMessage({ message: t(`errors.alert.badRequest`), type: "alert" }) + addToast(t(`errors.alert.badRequest`), { variant: "alert" }) console.log(e) }) .finally(() => { @@ -186,7 +180,6 @@ const Settings = () => { {t("nav.siteTitlePartners")} -
@@ -263,7 +256,6 @@ const Settings = () => { {deleteConfirmModalOpen && ( { setDeleteConfirmModalOpen(null) void mutate(cacheKey) diff --git a/sites/partners/src/pages/users/confirm.tsx b/sites/partners/src/pages/users/confirm.tsx index 09364ab4ac..16d0946520 100644 --- a/sites/partners/src/pages/users/confirm.tsx +++ b/sites/partners/src/pages/users/confirm.tsx @@ -1,7 +1,7 @@ import React from "react" import Head from "next/head" import Layout from "../../layouts" -import { t, SiteAlert } from "@bloom-housing/ui-components" +import { t } from "@bloom-housing/ui-components" import { FormUserConfirm } from "../../components/users/FormUserConfirm" const ConfirmPage = () => { @@ -12,11 +12,6 @@ const ConfirmPage = () => {
-
- - -
-
diff --git a/sites/partners/src/pages/users/index.tsx b/sites/partners/src/pages/users/index.tsx index ab68e11355..7a5c15bb09 100644 --- a/sites/partners/src/pages/users/index.tsx +++ b/sites/partners/src/pages/users/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useState } from "react" +import React, { useContext, useMemo, useState } from "react" import Head from "next/head" import dayjs from "dayjs" import { useSWRConfig } from "swr" @@ -7,8 +7,6 @@ import { useAgTable, t, Drawer, - SiteAlert, - AlertTypes, AlertBox, Icon, UniversalIconType, @@ -31,18 +29,11 @@ const Users = () => { const { profile } = useContext(AuthContext) const { mutate } = useSWRConfig() const [userDrawer, setUserDrawer] = useState(null) - const [alertMessage, setAlertMessage] = useState({ - type: "alert" as AlertTypes, - message: undefined, - }) const [errorAlert, setErrorAlert] = useState(false) const tableOptions = useAgTable() - const { onExport, csvExportLoading, csvExportError, csvExportSuccess } = useUsersExport() - useEffect(() => { - setErrorAlert(csvExportError) - }, [csvExportError]) + const { onExport, csvExportLoading } = useUsersExport() const columns = useMemo(() => { return [ @@ -138,8 +129,6 @@ const Users = () => { {t("nav.siteTitlePartners")} - - {csvExportSuccess && }
@@ -222,7 +211,6 @@ const Users = () => { setUserDrawer(null) void mutate(cacheKey) }} - setAlertMessage={setAlertMessage} /> diff --git a/sites/partners/styles/overrides.scss b/sites/partners/styles/overrides.scss index 81a4e97b65..c445607b6f 100644 --- a/sites/partners/styles/overrides.scss +++ b/sites/partners/styles/overrides.scss @@ -14,6 +14,10 @@ text-align: center; } + #seeds-toast-stack { + --toast-stack-inset-top: var(--seeds-s28); + } + .seeds-button { -webkit-font-smoothing: antialiased; // restore macOS styling that had been unset } diff --git a/sites/public/package.json b/sites/public/package.json index f6aa38b0fb..ef51cf1c5b 100644 --- a/sites/public/package.json +++ b/sites/public/package.json @@ -31,7 +31,7 @@ "dependencies": { "@bloom-housing/shared-helpers": "^7.7.1", "@bloom-housing/ui-components": "12.1.0", - "@bloom-housing/ui-seeds": "1.12.1", + "@bloom-housing/ui-seeds": "1.12.2", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", diff --git a/sites/public/src/components/account/ConfirmationModal.tsx b/sites/public/src/components/account/ConfirmationModal.tsx index 097194e393..4471b74944 100644 --- a/sites/public/src/components/account/ConfirmationModal.tsx +++ b/sites/public/src/components/account/ConfirmationModal.tsx @@ -1,20 +1,18 @@ -import { Modal, t, Form, Field, AlertBox } from "@bloom-housing/ui-components" +import { Modal, t, Form, Field } from "@bloom-housing/ui-components" import { Button } from "@bloom-housing/ui-seeds" -import { AuthContext } from "@bloom-housing/shared-helpers" +import { AuthContext, MessageContext } from "@bloom-housing/shared-helpers" import { useRouter } from "next/router" import { useContext, useEffect, useRef, useState } from "react" import { useForm } from "react-hook-form" import { emailRegex } from "../../lib/helpers" -export interface ConfirmationModalProps { - setSiteAlertMessage: (message: string, alertType: string) => void -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ConfirmationModalProps {} const ConfirmationModal = (props: ConfirmationModalProps) => { - const { setSiteAlertMessage } = props const { resendConfirmation, profile, confirmAccount } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const [openModal, setOpenModal] = useState(false) - const [modalMessage, setModalMessage] = useState(null) const router = useRouter() /* Form Handler */ @@ -30,11 +28,11 @@ const ConfirmationModal = (props: ConfirmationModalProps) => { const listingId = router.query?.listingId as string await resendConfirmation(email, listingId) - setSiteAlertMessage(t(`authentication.createAccount.emailSent`), "success") + addToast(t(`authentication.createAccount.emailSent`), { variant: "success" }) setOpenModal(false) } catch (err) { const { data } = err.response || {} - setModalMessage(t(`authentication.createAccount.errors.${data.message}`)) + addToast(t(`authentication.createAccount.errors.${data.message}`), { variant: "alert" }) } window.scrollTo(0, 0) } @@ -68,7 +66,7 @@ const ConfirmationModal = (props: ConfirmationModalProps) => { response: { data }, } = error if (data.statusCode === 406) { - setSiteAlertMessage(t(`authentication.createAccount.errors.${data.message}`), "alert") + addToast(t(`authentication.createAccount.errors.${data.message}`), { variant: "alert" }) } else { setOpenModal(true) } @@ -105,11 +103,6 @@ const ConfirmationModal = (props: ConfirmationModalProps) => { ]} > <> - {modalMessage && ( - setModalMessage(null)} type="alert"> - {modalMessage} - - )} { promptAction: t("application.timeout.action"), redirectPath: "/", alertMessage: t("application.timeout.afterMessage"), - alertType: "alert", onTimeout, }) } diff --git a/sites/public/src/components/listing/ListingView.tsx b/sites/public/src/components/listing/ListingView.tsx index 1e92097aa8..c6282baeb2 100644 --- a/sites/public/src/components/listing/ListingView.tsx +++ b/sites/public/src/components/listing/ListingView.tsx @@ -27,7 +27,6 @@ import { EventType, StandardTableData, ExpandableSection, - SiteAlert, } from "@bloom-housing/ui-components" import { cloudinaryPdfFromId, @@ -525,7 +524,6 @@ export const ListingView = (props: ListingProps) => { return (
- { diff --git a/sites/public/src/layouts/application.tsx b/sites/public/src/layouts/application.tsx index e1355fea13..b3c918734f 100644 --- a/sites/public/src/layouts/application.tsx +++ b/sites/public/src/layouts/application.tsx @@ -3,7 +3,7 @@ import dayjs from "dayjs" import { useRouter } from "next/router" import Link from "next/link" import Head from "next/head" -import { Message } from "@bloom-housing/ui-seeds" +import { Message, Toast } from "@bloom-housing/ui-seeds" import { SiteHeader, SiteFooter, @@ -11,13 +11,13 @@ import { FooterSection, MenuLink, t, - setSiteAlertMessage, } from "@bloom-housing/ui-components" -import { AuthContext, ExygyFooter } from "@bloom-housing/shared-helpers" +import { AuthContext, ExygyFooter, MessageContext } from "@bloom-housing/shared-helpers" import styles from "./application.module.scss" const Layout = (props) => { const { profile, signOut } = useContext(AuthContext) + const { toastMessagesRef, addToast } = useContext(MessageContext) const router = useRouter() const languages = @@ -58,9 +58,9 @@ const Layout = (props) => { title: t("nav.signOut"), onClick: () => { const signOutFxn = async () => { - setSiteAlertMessage(t(`authentication.signOut.success`), "notice") await router.push("/sign-in") - signOut() + await signOut() + addToast(t(`authentication.signOut.success`), { variant: "primary" }) } void signOutFxn() }, @@ -123,6 +123,11 @@ const Layout = (props) => { strings={{ skipToMainContent: t("t.skipToMainContent") }} />
+ {toastMessagesRef.current.map((toastMessage) => ( + + {toastMessage.message} + + ))} {props.children}
diff --git a/sites/public/src/pages/_app.tsx b/sites/public/src/pages/_app.tsx index c293c8ce9e..4de46a847d 100644 --- a/sites/public/src/pages/_app.tsx +++ b/sites/public/src/pages/_app.tsx @@ -14,6 +14,7 @@ import { LoggedInUserIdleTimeout, ConfigProvider, AuthProvider, + MessageProvider, } from "@bloom-housing/shared-helpers" import { headScript, bodyTopTag, pageChangeHandler } from "../lib/customScripts" import { AppSubmissionContext } from "../lib/applications/AppSubmissionContext" @@ -101,8 +102,10 @@ function BloomApp({ Component, router, pageProps }: AppProps) { > - conductor.reset()} /> - + + conductor.reset()} /> + + diff --git a/sites/public/src/pages/account/dashboard.tsx b/sites/public/src/pages/account/dashboard.tsx index a1b1273f79..a6489fa9ad 100644 --- a/sites/public/src/pages/account/dashboard.tsx +++ b/sites/public/src/pages/account/dashboard.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState, useContext } from "react" import Head from "next/head" import { NextRouter, withRouter } from "next/router" -import { t, SiteAlert, AlertBox } from "@bloom-housing/ui-components" +import { t, AlertBox } from "@bloom-housing/ui-components" import { PageView, pushGtmEvent, @@ -59,7 +59,6 @@ function Dashboard(props: DashboardProps) { )}
-

{t("nav.myDashboard")}

diff --git a/sites/public/src/pages/account/edit.tsx b/sites/public/src/pages/account/edit.tsx index a247b206cd..2b8cacbde8 100644 --- a/sites/public/src/pages/account/edit.tsx +++ b/sites/public/src/pages/account/edit.tsx @@ -11,7 +11,6 @@ import { emailRegex, t, AlertBox, - SiteAlert, AlertTypes, passwordRegex, DOBField, @@ -162,8 +161,6 @@ const Edit = () => { headingPriority={1} > <> - - {nameAlert && ( { const router = useRouter() const { profile, applicationsService } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const [validationError, setValidationError] = useState(false) const { conductor, application, listing } = useFormConductor("summary") let currentPageSection = 4 @@ -50,11 +52,11 @@ const ApplicationSummary = () => { useEffect(() => { if (listing && router.isReady) { if (listing?.status !== ListingsStatusEnum.active) { - setSiteAlertMessage(t("listings.applicationsClosedRedirect"), "alert") + addToast(t("listings.applicationsClosedRedirect"), { variant: "alert" }) void router.push(`/${router.locale}/listing/${listing?.id}/${listing.urlSlug}`) } } - }, [listing, router]) + }, [listing, router, addToast]) useEffect(() => { conductor.application.reachedReviewStep = true diff --git a/sites/public/src/pages/applications/review/terms.tsx b/sites/public/src/pages/applications/review/terms.tsx index 030ef92e17..9f4d2e8c7f 100644 --- a/sites/public/src/pages/applications/review/terms.tsx +++ b/sites/public/src/pages/applications/review/terms.tsx @@ -109,7 +109,7 @@ const ApplicationTerms = () => { default: return { text: "" } } - }, [listing, router.locale]) + }, [listing]) useEffect(() => { pushGtmEvent({ diff --git a/sites/public/src/pages/applications/start/choose-language.tsx b/sites/public/src/pages/applications/start/choose-language.tsx index a91aa8e3e8..47ed61296c 100644 --- a/sites/public/src/pages/applications/start/choose-language.tsx +++ b/sites/public/src/pages/applications/start/choose-language.tsx @@ -1,13 +1,14 @@ import React, { useCallback, useContext, useEffect, useState } from "react" import axios from "axios" import { useRouter } from "next/router" -import { ImageCard, t, setSiteAlertMessage } from "@bloom-housing/ui-components" +import { ImageCard, t } from "@bloom-housing/ui-components" import { imageUrlFromListing, OnClientSide, PageView, pushGtmEvent, AuthContext, + MessageContext, } from "@bloom-housing/shared-helpers" import { LanguagesEnum, @@ -42,6 +43,7 @@ const ApplicationChooseLanguage = () => { const [listing, setListing] = useState(null) const context = useContext(AppSubmissionContext) const { initialStateLoaded, profile } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const { conductor } = context const listingId = router.query.listingId @@ -73,11 +75,11 @@ const ApplicationChooseLanguage = () => { useEffect(() => { if (listing && router.isReady) { if (listing?.status !== ListingsStatusEnum.active && router.query.preview !== "true") { - setSiteAlertMessage(t("listings.applicationsClosedRedirect"), "alert") + addToast(t("listings.applicationsClosedRedirect"), { variant: "alert" }) void router.push(`/${router.locale}/listing/${listing?.id}/${listing?.urlSlug}`) } } - }, [listing, router]) + }, [listing, router, addToast]) const imageUrl = listing?.assets ? imageUrlFromListing(listing, parseInt(process.env.listingPhotoSize))[0] diff --git a/sites/public/src/pages/create-account.tsx b/sites/public/src/pages/create-account.tsx index 7f8335c802..f57d891be6 100644 --- a/sites/public/src/pages/create-account.tsx +++ b/sites/public/src/pages/create-account.tsx @@ -7,7 +7,6 @@ import { t, DOBField, AlertBox, - SiteAlert, Modal, passwordRegex, } from "@bloom-housing/ui-components" @@ -110,7 +109,6 @@ export default () => { {requestError} )} - { const router = useRouter() const { forgotPassword } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) /* Form Handler */ // This is causing a linting issue with unbound-method, see open issue as of 10/21/2020: @@ -41,7 +43,7 @@ const ForgotPassword = () => { const { status } = error.response || {} determineNetworkError(status, error) } - setSiteAlertMessage(t(`authentication.forgotPassword.message`), "notice") + addToast(t(`authentication.forgotPassword.message`), { variant: "primary" }) await router.push("/sign-in") } diff --git a/sites/public/src/pages/index.tsx b/sites/public/src/pages/index.tsx index ae77ce0c36..a0f059c143 100644 --- a/sites/public/src/pages/index.tsx +++ b/sites/public/src/pages/index.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useState } from "react" import Head from "next/head" -import { AlertBox, t, SiteAlert, ActionBlock, Icon } from "@bloom-housing/ui-components" +import { t, ActionBlock, Icon } from "@bloom-housing/ui-components" import { Button, Heading } from "@bloom-housing/ui-seeds" import { PageView, pushGtmEvent, AuthContext } from "@bloom-housing/shared-helpers" import { UserStatus } from "../lib/constants" @@ -21,7 +21,6 @@ export default function Home(props: IndexProps) { alertType: null, } const { profile } = useContext(AuthContext) - const [alertInfo, setAlertInfo] = useState(blankAlertInfo) useEffect(() => { pushGtmEvent({ @@ -39,26 +38,13 @@ export default function Home(props: IndexProps) { const metaDescription = t("pageDescription.welcome", { regionName: t("region.name") }) const metaImage = "" // TODO: replace with hero image - const alertClasses = "flex-grow mt-6 max-w-6xl w-full" + return ( {t("nav.siteTitle")} -
- - -
- {alertInfo.alertMessage && ( - setAlertInfo(blankAlertInfo)} - type={alertInfo.alertType} - > - {alertInfo.alertMessage} - - )} {heroTitle} @@ -115,9 +101,7 @@ export default function Home(props: IndexProps) { />
- setAlertInfo({ alertMessage, alertType })} - /> + ) } diff --git a/sites/public/src/pages/reset-password.tsx b/sites/public/src/pages/reset-password.tsx index fe1c8a95f7..9a01258293 100644 --- a/sites/public/src/pages/reset-password.tsx +++ b/sites/public/src/pages/reset-password.tsx @@ -1,17 +1,16 @@ import React, { useEffect, useState, useContext, useRef } from "react" import { useRouter } from "next/router" import { useForm } from "react-hook-form" -import { - Field, - Form, - t, - AlertBox, - SiteAlert, - setSiteAlertMessage, -} from "@bloom-housing/ui-components" +import { Field, Form, t, AlertBox } from "@bloom-housing/ui-components" import { Button } from "@bloom-housing/ui-seeds" import { CardSection } from "@bloom-housing/ui-seeds/src/blocks/Card" -import { PageView, pushGtmEvent, AuthContext, BloomCard } from "@bloom-housing/shared-helpers" +import { + PageView, + pushGtmEvent, + AuthContext, + BloomCard, + MessageContext, +} from "@bloom-housing/shared-helpers" import { UserStatus } from "../lib/constants" import FormsLayout from "../layouts/forms" @@ -19,6 +18,7 @@ const ResetPassword = () => { const router = useRouter() const { token } = router.query const { resetPassword } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) /* Form Handler */ // This is causing a linting issue with unbound-method, see open issue as of 10/21/2020: // https://github.com/react-hook-form/react-hook-form/issues/2887 @@ -42,7 +42,7 @@ const ResetPassword = () => { try { const user = await resetPassword(token.toString(), password, passwordConfirmation) - setSiteAlertMessage(t(`authentication.signIn.success`, { name: user.firstName }), "success") + addToast(t(`authentication.signIn.success`, { name: user.firstName }), { variant: "success" }) const redirectUrl = router.query?.redirectUrl as string const listingId = router.query?.listingId as string @@ -73,7 +73,6 @@ const ResetPassword = () => { {requestError} )} - { + const { addToast } = useContext(MessageContext) const router = useRouter() const { login, requestSingleUseCode, userService } = useContext(AuthContext) const signUpCopy = process.env.showMandatedAccounts + /* Form Handler */ // This is causing a linting issue with unbound-method, see open issue as of 10/21/2020: // https://github.com/react-hook-form/react-hook-form/issues/2887 @@ -68,8 +71,8 @@ const SignIn = () => { try { const user = await login(email, password) - setSiteAlertMessage(t(`authentication.signIn.success`, { name: user.firstName }), "success") await redirectToPage() + addToast(t(`authentication.signIn.success`, { name: user.firstName }), { variant: "success" }) } catch (error) { const { status } = error.response || {} determineNetworkError(status, error) @@ -95,7 +98,9 @@ const SignIn = () => { }) } else { const user = await login(email, password) - setSiteAlertMessage(t(`authentication.signIn.success`, { name: user.firstName }), "success") + addToast(t(`authentication.signIn.success`, { name: user.firstName }), { + variant: "success", + }) await redirectToPage() } } catch (error) { diff --git a/sites/public/src/pages/verify.tsx b/sites/public/src/pages/verify.tsx index 5e4592ff91..f65376b22f 100644 --- a/sites/public/src/pages/verify.tsx +++ b/sites/public/src/pages/verify.tsx @@ -8,13 +8,14 @@ import { DialogFooter, DialogHeader, } from "@bloom-housing/ui-seeds/src/overlays/Dialog" -import { Field, Form, t, SiteAlert, setSiteAlertMessage } from "@bloom-housing/ui-components" +import { Field, Form, t } from "@bloom-housing/ui-components" import { PageView, pushGtmEvent, useCatchNetworkError, BloomCard, AuthContext, + MessageContext, FormSignInErrorBox, } from "@bloom-housing/shared-helpers" import { UserStatus } from "../lib/constants" @@ -29,6 +30,7 @@ const Verify = () => { const { register, handleSubmit, errors, reset } = useForm() const { networkError, determineNetworkError, resetNetworkError } = useCatchNetworkError() const { requestSingleUseCode, loginViaSingleUseCode } = useContext(AuthContext) + const { addToast } = useContext(MessageContext) const redirectToPage = useRedirectToPrevPage("/account/dashboard") type FlowType = "create" | "login" @@ -60,9 +62,11 @@ const Verify = () => { const user = await loginViaSingleUseCode(email, code) setIsLoginLoading(false) if (flowType === "login") { - setSiteAlertMessage(t(`authentication.signIn.success`, { name: user.firstName }), "success") + addToast(t(`authentication.signIn.success`, { name: user.firstName }), { + variant: "success", + }) } else { - setSiteAlertMessage(t("authentication.createAccount.accountConfirmed"), "success") + addToast(t("authentication.createAccount.accountConfirmed"), { variant: "success" }) } await redirectToPage() } catch (error) { @@ -76,7 +80,6 @@ const Verify = () => { <> -