diff --git a/locales/en/unsubscribe-from-monthly-report.ftl b/locales/en/unsubscribe-from-monthly-report.ftl
new file mode 100644
index 00000000000..511ce3a625b
--- /dev/null
+++ b/locales/en/unsubscribe-from-monthly-report.ftl
@@ -0,0 +1,18 @@
+# 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/.
+
+# Confirm Unsubscription State
+
+unsubscribe-from-monthly-report-header = Unsubscribe from this email?
+unsubscribe-from-monthly-report-body = You’ll no longer receive the monthly { -brand-monitor } report, which tells you how many new exposures you’ve had each month and how many are fixed.
+unsubscribe-cta = Unsubscribe
+
+# Success Unsubscription State
+
+unsubscribe-success-from-monthly-report-header = You’re now unsubscribed
+unsubscribe-success-from-monthly-report-body = You can resubscribe or update your email preferences anytime from your { -brand-monitor } settings.
+
+# Error warning
+
+unsubscription-failed = Unsubscribe failed. Try again.
diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/SettingsPage.test.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/SettingsPage.test.tsx
index 89d9d21842e..561f3b3cba2 100644
--- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/SettingsPage.test.tsx
+++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/SettingsPage.test.tsx
@@ -57,6 +57,7 @@ jest.mock("next/navigation", () => ({
import { SettingsView } from "./View";
import { sanitizeEmailRow } from "../../../../../../functions/server/sanitize";
import { defaultExperimentData } from "../../../../../../../telemetry/generated/nimbus/experiments";
+import { SubscriberEmailPreferencesOutput } from "../../../../../../../db/tables/subscriber_email_preferences";
const subscriberId = 7;
const mockedSerializedSubscriber: SerializedSubscriber = {
@@ -207,11 +208,21 @@ const mockedSubscriptionBillingAmount = {
yearly: 13.37,
monthly: 42.42,
};
+const mockedSubscriberEmailPreferences: SubscriberEmailPreferencesOutput = {
+ id: 1337,
+ primary_email: "primary@example.com",
+ unsubscribe_token: "495398jfjvjfdj",
+ monthly_monitor_report_free: true,
+ monthly_monitor_report_free_at: new Date("1337-04-02T04:02:42.000Z"),
+ monthly_monitor_report: true,
+ monthly_monitor_report_at: new Date("1337-04-02T04:02:42.000Z"),
+};
it("passes the axe accessibility audit", async () => {
const { container } = render(
,
);
@@ -317,6 +329,7 @@ it("Add email address button is shown when fewer than five emails", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -353,6 +366,7 @@ it("preselects 'Send all breach alerts to the primary email address' if that's t
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -397,6 +411,7 @@ it("preselects 'Send breach alerts to the affected email address' if that's the
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -441,6 +456,7 @@ it("disables breach alert notification options if a user opts out of breach aler
enabledFeatureFlags={["UpdatedEmailPreferencesOption"]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -495,6 +511,7 @@ it("preselects primary email alert option", () => {
enabledFeatureFlags={["UpdatedEmailPreferencesOption"]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -535,6 +552,7 @@ it("unselects the breach alerts checkbox and sends a null value to the API", asy
enabledFeatureFlags={["UpdatedEmailPreferencesOption"]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -591,6 +609,7 @@ it("preselects the affected email comms option after a user decides to enable br
enabledFeatureFlags={["UpdatedEmailPreferencesOption"]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -638,6 +657,7 @@ it("sends a call to the API to change the email alert preferences when changing
enabledFeatureFlags={["UpdatedEmailPreferencesOption"]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -698,12 +718,13 @@ it("checks that monthly monitor report is enabled", () => {
]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
const monthlyMonitorReportBtn = screen.getByLabelText(
- "Monthly Monitor Plus report",
+ "Monthly Monitor report",
{ exact: false },
);
expect(monthlyMonitorReportBtn).toHaveAttribute("aria-checked", "true");
@@ -745,11 +766,12 @@ it("sends an API call to disable monthly monitor reports", async () => {
]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
const monthlyMonitorReportBtn = screen.getByLabelText(
- "Monthly Monitor Plus report",
+ "Monthly Monitor report",
{ exact: false },
);
@@ -791,6 +813,7 @@ it("refreshes the session token after changing email alert preferences, to ensur
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -827,6 +850,7 @@ it("marks unverified email addresses as such", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -864,6 +888,7 @@ it("calls the API to resend a verification email if requested to", async () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -911,6 +936,7 @@ it("calls the 'remove' action when clicking the rubbish bin icon", async () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -948,6 +974,7 @@ it("hides the Plus cancellation link if the user doesn't have Plus", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -984,6 +1011,7 @@ it("shows the Plus cancellation link if the user has Plus", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1027,6 +1055,7 @@ it("takes you through the cancellation dialog flow all the way to subplat", asyn
enabledFeatureFlags={["ConfirmCancellation", "CancellationFlow"]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1105,6 +1134,7 @@ it("closes the cancellation survey if the user selects nevermind, take me back",
enabledFeatureFlags={["ConfirmCancellation", "CancellationFlow"]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1162,6 +1192,7 @@ it("closes the cancellation dialog", async () => {
enabledFeatureFlags={["CancellationFlow"]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1212,6 +1243,7 @@ it("shows the account deletion button if the user does not have Plus", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1253,6 +1285,7 @@ it("warns about the consequences before deleting a free user's account", async (
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1296,6 +1329,7 @@ it("shows a loading state while account deletion is in progress", async () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1340,6 +1374,7 @@ it("shows the account deletion button if the user has Plus", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1381,6 +1416,7 @@ it("warns about the consequences before deleting a Plus user's account", async (
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1433,6 +1469,7 @@ it.skip("calls the 'add' action when adding another email address", async () =>
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1468,6 +1505,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1512,6 +1550,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1556,6 +1595,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1601,6 +1641,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1645,6 +1686,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1694,6 +1736,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1739,6 +1782,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1788,6 +1832,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1832,6 +1877,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1881,6 +1927,7 @@ describe("to learn about usage", () => {
enabledFeatureFlags={[]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -1942,6 +1989,7 @@ it("selects the coupon code discount cta and shows the all-set dialog step", asy
]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -2028,6 +2076,7 @@ it("shows error message if the applying the coupon code function was unsuccessfu
]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
@@ -2094,6 +2143,7 @@ it("does not show the coupon code if a user already has a coupon set", async ()
]}
experimentData={defaultExperimentData}
isMonthlySubscriber={true}
+ data={mockedSubscriberEmailPreferences}
/>
,
);
diff --git a/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/UnsubscribeMonthlyReport.module.scss b/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/UnsubscribeMonthlyReport.module.scss
new file mode 100644
index 00000000000..c0f3d4599de
--- /dev/null
+++ b/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/UnsubscribeMonthlyReport.module.scss
@@ -0,0 +1,24 @@
+@import "../../../../tokens";
+
+.unSubscribeMonthlyReportContainer {
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: $spacing-lg $spacing-sm;
+ gap: $spacing-lg;
+ text-align: center;
+
+ @media screen and (min-width: $screen-md) {
+ max-width: $content-lg;
+ }
+
+ h1 {
+ font: $text-title-md;
+ }
+
+ .cta {
+ max-width: $content-xs;
+ }
+}
diff --git a/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/UnsubscribeMonthlyReportView.tsx b/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/UnsubscribeMonthlyReportView.tsx
new file mode 100644
index 00000000000..f536c94e2d5
--- /dev/null
+++ b/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/UnsubscribeMonthlyReportView.tsx
@@ -0,0 +1,62 @@
+/* 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 { useState } from "react";
+import { Button } from "../../../../components/client/Button";
+import styles from "./UnsubscribeMonthlyReport.module.scss";
+import UnsubscriptionImage from "./images/confirm-unsubscribe.svg";
+import Image from "next/image";
+import { useL10n } from "../../../../hooks/l10n";
+
+export const UnsubscribeMonthlyReportView = ({ token }: { token: string }) => {
+ const [unsubscribeSuccess, setUnsubscribeSuccess] = useState(false);
+
+ const l10n = useL10n();
+ const copy = {
+ confirmation: {
+ header: l10n.getString("unsubscribe-from-monthly-report-header"),
+ body: l10n.getString("unsubscribe-from-monthly-report-body"),
+ },
+ success: {
+ header: l10n.getString("unsubscribe-success-from-monthly-report-header"),
+ body: l10n.getString("unsubscribe-success-from-monthly-report-body"),
+ },
+ };
+
+ const { header, body } = unsubscribeSuccess
+ ? copy.success
+ : copy.confirmation;
+
+ const handleUnsubscription = async () => {
+ try {
+ const response = await fetch(`/api/unsubscribe?token=${token}`, {
+ method: "GET",
+ });
+
+ if (response.ok) {
+ setUnsubscribeSuccess(true);
+ }
+ } catch (e) {
+ console.error("Error during unsubscription:", e);
+ }
+ };
+
+ return (
+
+
+
{header}
+
{body}
+
+
+ );
+};
diff --git a/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/images/confirm-unsubscribe.svg b/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/images/confirm-unsubscribe.svg
new file mode 100644
index 00000000000..3d41c32f603
--- /dev/null
+++ b/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/images/confirm-unsubscribe.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/page.tsx b/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/page.tsx
new file mode 100644
index 00000000000..5380754b784
--- /dev/null
+++ b/src/app/(proper_react)/(redesign)/(public)/unsubscribe-from-monthly-report/page.tsx
@@ -0,0 +1,18 @@
+/* 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 { UnsubscribeMonthlyReportView } from "./UnsubscribeMonthlyReportView";
+
+export default function Page({
+ searchParams,
+}: {
+ searchParams: { token?: string };
+}) {
+ const token = searchParams.token ?? "";
+ if (!token) {
+ console.error("Unsubscription token not provided");
+ }
+
+ return ;
+}