Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MNTOR-1809 - Add chart into dashboard top banner #3232

Merged
merged 28 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2a6ae97
MNTOR-1809 - Add chart into dashboard top banner
codemist Jul 19, 2023
ee4c446
clean up flex values
codemist Jul 19, 2023
2115741
add about exposure num modal
codemist Jul 19, 2023
9cd205f
first pass
mansaj Jul 22, 2023
876bd1d
add summary metrics for dashboard
mansaj Jul 23, 2023
7c63c22
add types
mansaj Jul 24, 2023
2330cc5
todo comment
mansaj Jul 24, 2023
f6fadea
review comment
mansaj Jul 24, 2023
e8c6ca5
review comment
mansaj Jul 24, 2023
b1438b5
map for localized key
mansaj Jul 24, 2023
a90adc4
inject chart data into chart component
codemist Jul 25, 2023
4efb33c
use variable for num email addresses
codemist Jul 25, 2023
3b27dcc
fix storybook for dashboard
codemist Jul 25, 2023
30862d5
switch between pre-scan and post-scan views
codemist Jul 25, 2023
1d593d6
fix breaking test
codemist Jul 25, 2023
1215501
rm unused vars
codemist Jul 25, 2023
919f46b
fix l10n lint error and alphatetically organize vars
codemist Jul 25, 2023
014e567
add correct data in dashboard top banner desc
codemist Jul 25, 2023
f8afc68
rm console log
codemist Jul 25, 2023
04dd956
lint
codemist Jul 25, 2023
f7aac09
lint
codemist Jul 25, 2023
d7844bd
pluralize string
codemist Jul 25, 2023
58d16f5
fix stories
codemist Jul 25, 2023
d614a31
add total nums (#3252)
mansaj Jul 25, 2023
c7c4355
add special character for ×
codemist Jul 25, 2023
2dce8fc
arrange colours of chart by odd first, then even
codemist Jul 25, 2023
ebfcd2c
remove unused var
codemist Jul 26, 2023
e8704b5
Merge branch 'main' into mntor-1809-scan-banner
codemist Jul 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion locales-pending/premium.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,19 @@ exposure-chart-legend-heading-type = Exposure
exposure-chart-legend-heading-nr = Number
# Variables:
# $nr (number) - Number of a particular type of exposure found for the user
exposure-chart-legend-value-nr = { $nr }x
exposure-chart-legend-value-nr = { $nr }×
exposure-chart-caption = This chart shows how many times your info is actively exposed.

modal-active-number-of-exposures-title = About your number of active exposures
# Variables:
# $limit (number) - Number of email addresses included in the plan
modal-active-number-of-exposures-part-one =
{ $limit ->
[one] This chart includes the total number of times we found each type of data exposed across all data broker profiles and all data breaches for the { $limit } email address that you are currently monitoring.
*[other] This chart includes the total number of times we found each type of data exposed across all data broker profiles and all data breaches for up to { $limit } email addresses that you are currently monitoring.
}
modal-active-number-of-exposures-part-two = For example, if you have 10 exposures of your phone number, that might mean one phone number is exposed across 10 different sites, or it could mean 2 different phone numbers were exposed across 5 different sites.
modal-active-number-of-exposures-part-three = This chart does not include any exposures that are in-progress of being auto-removed. Once your exposures are fixed, they will be added to your total number of fixed exposures on the Fixed page.
codemist marked this conversation as resolved.
Show resolved Hide resolved

# Here's What We Fixed Progress Card

Expand Down
1 change: 1 addition & 0 deletions locales/en/data-classes.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ financial-investments = Financial investments
financial-transactions = Financial transactions
fitness-levels = Fitness levels
flights-taken = Flights taken
full-names = Full name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codemist

I missed this one in review (not sure if it was there when I reviewed, but that's not relevant).

All categories in this file are plurals. Also, why is this category being added in this PR? Just a coincidence?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@flodolo Me thinks it's related to this: #3244 (comment)
We have a bunch of these breach categories in the new dashboard UI now, so rather than add duplicate strings for localizers to localize, we reused the existing translated strings in locales/*/data-classes.ftl (although yes, that does slightly blur the fact that 99.999% of the values in that file are HIBP specific except this one).

git grep -n "full-names" | cat

locales/en/data-classes.ftl:62:full-names = Full name
src/app/(proper_react)/redesign/(authenticated)/user/dashboard/Dashboard.stories.tsx:158:    { "full-names": 98 },
src/app/functions/server/dashboard.ts:40:  fullNames: "full-names",

And yes, good callout that now there is some vague pluralizing where the ID is plural and the value is singular.
Not sure which way to fix that one, or if you think we should remove "full-names" entirely and move it into one of the files in /locales-pending/*.ftl. Plural would be more consistent with the rest of the values in data-classes.ftl, but I'm not clear on if that'd make the dashboard UI less clear, so I defer to UX/content/@codemist.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reusing strings is a bad idea, especially if we mix them from different sources. Some locales, for example, are translating categories lowercase.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's less than ideal in terms of localizability, but one way to minimize the impact would be to capitalize the first letter of the category (using a locale-sensitive library) when used in a different context.

genders = Genders
geographic-locations = Geographic locations
government-issued-ids = Government issued IDs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { View as DashboardEl } from "./View";
import { HibpLikeDbBreach } from "../../../../../../utils/hibp";
import { ScanResult } from "../../../../../functions/server/onerep";
import { Shell } from "../../../Shell";
import { StateAbbr } from "../../../../../../utils/states";
import { getEnL10nSync } from "../../../../../functions/server/mockL10n";
import { createRandomScan } from "../../../../../../apiMocks/mockData";
import { DashboardSummary } from "../../../../../functions/server/dashboard";

const meta: Meta<typeof DashboardEl> = {
title: "Pages/Dashboard",
Expand All @@ -19,33 +19,6 @@ const meta: Meta<typeof DashboardEl> = {
export default meta;
type Story = StoryObj<typeof DashboardEl>;

const _ScanResultMockItem: ScanResult = {
id: 1,
profile_id: 1,
first_name: "John",
last_name: "Doe",
middle_name: "string",
age: `${30}`,
addresses: [
{
city: "123",
state: "State" as StateAbbr,
street: "Street",
zip: "123456",
},
],
phones: [""],
emails: [""],
data_broker: "Familytree.com",
created_at: "11/09/23",
updated_at: "11/09/23",
url: "",
link: "",
relatives: [],
status: "new",
data_broker_id: 0,
};

const BreachMockItem1: HibpLikeDbBreach = {
AddedDate: new Date("2018-11-07T14:48:00.000Z"),
BreachDate: "11/09/23",
Expand Down Expand Up @@ -138,7 +111,57 @@ const breachItemArraySample: HibpLikeDbBreach[] = [
BreachMockItem4,
];

export const Dashboard: Story = {
const dashboardSummaryNoScan: DashboardSummary = {
dataBreachTotalNum: 0,
dataBrokerTotalNum: 51,
totalExposures: 51,
allExposures: {
emailAddresses: 0,
phoneNumbers: 0,
addresses: 0,
familyMembers: 0,
fullNames: 0,
socialSecurityNumbers: 0,
ipAddresses: 0,
passwords: 0,
creditCardNumbers: 0,
pinNumbers: 0,
securityQuestions: 0,
},
sanitizedExposures: [
{ "email-addresses": 30 },
{ "phone-numbers": 19 },
{ "social-security-numbers": 2 },
],
};

const dashboardSummaryWithScan: DashboardSummary = {
dataBreachTotalNum: 88,
dataBrokerTotalNum: 217,
totalExposures: 305,
allExposures: {
emailAddresses: 0,
phoneNumbers: 0,
addresses: 0,
familyMembers: 0,
fullNames: 0,
socialSecurityNumbers: 0,
ipAddresses: 0,
passwords: 0,
creditCardNumbers: 0,
pinNumbers: 0,
securityQuestions: 0,
},
sanitizedExposures: [
{ "physical-addresses": 90 },
{ "family-members-names": 29 },
{ "full-names": 98 },
{ "phone-numbers": 8 },
{ "other-data-class": 80 },
],
};

export const DashboardWithScan: Story = {
render: () => (
<Shell l10n={getEnL10nSync()} session={null}>
<DashboardEl
Expand All @@ -165,7 +188,40 @@ export const Dashboard: Story = {
}}
userScannedResults={scannedResultsArraySample}
locale={"en"}
isUserScannedResults={true}
bannerData={dashboardSummaryWithScan}
/>
</Shell>
),
};

export const DashboardWithoutScan: Story = {
render: () => (
<Shell l10n={getEnL10nSync()} session={null}>
<DashboardEl
user={{ email: "[email protected]" }}
userBreaches={{
emailVerifiedCount: 0,
emailTotalCount: 0,
emailSelectIndex: 0,
ssnBreaches: [],
phoneBreaches: [],
passwordBreaches: [],
breachesData: {
unverifiedEmails: [],
verifiedEmails: [
{
breaches: breachItemArraySample,
email: "[email protected]",
id: 0,
primary: true,
verified: true,
},
],
},
}}
userScannedResults={[]}
locale={"en"}
bannerData={dashboardSummaryNoScan}
/>
</Shell>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@ import { it, expect } from "@jest/globals";
import { render } from "@testing-library/react";
import { composeStory } from "@storybook/react";
import { axe } from "jest-axe";
import Meta, { Dashboard } from "./Dashboard.stories";
import Meta, {
DashboardWithScan,
DashboardWithoutScan,
} from "./Dashboard.stories";

jest.mock("next/navigation", () => ({
useRouter: jest.fn(),
usePathname: jest.fn(),
}));

it("passes the axe accessibility test suite", async () => {
const ComposedDashboard = composeStory(Dashboard, Meta);
const ComposedDashboard = composeStory(DashboardWithScan, Meta);
const { container } = render(<ComposedDashboard />);
expect(await axe(container)).toHaveNoViolations();
});

it("passes the axe accessibility test suite", async () => {
codemist marked this conversation as resolved.
Show resolved Hide resolved
const ComposedDashboard = composeStory(DashboardWithoutScan, Meta);
const { container } = render(<ComposedDashboard />);
expect(await axe(container)).toHaveNoViolations();
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,59 @@

.container {
display: flex;
flex-direction: row;
flex-direction: column;
border: 1px solid rgba($color-purple-50, 0.2);
border-radius: $border-radius-md;
padding: $layout-lg $spacing-xl;
padding: $layout-xs;
background-color: $color-white;
background-image: url("./images/dashboard-top-banner-wave.svg");
background-size: cover;
background-position: center;
gap: $layout-xs;
align-items: center;

.content {
@media screen and (min-width: $screen-md) {
padding: $layout-lg $layout-xs;
flex-direction: row;
}

.explainerContentWrapper {
padding: $spacing-sm;
display: flex;
flex-direction: column;
gap: $spacing-md;
align-items: center;

h3 {
font: $text-title-2xs;
@media screen and (min-width: $screen-md) {
padding: $spacing-md;
justify-content: center;
flex: 0.5 1 0%;

@media screen and (min-width: $screen-xl) {
flex: 1 1 0%;
}
}

.cta {
align-self: start;
.explainerContent {
display: flex;
gap: $spacing-md;
flex-direction: column;

@media screen and (min-width: $screen-md) {
max-width: $content-sm;
}

h3 {
font: $text-title-2xs;
}

.cta {
align-self: start;
}
}
}

& > * {
flex: 1 1 0%;
.chart {
@media screen and (min-width: $screen-md) {
flex: 1 1 0%;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,46 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import styles from "./DashboardTopBanner.module.scss";
import { ReactElement } from "react";
import { Button } from "../../../../../components/server/Button";
import { useL10n } from "../../../../../hooks/l10n";
import { useRouter } from "next/navigation";
import { DoughnutChart as Chart } from "../../../../../components/client/Chart";
import { DashboardSummary } from "../../../../../functions/server/dashboard";

export type DashboardTopBannerProps = {
type:
| "LetsFixDataContent"
| "DataBrokerScanUpsellContent"
| "NoExposuresFoundContent"
| "ResumeBreachResolutionContent"
| "YourDataIsProtected";
chart: ReactElement;
| "YourDataIsProtectedContent";
bannerData: DashboardSummary;
};

export const DashboardTopBanner = (props: DashboardTopBannerProps) => {
const l10n = useL10n();
const router = useRouter();

const chartData: [string, number][] = props.bannerData.sanitizedExposures.map(
(obj) => {
const [key, value] = Object.entries(obj)[0];
return [l10n.getString(key), value];
}
);

const contentData = {
LetsFixDataContent: {
headline: l10n.getString("dashboard-top-banner-protect-your-data-title"),
description: l10n.getString(
"dashboard-top-banner-protect-your-data-description",
{
// TODO: Replace all mocked exposure data
data_breach_total_num: 95,
data_broker_total_num: 15,
data_breach_total_num: props.bannerData.dataBreachTotalNum,
data_broker_total_num: props.bannerData.dataBrokerTotalNum,
mansaj marked this conversation as resolved.
Show resolved Hide resolved
}
),
cta: {
content: l10n.getString("dashboard-top-banner-protect-your-data-cta"),
onClick: () => {
router.push(
`/redesign/user/dashboard/fix/data-broker-profiles/view-data-brokers`
);
window.location.href =
"/redesign/user/dashboard/fix/data-broker-profiles/view-data-brokers";
},
},
},
Expand Down Expand Up @@ -83,6 +87,7 @@ export const DashboardTopBanner = (props: DashboardTopBannerProps) => {
description: l10n.getString(
"dashboard-top-banner-lets-keep-protecting-description",
{
//TODO: Add remaining total exposures
remaining_exposures_total_num: 40,
}
),
Expand All @@ -95,13 +100,14 @@ export const DashboardTopBanner = (props: DashboardTopBannerProps) => {
},
},
},
YourDataIsProtected: {
YourDataIsProtectedContent: {
headline: l10n.getString(
"dashboard-top-banner-your-data-is-protected-title"
),
description: l10n.getString(
"dashboard-top-banner-your-data-is-protected-description",
{
//TODO: Add original count of exposures
starting_exposure_total_num: 100,
}
),
Expand All @@ -120,20 +126,22 @@ export const DashboardTopBanner = (props: DashboardTopBannerProps) => {

return (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.explainerContentWrapper}>
{content && (
<>
<div className={styles.explainerContent}>
<h3>{content.headline}</h3>
<p>{content.description}</p>
<span className={styles.cta}>
<Button variant="primary" small onClick={content.cta.onClick}>
{content.cta.content}
</Button>
</span>
</>
</div>
)}
</div>
<div className={styles.chart}>Chart goes here</div>
<div className={styles.chart}>
<Chart data={chartData} />
</div>
</div>
);
};
Loading