From 5906793a307e11981b0d80c2847072115b29c657 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Wed, 22 Nov 2023 22:55:04 +0100 Subject: [PATCH 01/19] feat: Mark high-risk breaches and security tips as resolved --- .../HighRiskBreachLayout.tsx | 48 +++++++++++++++---- .../SecurityRecommendationsLayout.tsx | 36 +++++++++++++- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 1870630a3fd..5c6d22ad711 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -5,6 +5,7 @@ "use client"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { ResolutionContainer } from "../ResolutionContainer"; import { ResolutionContent } from "../ResolutionContent"; import { Button } from "../../../../../../../components/server/Button"; @@ -31,6 +32,7 @@ export type HighRiskBreachLayoutProps = { export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { const l10n = useL10n(); + const router = useRouter(); const stepMap: Record = { ssn: "HighRiskSsn", @@ -57,8 +59,39 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { // The non-null assertion here should be safe since we already did this check // in `./[type]/page.tsx`: const { title, illustration, content, exposedData, type } = pageData!; - const hasBreaches = type !== "none"; + const isHighRiskBreachesStep = type !== "none"; const isStepDone = type === "done"; + const hasExposedData = exposedData.length; + + const handlePrimaryButtonPress = async () => { + if (!isHighRiskBreachesStep || !hasExposedData) { + router.push(nextStep.href); + return; + } + + try { + const response = await fetch("/api/v1/user/breaches/bulk-resolve", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + dataType: type, + }), + }); + + const result = await response.json(); + if (!result?.success) { + throw new Error( + `Could not resolve breach data class of type: ${props.type}`, + ); + } + + router.push(nextStep.href); + } catch (_error) { + // do nothing + } + }; return ( { - // TODO: MNTOR-1700 Add routing logic + fix event here - }} + autoFocus={true} + onPress={() => void handlePrimaryButtonPress()} > { // Theoretically, this page should never be shown if the user // has no breaches, unless the user directly visits its URL, so // no tests represents it either: /* c8 ignore next 3 */ - hasBreaches + isHighRiskBreachesStep ? l10n.getString("high-risk-breach-mark-as-fixed") : l10n.getString("high-risk-breach-none-continue") } - {hasBreaches && ( + {isHighRiskBreachesStep && ( {l10n.getString("high-risk-breach-skip")} @@ -107,7 +137,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { // Theoretically, this page should never be shown if the user has no // breaches, unless the user directly visits its URL, so no tests // represents it either: - estimatedTime={!isStepDone && hasBreaches ? 15 : undefined} + estimatedTime={!isStepDone && isHighRiskBreachesStep ? 15 : undefined} isStepDone={isStepDone} data={props.data} > diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index c9e4eb91eb2..03abe9a8fae 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -4,6 +4,7 @@ "use client"; +import { useRouter } from "next/navigation"; import { SecurityRecommendationTypes, getSecurityRecommendationsByType, @@ -32,6 +33,7 @@ export function SecurityRecommendationsLayout( props: SecurityRecommendationsLayoutProps, ) { const l10n = useL10n(); + const router = useRouter(); const stepMap: Record = { email: "SecurityTipsEmail", @@ -58,6 +60,37 @@ export function SecurityRecommendationsLayout( // The non-null assertion here should be safe since we already did this check // in `./[type]/page.tsx`: const { title, illustration, content, exposedData } = pageData!; + const hasExposedData = exposedData.length; + + const handlePrimaryButtonPress = async () => { + if (!hasExposedData) { + router.push(nextStep.href); + return; + } + + try { + const response = await fetch("/api/v1/user/breaches/bulk-resolve", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + dataType: props.type, + }), + }); + + const result = await response.json(); + if (!result?.success) { + throw new Error( + `Could not resolve breach data class of type: ${props.type}`, + ); + } + + router.push(nextStep.href); + } catch (_error) { + // do nothing + } + }; return ( void handlePrimaryButtonPress()} > {l10n.getString("security-recommendation-steps-cta-label")} From 5fd513feccc28b00e4d37352c1df0dac48ebd1e4 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 23 Nov 2023 16:52:18 +0100 Subject: [PATCH 02/19] feat: Determine data class to resolve --- .../HighRiskBreachLayout.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 5c6d22ad711..61a8c22b70f 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -23,6 +23,7 @@ import { } from "../../../../../../../functions/server/getRelevantGuidedSteps"; import { getGuidedExperienceBreaches } from "../../../../../../../functions/universal/guidedExperienceBreaches"; import { hasPremium } from "../../../../../../../functions/universal/user"; +import { HighRiskDataTypes } from "../../../../../../../functions/universal/breach"; export type HighRiskBreachLayoutProps = { type: HighRiskBreachTypes; @@ -63,9 +64,25 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { const isStepDone = type === "done"; const hasExposedData = exposedData.length; + if (hasExposedData) { + router.push(nextStep.href); + } + const handlePrimaryButtonPress = async () => { - if (!isHighRiskBreachesStep || !hasExposedData) { - router.push(nextStep.href); + const highRiskBreachClasses: Record< + HighRiskBreachTypes, + (typeof HighRiskDataTypes)[keyof typeof HighRiskDataTypes] | null + > = { + ssn: HighRiskDataTypes.SSN, + "credit-card": HighRiskDataTypes.CreditCard, + "bank-account": HighRiskDataTypes.BankAccount, + pin: HighRiskDataTypes.PIN, + none: null, + done: null, + }; + + const dataType = highRiskBreachClasses[type]; + if (!dataType || !hasExposedData) { return; } @@ -75,9 +92,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - dataType: type, - }), + body: JSON.stringify({ dataType }), }); const result = await response.json(); From 8ccbd66c691704b108f68a53018b85637a0b45f9 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 23 Nov 2023 16:53:36 +0100 Subject: [PATCH 03/19] chore: Update optional chaining of session props --- src/app/api/v1/user/breaches/bulk-resolve/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/api/v1/user/breaches/bulk-resolve/route.ts b/src/app/api/v1/user/breaches/bulk-resolve/route.ts index 25ce42a1e62..1519dac221b 100644 --- a/src/app/api/v1/user/breaches/bulk-resolve/route.ts +++ b/src/app/api/v1/user/breaches/bulk-resolve/route.ts @@ -19,7 +19,7 @@ import { export async function PUT(req: NextRequest): Promise { const session = await getServerSession(authOptions); - if (!session?.user?.subscriber || typeof session.user?.email !== "string") { + if (!session?.user?.subscriber || typeof session?.user?.email !== "string") { return new NextResponse( JSON.stringify({ success: false, message: "Unauthenticated" }), { status: 401 }, @@ -28,7 +28,7 @@ export async function PUT(req: NextRequest): Promise { try { const subscriber: Subscriber = await getSubscriberByEmail( - session?.user?.email, + session.user.email, ); const allBreaches = await getBreaches(); const { dataType: dataTypeToResolve }: BreachBulkResolutionRequest = From ea2d20b7fbe02aea09e557229fe29fa4758bb039 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 23 Nov 2023 20:22:59 +0100 Subject: [PATCH 04/19] chore: Only add guided experience breaches to data classes that are not already resolved --- .../HighRiskBreachLayout.tsx | 2 +- .../universal/guidedExperienceBreaches.ts | 57 ++++++++++++------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 61a8c22b70f..eaf5aaccc35 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -64,7 +64,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { const isStepDone = type === "done"; const hasExposedData = exposedData.length; - if (hasExposedData) { + if (!hasExposedData) { router.push(nextStep.href); } diff --git a/src/app/functions/universal/guidedExperienceBreaches.ts b/src/app/functions/universal/guidedExperienceBreaches.ts index 9bb73343dee..f37f9a63743 100644 --- a/src/app/functions/universal/guidedExperienceBreaches.ts +++ b/src/app/functions/universal/guidedExperienceBreaches.ts @@ -6,6 +6,19 @@ import { BreachDataTypes } from "./breach"; import { SubscriberBreach } from "../../../utils/subscriberBreaches"; import { GuidedExperienceBreaches } from "../server/getUserBreaches"; +function isUnresolvedDataBreachClass( + breach: SubscriberBreach, + breachDataClass: (typeof BreachDataTypes)[keyof typeof BreachDataTypes], +): boolean { + const affectedDataClasses = breach.dataClassesEffected.map( + (dataClass) => Object.keys(dataClass)[0], + ); + return ( + affectedDataClasses.includes(breachDataClass) && + !breach.resolvedDataClasses.includes(breachDataClass) + ); +} + export function getGuidedExperienceBreaches( subscriberBreaches: SubscriberBreach[], emails: string[], @@ -28,47 +41,51 @@ export function getGuidedExperienceBreaches( }, emails, }; - subscriberBreaches.forEach((b) => { + + subscriberBreaches.forEach((breach) => { // high risks - if (b.dataClasses.includes(BreachDataTypes.SSN)) { - guidedExperienceBreaches.highRisk.ssnBreaches.push(b); + if (isUnresolvedDataBreachClass(breach, BreachDataTypes.SSN)) { + guidedExperienceBreaches.highRisk.ssnBreaches.push(breach); } - if (b.dataClasses.includes(BreachDataTypes.CreditCard)) { - guidedExperienceBreaches.highRisk.creditCardBreaches.push(b); + if (isUnresolvedDataBreachClass(breach, BreachDataTypes.CreditCard)) { + guidedExperienceBreaches.highRisk.creditCardBreaches.push(breach); } - if (b.dataClasses.includes(BreachDataTypes.PIN)) { - guidedExperienceBreaches.highRisk.pinBreaches.push(b); + if (isUnresolvedDataBreachClass(breach, BreachDataTypes.PIN)) { + guidedExperienceBreaches.highRisk.pinBreaches.push(breach); } - if (b.dataClasses.includes(BreachDataTypes.BankAccount)) { - guidedExperienceBreaches.highRisk.bankBreaches.push(b); + if (isUnresolvedDataBreachClass(breach, BreachDataTypes.BankAccount)) { + guidedExperienceBreaches.highRisk.bankBreaches.push(breach); } // passwords - // TODO: Add tests when passwords component has been made - MNTOR-1712 /* c8 ignore start */ - if (b.dataClasses.includes(BreachDataTypes.Passwords)) { - guidedExperienceBreaches.passwordBreaches.passwords.push(b); + if (isUnresolvedDataBreachClass(breach, BreachDataTypes.Passwords)) { + guidedExperienceBreaches.passwordBreaches.passwords.push(breach); } - if (b.dataClasses.includes(BreachDataTypes.SecurityQuestions)) { - guidedExperienceBreaches.passwordBreaches.securityQuestions.push(b); + if ( + isUnresolvedDataBreachClass(breach, BreachDataTypes.SecurityQuestions) + ) { + guidedExperienceBreaches.passwordBreaches.securityQuestions.push(breach); } // security recommendations // TODO: Add tests when security recs work is merged in - if (b.dataClasses.includes(BreachDataTypes.Phone)) { - guidedExperienceBreaches.securityRecommendations.phoneNumber.push(b); + if (isUnresolvedDataBreachClass(breach, BreachDataTypes.Phone)) { + guidedExperienceBreaches.securityRecommendations.phoneNumber.push(breach); } - if (b.dataClasses.includes(BreachDataTypes.Email)) { - guidedExperienceBreaches.securityRecommendations.emailAddress.push(b); + if (isUnresolvedDataBreachClass(breach, BreachDataTypes.Email)) { + guidedExperienceBreaches.securityRecommendations.emailAddress.push( + breach, + ); } - if (b.dataClasses.includes(BreachDataTypes.IP)) { - guidedExperienceBreaches.securityRecommendations.IPAddress.push(b); + if (isUnresolvedDataBreachClass(breach, BreachDataTypes.IP)) { + guidedExperienceBreaches.securityRecommendations.IPAddress.push(breach); } /* c8 ignore stop */ }); From 36a477b4f578722162ae58c31cc8c13ee3ba2f1d Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 23 Nov 2023 20:40:40 +0100 Subject: [PATCH 05/19] chore: Disable fix button while resolving breaches --- .../HighRiskBreachLayout.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index eaf5aaccc35..89f1d367c3c 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -4,6 +4,7 @@ "use client"; +import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { ResolutionContainer } from "../ResolutionContainer"; @@ -32,6 +33,7 @@ export type HighRiskBreachLayoutProps = { }; export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { + const [isResolving, setIsResolving] = useState(false); const l10n = useL10n(); const router = useRouter(); @@ -64,11 +66,8 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { const isStepDone = type === "done"; const hasExposedData = exposedData.length; - if (!hasExposedData) { - router.push(nextStep.href); - } - const handlePrimaryButtonPress = async () => { + setIsResolving(true); const highRiskBreachClasses: Record< HighRiskBreachTypes, (typeof HighRiskDataTypes)[keyof typeof HighRiskDataTypes] | null @@ -82,7 +81,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { }; const dataType = highRiskBreachClasses[type]; - if (!dataType || !hasExposedData) { + if (!dataType || !hasExposedData || isResolving) { return; } @@ -102,9 +101,9 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { ); } - router.push(nextStep.href); + router.push("/redesign/user/dashboard/fix/high-risk-data-breaches/done"); } catch (_error) { - // do nothing + setIsResolving(false); } }; @@ -130,6 +129,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { small autoFocus={true} onPress={() => void handlePrimaryButtonPress()} + disabled={isResolving} > { // Theoretically, this page should never be shown if the user From dfafa64992886a90fb07ab41794b3ec552aff838 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 23 Nov 2023 21:29:07 +0100 Subject: [PATCH 06/19] chore: Redirect after security tips are resolved --- .../SecurityRecommendationsLayout.tsx | 30 ++++++++++++++----- src/app/functions/universal/breach.ts | 6 ++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index 03abe9a8fae..c3b2f5d3034 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -4,6 +4,7 @@ "use client"; +import { useState } from "react"; import { useRouter } from "next/navigation"; import { SecurityRecommendationTypes, @@ -22,6 +23,7 @@ import { } from "../../../../../../../functions/server/getRelevantGuidedSteps"; import { getGuidedExperienceBreaches } from "../../../../../../../functions/universal/guidedExperienceBreaches"; import { hasPremium } from "../../../../../../../functions/universal/user"; +import { SecurityRecommendationDataTypes } from "../../../../../../../functions/universal/breach"; export interface SecurityRecommendationsLayoutProps { type: SecurityRecommendationTypes; @@ -32,6 +34,7 @@ export interface SecurityRecommendationsLayoutProps { export function SecurityRecommendationsLayout( props: SecurityRecommendationsLayoutProps, ) { + const [isResolving, setIsResolving] = useState(false); const l10n = useL10n(); const router = useRouter(); @@ -63,8 +66,20 @@ export function SecurityRecommendationsLayout( const hasExposedData = exposedData.length; const handlePrimaryButtonPress = async () => { - if (!hasExposedData) { - router.push(nextStep.href); + setIsResolving(true); + const securityRecommendatioBreachClasses: Record< + SecurityRecommendationTypes, + | (typeof SecurityRecommendationDataTypes)[keyof typeof SecurityRecommendationDataTypes] + | null + > = { + email: SecurityRecommendationDataTypes.Email, + phone: SecurityRecommendationDataTypes.Phone, + ip: SecurityRecommendationDataTypes.IP, + done: null, + }; + + const dataType = securityRecommendatioBreachClasses[props.type]; + if (!dataType || !hasExposedData || isResolving) { return; } @@ -74,9 +89,7 @@ export function SecurityRecommendationsLayout( headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - dataType: props.type, - }), + body: JSON.stringify({ dataType }), }); const result = await response.json(); @@ -86,9 +99,9 @@ export function SecurityRecommendationsLayout( ); } - router.push(nextStep.href); + router.push("/redesign/user/dashboard/fix/high-risk-data-breaches/done"); } catch (_error) { - // do nothing + setIsResolving(false); } }; @@ -96,7 +109,7 @@ export function SecurityRecommendationsLayout( void handlePrimaryButtonPress()} + disabled={isResolving} > {l10n.getString("security-recommendation-steps-cta-label")} diff --git a/src/app/functions/universal/breach.ts b/src/app/functions/universal/breach.ts index 0c9b9905ce1..c35ce7a207d 100644 --- a/src/app/functions/universal/breach.ts +++ b/src/app/functions/universal/breach.ts @@ -26,3 +26,9 @@ export const HighRiskDataTypes = { BankAccount: BreachDataTypes.BankAccount, PIN: BreachDataTypes.PIN, } as const; + +export const SecurityRecommendationDataTypes = { + Email: BreachDataTypes.Email, + Phone: BreachDataTypes.Phone, + IP: BreachDataTypes.IP, +} as const; From 4ad7fb80ad84fd2ffcee7881f5d1281245233f60 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 23 Nov 2023 22:04:58 +0100 Subject: [PATCH 07/19] chore: Navigate to next resolution step --- .../high-risk-data-breaches/HighRiskBreachLayout.tsx | 9 ++++++++- .../SecurityRecommendationsLayout.tsx | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 89f1d367c3c..1302b75536c 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -16,6 +16,7 @@ import { FixView } from "../FixView"; import { HighRiskBreachTypes, getHighRiskBreachesByType, + highRiskBreachTypes, } from "./highRiskBreachData"; import { StepDeterminationData, @@ -101,7 +102,13 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { ); } - router.push("/redesign/user/dashboard/fix/high-risk-data-breaches/done"); + const nextStepMapId = + !isStepDone && + highRiskBreachTypes[highRiskBreachTypes.indexOf(props.type) + 1]; + const nextRoute = nextStepMapId + ? `/redesign/user/dashboard/fix/high-risk-data-breaches/${nextStepMapId}` + : nextStep.href; + router.push(nextRoute); } catch (_error) { setIsResolving(false); } diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index c3b2f5d3034..303638b6bc1 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -9,6 +9,7 @@ import { useRouter } from "next/navigation"; import { SecurityRecommendationTypes, getSecurityRecommendationsByType, + securityRecommendationTypes, } from "./securityRecommendationsData"; import { ResolutionContainer } from "../ResolutionContainer"; import { ResolutionContent } from "../ResolutionContent"; @@ -99,7 +100,15 @@ export function SecurityRecommendationsLayout( ); } - router.push("/redesign/user/dashboard/fix/high-risk-data-breaches/done"); + const nextStepMapId = + !isStepDone && + securityRecommendationTypes[ + securityRecommendationTypes.indexOf(props.type) + 1 + ]; + const nextRoute = nextStepMapId + ? `/redesign/user/dashboard/fix/security-recommendations/${nextStepMapId}` + : nextStep.href; + router.push(nextRoute); } catch (_error) { setIsResolving(false); } From dda719f543d5d7798fee64699251fa66582c4305 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Thu, 23 Nov 2023 22:19:47 +0100 Subject: [PATCH 08/19] chore: Redirect to celebration screen when all steps in the current section are resolved --- .../HighRiskBreachLayout.tsx | 17 +++++++++-------- .../LeakedPasswordsLayout.tsx | 16 +++++++++------- .../SecurityRecommendationsLayout.tsx | 19 +++++++++---------- src/app/components/client/FixNavigation.tsx | 2 -- .../server/getRelevantGuidedSteps.ts | 12 ++++++++++++ 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 1302b75536c..4f06a13694d 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -6,7 +6,7 @@ import { useState } from "react"; import Link from "next/link"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { ResolutionContainer } from "../ResolutionContainer"; import { ResolutionContent } from "../ResolutionContent"; import { Button } from "../../../../../../../components/server/Button"; @@ -16,11 +16,11 @@ import { FixView } from "../FixView"; import { HighRiskBreachTypes, getHighRiskBreachesByType, - highRiskBreachTypes, } from "./highRiskBreachData"; import { StepDeterminationData, StepLink, + getIsNextStepSectionFromPathname, getNextGuidedStep, } from "../../../../../../../functions/server/getRelevantGuidedSteps"; import { getGuidedExperienceBreaches } from "../../../../../../../functions/universal/guidedExperienceBreaches"; @@ -34,9 +34,10 @@ export type HighRiskBreachLayoutProps = { }; export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { - const [isResolving, setIsResolving] = useState(false); const l10n = useL10n(); const router = useRouter(); + const pathname = usePathname(); + const [isResolving, setIsResolving] = useState(false); const stepMap: Record = { ssn: "HighRiskSsn", @@ -102,11 +103,11 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { ); } - const nextStepMapId = - !isStepDone && - highRiskBreachTypes[highRiskBreachTypes.indexOf(props.type) + 1]; - const nextRoute = nextStepMapId - ? `/redesign/user/dashboard/fix/high-risk-data-breaches/${nextStepMapId}` + const nextRoute = getIsNextStepSectionFromPathname({ + currentPath: pathname, + nextPath: nextStep.href, + }) + ? "/redesign/user/dashboard/fix/high-risk-data-breaches/done" : nextStep.href; router.push(nextRoute); } catch (_error) { diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx index 1f47f0a2a7a..499279d0fcd 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx @@ -37,6 +37,7 @@ export interface LeakedPasswordsLayoutProps { export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { const l10n = useL10n(); const router = useRouter(); + const [isResolving, setIsResolving] = useState(false); const [subscriberBreaches, setSubscriberBreaches] = useState( props.data.subscriberBreaches, ); @@ -100,7 +101,7 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { formattedDataClasses, ); - // Manually move to the next step when mark is fixed is selected + // Manually move to the next step when breach has been marked as fixed. const updatedSubscriberBreaches = subscriberBreaches.map( (subscriberBreach) => { if (subscriberBreach.id === unresolvedPasswordBreach.id) { @@ -117,13 +118,13 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { if (!isComplete) { setSubscriberBreaches(updatedSubscriberBreaches); + return; } - // If all breaches in the step is fully resolved, take users to the next step - else { - router.push(nextStep.href); - } - } catch (error) { - console.error("Error updating breach status", error); + // If all breaches in the step are fully resolved, + // take users to the celebration view. + router.push("/redesign/user/dashboard/fix/leaked-passwords/done"); + } catch (_error) { + setIsResolving(false); } }; /* c8 ignore stop */ @@ -155,6 +156,7 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { void handleUpdateBreachStatus(); }} autoFocus={true} + disabled={isResolving} > {l10n.getString("leaked-passwords-mark-as-fixed")} diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index 303638b6bc1..033e500e13c 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -5,11 +5,10 @@ "use client"; import { useState } from "react"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { SecurityRecommendationTypes, getSecurityRecommendationsByType, - securityRecommendationTypes, } from "./securityRecommendationsData"; import { ResolutionContainer } from "../ResolutionContainer"; import { ResolutionContent } from "../ResolutionContent"; @@ -20,6 +19,7 @@ import { FixView } from "../FixView"; import { StepDeterminationData, StepLink, + getIsNextStepSectionFromPathname, getNextGuidedStep, } from "../../../../../../../functions/server/getRelevantGuidedSteps"; import { getGuidedExperienceBreaches } from "../../../../../../../functions/universal/guidedExperienceBreaches"; @@ -35,9 +35,10 @@ export interface SecurityRecommendationsLayoutProps { export function SecurityRecommendationsLayout( props: SecurityRecommendationsLayoutProps, ) { - const [isResolving, setIsResolving] = useState(false); const l10n = useL10n(); const router = useRouter(); + const pathname = usePathname(); + const [isResolving, setIsResolving] = useState(false); const stepMap: Record = { email: "SecurityTipsEmail", @@ -100,13 +101,11 @@ export function SecurityRecommendationsLayout( ); } - const nextStepMapId = - !isStepDone && - securityRecommendationTypes[ - securityRecommendationTypes.indexOf(props.type) + 1 - ]; - const nextRoute = nextStepMapId - ? `/redesign/user/dashboard/fix/security-recommendations/${nextStepMapId}` + const nextRoute = getIsNextStepSectionFromPathname({ + currentPath: pathname, + nextPath: nextStep.href, + }) + ? "/redesign/user/dashboard/fix/security-recommendations/done" : nextStep.href; router.push(nextRoute); } catch (_error) { diff --git a/src/app/components/client/FixNavigation.tsx b/src/app/components/client/FixNavigation.tsx index 961269d8c99..7bd18c42f50 100644 --- a/src/app/components/client/FixNavigation.tsx +++ b/src/app/components/client/FixNavigation.tsx @@ -57,8 +57,6 @@ export const Steps = (props: { breachesByClassification.highRisk, ).reduce((acc, array) => acc + array.length, 0); const totalDataBrokerProfiles = - // No tests simulate the absence of scan data yet: - /* c8 ignore next */ props.data.latestScanData?.results.length ?? 0; const totalPasswordBreaches = Object.values( breachesByClassification.passwordBreaches, diff --git a/src/app/functions/server/getRelevantGuidedSteps.ts b/src/app/functions/server/getRelevantGuidedSteps.ts index b7103681bfc..4a94cc90554 100644 --- a/src/app/functions/server/getRelevantGuidedSteps.ts +++ b/src/app/functions/server/getRelevantGuidedSteps.ts @@ -286,3 +286,15 @@ export function hasCompletedStep( return false as never; } + +export function getIsNextStepSectionFromPathname({ + currentPath, + nextPath, +}: { + currentPath: string; + nextPath: StepLink["href"]; +}): boolean { + const getBasePath = (path: string) => + path.substring(0, path.lastIndexOf("/")); + return getBasePath(currentPath) !== getBasePath(nextPath); +} From 8e73cc0e65dc34ebd19dc005fc36a424dab19d62 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 13:02:42 +0100 Subject: [PATCH 09/19] feat: Redirect to celebration screen after leaked passwords steps are resolved --- .../HighRiskBreachLayout.tsx | 2 +- .../LeakedPasswordsLayout.tsx | 129 ++++++++++-------- .../SecurityRecommendationsLayout.tsx | 2 +- src/app/functions/universal/breach.ts | 5 + 4 files changed, 77 insertions(+), 61 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 4f06a13694d..1f82690d64d 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -69,7 +69,6 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { const hasExposedData = exposedData.length; const handlePrimaryButtonPress = async () => { - setIsResolving(true); const highRiskBreachClasses: Record< HighRiskBreachTypes, (typeof HighRiskDataTypes)[keyof typeof HighRiskDataTypes] | null @@ -87,6 +86,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { return; } + setIsResolving(true); try { const response = await fetch("/api/v1/user/breaches/bulk-resolve", { method: "PUT", diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx index 499279d0fcd..0e89c2ae189 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx @@ -27,6 +27,7 @@ import { getGuidedExperienceBreaches } from "../../../../../../../functions/univ import { hasPremium } from "../../../../../../../functions/universal/user"; import { useState } from "react"; import { useRouter } from "next/navigation"; +import { LeakedPasswordsDataTypes } from "../../../../../../../functions/universal/breach"; export interface LeakedPasswordsLayoutProps { type: LeakedPasswordsTypes; @@ -49,12 +50,13 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { "security-questions-done": "LeakedPasswordsSecurityQuestion", none: "LeakedPasswordsSecurityQuestion", }; + const isStepDone = + props.type === "passwords-done" || props.type === "security-questions-done"; const guidedExperienceBreaches = getGuidedExperienceBreaches( subscriberBreaches, props.subscriberEmails, ); - const unresolvedPasswordBreach = findFirstUnresolvedBreach( guidedExperienceBreaches, props.type, @@ -68,15 +70,7 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { ) ?? ""; const nextStep = getNextGuidedStep(props.data, stepMap[props.type]); - - // Data class string to push to resolutionsChecked array - const resolvedDataClassName = - props.type === "passwords" ? "passwords" : "security-questions-and-answers"; - - const isStepDone = - props.type === "passwords-done" || props.type === "security-questions-done"; - - const unresolvedPasswordBreachContent = getLeakedPasswords({ + const pageData = getLeakedPasswords({ dataType: props.type, breaches: guidedExperienceBreaches, l10n, @@ -84,13 +78,31 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { nextStep, }); + // The non-null assertion here should be safe since we already did this check + // in `./[type]/page.tsx`: + const { title, illustration, content } = pageData!; + const handleUpdateBreachStatus = async () => { - if (!unresolvedPasswordBreach || !emailAffected) { + const leakedPasswordsBreachClasses: Record< + LeakedPasswordsTypes, + | (typeof LeakedPasswordsDataTypes)[keyof typeof LeakedPasswordsDataTypes] + | null + > = { + passwords: LeakedPasswordsDataTypes.Passwords, + "passwords-done": null, + "security-questions": LeakedPasswordsDataTypes.SecurityQuestions, + "security-questions-done": null, + none: null, + }; + + const dataType = leakedPasswordsBreachClasses[props.type]; + if (!dataType || !unresolvedPasswordBreach || !emailAffected) { return; } + setIsResolving(true); try { - unresolvedPasswordBreach.resolvedDataClasses.push(resolvedDataClassName); + unresolvedPasswordBreach.resolvedDataClasses.push(dataType); // FIXME/BUG: MNTOR-2562 Remove empty [""] string const formattedDataClasses = unresolvedPasswordBreach.resolvedDataClasses.filter(Boolean); @@ -105,7 +117,7 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { const updatedSubscriberBreaches = subscriberBreaches.map( (subscriberBreach) => { if (subscriberBreach.id === unresolvedPasswordBreach.id) { - subscriberBreach.resolvedDataClasses.push(resolvedDataClassName); + subscriberBreach.resolvedDataClasses.push(dataType); } return subscriberBreach; }, @@ -118,11 +130,16 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { if (!isComplete) { setSubscriberBreaches(updatedSubscriberBreaches); + setIsResolving(false); return; } // If all breaches in the step are fully resolved, // take users to the celebration view. - router.push("/redesign/user/dashboard/fix/leaked-passwords/done"); + const doneSlug: LeakedPasswordsTypes = + props.type === "passwords" + ? "passwords-done" + : "security-questions-done"; + router.push(`/redesign/user/dashboard/fix/leaked-passwords/${doneSlug}`); } catch (_error) { setIsResolving(false); } @@ -130,52 +147,46 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { /* c8 ignore stop */ return ( - unresolvedPasswordBreachContent && - unresolvedPasswordBreach && ( - + + + + {l10n.getString("leaked-passwords-skip")} + + + ) + } + estimatedTime={!isStepDone ? 4 : undefined} + isStepDone={isStepDone} data={props.data} - nextStep={nextStep} - currentSection="leaked-passwords" - hideProgressIndicator={isStepDone} - showConfetti={isStepDone} > - - - - {l10n.getString("leaked-passwords-skip")} - - - ) - } - estimatedTime={!isStepDone ? 4 : undefined} - isStepDone={isStepDone} - data={props.data} - > - - - - ) + + + ); } diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index 033e500e13c..119301da0d0 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -68,7 +68,6 @@ export function SecurityRecommendationsLayout( const hasExposedData = exposedData.length; const handlePrimaryButtonPress = async () => { - setIsResolving(true); const securityRecommendatioBreachClasses: Record< SecurityRecommendationTypes, | (typeof SecurityRecommendationDataTypes)[keyof typeof SecurityRecommendationDataTypes] @@ -85,6 +84,7 @@ export function SecurityRecommendationsLayout( return; } + setIsResolving(true); try { const response = await fetch("/api/v1/user/breaches/bulk-resolve", { method: "PUT", diff --git a/src/app/functions/universal/breach.ts b/src/app/functions/universal/breach.ts index c35ce7a207d..37851316a7d 100644 --- a/src/app/functions/universal/breach.ts +++ b/src/app/functions/universal/breach.ts @@ -27,6 +27,11 @@ export const HighRiskDataTypes = { PIN: BreachDataTypes.PIN, } as const; +export const LeakedPasswordsDataTypes = { + Passwords: BreachDataTypes.Passwords, + SecurityQuestions: BreachDataTypes.SecurityQuestions, +} as const; + export const SecurityRecommendationDataTypes = { Email: BreachDataTypes.Email, Phone: BreachDataTypes.Phone, From 084efb013809fa6032c01c9d19e09fcc8392a347 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 13:15:53 +0100 Subject: [PATCH 10/19] chore: Show guided step navigation count only if they are not resolved --- src/app/components/client/FixNavigation.tsx | 49 ++++++++++++--------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/app/components/client/FixNavigation.tsx b/src/app/components/client/FixNavigation.tsx index 7bd18c42f50..a871fd5fd59 100644 --- a/src/app/components/client/FixNavigation.tsx +++ b/src/app/components/client/FixNavigation.tsx @@ -4,6 +4,7 @@ "use client"; +import { ReactNode } from "react"; import Image from "next/image"; import styles from "./FixNavigation.module.scss"; import stepDataBrokerProfilesIcon from "../../(proper_react)/redesign/(authenticated)/user/dashboard/fix/images/step-counter-data-broker-profiles.svg"; @@ -67,6 +68,18 @@ export const Steps = (props: { return value.length > 0; }).length; + const StepLabel = ({ + label, + count, + }: { + label: string; + count: number; + }): ReactNode => ( +
+ {label} {count > 0 && `(${count})`} +
+ ); + return (
    {isEligibleForStep(props.data, "Scan") && ( @@ -85,11 +98,10 @@ export const Steps = (props: {
    - -
    - {l10n.getString("fix-flow-nav-data-broker-profiles")} ( - {totalDataBrokerProfiles}) -
    + )}
  • - -
    - {l10n.getString("fix-flow-nav-high-risk-data-breaches")} ( - {totalHighRiskBreaches}) -
    +
  • - -
    - {l10n.getString("fix-flow-nav-leaked-passwords")} ( - {totalPasswordBreaches}) -
    +
  • - -
    - {l10n.getString("fix-flow-nav-security-recommendations")} ( - {totalSecurityRecommendations}) -
    +
  • From 4d30de405c236cf2afb991c1d2d7992a55298bbf Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 13:44:23 +0100 Subject: [PATCH 11/19] fix: Unit tests --- .../HighRiskBreachLayout.tsx | 4 ++ .../[type]/HighRiskBreachLayout.test.tsx | 5 ++ .../SecurityRecommendationsLayout.tsx | 4 ++ .../[type]/SecurityRecommendations.test.tsx | 5 ++ .../guidedExperienceBreaches.test.ts | 64 ++++++++++++++++++- 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 1f82690d64d..63f43e7976e 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -68,6 +68,8 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { const isStepDone = type === "done"; const hasExposedData = exposedData.length; + // TODO: Write unit tests MNTOR-2560 + /* c8 ignore start */ const handlePrimaryButtonPress = async () => { const highRiskBreachClasses: Record< HighRiskBreachTypes, @@ -114,6 +116,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { setIsResolving(false); } }; + /* c8 ignore stop */ return ( void handlePrimaryButtonPress()} disabled={isResolving} > diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/[type]/HighRiskBreachLayout.test.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/[type]/HighRiskBreachLayout.test.tsx index 61837d7e396..cac121595fe 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/[type]/HighRiskBreachLayout.test.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/[type]/HighRiskBreachLayout.test.tsx @@ -16,6 +16,11 @@ import Meta, { HighRiskBreachDoneStory, } from "./HighRiskDataBreach.stories"; +jest.mock("next/navigation", () => ({ + useRouter: jest.fn(), + usePathname: jest.fn(), +})); + beforeEach(() => { setupJestCanvasMock(); }); diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index 119301da0d0..deed8ec2d16 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -67,6 +67,8 @@ export function SecurityRecommendationsLayout( const { title, illustration, content, exposedData } = pageData!; const hasExposedData = exposedData.length; + // TODO: Write unit tests MNTOR-2560 + /* c8 ignore start */ const handlePrimaryButtonPress = async () => { const securityRecommendatioBreachClasses: Record< SecurityRecommendationTypes, @@ -112,6 +114,7 @@ export function SecurityRecommendationsLayout( setIsResolving(false); } }; + /* c8 ignore stop */ return ( void handlePrimaryButtonPress()} disabled={isResolving} > diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/[type]/SecurityRecommendations.test.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/[type]/SecurityRecommendations.test.tsx index ca17bfcdb9b..ac95101f89d 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/[type]/SecurityRecommendations.test.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/[type]/SecurityRecommendations.test.tsx @@ -14,6 +14,11 @@ import Meta, { DoneStory, } from "./SecurityRecommendations.stories"; +jest.mock("next/navigation", () => ({ + useRouter: jest.fn(), + usePathname: jest.fn(), +})); + beforeEach(() => { setupJestCanvasMock(); }); diff --git a/src/app/functions/universal/guidedExperienceBreaches.test.ts b/src/app/functions/universal/guidedExperienceBreaches.test.ts index c0f314d0f0b..38a99f88f47 100644 --- a/src/app/functions/universal/guidedExperienceBreaches.test.ts +++ b/src/app/functions/universal/guidedExperienceBreaches.test.ts @@ -7,7 +7,7 @@ import { getGuidedExperienceBreaches } from "./guidedExperienceBreaches"; import { SubscriberBreach } from "../../../utils/subscriberBreaches"; import { BreachDataTypes } from "./breach"; -it("getGuidedExperienceBreaches: return guided experience", () => { +it("getGuidedExperienceBreaches: return all guided experience breaches if they have relevant data classes", () => { const subBreach: SubscriberBreach = { addedDate: new Date(), breachDate: new Date(), @@ -35,7 +35,20 @@ it("getGuidedExperienceBreaches: return guided experience", () => { name: "", title: "", emailsAffected: ["test@mozilla.com"], - dataClassesEffected: [], + dataClassesEffected: [ + { [BreachDataTypes.PIN]: 1 }, + { [BreachDataTypes.Passwords]: 1 }, + { [BreachDataTypes.Address]: 1 }, + { [BreachDataTypes.BankAccount]: 1 }, + { [BreachDataTypes.CreditCard]: 1 }, + { [BreachDataTypes.DoB]: 1 }, + { [BreachDataTypes.Email]: 1 }, + { [BreachDataTypes.HistoricalPasswords]: 1 }, + { [BreachDataTypes.IP]: 1 }, + { [BreachDataTypes.Phone]: 1 }, + { [BreachDataTypes.SSN]: 1 }, + { [BreachDataTypes.SecurityQuestions]: 1 }, + ], }; const guidedExp = getGuidedExperienceBreaches( @@ -53,3 +66,50 @@ it("getGuidedExperienceBreaches: return guided experience", () => { expect(guidedExp.securityRecommendations.emailAddress).toHaveLength(1); expect(guidedExp.securityRecommendations.IPAddress).toHaveLength(1); }); + +it("getGuidedExperienceBreaches: exclude guided experience breaches if they do note have the relevant classes", () => { + const subBreach: SubscriberBreach = { + addedDate: new Date(), + breachDate: new Date(), + dataClasses: [ + BreachDataTypes.PIN, + BreachDataTypes.Passwords, + BreachDataTypes.Address, + BreachDataTypes.BankAccount, + BreachDataTypes.CreditCard, + BreachDataTypes.DoB, + BreachDataTypes.Email, + BreachDataTypes.HistoricalPasswords, + BreachDataTypes.IP, + BreachDataTypes.Phone, + BreachDataTypes.SSN, + BreachDataTypes.SecurityQuestions, + ], + resolvedDataClasses: [], + description: "", + domain: "", + id: 1, + isResolved: false, + favIconUrl: "", + modifiedDate: new Date(), + name: "", + title: "", + emailsAffected: ["test@mozilla.com"], + dataClassesEffected: [{ [BreachDataTypes.PIN]: 1 }], + }; + + const guidedExp = getGuidedExperienceBreaches( + [subBreach], + ["test@mozilla.com"], + ); + expect(guidedExp.highRisk.pinBreaches).toHaveLength(1); + expect(guidedExp.emails).toHaveLength(1); + expect(guidedExp.highRisk.ssnBreaches).toHaveLength(0); + expect(guidedExp.highRisk.creditCardBreaches).toHaveLength(0); + expect(guidedExp.highRisk.bankBreaches).toHaveLength(0); + expect(guidedExp.passwordBreaches.passwords).toHaveLength(0); + expect(guidedExp.passwordBreaches.securityQuestions).toHaveLength(0); + expect(guidedExp.securityRecommendations.phoneNumber).toHaveLength(0); + expect(guidedExp.securityRecommendations.emailAddress).toHaveLength(0); + expect(guidedExp.securityRecommendations.IPAddress).toHaveLength(0); +}); From 699b1e83e2cf5523162e1df3877a6e59650ee436 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 13:54:48 +0100 Subject: [PATCH 12/19] chore: Add unit tests for getIsNextStepSectionFromPathname --- .../server/getRelevantGuidedSteps.test.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/app/functions/server/getRelevantGuidedSteps.test.ts b/src/app/functions/server/getRelevantGuidedSteps.test.ts index ae06aff4c95..6ac10171f85 100644 --- a/src/app/functions/server/getRelevantGuidedSteps.test.ts +++ b/src/app/functions/server/getRelevantGuidedSteps.test.ts @@ -3,7 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { describe, expect, it } from "@jest/globals"; -import { getNextGuidedStep } from "./getRelevantGuidedSteps"; +import { + getNextGuidedStep, + getIsNextStepSectionFromPathname, +} from "./getRelevantGuidedSteps"; import { createRandomBreach, createRandomScanResult, @@ -857,3 +860,25 @@ describe("getNextGuidedStep", () => { }); }); }); + +describe("getIsNextStepSectionFromPathname", () => { + it("returns false if the next step pathname links to the next step section", () => { + expect( + getIsNextStepSectionFromPathname({ + currentPath: + "/redesign/user/dashboard/fix/high-risk-data-breaches/credit-card", + nextPath: "/redesign/user/dashboard/fix/high-risk-data-breaches/pin", + }), + ).toBeFalsy(); + }); + + it("returns true if the next step pathname links to the next step section", () => { + expect( + getIsNextStepSectionFromPathname({ + currentPath: + "/redesign/user/dashboard/fix/high-risk-data-breaches/credit-card", + nextPath: "/redesign/user/dashboard/fix/security-recommendations/phone", + }), + ).toBeTruthy(); + }); +}); From 76fc5425c75c14ef2542d21b01becdc7328cb433 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 15:24:00 +0100 Subject: [PATCH 13/19] chore: Derive step section change from step ID --- .../HighRiskBreachLayout.tsx | 14 ++++------ .../SecurityRecommendationsLayout.tsx | 14 ++++------ .../server/getRelevantGuidedSteps.test.ts | 27 +------------------ .../server/getRelevantGuidedSteps.ts | 12 --------- 4 files changed, 11 insertions(+), 56 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 63f43e7976e..f14482c6e78 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -6,7 +6,7 @@ import { useState } from "react"; import Link from "next/link"; -import { usePathname, useRouter } from "next/navigation"; +import { useRouter } from "next/navigation"; import { ResolutionContainer } from "../ResolutionContainer"; import { ResolutionContent } from "../ResolutionContent"; import { Button } from "../../../../../../../components/server/Button"; @@ -20,7 +20,6 @@ import { import { StepDeterminationData, StepLink, - getIsNextStepSectionFromPathname, getNextGuidedStep, } from "../../../../../../../functions/server/getRelevantGuidedSteps"; import { getGuidedExperienceBreaches } from "../../../../../../../functions/universal/guidedExperienceBreaches"; @@ -36,7 +35,6 @@ export type HighRiskBreachLayoutProps = { export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { const l10n = useL10n(); const router = useRouter(); - const pathname = usePathname(); const [isResolving, setIsResolving] = useState(false); const stepMap: Record = { @@ -105,12 +103,10 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { ); } - const nextRoute = getIsNextStepSectionFromPathname({ - currentPath: pathname, - nextPath: nextStep.href, - }) - ? "/redesign/user/dashboard/fix/high-risk-data-breaches/done" - : nextStep.href; + const isCurrentStepSection = Object.values(stepMap).includes(nextStep.id); + const nextRoute = isCurrentStepSection + ? nextStep.href + : "/redesign/user/dashboard/fix/high-risk-data-breaches/done"; router.push(nextRoute); } catch (_error) { setIsResolving(false); diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index deed8ec2d16..86bfb2611b9 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -5,7 +5,7 @@ "use client"; import { useState } from "react"; -import { usePathname, useRouter } from "next/navigation"; +import { useRouter } from "next/navigation"; import { SecurityRecommendationTypes, getSecurityRecommendationsByType, @@ -19,7 +19,6 @@ import { FixView } from "../FixView"; import { StepDeterminationData, StepLink, - getIsNextStepSectionFromPathname, getNextGuidedStep, } from "../../../../../../../functions/server/getRelevantGuidedSteps"; import { getGuidedExperienceBreaches } from "../../../../../../../functions/universal/guidedExperienceBreaches"; @@ -37,7 +36,6 @@ export function SecurityRecommendationsLayout( ) { const l10n = useL10n(); const router = useRouter(); - const pathname = usePathname(); const [isResolving, setIsResolving] = useState(false); const stepMap: Record = { @@ -103,12 +101,10 @@ export function SecurityRecommendationsLayout( ); } - const nextRoute = getIsNextStepSectionFromPathname({ - currentPath: pathname, - nextPath: nextStep.href, - }) - ? "/redesign/user/dashboard/fix/security-recommendations/done" - : nextStep.href; + const isCurrentStepSection = Object.values(stepMap).includes(nextStep.id); + const nextRoute = isCurrentStepSection + ? nextStep.href + : "/redesign/user/dashboard/fix/security-recommendations/done"; router.push(nextRoute); } catch (_error) { setIsResolving(false); diff --git a/src/app/functions/server/getRelevantGuidedSteps.test.ts b/src/app/functions/server/getRelevantGuidedSteps.test.ts index 6ac10171f85..ae06aff4c95 100644 --- a/src/app/functions/server/getRelevantGuidedSteps.test.ts +++ b/src/app/functions/server/getRelevantGuidedSteps.test.ts @@ -3,10 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { describe, expect, it } from "@jest/globals"; -import { - getNextGuidedStep, - getIsNextStepSectionFromPathname, -} from "./getRelevantGuidedSteps"; +import { getNextGuidedStep } from "./getRelevantGuidedSteps"; import { createRandomBreach, createRandomScanResult, @@ -860,25 +857,3 @@ describe("getNextGuidedStep", () => { }); }); }); - -describe("getIsNextStepSectionFromPathname", () => { - it("returns false if the next step pathname links to the next step section", () => { - expect( - getIsNextStepSectionFromPathname({ - currentPath: - "/redesign/user/dashboard/fix/high-risk-data-breaches/credit-card", - nextPath: "/redesign/user/dashboard/fix/high-risk-data-breaches/pin", - }), - ).toBeFalsy(); - }); - - it("returns true if the next step pathname links to the next step section", () => { - expect( - getIsNextStepSectionFromPathname({ - currentPath: - "/redesign/user/dashboard/fix/high-risk-data-breaches/credit-card", - nextPath: "/redesign/user/dashboard/fix/security-recommendations/phone", - }), - ).toBeTruthy(); - }); -}); diff --git a/src/app/functions/server/getRelevantGuidedSteps.ts b/src/app/functions/server/getRelevantGuidedSteps.ts index 4a94cc90554..b7103681bfc 100644 --- a/src/app/functions/server/getRelevantGuidedSteps.ts +++ b/src/app/functions/server/getRelevantGuidedSteps.ts @@ -286,15 +286,3 @@ export function hasCompletedStep( return false as never; } - -export function getIsNextStepSectionFromPathname({ - currentPath, - nextPath, -}: { - currentPath: string; - nextPath: StepLink["href"]; -}): boolean { - const getBasePath = (path: string) => - path.substring(0, path.lastIndexOf("/")); - return getBasePath(currentPath) !== getBasePath(nextPath); -} From 776845fa1ba3e97e0519b4d9d1da333ae436977d Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 18:17:23 +0100 Subject: [PATCH 14/19] chor: Explicitly check exposedData length --- .../fix/high-risk-data-breaches/HighRiskBreachLayout.tsx | 2 +- .../security-recommendations/SecurityRecommendationsLayout.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index f14482c6e78..50164ac68d3 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -64,7 +64,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { const { title, illustration, content, exposedData, type } = pageData!; const isHighRiskBreachesStep = type !== "none"; const isStepDone = type === "done"; - const hasExposedData = exposedData.length; + const hasExposedData = exposedData.length > 0; // TODO: Write unit tests MNTOR-2560 /* c8 ignore start */ diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index 86bfb2611b9..a3095cf5b8e 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -63,7 +63,7 @@ export function SecurityRecommendationsLayout( // The non-null assertion here should be safe since we already did this check // in `./[type]/page.tsx`: const { title, illustration, content, exposedData } = pageData!; - const hasExposedData = exposedData.length; + const hasExposedData = exposedData.length > 0; // TODO: Write unit tests MNTOR-2560 /* c8 ignore start */ From cf21d5d978fb7d9ad02217b6dd6feccc2089c4cb Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 18:20:38 +0100 Subject: [PATCH 15/19] chore: Test getGuidedExperienceBreaches with more than one affected dataClass --- .../functions/universal/guidedExperienceBreaches.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/functions/universal/guidedExperienceBreaches.test.ts b/src/app/functions/universal/guidedExperienceBreaches.test.ts index 38a99f88f47..987bdc3bc18 100644 --- a/src/app/functions/universal/guidedExperienceBreaches.test.ts +++ b/src/app/functions/universal/guidedExperienceBreaches.test.ts @@ -95,7 +95,10 @@ it("getGuidedExperienceBreaches: exclude guided experience breaches if they do n name: "", title: "", emailsAffected: ["test@mozilla.com"], - dataClassesEffected: [{ [BreachDataTypes.PIN]: 1 }], + dataClassesEffected: [ + { [BreachDataTypes.PIN]: 1 }, + { [BreachDataTypes.Passwords]: 2 }, + ], }; const guidedExp = getGuidedExperienceBreaches( @@ -107,7 +110,7 @@ it("getGuidedExperienceBreaches: exclude guided experience breaches if they do n expect(guidedExp.highRisk.ssnBreaches).toHaveLength(0); expect(guidedExp.highRisk.creditCardBreaches).toHaveLength(0); expect(guidedExp.highRisk.bankBreaches).toHaveLength(0); - expect(guidedExp.passwordBreaches.passwords).toHaveLength(0); + expect(guidedExp.passwordBreaches.passwords).toHaveLength(1); expect(guidedExp.passwordBreaches.securityQuestions).toHaveLength(0); expect(guidedExp.securityRecommendations.phoneNumber).toHaveLength(0); expect(guidedExp.securityRecommendations.emailAddress).toHaveLength(0); From 7ee119d92ee6e5b431d6d06af6e9558f0c0c44e0 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 18:41:02 +0100 Subject: [PATCH 16/19] chore: Add comment to condition that guards the resolution request --- .../fix/high-risk-data-breaches/HighRiskBreachLayout.tsx | 4 ++++ .../SecurityRecommendationsLayout.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 50164ac68d3..8c393dfdf52 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -82,6 +82,10 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { }; const dataType = highRiskBreachClasses[type]; + // Only attempt to resolve the breaches if the following conditions are true: + // - There is a matching data class type in this step + // - The current step has unresolved exposed data + // - There is no pending breach resolution request if (!dataType || !hasExposedData || isResolving) { return; } diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index a3095cf5b8e..f4f678d511a 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -80,6 +80,10 @@ export function SecurityRecommendationsLayout( }; const dataType = securityRecommendatioBreachClasses[props.type]; + // Only attempt to resolve the breaches if the following conditions are true: + // - There is a matching data class type in this step + // - The current step has unresolved exposed data + // - There is no pending breach resolution request if (!dataType || !hasExposedData || isResolving) { return; } From 96e5dc7fa90e8c3e26a37115734474bba8ec6a48 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 18:48:39 +0100 Subject: [PATCH 17/19] chore: Add type to body for the bulk resolution request --- .../fix/high-risk-data-breaches/HighRiskBreachLayout.tsx | 4 +++- .../SecurityRecommendationsLayout.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 8c393dfdf52..3894da238c8 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -25,6 +25,7 @@ import { import { getGuidedExperienceBreaches } from "../../../../../../../functions/universal/guidedExperienceBreaches"; import { hasPremium } from "../../../../../../../functions/universal/user"; import { HighRiskDataTypes } from "../../../../../../../functions/universal/breach"; +import { BreachBulkResolutionRequest } from "../../../../../../../(nextjs_migration)/(authenticated)/user/breaches/breaches"; export type HighRiskBreachLayoutProps = { type: HighRiskBreachTypes; @@ -92,12 +93,13 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { setIsResolving(true); try { + const body: BreachBulkResolutionRequest = { dataType }; const response = await fetch("/api/v1/user/breaches/bulk-resolve", { method: "PUT", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ dataType }), + body: JSON.stringify(body), }); const result = await response.json(); diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index f4f678d511a..5da9e61de09 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -24,6 +24,7 @@ import { import { getGuidedExperienceBreaches } from "../../../../../../../functions/universal/guidedExperienceBreaches"; import { hasPremium } from "../../../../../../../functions/universal/user"; import { SecurityRecommendationDataTypes } from "../../../../../../../functions/universal/breach"; +import { BreachBulkResolutionRequest } from "../../../../../../../(nextjs_migration)/(authenticated)/user/breaches/breaches"; export interface SecurityRecommendationsLayoutProps { type: SecurityRecommendationTypes; @@ -90,12 +91,13 @@ export function SecurityRecommendationsLayout( setIsResolving(true); try { + const body: BreachBulkResolutionRequest = { dataType }; const response = await fetch("/api/v1/user/breaches/bulk-resolve", { method: "PUT", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ dataType }), + body: JSON.stringify(body), }); const result = await response.json(); From 7d28a465c21013e253f88f5636c152ad7fb7e871 Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 18:55:34 +0100 Subject: [PATCH 18/19] Update src/app/functions/universal/guidedExperienceBreaches.test.ts Co-authored-by: Peter deHaan --- src/app/functions/universal/guidedExperienceBreaches.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/functions/universal/guidedExperienceBreaches.test.ts b/src/app/functions/universal/guidedExperienceBreaches.test.ts index 987bdc3bc18..a854b42087a 100644 --- a/src/app/functions/universal/guidedExperienceBreaches.test.ts +++ b/src/app/functions/universal/guidedExperienceBreaches.test.ts @@ -67,7 +67,7 @@ it("getGuidedExperienceBreaches: return all guided experience breaches if they h expect(guidedExp.securityRecommendations.IPAddress).toHaveLength(1); }); -it("getGuidedExperienceBreaches: exclude guided experience breaches if they do note have the relevant classes", () => { +it("getGuidedExperienceBreaches: exclude guided experience breaches if they do not have the relevant classes", () => { const subBreach: SubscriberBreach = { addedDate: new Date(), breachDate: new Date(), From fd8634a4de4b02e56e498acf08fc0218cb4faafd Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Fri, 24 Nov 2023 20:21:00 +0100 Subject: [PATCH 19/19] chore: Add comment for investigating client error being captured by Sentry --- .../fix/high-risk-data-breaches/HighRiskBreachLayout.tsx | 1 + .../dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx | 1 + .../security-recommendations/SecurityRecommendationsLayout.tsx | 1 + 3 files changed, 3 insertions(+) diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx index 3894da238c8..d0036b82b69 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/high-risk-data-breaches/HighRiskBreachLayout.tsx @@ -115,6 +115,7 @@ export function HighRiskBreachLayout(props: HighRiskBreachLayoutProps) { : "/redesign/user/dashboard/fix/high-risk-data-breaches/done"; router.push(nextRoute); } catch (_error) { + // TODO: MNTOR-2563: Capture client error with @next/sentry setIsResolving(false); } }; diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx index 0e89c2ae189..275751b5c3b 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/leaked-passwords/LeakedPasswordsLayout.tsx @@ -141,6 +141,7 @@ export function LeakedPasswordsLayout(props: LeakedPasswordsLayoutProps) { : "security-questions-done"; router.push(`/redesign/user/dashboard/fix/leaked-passwords/${doneSlug}`); } catch (_error) { + // TODO: MNTOR-2563: Capture client error with @next/sentry setIsResolving(false); } }; diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx index 5da9e61de09..15da3c77dc4 100644 --- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx +++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/security-recommendations/SecurityRecommendationsLayout.tsx @@ -113,6 +113,7 @@ export function SecurityRecommendationsLayout( : "/redesign/user/dashboard/fix/security-recommendations/done"; router.push(nextRoute); } catch (_error) { + // TODO: MNTOR-2563: Capture client error with @next/sentry setIsResolving(false); } };