Skip to content

Commit

Permalink
Notice empty state for privacy center (#3640)
Browse files Browse the repository at this point in the history
  • Loading branch information
allisonking authored Jun 23, 2023
1 parent f954d4b commit c55abb0
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ The types of changes are:

## [Unreleased](https://github.com/ethyca/fides/compare/2.15.0...main)

### Added
- Empty state for when there are no relevant privacy notices in the privacy center [#3640](https://github.com/ethyca/fides/pull/3640)
### Fixed

- Render linebreaks in the Fides.js overlay descriptions, etc. [#3665](https://github.com/ethyca/fides/pull/3665)
Expand Down
39 changes: 39 additions & 0 deletions clients/privacy-center/components/modals/NoticeEmptyStateModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
} from "@fidesui/react";

const NoticeEmptyStateModal = ({
isOpen,
onClose,
}: {
isOpen: boolean;
onClose: () => void;
}) => (
<Modal isOpen={isOpen} onClose={onClose} isCentered>
<ModalOverlay />
<ModalContent textAlign="center" data-testid="notice-empty-state">
<ModalHeader fontSize="lg" pt={6} fontWeight={500}>
Consent management unavailable
</ModalHeader>
<ModalBody py={0}>
<Text fontSize="sm" fontWeight={400} color="gray.500">
Consent management is unavailable in your area.
</Text>
</ModalBody>
<ModalFooter display="flex" justifyContent="center" py={6}>
<Button size="sm" colorScheme="primary" width="100%" onClick={onClose}>
Ok
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);

export default NoticeEmptyStateModal;
5 changes: 3 additions & 2 deletions clients/privacy-center/cypress/e2e/consent-notices.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,11 @@ describe("Privacy notice driven consent", () => {

it("uses the device id found in an already existing cookie", () => {
const uuid = "4fbb6edf-34f6-4717-a6f1-541fd1e5d585";
const now = "2023-04-28T12:00:00.000Z";
const createdAt = "2023-04-28T12:00:00.000Z";
const updatedAt = "2023-04-29T12:00:00.000Z";
const cookie = {
identity: { fides_user_device_id: uuid },
fides_meta: { version: "0.9.0", createdAt: now },
fides_meta: { version: "0.9.0", createdAt, updatedAt },
consent: {},
};
cy.setCookie(CONSENT_COOKIE_NAME, JSON.stringify(cookie));
Expand Down
6 changes: 4 additions & 2 deletions clients/privacy-center/cypress/e2e/consent.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe("Consent modal deeplink", () => {
beforeEach(() => {
cy.visit("/?showConsentModal=true");
cy.loadConfigFixture("config/config_consent.json").as("config");
cy.overrideSettings({ IS_OVERLAY_ENABLED: false });
cy.intercept("POST", `${API_URL}/consent-request`, {
body: {
consent_request_id: "consent-request-id",
Expand Down Expand Up @@ -56,6 +57,7 @@ describe("Consent settings", () => {
beforeEach(() => {
cy.visit("/");
cy.loadConfigFixture("config/config_consent.json").as("config");
cy.overrideSettings({ IS_OVERLAY_ENABLED: false });
});

describe("when the user isn't verified", () => {
Expand Down Expand Up @@ -203,7 +205,7 @@ describe("Consent settings", () => {
cy.visit("/consent");
cy.getByTestId("consent");
cy.loadConfigFixture("config/config_consent.json").as("config");
cy.overrideSettings({ IS_OVERLAY_DISABLED: true });
cy.overrideSettings({ IS_OVERLAY_ENABLED: false });
});

it("populates its header and description from config", () => {
Expand Down Expand Up @@ -336,7 +338,7 @@ describe("Consent settings", () => {
cy.visit("/consent?globalPrivacyControl=true");
cy.getByTestId("consent");
cy.loadConfigFixture("config/config_consent.json").as("config");
cy.overrideSettings({ IS_OVERLAY_DISABLED: true });
cy.overrideSettings({ IS_OVERLAY_ENABLED: false });
});

it("applies the GPC defaults", () => {
Expand Down
26 changes: 25 additions & 1 deletion clients/privacy-center/cypress/e2e/home.cy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Config } from "~/types/config";
import { API_URL } from "../support/constants";

describe("Home", () => {
it("renders the configured page info", () => {
cy.visit("/");
cy.getByTestId("home");
cy.loadConfigFixture("config/config_all.json").then((config) => {
cy.loadConfigFixture("config/config_all.json").then((config: Config) => {
// DEFER: Test *all* the configurable display options
// (see https://github.com/ethyca/fides/issues/3216)

Expand Down Expand Up @@ -32,6 +35,27 @@ describe("Home", () => {
cy.get("body").should("have.css", "background-color", "rgb(255, 99, 71)");
});

it("should show an empty state when notice-driven but there are no notices", () => {
const geolocationApiUrl = "https://www.example.com/location";
const settings = {
IS_OVERLAY_ENABLED: true,
IS_GEOLOCATION_ENABLED: true,
GEOLOCATION_API_URL: geolocationApiUrl,
};
cy.intercept("GET", geolocationApiUrl, {
fixture: "consent/geolocation.json",
}).as("getGeolocation");
// Will return undefined when there are no relevant privacy notices
cy.intercept("GET", `${API_URL}/privacy-experience/*`, {
body: undefined,
}).as("getExperience");
cy.visit("/");
cy.overrideSettings(settings);

cy.getByTestId("card").contains("Manage your consent").click();
cy.getByTestId("notice-empty-state");
});

describe("when handling errors", () => {
// Allow uncaught exceptions to occur without failing the test
beforeEach(() => {
Expand Down
5 changes: 4 additions & 1 deletion clients/privacy-center/features/consent/consent.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ export const consentSlice = createSlice({
});
},

setFidesUserDeviceId(draftState, { payload }: PayloadAction<string>) {
setFidesUserDeviceId(
draftState,
{ payload }: PayloadAction<string | undefined>
) {
draftState.fidesUserDeviceId = payload;
},
},
Expand Down
10 changes: 8 additions & 2 deletions clients/privacy-center/features/consent/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getOrMakeFidesCookie } from "fides-js";
import { getOrMakeFidesCookie, isNewFidesCookie } from "fides-js";
import { useEffect } from "react";
import { useAppDispatch, useAppSelector } from "~/app/hooks";
import { PrivacyNoticeRegion } from "~/types/api";
Expand Down Expand Up @@ -30,7 +30,13 @@ export const useSubscribeToPrivacyExperienceQuery = () => {
const dispatch = useAppDispatch();
const { IS_GEOLOCATION_ENABLED, GEOLOCATION_API_URL } = useSettings();
const cookie = getOrMakeFidesCookie();
const { fides_user_device_id: fidesUserDeviceId } = cookie.identity;
const hasExistingCookie = !isNewFidesCookie(cookie);
// fidesUserDeviceId is only stable in this function if the cookie already exists
// Using it when the cookie is new results in an unstable device ID and can
// cause infinite fetching of the privacy experience, so make sure we only use a saved one
const fidesUserDeviceId = hasExistingCookie
? cookie.identity.fides_user_device_id
: undefined;

const skipFetchExperience = !useAppSelector(selectIsNoticeDriven);
const skipFetchGeolocation =
Expand Down
39 changes: 37 additions & 2 deletions clients/privacy-center/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Flex, Heading, Text, Stack, useToast } from "@fidesui/react";
import {
Flex,
Heading,
Text,
Stack,
useToast,
useDisclosure,
} from "@fidesui/react";
import React, { useEffect, useState } from "react";
import type { NextPage } from "next";
import { useRouter } from "next/router";
Expand All @@ -16,6 +23,11 @@ import { useGetIdVerificationConfigQuery } from "~/features/id-verification";
import PrivacyCard from "~/components/PrivacyCard";
import ConsentCard from "~/components/consent/ConsentCard";
import { useConfig } from "~/features/common/config.slice";
import { useSubscribeToPrivacyExperienceQuery } from "~/features/consent/hooks";
import { useAppSelector } from "~/app/hooks";
import { selectPrivacyExperience } from "~/features/consent/consent.slice";
import NoticeEmptyStateModal from "~/components/modals/NoticeEmptyStateModal";
import { selectIsNoticeDriven } from "~/features/common/settings.slice";

const Home: NextPage = () => {
const router = useRouter();
Expand Down Expand Up @@ -48,6 +60,24 @@ const Home: NextPage = () => {
let isConsentModalOpen = isConsentModalOpenConst;
const getIdVerificationConfigQuery = useGetIdVerificationConfigQuery();

// Subscribe to experiences just to see if there are any notices.
// The subscription automatically handles skipping if overlay is not enabled
useSubscribeToPrivacyExperienceQuery();
const noticeEmptyStateModal = useDisclosure();
const experience = useAppSelector(selectPrivacyExperience);
const isNoticeDriven = useAppSelector(selectIsNoticeDriven);
const emptyNotices =
experience?.privacy_notices == null ||
experience.privacy_notices.length === 0;

const handleConsentCardOpen = () => {
if (isNoticeDriven && emptyNotices) {
noticeEmptyStateModal.onOpen();
} else {
onConsentModalOpen();
}
};

useEffect(() => {
if (getIdVerificationConfigQuery.isError) {
// TODO(#2299): Use error utils from shared package.
Expand Down Expand Up @@ -88,7 +118,7 @@ const Home: NextPage = () => {
title={config.consent.button.title}
iconPath={config.consent.button.icon_path}
description={config.consent.button.description}
onOpen={onConsentModalOpen}
onOpen={handleConsentCardOpen}
/>
);
if (router.query?.showConsentModal === "true") {
Expand Down Expand Up @@ -178,6 +208,11 @@ const Home: NextPage = () => {
isVerificationRequired={isVerificationRequired}
successHandler={consentModalSuccessHandler}
/>

<NoticeEmptyStateModal
isOpen={noticeEmptyStateModal.isOpen}
onClose={noticeEmptyStateModal.onClose}
/>
</main>
);
};
Expand Down

0 comments on commit c55abb0

Please sign in to comment.