diff --git a/.storybook/preview.js b/.storybook/preview.js index e57ea9101b..030b18b526 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -7,20 +7,11 @@ import { } from "../src/core/token"; import "../src/core/mount"; import Store from "../src/components/store"; -import { store } from "../src/core/store"; import React from "react"; -import { updateStatus } from "../src/core/user.slice"; import { withErrorBoundary } from "react-error-boundary"; import ErrorBoundaryAlert from "../src/components/error-boundary-alert/ErrorBoundaryAlert"; -if (process.env.NODE_ENV === "test") { - store.dispatch( - updateStatus({ - hasToken: true - }) - ); -} const getSessionStorage = (type) => window.sessionStorage.getItem(type); const userToken = diff --git a/src/apps/adgangsplatformen/auth.dev.jsx b/src/apps/adgangsplatformen/auth.dev.jsx deleted file mode 100644 index b494b3c604..0000000000 --- a/src/apps/adgangsplatformen/auth.dev.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import Auth from "./auth"; - -export default { - title: "SB Utilities / Adgangsplatformen" -}; - -const Template = (args) => ; - -export const SignIn = Template.bind({}); diff --git a/src/apps/adgangsplatformen/auth.jsx b/src/apps/adgangsplatformen/auth.jsx deleted file mode 100644 index 459eaf261d..0000000000 --- a/src/apps/adgangsplatformen/auth.jsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, { useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import fetch from "unfetch"; -import { - getToken, - setToken, - TOKEN_LIBRARY_KEY, - TOKEN_USER_KEY -} from "../../core/token"; -import { - setStatusAuthenticated, - setStatusUnauthenticated -} from "../../core/user.slice"; - -const ORIGIN = window.location.origin; -const PATHNAME = window.location.pathname.replace("/iframe.html", "/"); - -const CLIENT_ID = process.env.STORYBOOK_CLIENT_ID; -const REDIRECT_URL = `${ORIGIN}${PATHNAME}?path=/story/sb-utilities-adgangsplatformen--sign-in`; - -function Auth() { - const dispatch = useDispatch(); - const status = useSelector((s) => s.user.status); - - const handleCleanUp = useCallback(() => { - window.sessionStorage.removeItem(TOKEN_USER_KEY); - dispatch(setStatusUnauthenticated()); - }, [dispatch]); - - const handleSignIn = () => { - window.parent.location.href = `https://login.bib.dk/oauth/authorize?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URL}`; - }; - - const handleSignOut = () => { - handleCleanUp(); - const token = getToken(TOKEN_USER_KEY); - window.parent.location.href = `https://login.bib.dk/logout/?access_token=${token}`; - }; - - React.useEffect(() => { - const urlParams = new URLSearchParams(window.location.search); - const code = urlParams.get("code"); - - if (!code) { - return; - } - - fetch("https://login.bib.dk/oauth/token", { - method: "POST", - headers: {}, - body: new URLSearchParams({ - grant_type: "authorization_code", - code, - client_id: CLIENT_ID, - client_secret: "secret", - redirect_uri: REDIRECT_URL - }) - }) - .then((res) => res.json()) - .then((res) => { - // eslint-disable-next-line camelcase - if (!res?.access_token) { - throw res; - } - - // We need to make the token available in two contexts: - // 1. Subsequent browser reloads. Consequently we set the token into sessionstorage, which are read by preview.js. - window.sessionStorage.setItem(TOKEN_USER_KEY, res.access_token); - // 2. Current storybook context. - setToken(TOKEN_USER_KEY, res.access_token); - setToken(TOKEN_LIBRARY_KEY, res.access_token); - - dispatch(setStatusAuthenticated()); - }) - .catch((err) => { - // eslint-disable-next-line no-console - console.error(err); - handleCleanUp(); - }); - }, [dispatch, handleCleanUp]); - - return ( -
-

Adgangsplatformen

-
- Status: - {status === "authenticated" ? ( - Signed in - ) : ( - Signed out - )} -
- -
- - - -
-
- ); -} - -export default Auth; diff --git a/src/apps/adgangsplatformen/auth.test.js b/src/apps/adgangsplatformen/auth.test.js deleted file mode 100644 index d95c94d620..0000000000 --- a/src/apps/adgangsplatformen/auth.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { TOKEN_USER_KEY } from "../../core/token"; - -describe("Authentication", () => { - beforeEach(() => { - cy.window().then((win) => { - win.sessionStorage.removeItem(TOKEN_USER_KEY); - }); - }); - it("Loads story without auth code", () => { - cy.server(); - cy.visit( - "/iframe.html?path=/story/sb-utilities-adgangsplatformen--sign-in" - ); - cy.window() - .its("sessionStorage") - .invoke("getItem", TOKEN_USER_KEY) - .should("not.exist"); - }); - it("Loads story with auth code", () => { - cy.server(); - cy.route({ - method: "POST", - url: "https://login.bib.dk/oauth/token", - status: 200, - response: { - statusCode: 200, - access_token: "random_token" - } - }); - cy.visit( - "/iframe.html?path=/story/sb-utilities-adgangsplatformen--sign-in&code=test" - ); - cy.window() - .its("sessionStorage") - .invoke("getItem", TOKEN_USER_KEY) - .should("eq", "random_token"); - }); -}); diff --git a/src/apps/dashboard/dashboard.dev.tsx b/src/apps/dashboard/dashboard.dev.tsx index 65833b034d..4ab1a27557 100644 --- a/src/apps/dashboard/dashboard.dev.tsx +++ b/src/apps/dashboard/dashboard.dev.tsx @@ -12,10 +12,12 @@ import deleteReservationModalArgs from "../../core/storybook/deleteReservationMo import reservationListArgs from "../../core/storybook/reservationListArgs"; import globalTextArgs from "../../core/storybook/globalTextArgs"; import globalConfigArgs from "../../core/storybook/globalConfigArgs"; +import blockedArgs from "../../core/storybook/blockedArgs"; export default { title: "Apps / Dashboard", argTypes: { + ...blockedArgs, ...serviceUrlArgs, ...groupModalArgs, ...loanGroupModalArgs, diff --git a/src/apps/menu/menu-logged-in/MenuLoggedInContent.tsx b/src/apps/menu/menu-logged-in/MenuLoggedInContent.tsx index efbc9b47fc..f2ba44cae3 100644 --- a/src/apps/menu/menu-logged-in/MenuLoggedInContent.tsx +++ b/src/apps/menu/menu-logged-in/MenuLoggedInContent.tsx @@ -13,6 +13,7 @@ import DashboardNotificationList from "../../dashboard/dashboard-notification-li import useReservations from "../../../core/utils/useReservations"; import useLoans from "../../../core/utils/useLoans"; import { usePatronData } from "../../../core/utils/helpers/usePatronData"; +import { resetPersistedData } from "../../../core/store"; interface MenuLoggedInContentProps { pageSize: number; @@ -111,6 +112,7 @@ const MenuLoggedInContent: FC = ({ pageSize }) => {
resetPersistedData()} href={logoutUrl} > {t("menuLogOutText")} diff --git a/src/components/atoms/links/Link.tsx b/src/components/atoms/links/Link.tsx index 74dd196ea0..77ac78cc60 100644 --- a/src/components/atoms/links/Link.tsx +++ b/src/components/atoms/links/Link.tsx @@ -3,6 +3,7 @@ import { getLinkHandler } from "./getLinkHandler"; export interface LinkProps { href: URL; + onClick?: () => Promise; children: React.ReactNode; isNewTab?: boolean; className?: string; @@ -16,6 +17,7 @@ export interface LinkProps { const Link: React.FC = ({ href, + onClick, children, isNewTab = false, className, @@ -42,6 +44,14 @@ const Link: React.FC = ({ trackClick }); + const onclickHandler = onClick + ? ( + e: + | React.MouseEvent + | React.KeyboardEvent + ) => onClick().then(() => handleClick(e)) + : handleClick; + return ( = ({ target={isNewTab ? "_blank" : undefined} rel="noreferrer" className={className} - onClick={handleClick} + onClick={onclickHandler} onKeyUp={handleKeyUp} aria-labelledby={ariaLabelledBy} tabIndex={isHiddenFromScreenReaders ? -1 : 0} diff --git a/src/components/blocked-patron/BlockedPatron.test.ts b/src/components/blocked-patron/BlockedPatron.test.ts index 7a0ee88a1d..dbfe44ce86 100644 --- a/src/components/blocked-patron/BlockedPatron.test.ts +++ b/src/components/blocked-patron/BlockedPatron.test.ts @@ -1,9 +1,26 @@ +import { resetPersistedData } from "../../core/store"; + describe("Patron page", () => { before(() => { + // Make sure we have a clean slate before we start testing. + resetPersistedData(); cy.createFakeAuthenticatedSession(); cy.createFakeLibrarySession(); }); + beforeEach(() => { + cy.intercept("GET", "**/external/agencyid/patrons/patronid/loans/v2**", { + statusCode: 200, + body: [] + }); + cy.intercept("GET", "**/v1/user/**", { + statusCode: 200, + body: { + loans: [] + } + }); + }); + it("Patron not blocked", () => { cy.intercept("GET", "**/external/agencyid/patrons/patronid/v2**", { patron: { @@ -14,6 +31,7 @@ describe("Patron page", () => { cy.get(".modal").should("not.exist"); }); + // TYPE: E (fee) // Blocked types: // https://github.com/itk-dev/dpl-react/blob/develop/src/core/utils/types/BlockedTypes.ts it("Patron blocked E", () => { @@ -30,15 +48,33 @@ describe("Patron page", () => { }); cy.visit("/iframe.html?path=/story/apps-loan-list--loan-list-entry"); cy.getBySel("modal").should("exist"); - cy.getBySel("modal").should("exist"); cy.getBySel("modal").get("h1").should("exist"); cy.getBySel("modal").get("p").should("exist"); cy.getBySel("modal").get("a").should("exist"); }); + it("Does NOT show the blocked modal again once it has been shown", () => { + cy.intercept("GET", "**/external/agencyid/patrons/patronid/v2**", { + patron: { + blockStatus: [ + { + blockedReason: "E", + blockedSince: "", + message: "" + } + ] + } + }); + cy.visit("/iframe.html?path=/story/apps-loan-list--loan-list-entry"); + cy.getBySel("modal").should("not.exist"); + }); + + // TYPE: U (exclusion) // Blocked types: // https://github.com/itk-dev/dpl-react/blob/develop/src/core/utils/types/BlockedTypes.ts it("Patron blocked U", () => { + // To make sure that the modal is shown, we need to reset the persisted data. + resetPersistedData(); cy.intercept("GET", "**/external/agencyid/patrons/patronid/v2**", { patron: { blockStatus: [ @@ -57,9 +93,12 @@ describe("Patron page", () => { cy.getBySel("modal").get("a").should("not.exist"); }); + // TYPE: W (selfcreated) // Blocked types: // https://github.com/itk-dev/dpl-react/blob/develop/src/core/utils/types/BlockedTypes.ts it("Patron blocked W", () => { + // To make sure that the modal is shown, we need to reset the persisted data. + resetPersistedData(); cy.intercept("GET", "**/external/agencyid/patrons/patronid/v2**", { patron: { blockStatus: [ diff --git a/src/components/blocked-patron/blocked-modal/BlockedModal.tsx b/src/components/blocked-patron/blocked-modal/BlockedModal.tsx index bc21ccaa2c..04b60fda97 100644 --- a/src/components/blocked-patron/blocked-modal/BlockedModal.tsx +++ b/src/components/blocked-patron/blocked-modal/BlockedModal.tsx @@ -1,29 +1,37 @@ -import React, { FC } from "react"; +import React, { FC, useEffect } from "react"; import Modal from "../../../core/utils/modal"; import { useText } from "../../../core/utils/text"; import BlockedTypes from "../../../core/utils/types/BlockedTypes"; import { useUrls } from "../../../core/utils/url"; import Link from "../../atoms/links/Link"; -import { getModalIds } from "../../../core/utils/helpers/modal-helpers"; +import { useSetHasBeenVisible } from "../../../core/blockedModal.slice"; +import { useBlockedModalHasBeenVisible } from "../helper"; interface BlockedModalProps { - blockedStatus: string; + blockedStatus: BlockedTypes; } +export const getBlockedModalId = () => "blocked-patron"; + const BlockedModal: FC = ({ blockedStatus }) => { const t = useText(); const u = useUrls(); const blockedPatronELinkUrl = u("blockedPatronELinkUrl", true); - const { blockedModal } = getModalIds(); + const modalId = getBlockedModalId(); + const setHasBeenVisible = useSetHasBeenVisible(); + // Used to check whether the modal has been opened by another component, + // the modal should really only be showed once. + const hasBeenVisible = useBlockedModalHasBeenVisible(); - // If the user isn't actually blocked, don't render the modal. - if (!blockedStatus || blockedStatus === "") { - return null; - } + useEffect(() => { + if (!hasBeenVisible) { + setHasBeenVisible(); + } + }, [blockedStatus, hasBeenVisible, setHasBeenVisible]); return ( { + const { + data: { hasBeenVisible } + } = useSelector((state: RootState) => state.blockedModal); + + return Boolean(hasBeenVisible); +}; + +export default {}; diff --git a/src/core/blockedModal.slice.ts b/src/core/blockedModal.slice.ts index 72e7fe83cd..f1875fca72 100644 --- a/src/core/blockedModal.slice.ts +++ b/src/core/blockedModal.slice.ts @@ -1,4 +1,5 @@ import { createSlice } from "@reduxjs/toolkit"; +import { useDispatch } from "react-redux"; const initialState: { data: { [key: string]: boolean } | Record; @@ -20,6 +21,11 @@ export const blockedModalSlice = createSlice({ } }); -export const { setHasBeenVisible } = blockedModalSlice.actions; +export const useSetHasBeenVisible = () => { + const { setHasBeenVisible } = blockedModalSlice.actions; + const dispatch = useDispatch(); + + return () => dispatch(setHasBeenVisible({ hasBeenVisible: true })); +}; export default blockedModalSlice.reducer; diff --git a/src/core/configuration/modal-ids.json b/src/core/configuration/modal-ids.json index 66601f889d..4161224602 100644 --- a/src/core/configuration/modal-ids.json +++ b/src/core/configuration/modal-ids.json @@ -11,7 +11,6 @@ "userMenuAuthenticated": "user-modal-authenticated", "userMenuAnonymous": "user-modal-anonymous", "userMenuUnregistered": "user-modal-unregistered", - "blockedModal": "blocked-modal", "reservationsReady": "reservations-ready", "reservationsQueued": "reservations-queued" } diff --git a/src/core/store.ts b/src/core/store.ts index 348a1e57bd..2a7ac59361 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -10,7 +10,6 @@ import { import { persistReducer, persistStore } from "redux-persist"; import storage from "redux-persist/lib/storage/session"; import textReducer from "./text.slice"; -import userReducer from "./user.slice"; import modalReducer from "./modal.slice"; import urlReducer from "./url.slice"; import filterReducer from "./filter.slice"; @@ -26,7 +25,7 @@ import extractServiceBaseUrls from "./utils/reduxMiddleware/extractServiceBaseUr const persistConfig = { key: "dpl-react", storage, - blacklist: ["text", "url", "modal", "config", "blockedModal"] + blacklist: ["text", "url", "modal", "config"] }; export const store = configureStore({ @@ -37,7 +36,6 @@ export const store = configureStore({ reducer: persistReducer( persistConfig, combineReducers({ - user: userReducer, text: textReducer, modal: modalReducer, url: urlReducer, @@ -56,3 +54,5 @@ export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch; export type TypedDispatch = ThunkDispatch; export const useSelector: TypedUseSelectorHook = rawUseSelector; + +export const resetPersistedData = async () => persistor.purge(); diff --git a/src/core/user.js b/src/core/user.js deleted file mode 100644 index 898819b937..0000000000 --- a/src/core/user.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * A simple collection of functionality in regards to the current user. - * - * @class User - */ -import { hasToken } from "./token"; -import { store, persistor } from "./store"; -import { updateStatus, attemptAuthentication } from "./user.slice"; - -const selectStatus = (state) => state.user.status; - -class User { - /** - * Used to keep track if we started attempting in this page view. - * - * @static - */ - static #attemptingThisRequest = false; - - /** - * Returns whether a user is authenticated of not. - * - * @static - * @returns {boolean} - * @memberof User - */ - static isAuthenticated() { - store.dispatch( - updateStatus({ - hasToken: hasToken("user"), - doFail: !User.#attemptingThisRequest - }) - ); - return selectStatus(store.getState()) === "authenticated"; - } - - /** - * Redirect to login. - * - * @param {string} loginUrl the URL to redirect to. - */ - static authenticate(loginUrl) { - // Switch state to attempting and flush state to session storage - // before redirecting. - store.dispatch(attemptAuthentication()).then(() => persistor.flush()); - User.#attemptingThisRequest = true; - window.location.href = loginUrl; - } - - /** - * Whether authentication failed. - * - * Will return true if we just tried authenticating and it failed. - * - * @returns {boolean} - */ - static authenticationFailed() { - // isAuthenticated() will ensure state is up to date. - return ( - !this.isAuthenticated() && selectStatus(store.getState()) === "failed" - ); - } -} - -export default User; diff --git a/src/core/user.slice.js b/src/core/user.slice.js deleted file mode 100644 index f6649d3524..0000000000 --- a/src/core/user.slice.js +++ /dev/null @@ -1,44 +0,0 @@ -import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; - -// This thunk doesn't actually do anything but resolve straight away, -// but this allows us to chain a `.then()` on the action on the caller -// side. -export const attemptAuthentication = createAsyncThunk( - "user/attemptAuthentication", - () => Promise.resolve() -); - -const userSlice = createSlice({ - name: "user", - initialState: { status: "unauthenticated" }, - reducers: { - updateStatus(state, action) { - if (state.status === "unauthenticated" || state.status === "attempting") { - if (action.payload.hasToken) { - state.status = "authenticated"; - } else if (action.payload.doFail && state.status === "attempting") { - state.status = "failed"; - } - } - }, - setStatusAuthenticated(state) { - state.status = "authenticated"; - }, - setStatusUnauthenticated(state) { - state.status = "unauthenticated"; - } - }, - extraReducers: { - [attemptAuthentication.pending]: (state) => { - state.status = "attempting"; - } - } -}); - -export const { - updateStatus, - setStatusAuthenticated, - setStatusUnauthenticated -} = userSlice.actions; - -export default userSlice.reducer; diff --git a/src/core/utils/helpers/usePatronData.ts b/src/core/utils/helpers/usePatronData.ts index c1ff3fd30f..994bc53dd4 100644 --- a/src/core/utils/helpers/usePatronData.ts +++ b/src/core/utils/helpers/usePatronData.ts @@ -1,4 +1,6 @@ import { useGetPatronInformationByPatronIdV2 } from "../../fbs/fbs"; +import BlockedTypes from "../types/BlockedTypes"; +import { Patron } from "../types/entities"; import { isAnonymous } from "./user"; export const usePatronData = () => @@ -6,4 +8,14 @@ export const usePatronData = () => enabled: !isAnonymous() }); +export const getBlockedStatus = (patron?: Patron) => { + // TODO: Investigate if we need to consider multiple + // block statuses and not only the first. + if (patron?.blockStatus && patron?.blockStatus?.length > 0) { + return patron.blockStatus[0].blockedReason as BlockedTypes; + } + // We cannot resolve the block status so we return unknown + return BlockedTypes.unknown; +}; + export default {}; diff --git a/src/core/utils/types/BlockedTypes.ts b/src/core/utils/types/BlockedTypes.ts index a05cf5165c..33e60b0fa5 100644 --- a/src/core/utils/types/BlockedTypes.ts +++ b/src/core/utils/types/BlockedTypes.ts @@ -1,11 +1,24 @@ enum BlockedTypes { - extendedExclusion = "F", // Extended exclusion - deceased = "D", // Deceased - fee = "E", // Patron has fee - selfcreated = "W", // Self created at website - stolen = "O", // library card stolen - exclusion = "U", // Exclusion - automatonBlocked = "S" // Blocked by self service automaton + // Extended suspension + extendedSuspension = "F", + // Deceased + deceased = "D", + // Patron has fee + fee = "E", + + // The user has not been assigned a correct patron category. + // This should be pretty rare. + // In Danish this is also called "Selvoprettet på web" + missingPatronCategory = "W", + + // library card stolen + accountStolen = "O", + // Suspension + suspension = "U", + // Blocked by self service + blockedFromSelfservice = "S", + // A fallback for unknown types + unknown = "unknown" } export default BlockedTypes; diff --git a/src/core/utils/withIsPatronBlockedHoc.tsx b/src/core/utils/withIsPatronBlockedHoc.tsx index d402d5910f..d306f642bb 100644 --- a/src/core/utils/withIsPatronBlockedHoc.tsx +++ b/src/core/utils/withIsPatronBlockedHoc.tsx @@ -1,14 +1,12 @@ import React, { useEffect, useState, ComponentType, FC } from "react"; -import { useDispatch } from "react-redux"; -import BlockedModal from "../../components/blocked-patron/blocked-modal/BlockedModal"; +import BlockedModal, { + getBlockedModalId +} from "../../components/blocked-patron/blocked-modal/BlockedModal"; import { AuthenticatedPatronV6 } from "../fbs/model"; import { useModalButtonHandler } from "./modal"; -import { setHasBeenVisible } from "../blockedModal.slice"; -import { RootState, useSelector } from "../store"; import BlockedTypes from "./types/BlockedTypes"; -import { redirectTo } from "./helpers/url"; -import { getModalIds } from "./helpers/modal-helpers"; -import { usePatronData } from "./helpers/usePatronData"; +import { getBlockedStatus, usePatronData } from "./helpers/usePatronData"; +import { useBlockedModalHasBeenVisible } from "../../components/blocked-patron/helper"; export interface PatronProps { patron: AuthenticatedPatronV6 | null | undefined; @@ -24,63 +22,54 @@ type InputProps = { const withIsPatronBlockedHoc =

(Component: ComponentType

): FC

=> ({ redirectOnBlocked, ...props }) => { - const dispatch = useDispatch(); + // Used to check whether the modal has been opened by another component, + // the modal should really only be showed once. + const hasBeenVisible = useBlockedModalHasBeenVisible(); const { open } = useModalButtonHandler(); - const { blockedModal } = getModalIds(); + const blockedModalId = getBlockedModalId(); const [blockedFromViewingContentArray] = useState([ - BlockedTypes.deceased, - BlockedTypes.automatonBlocked, - BlockedTypes.extendedExclusion, - BlockedTypes.stolen + BlockedTypes.extendedSuspension ]); - const [blockedStatus, setBlockedStatus] = useState(); const [blockedFromViewingContent, setBlockedFromViewingContent] = useState< boolean | null >(null); const { data: patronData } = usePatronData(); - - // Used to check whether the modal has been opened by another component, - // the modal should really only be showed once. - const { - data: { hasBeenVisible } - } = useSelector((state: RootState) => state.blockedModal); + const blockedStatus = getBlockedStatus(patronData?.patron); useEffect(() => { - if (!patronData) { + // If we do not know the blocked status, we do not have to do anything. + if (!patronData?.patron) { return; } - if ( - patronData?.patron?.blockStatus && - patronData?.patron?.blockStatus?.length > 0 - ) { - setBlockedStatus(patronData.patron.blockStatus[0].blockedReason); - // As above comment, only opens modal if it has not already been visible. - if (!hasBeenVisible && typeof blockedModal === "string") { - open(blockedModal); - dispatch(setHasBeenVisible({ hasBeenVisible: true })); - } - } else { - setBlockedFromViewingContent(false); - } - }, [blockedModal, dispatch, hasBeenVisible, open, patronData]); - useEffect(() => { - if (!blockedStatus) { - return; + // Open modal if it has not been shown yet and we have a blocked status. + if (blockedStatus !== BlockedTypes.unknown && !hasBeenVisible) { + open(blockedModalId); } - if (blockedFromViewingContentArray.includes(blockedStatus)) { - setBlockedFromViewingContent(true); - redirectTo(new URL(redirectOnBlocked)); - } else { - setBlockedFromViewingContent(false); + + // Make sure to hide the content behind the modal on certain blocked statuses. + if (blockedFromViewingContent === null) { + setBlockedFromViewingContent( + blockedFromViewingContentArray.includes(blockedStatus) + ); } - }, [blockedFromViewingContentArray, blockedStatus, redirectOnBlocked]); + }, [ + blockedFromViewingContent, + blockedFromViewingContentArray, + blockedModalId, + blockedStatus, + hasBeenVisible, + open, + patronData?.patron + ]); return ( <> - + {blockedStatus !== BlockedTypes.unknown && ( + + )} {!blockedFromViewingContent && (