Skip to content

Commit

Permalink
Prevent dismissal of banner and overlay (#4470)
Browse files Browse the repository at this point in the history
  • Loading branch information
galvana committed Dec 8, 2023
1 parent 42836e9 commit d4c01e6
Show file tree
Hide file tree
Showing 15 changed files with 537 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The types of changes are:
- Readonly consent management table and modal [#4456](https://github.com/ethyca/fides/pull/4456), [#4477](https://github.com/ethyca/fides/pull/4477)
- Access and erasure support for Gong [#4461](https://github.com/ethyca/fides/pull/4461)
- Add new UI for CSV consent reporting [#4488](https://github.com/ethyca/fides/pull/4488)
- Option to prevent the dismissal of the consent banner and modal [#4470](https://github.com/ethyca/fides/pull/4470)

### Changed
- Increased max number of preferences allowed in privacy preference API calls [#4469](https://github.com/ethyca/fides/pull/4469)
Expand Down
3 changes: 3 additions & 0 deletions clients/fides-js/src/components/CloseButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import { h } from "preact";
const CloseButton = ({
onClick,
ariaLabel,
hidden = false,
}: {
onClick?: () => void;
ariaLabel?: string;
hidden?: boolean;
}) => (
<button
type="button"
aria-label={ariaLabel}
className="fides-close-button"
onClick={onClick}
style={{ visibility: hidden ? "hidden" : "visible" }}
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none">
<path
Expand Down
39 changes: 37 additions & 2 deletions clients/fides-js/src/components/ConsentBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { h, FunctionComponent, ComponentChildren, VNode } from "preact";
import { useState, useEffect } from "preact/hooks";
import { useState, useEffect, useRef } from "preact/hooks";
import { getConsentContext } from "../lib/consent-context";
import { ExperienceConfig } from "../lib/consent-types";
import CloseButton from "./CloseButton";
Expand Down Expand Up @@ -35,6 +35,8 @@ const ConsentBanner: FunctionComponent<BannerProps> = ({
renderButtonGroup,
className,
}) => {
const ref = useRef<HTMLDivElement>(null);

const [isMobile, setIsMobile] = useState(window.innerWidth < 768);

useEffect(() => {
Expand All @@ -50,6 +52,34 @@ const ConsentBanner: FunctionComponent<BannerProps> = ({
};
}, []);

// add listeners for ESC and clicking outside of component
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
onClose();
}
};

const handleEsc = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onClose();
}
};

if (bannerIsOpen && !window.Fides.options.preventDismissal) {
window.addEventListener("mousedown", handleClickOutside);
window.addEventListener("keydown", handleEsc);
} else {
window.removeEventListener("mousedown", handleClickOutside);
window.removeEventListener("keydown", handleEsc);
}

return () => {
window.removeEventListener("mousedown", handleClickOutside);
window.removeEventListener("keydown", handleEsc);
};
}, [onClose, bannerIsOpen, ref]);

const showGpcBadge = getConsentContext().globalPrivacyControl;

useEffect(() => {
Expand All @@ -64,10 +94,15 @@ const ConsentBanner: FunctionComponent<BannerProps> = ({
className={`fides-banner fides-banner-bottom
${bannerIsOpen ? "" : "fides-banner-hidden"}
${className || ""}`}
ref={ref}
>
<div id="fides-banner">
<div id="fides-banner-inner">
<CloseButton ariaLabel="Close banner" onClick={onClose} />
<CloseButton
ariaLabel="Close banner"
onClick={onClose}
hidden={window.Fides.options.preventDismissal}
/>
<div
id="fides-banner-inner-container"
style={{
Expand Down
6 changes: 5 additions & 1 deletion clients/fides-js/src/components/ConsentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ const ConsentModal = ({
>
<div className="fides-modal-header">
<div />
<CloseButton ariaLabel="Close modal" onClick={closeButton.onClick} />
<CloseButton
ariaLabel="Close modal"
onClick={closeButton.onClick}
hidden={window.Fides.options.preventDismissal}
/>
</div>
<ConsentContent
title={title}
Expand Down
19 changes: 16 additions & 3 deletions clients/fides-js/src/components/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface Props {
experience: PrivacyExperience;
cookie: FidesCookie;
onOpen: () => void;
onDismiss: () => void;
renderBanner: (props: RenderBannerProps) => VNode | null;
renderModalContent: () => VNode;
renderModalFooter: (props: RenderModalFooter) => VNode;
Expand All @@ -39,6 +40,7 @@ const Overlay: FunctionComponent<Props> = ({
options,
cookie,
onOpen,
onDismiss,
renderBanner,
renderModalContent,
renderModalFooter,
Expand All @@ -52,15 +54,23 @@ const Overlay: FunctionComponent<Props> = ({
const dispatchCloseEvent = useCallback(
({ saved = false }: { saved?: boolean }) => {
dispatchFidesEvent("FidesModalClosed", cookie, options.debug, { saved });
if (!saved) {
onDismiss();
}
},
[cookie, options.debug]
);

const { instance, attributes } = useA11yDialog({
id: "fides-modal",
role: "alertdialog",
role: window.Fides.options.preventDismissal ? "alertdialog" : "dialog",
title: experience?.experience_config?.title || "",
onClose: () => dispatchCloseEvent({ saved: false }),
onClose: () => {
dispatchCloseEvent({ saved: false });
},
onEsc: () => {
dispatchCloseEvent({ saved: false });
},
});

const handleOpenModal = useCallback(() => {
Expand Down Expand Up @@ -103,8 +113,8 @@ const Overlay: FunctionComponent<Props> = ({
// Update modal link to trigger modal on click
const modalLink = modalLinkEl;
modalLink.onclick = () => {
handleOpenModal();
setBannerIsOpen(false);
handleOpenModal();
};
// Update to show the pre-existing modal link in the DOM
modalLink.classList.add("fides-modal-link-shown");
Expand Down Expand Up @@ -140,6 +150,9 @@ const Overlay: FunctionComponent<Props> = ({

return (
<div>
{bannerIsOpen && window.Fides.options.preventDismissal && (
<div className="fides-modal-overlay" />
)}
{showBanner
? renderBanner({
isOpen: bannerIsOpen,
Expand Down
10 changes: 9 additions & 1 deletion clients/fides-js/src/components/notices/NoticeOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ const NoticeOverlay: FunctionComponent<OverlayProps> = ({
});
}, [cookie, options.debug]);

const handleDismiss = useCallback(() => {
handleUpdatePreferences(ConsentMethod.dismiss, initialEnabledNoticeKeys);
}, [handleUpdatePreferences, initialEnabledNoticeKeys]);

if (!experience.experience_config) {
debugLog(options.debug, "No experience config found");
return null;
Expand All @@ -146,11 +150,15 @@ const NoticeOverlay: FunctionComponent<OverlayProps> = ({
experience={experience}
cookie={cookie}
onOpen={dispatchOpenOverlayEvent}
onDismiss={handleDismiss}
renderBanner={({ isOpen, onClose, onSave, onManagePreferencesClick }) => (
<ConsentBanner
bannerIsOpen={isOpen}
onOpen={dispatchOpenBannerEvent}
onClose={onClose}
onClose={() => {
onClose();
handleDismiss();
}}
experience={experienceConfig}
renderButtonGroup={({ isMobile }) => (
<NoticeConsentButtons
Expand Down
10 changes: 9 additions & 1 deletion clients/fides-js/src/components/tcf/TcfOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ const TcfOverlay: FunctionComponent<OverlayProps> = ({
});
}, [cookie, options.debug]);

const handleDismiss = useCallback(() => {
handleUpdateAllPreferences(ConsentMethod.dismiss, initialEnabledIds);
}, [handleUpdateAllPreferences, initialEnabledIds]);

if (!experience.experience_config) {
debugLog(options.debug, "No experience config found");
return null;
Expand All @@ -345,6 +349,7 @@ const TcfOverlay: FunctionComponent<OverlayProps> = ({
setActiveTabIndex(2);
}}
onOpen={dispatchOpenOverlayEvent}
onDismiss={handleDismiss}
renderBanner={({ isOpen, onClose, onSave, onManagePreferencesClick }) => {
const goToVendorTab = () => {
onManagePreferencesClick();
Expand All @@ -354,7 +359,10 @@ const TcfOverlay: FunctionComponent<OverlayProps> = ({
<ConsentBanner
bannerIsOpen={isOpen}
onOpen={dispatchOpenBannerEvent}
onClose={onClose}
onClose={() => {
onClose();
handleDismiss();
}}
experience={experienceConfig}
onVendorPageClick={goToVendorTab}
renderButtonGroup={({ isMobile }) => (
Expand Down
1 change: 1 addition & 0 deletions clients/fides-js/src/fides-tcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ _Fides = {
fidesTcfGdprApplies: true,
gppExtensionPath: "",
customOptionsPath: null,
preventDismissal: false,
},
fides_meta: {},
identity: {},
Expand Down
1 change: 1 addition & 0 deletions clients/fides-js/src/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ _Fides = {
fidesTcfGdprApplies: false,
gppExtensionPath: "",
customOptionsPath: null,
preventDismissal: false,
},
fides_meta: {},
identity: {},
Expand Down
20 changes: 16 additions & 4 deletions clients/fides-js/src/lib/a11y-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import A11yDialogLib from "a11y-dialog";
import { useCallback, useEffect, useState } from "preact/hooks";

const useA11yDialogInstance = () => {
const useA11yDialogInstance = (onEsc?: () => void) => {
const [instance, setInstance] = useState<A11yDialogLib | null>(null);
const container = useCallback((node: Element) => {
if (node !== null) {
Expand All @@ -15,8 +15,19 @@ const useA11yDialogInstance = () => {
.on("show", () => {
document.documentElement.style.overflowY = "hidden";
})
.on("hide", () => {
.on("hide", (_: Element, event?: Event) => {
document.documentElement.style.overflowY = "";

// a11y-dialog natively supports dismissing a dialog by pressing ESC
// but it doesn't allow any custom functions to be associated
// with an ESC press, so we have to do this manually
if (
onEsc &&
event instanceof KeyboardEvent &&
event.key === "Escape"
) {
onEsc();
}
});
setInstance(dialog);
}
Expand All @@ -29,9 +40,10 @@ interface Props {
id: string;
title: string;
onClose?: () => void;
onEsc?: () => void;
}
export const useA11yDialog = ({ role, id, onClose }: Props) => {
const { instance, container: ref } = useA11yDialogInstance();
export const useA11yDialog = ({ role, id, onClose, onEsc }: Props) => {
const { instance, container: ref } = useA11yDialogInstance(onEsc);
const isAlertDialog = role === "alertdialog";
const titleId = `${id}-title`;

Expand Down
3 changes: 3 additions & 0 deletions clients/fides-js/src/lib/consent-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export type FidesOptions = {

// A custom path to fetch OverrideOptions (e.g. "window.config.overrides"). Defaults to window.fides_overrides
customOptionsPath: string | null;

// Prevents the banner and modal from being dismissed
preventDismissal: boolean;
};

export type GetPreferencesFnResp = {
Expand Down
6 changes: 6 additions & 0 deletions clients/privacy-center/app/server-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface PrivacyCenterSettings {
IS_FORCED_TCF: boolean; // whether to force the privacy center to use the fides-tcf.js bundle
IS_GPP_ENABLED: boolean; // whether GPP is enabled
GPP_EXTENSION_PATH: string; // The path of the GPP extension file `fides-ext-gpp.js`. Defaults to `/fides-ext-gpp.js`
PREVENT_DISMISSAL: boolean; // whether or not the user is allowed to dismiss the banner/overlay
}

/**
Expand Down Expand Up @@ -82,6 +83,7 @@ export type PrivacyCenterClientSettings = Pick<
| "IS_FORCED_TCF"
| "IS_GPP_ENABLED"
| "GPP_EXTENSION_PATH"
| "PREVENT_DISMISSAL"
>;

export type Styles = string;
Expand Down Expand Up @@ -349,6 +351,9 @@ export const loadPrivacyCenterEnvironment =
GPP_EXTENSION_PATH:
process.env.FIDES_PRIVACY_CENTER__GPP_EXTENSION_PATH ||
"/fides-ext-gpp.js",
PREVENT_DISMISSAL: process.env.FIDES_PRIVACY_CENTER__PREVENT_DISMISSAL
? process.env.FIDES_PRIVACY_CENTER__PREVENT_DISMISSAL === "true"
: false,
};

// Load configuration file (if it exists)
Expand Down Expand Up @@ -378,6 +383,7 @@ export const loadPrivacyCenterEnvironment =
IS_FORCED_TCF: settings.IS_FORCED_TCF,
IS_GPP_ENABLED: settings.IS_GPP_ENABLED,
GPP_EXTENSION_PATH: settings.GPP_EXTENSION_PATH,
PREVENT_DISMISSAL: settings.PREVENT_DISMISSAL,
};

// For backwards-compatibility, override FIDES_API_URL with the value from the config file if present
Expand Down
Loading

0 comments on commit d4c01e6

Please sign in to comment.