* create false door test component

* localize all strings

* z-index

* remove use of lineBreak var, and add descriptive comment about false door test

* update alt var

* update comment

* add feature flag

* add falsedoortest flag

* add pointer

* add locale check

* dismiss on selecting cta

* switch to variant for dashboard

* add group comment

* remove unset

* add focus state

* fix lint

* add z-index comment

* move link to .env file

* add utm params

* add unit tests

* change name to falsedoorbanner

* change name to falsedoorbanner

* remove props export from layout

* fix build fail

* fix lint

* rename falsedoortest
This commit is contained in:
Kaitlyn Andres 2023-08-16 11:54:24 -04:00 коммит произвёл GitHub
Родитель 33144083d5
Коммит 3a4f1e4298
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 378 добавлений и 15 удалений

Просмотреть файл

@ -940,3 +940,13 @@ breach-detail-cta-signup = Check for breaches
floating-banner-text = Boost your online security with news, tips, and updates from { -brand-Mozilla }.
floating-banner-link-label = Sign up
floating-banner-dismiss-button-label = No thanks
## False door test
# Strings used in a banner (false door test) to observe engagement with Monitor premium and gauge user interest.
false-door-test-content-part-one = No one should be able to buy your personal information.
false-door-test-content-part-two = Automatically remove data from sites trying to sell it.
false-door-test-content-part-two-dashboard = Auto-delete data from sites trying to sell it.
false-door-test-cta = Count me in
false-door-test-popup-close = Close

70
package-lock.json сгенерированный
Просмотреть файл

@ -40,6 +40,7 @@
"pg": "^8.9.0",
"react": "^18.2.0",
"react-aria": "^3.26.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-stately": "^3.23.0",
"uuid": "^9.0.0"
@ -15344,6 +15345,11 @@
"@types/node": "*"
}
},
"node_modules/@types/cookie": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
},
"node_modules/@types/detect-port": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.3.tgz",
@ -15442,6 +15448,15 @@
"@types/node": "*"
}
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@ -28275,6 +28290,19 @@
"react-dom": ">=16.8.0"
}
},
"node_modules/react-cookie": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz",
"integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.0.1",
"hoist-non-react-statics": "^3.0.0",
"universal-cookie": "^4.0.0"
},
"peerDependencies": {
"react": ">= 16.3.0"
}
},
"node_modules/react-docgen": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-5.4.3.tgz",
@ -31429,6 +31457,15 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/universal-cookie": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",
"integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==",
"dependencies": {
"@types/cookie": "^0.3.3",
"cookie": "^0.4.0"
}
},
"node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
@ -43786,6 +43823,11 @@
"@types/node": "*"
}
},
"@types/cookie": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
},
"@types/detect-port": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.3.tgz",
@ -43884,6 +43926,15 @@
"@types/node": "*"
}
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@ -53562,6 +53613,16 @@
"dev": true,
"requires": {}
},
"react-cookie": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz",
"integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==",
"requires": {
"@types/hoist-non-react-statics": "^3.0.1",
"hoist-non-react-statics": "^3.0.0",
"universal-cookie": "^4.0.0"
}
},
"react-docgen": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-5.4.3.tgz",
@ -55909,6 +55970,15 @@
"unist-util-is": "^4.0.0"
}
},
"universal-cookie": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",
"integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==",
"requires": {
"@types/cookie": "^0.3.3",
"cookie": "^0.4.0"
}
},
"universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",

Просмотреть файл

@ -76,6 +76,7 @@
"pg": "^8.9.0",
"react": "^18.2.0",
"react-aria": "^3.26.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-stately": "^3.23.0",
"uuid": "^9.0.0"
@ -95,8 +96,8 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest-axe": "^3.5.5",
"@types/adm-zip": "^0.5.0",
"@types/jest-axe": "^3.5.5",
"@types/jsonwebtoken": "^9.0.2",
"@types/jwk-to-pem": "^2.0.1",
"@types/nodemailer": "^6.4.8",
@ -125,8 +126,8 @@
"stylelint": "^15.6.0",
"stylelint-config-recommended-scss": "^11.0.0",
"stylelint-scss": "^5.0.0",
"ts-node": "^10.9.1",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
}
}

Просмотреть файл

@ -0,0 +1,90 @@
@import "../../../tokens.scss";
.falseDoorTestWrapper {
--monitorLogoSizeSmall: 30px;
--monitorLogoSizeBig: 50px;
font: $text-body-md;
position: fixed;
bottom: 0;
left: 0;
// Overlap all regular content on the site with a z-index,
// except for ModalOverlay which, at the time of writing, has a z-index of 2
z-index: 1;
padding-block: $spacing-md;
width: 100%;
align-items: center;
background-color: #ffdff0;
display: flex;
flex-direction: row;
justify-content: space-between;
gap: $spacing-xl;
@media screen and (min-width: $screen-lg) {
padding-inline: $spacing-2xl;
padding-block: $spacing-md;
}
.dismiss {
align-self: flex-start;
margin-left: auto;
color: $color-grey-40;
&:focus {
border: 1px solid $color-informational-active;
outline: $border-focus-width solid $color-informational-focus;
}
}
.content {
display: flex;
flex-direction: row;
flex: column;
flex-wrap: wrap;
align-items: center;
gap: $spacing-md;
width: 100%;
justify-content: space-between;
.imageAndCopy {
display: flex;
gap: $spacing-md;
}
@media screen and (min-width: $screen-lg) {
padding-left: $spacing-2xl;
}
.logo {
width: var(--monitorLogoSizeSmall);
height: var(--monitorLogoSizeSmall);
@media screen and (min-width: $screen-lg) {
width: var(--monitorLogoSizeBig);
height: var(--monitorLogoSizeBig);
}
}
p {
margin: 0;
}
.cta {
text-decoration: none;
padding-inline: $spacing-xl;
padding-block: $spacing-sm;
background: $color-blue-50;
font-weight: 600;
color: $color-white;
border-radius: $border-radius-xs;
cursor: pointer;
&:focus {
outline: $border-focus-width solid $color-blue-30;
}
@media screen and (min-width: $screen-lg) {
margin-left: auto;
}
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import type { Meta, StoryObj } from "@storybook/react";
import { FalseDoorBanner } from "./FalseDoorBanner";
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta<typeof FalseDoorBanner> = {
title: "False Door Banner",
component: FalseDoorBanner,
};
export default meta;
type Story = StoryObj<typeof FalseDoorBanner>;
export const FalseDoorBannerDashboard: Story = {
args: {
checkIsOnDashboard: true,
link: "example.com",
},
};
export const FalseDoorBannerDefault: Story = {
args: {
checkIsOnDashboard: false,
link: "example.com",
},
};

Просмотреть файл

@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { render, screen } from "@testing-library/react";
import { it, expect } from "@jest/globals";
import { composeStory } from "@storybook/react";
import Meta, {
FalseDoorBannerDashboard,
FalseDoorBannerDefault,
} from "./FalseDoorBanner.stories";
it("shows false door test variant on dashboard", () => {
const ComposedFalseDoorBanner = composeStory(FalseDoorBannerDashboard, Meta);
render(<ComposedFalseDoorBanner />);
// substring match https://testing-library.com/docs/queries/about/#textmatch
const bannerElem = screen.getByText(
"Auto-delete data from sites trying to sell it",
{ exact: false }
);
expect(bannerElem).toBeInTheDocument();
});
it("shows false door test", () => {
const ComposedFalseDoorBanner = composeStory(FalseDoorBannerDefault, Meta);
render(<ComposedFalseDoorBanner />);
// substring match https://testing-library.com/docs/queries/about/#textmatch
const bannerElem = screen.getByText(
"Automatically remove data from sites trying to sell it",
{ exact: false }
);
expect(bannerElem).toBeInTheDocument();
});

Просмотреть файл

@ -0,0 +1,108 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use client";
import Image from "next/image";
import ShieldIcon from "./assets/shield-icon.svg";
import ShieldOutlineIcon from "./assets/shield-outline-icon.svg";
import styles from "./FalseDoorBanner.module.scss";
import { CloseBtn } from "../../../components/server/Icons";
import { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
import { useL10n } from "../../../hooks/l10n";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { appendUtmParams } from "../../../../utils/utmParams";
type HandleFalseDoorBanner = {
link: string;
};
// Ignoring full coverage here because we'll be creating a custom cookies hook
// TODO: MNTOR-2043
/* c8 ignore start */
export const HandleFalseDoorTest = (props: HandleFalseDoorBanner) => {
const [cookies, setCookie] = useCookies(["falseDoorDismissed"]);
const [shouldShowFalseDoor, setShouldShowFalseDoor] = useState(false);
const pathname = usePathname();
const isOnDashboard = pathname === "/user/breaches";
const waitlistLink = appendUtmParams({
linkUrl: props.link,
utmParams: {
utm_source: "monitor",
utm_medium: "monitor-product",
utm_content: `banner-phase-1-us_${pathname}`,
},
});
const handleDismiss = () => {
setCookie("falseDoorDismissed", "true", { path: "/" });
};
useEffect(() => {
setShouldShowFalseDoor(!cookies.falseDoorDismissed);
}, [cookies.falseDoorDismissed]);
return (
<>
{shouldShowFalseDoor && (
<FalseDoorBanner
checkIsOnDashboard={isOnDashboard}
link={waitlistLink}
onDismiss={handleDismiss}
/>
)}
</>
);
};
/* c8 ignore stop */
type FalseDoorBanner = {
onDismiss?: () => void;
checkIsOnDashboard: boolean;
link: string;
};
export const FalseDoorBanner = (props: FalseDoorBanner) => {
const l10n = useL10n();
const icon = props.checkIsOnDashboard ? ShieldOutlineIcon : ShieldIcon;
const content = (
<p>
{l10n.getString("false-door-test-content-part-one")}
<br />
{props.checkIsOnDashboard
? l10n.getString("false-door-test-content-part-two-dashboard")
: l10n.getString("false-door-test-content-part-two")}
</p>
);
return (
<div className={styles.falseDoorTestWrapper}>
<div className={styles.content}>
<div className={styles.imageAndCopy}>
<Image src={icon} alt="" className={styles.logo} />
{content}
</div>
<Link
className={styles.cta}
target="_blank"
href={props.link}
onClick={props.onDismiss}
>
{l10n.getString("false-door-test-cta")}
</Link>
</div>
<button className={styles.dismiss} onClick={props.onDismiss}>
<CloseBtn
alt={l10n.getString("false-door-test-popup-close")}
width="15"
height="15"
/>
</button>
</div>
);
};

Просмотреть файл

@ -0,0 +1,5 @@
<svg width="70" height="72" viewBox="0 0 70 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5288 16.8559C15.113 27.7003 15.7061 36.4495 18.3751 43.3568C21.0322 50.2333 25.8731 55.5922 34.8519 59.2261C43.8307 55.5922 48.6715 50.2333 51.3287 43.3568C53.9976 36.4495 54.5907 27.7003 54.175 16.8559L34.8519 11.8552L15.5288 16.8559ZM34.8519 7.74902L11.7785 13.7203C10.4967 38.2691 13.3168 55.5196 34.8519 63.4814C56.387 55.5196 59.2071 38.2691 57.9253 13.7203L34.8519 7.74902Z" fill="#E21587"/>
<path d="M15.4043 16.3722L15.0442 16.4654L15.03 16.8371C14.6137 27.6957 15.2021 36.5305 17.9095 43.5373C20.619 50.5495 25.565 56.0069 34.6651 59.6899L34.8527 59.7658L35.0403 59.6899C44.1404 56.0069 49.0863 50.5495 51.7959 43.5373C54.5033 36.5305 55.0917 27.6957 54.6754 16.8371L54.6612 16.4654L54.3011 16.3722L34.978 11.3714L34.8527 11.339L34.7274 11.3714L15.4043 16.3722Z" fill="#E21587" stroke="#E21587"/>
<path d="M27.1211 32.8427L32.7565 38.4781C33.1892 38.9109 33.9056 38.8569 34.2687 38.3642L44.8798 23.9634" stroke="#FFDFF0" stroke-width="2.5" stroke-linecap="round"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

Просмотреть файл

@ -0,0 +1,4 @@
<svg width="70" height="69" viewBox="0 0 70 69" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5288 16.1067C15.113 26.4691 15.7061 34.8295 18.3751 41.4297C21.0322 48.0007 25.8731 53.1213 34.8519 56.5938C43.8307 53.1213 48.6715 48.0007 51.3287 41.4297C53.9976 34.8295 54.5907 26.4691 54.175 16.1067L34.8519 11.3282L15.5288 16.1067ZM34.8519 7.40454L11.7785 13.1105C10.4967 36.5682 13.3168 53.052 34.8519 60.6599C56.387 53.052 59.2071 36.5682 57.9253 13.1105L34.8519 7.40454Z" fill="#E21587"/>
<path d="M27.1211 33.2614L32.7565 38.8968C33.1892 39.3296 33.9056 39.2756 34.2687 38.7829L44.8798 24.3821" stroke="#E21587" stroke-width="2.5" stroke-linecap="round"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 720 B

Просмотреть файл

@ -6,9 +6,23 @@ import { ReactNode } from "react";
import Script from "next/script";
import { L10nProvider } from "../../contextProviders/localization";
import { getL10nBundles } from "../functions/server/l10n";
import { HandleFalseDoorTest } from "./components/client/FalseDoorBanner";
import { isFlagEnabled } from "../functions/server/featureFlags";
import { getCountryCode } from "../functions/server/getCountryCode";
import { headers } from "next/headers";
import AppConstants from "../../appConstants";
export default function MigrationLayout({ children }: { children: ReactNode }) {
export default async function MigrationLayout({
children,
}: {
children: ReactNode;
}) {
const headersList = headers();
const l10nBundles = getL10nBundles();
const countryCode = getCountryCode(headersList);
const falseDoorFlag = await isFlagEnabled("FalseDoorTest");
const waitlistLink = AppConstants.FALSE_DOOR_TEST_LINK_PHASE_ONE;
return (
<L10nProvider bundleSources={l10nBundles}>
{/* This script predates the use of React and thus shouldnt wait for
@ -22,6 +36,9 @@ export default function MigrationLayout({ children }: { children: ReactNode }) {
/>
<Script type="module" src="/nextjs_migration/client/js/analytics.js" />
{children}
{falseDoorFlag && waitlistLink && countryCode.toLowerCase() === "us" && (
<HandleFalseDoorTest link={waitlistLink} />
)}
</L10nProvider>
);
}

Просмотреть файл

@ -7,10 +7,7 @@
import Link from "next/link";
import Image from "next/image";
import { ReactNode } from "react";
import {
FixNavigation,
NavigationItem,
} from "../../../../../../components/client/FixNavigation";
import { FixNavigation } from "../../../../../../components/client/FixNavigation";
import styles from "./fix.module.scss";
import ImageArrowLeft from "./images/icon-arrow-left.svg";
import ImageArrowRight from "./images/icon-arrow-right.svg";
@ -21,12 +18,6 @@ import stepHighRiskDataBreachesIcon from "./images/step-counter-high-risk.svg";
import stepLeakedPasswordsIcon from "./images/step-counter-leaked-passwords.svg";
import stepSecurityRecommendationsIcon from "./images/step-counter-security-recommendations.svg";
export type Props = {
navArrowBackVisible: boolean;
children: ReactNode;
navigationItems: Array<NavigationItem>;
};
// TODO:
// Add logic to protect routes for specific users (premium/not, scan started/not)
// Question: Can FXA redirect user back to specific URL (for returning upgrade users during fix data broker)
@ -57,7 +48,11 @@ function NavigationArrowNext() {
);
}
const FixLayout = (props: Props) => {
export type FixLayoutProps = {
children: ReactNode;
};
const FixLayout = (props: FixLayoutProps) => {
const navigationItemsContent = [
{
key: "data-broker-profiles",

Просмотреть файл

@ -11,6 +11,7 @@ import { Session } from "next-auth";
export type FeatureFlagsEnabled = {
FreeBrokerScan: boolean;
PremiumBrokerRemoval: boolean;
FalseDoorTest: boolean;
};
export async function isFlagEnabled(

Просмотреть файл

@ -60,7 +60,8 @@ const optionalEnvVars = [
'PORT',
'RECRUITMENT_BANNER_LINK',
'RECRUITMENT_BANNER_TEXT',
'SENTRY_DSN_LEGACY'
'SENTRY_DSN_LEGACY',
'FALSE_DOOR_TEST_LINK_PHASE_ONE'
]
/** @type {Record<string, string>} */