From 6b1652e34c458ce64b6d2f3f84b64d049bf57a7c Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 3 Aug 2022 21:40:04 +0200 Subject: [PATCH] [SM-108] Refactor Reports - Add storybook stories (#3204) --- apps/web/.eslintrc.json | 2 +- .../src/app/common/base.events.component.ts | 2 +- .../app/modules/loose-components.module.ts | 27 ---- apps/web/src/app/modules/shared.module.ts | 3 + .../trial-initiation/billing.component.ts | 2 +- .../organization-subscription.component.ts | 2 +- .../accept-family-sponsorship.component.ts | 2 +- ...families-for-enterprise-setup.component.ts | 4 +- .../exposed-passwords-report.component.ts | 5 +- .../inactive-two-factor-report.component.ts | 5 +- .../reused-passwords-report.component.ts | 5 +- .../unsecured-websites-report.component.ts | 5 +- .../tools/weak-passwords-report.component.ts | 5 +- apps/web/src/app/oss-routing.module.ts | 4 +- apps/web/src/app/reports/index.ts | 3 + .../src/app/reports/models/report-entry.ts | 9 ++ .../src/app/reports/models/report-variant.ts | 5 + .../{ => pages}/breach-report.component.html | 0 .../{ => pages}/breach-report.component.ts | 0 .../{ => pages}/cipher-report.component.ts | 11 +- .../exposed-passwords-report.component.html | 0 .../exposed-passwords-report.component.ts | 0 .../inactive-two-factor-report.component.html | 0 .../inactive-two-factor-report.component.ts | 0 .../reports/pages/reports-home.component.html | 7 ++ .../reports/pages/reports-home.component.ts | 52 ++++++++ .../reused-passwords-report.component.html | 0 .../reused-passwords-report.component.ts | 0 .../unsecured-websites-report.component.html | 0 .../unsecured-websites-report.component.ts | 0 .../weak-passwords-report.component.html | 0 .../weak-passwords-report.component.ts | 0 .../report-card.component.html | 17 +-- .../report-card/report-card.component.ts | 23 ++++ .../report-card/report-card.stories.ts | 51 ++++++++ .../app/reports/report-list.component.html | 11 -- .../src/app/reports/report-list.component.ts | 18 --- .../report-list/report-list.component.html | 11 ++ .../report-list/report-list.component.ts | 11 ++ .../report-list/report-list.stories.ts | 43 +++++++ ...ent.html => reports-layout.component.html} | 0 ...mponent.ts => reports-layout.component.ts} | 6 +- .../src/app/reports/reports-routing.module.ts | 27 ++-- apps/web/src/app/reports/reports.module.ts | 34 +++++ apps/web/src/app/reports/reports.ts | 51 ++++++++ .../web/src/app/settings/payment.component.ts | 2 +- .../app/shared/guards/has-premium.guard.ts | 31 +++++ .../tests/preloaded-english-i18n.module.ts | 38 ++++++ apps/web/tsconfig.json | 1 + libs/components/src/icon/icon.component.ts | 24 ++++ libs/components/src/icon/icon.module.ts | 11 ++ libs/components/src/icon/icon.stories.ts | 27 ++++ .../components/src/icon/icons.ts | 119 ++---------------- libs/components/src/icon/index.ts | 1 + libs/components/src/index.ts | 7 +- tailwind.config.js | 6 +- tsconfig.json | 5 +- 57 files changed, 512 insertions(+), 223 deletions(-) create mode 100644 apps/web/src/app/reports/index.ts create mode 100644 apps/web/src/app/reports/models/report-entry.ts create mode 100644 apps/web/src/app/reports/models/report-variant.ts rename apps/web/src/app/reports/{ => pages}/breach-report.component.html (100%) rename apps/web/src/app/reports/{ => pages}/breach-report.component.ts (100%) rename apps/web/src/app/reports/{ => pages}/cipher-report.component.ts (87%) rename apps/web/src/app/reports/{ => pages}/exposed-passwords-report.component.html (100%) rename apps/web/src/app/reports/{ => pages}/exposed-passwords-report.component.ts (100%) rename apps/web/src/app/reports/{ => pages}/inactive-two-factor-report.component.html (100%) rename apps/web/src/app/reports/{ => pages}/inactive-two-factor-report.component.ts (100%) create mode 100644 apps/web/src/app/reports/pages/reports-home.component.html create mode 100644 apps/web/src/app/reports/pages/reports-home.component.ts rename apps/web/src/app/reports/{ => pages}/reused-passwords-report.component.html (100%) rename apps/web/src/app/reports/{ => pages}/reused-passwords-report.component.ts (100%) rename apps/web/src/app/reports/{ => pages}/unsecured-websites-report.component.html (100%) rename apps/web/src/app/reports/{ => pages}/unsecured-websites-report.component.ts (100%) rename apps/web/src/app/reports/{ => pages}/weak-passwords-report.component.html (100%) rename apps/web/src/app/reports/{ => pages}/weak-passwords-report.component.ts (100%) rename apps/web/src/app/reports/{ => report-card}/report-card.component.html (54%) create mode 100644 apps/web/src/app/reports/report-card/report-card.component.ts create mode 100644 apps/web/src/app/reports/report-card/report-card.stories.ts delete mode 100644 apps/web/src/app/reports/report-list.component.html delete mode 100644 apps/web/src/app/reports/report-list.component.ts create mode 100644 apps/web/src/app/reports/report-list/report-list.component.html create mode 100644 apps/web/src/app/reports/report-list/report-list.component.ts create mode 100644 apps/web/src/app/reports/report-list/report-list.stories.ts rename apps/web/src/app/reports/{reports.component.html => reports-layout.component.html} (100%) rename apps/web/src/app/reports/{reports.component.ts => reports-layout.component.ts} (80%) create mode 100644 apps/web/src/app/reports/reports.module.ts create mode 100644 apps/web/src/app/reports/reports.ts create mode 100644 apps/web/src/app/shared/guards/has-premium.guard.ts create mode 100644 apps/web/src/app/tests/preloaded-english-i18n.module.ts create mode 100644 libs/components/src/icon/icon.component.ts create mode 100644 libs/components/src/icon/icon.module.ts create mode 100644 libs/components/src/icon/icon.stories.ts rename apps/web/src/app/reports/report-card.component.ts => libs/components/src/icon/icons.ts (73%) create mode 100644 libs/components/src/icon/index.ts diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json index d2fa3fe89a..74194a6b71 100644 --- a/apps/web/.eslintrc.json +++ b/apps/web/.eslintrc.json @@ -6,7 +6,7 @@ "no-restricted-imports": [ "error", { - "patterns": ["**/core/*"] + "patterns": ["**/core/*", "**/reports/*"] } ] } diff --git a/apps/web/src/app/common/base.events.component.ts b/apps/web/src/app/common/base.events.component.ts index d7368a3f46..31d44f6825 100644 --- a/apps/web/src/app/common/base.events.component.ts +++ b/apps/web/src/app/common/base.events.component.ts @@ -9,7 +9,7 @@ import { EventResponse } from "@bitwarden/common/models/response/eventResponse"; import { ListResponse } from "@bitwarden/common/models/response/listResponse"; import { EventView } from "@bitwarden/common/models/view/eventView"; -import { EventService } from "src/app/core"; +import { EventService } from "../core"; @Directive() export abstract class BaseEventsComponent { diff --git a/apps/web/src/app/modules/loose-components.module.ts b/apps/web/src/app/modules/loose-components.module.ts index 1d6d66b478..ecf8e83096 100644 --- a/apps/web/src/app/modules/loose-components.module.ts +++ b/apps/web/src/app/modules/loose-components.module.ts @@ -79,15 +79,6 @@ import { AttachmentsComponent as OrgAttachmentsComponent } from "../organization import { CiphersComponent as OrgCiphersComponent } from "../organizations/vault/ciphers.component"; import { CollectionsComponent as OrgCollectionsComponent } from "../organizations/vault/collections.component"; import { ProvidersComponent } from "../providers/providers.component"; -import { BreachReportComponent } from "../reports/breach-report.component"; -import { ExposedPasswordsReportComponent } from "../reports/exposed-passwords-report.component"; -import { InactiveTwoFactorReportComponent } from "../reports/inactive-two-factor-report.component"; -import { ReportCardComponent } from "../reports/report-card.component"; -import { ReportListComponent } from "../reports/report-list.component"; -import { ReportsComponent } from "../reports/reports.component"; -import { ReusedPasswordsReportComponent } from "../reports/reused-passwords-report.component"; -import { UnsecuredWebsitesReportComponent } from "../reports/unsecured-websites-report.component"; -import { WeakPasswordsReportComponent } from "../reports/weak-passwords-report.component"; import { AccessComponent } from "../send/access.component"; import { AddEditComponent as SendAddEditComponent } from "../send/add-edit.component"; import { EffluxDatesComponent as SendEffluxDatesComponent } from "../send/efflux-dates.component"; @@ -190,7 +181,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga AttachmentsComponent, BillingSyncApiKeyComponent, BillingSyncKeyComponent, - BreachReportComponent, BulkActionsComponent, BulkDeleteComponent, BulkMoveComponent, @@ -216,13 +206,11 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga EmergencyAccessTakeoverComponent, EmergencyAccessViewComponent, EmergencyAddEditComponent, - ExposedPasswordsReportComponent, FamiliesForEnterpriseSetupComponent, FolderAddEditComponent, FooterComponent, FrontendLayoutComponent, HintComponent, - InactiveTwoFactorReportComponent, LockComponent, LoginComponent, MasterPasswordPolicyComponent, @@ -281,12 +269,8 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga RecoverTwoFactorComponent, RegisterComponent, RemovePasswordComponent, - ReportCardComponent, - ReportListComponent, - ReportsComponent, RequireSsoPolicyComponent, ResetPasswordPolicyComponent, - ReusedPasswordsReportComponent, SecurityComponent, SecurityKeysComponent, SendAddEditComponent, @@ -313,7 +297,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga TwoFactorVerifyComponent, TwoFactorWebAuthnComponent, TwoFactorYubiKeyComponent, - UnsecuredWebsitesReportComponent, UpdateKeyComponent, UpdateLicenseComponent, UpdatePasswordComponent, @@ -326,7 +309,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga VerifyEmailComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, - WeakPasswordsReportComponent, ], exports: [ PremiumBadgeComponent, @@ -343,7 +325,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga AdjustSubscription, ApiKeyComponent, AttachmentsComponent, - BreachReportComponent, BulkActionsComponent, BulkDeleteComponent, BulkMoveComponent, @@ -369,13 +350,11 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga EmergencyAccessTakeoverComponent, EmergencyAccessViewComponent, EmergencyAddEditComponent, - ExposedPasswordsReportComponent, FamiliesForEnterpriseSetupComponent, FolderAddEditComponent, FooterComponent, FrontendLayoutComponent, HintComponent, - InactiveTwoFactorReportComponent, LockComponent, LoginComponent, MasterPasswordPolicyComponent, @@ -433,12 +412,8 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga RecoverTwoFactorComponent, RegisterComponent, RemovePasswordComponent, - ReportCardComponent, - ReportListComponent, - ReportsComponent, RequireSsoPolicyComponent, ResetPasswordPolicyComponent, - ReusedPasswordsReportComponent, SecurityComponent, SecurityKeysComponent, SendAddEditComponent, @@ -465,7 +440,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga TwoFactorVerifyComponent, TwoFactorWebAuthnComponent, TwoFactorYubiKeyComponent, - UnsecuredWebsitesReportComponent, UpdateKeyComponent, UpdateLicenseComponent, UpdatePasswordComponent, @@ -478,7 +452,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga VerifyEmailComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, - WeakPasswordsReportComponent, ], }) export class LooseComponentsModule {} diff --git a/apps/web/src/app/modules/shared.module.ts b/apps/web/src/app/modules/shared.module.ts index 10469e9423..6d8b811de6 100644 --- a/apps/web/src/app/modules/shared.module.ts +++ b/apps/web/src/app/modules/shared.module.ts @@ -64,6 +64,7 @@ import { FormFieldModule, SubmitButtonModule, MenuModule, + IconModule, } from "@bitwarden/components"; import { PaymentComponent } from "../settings/payment.component"; @@ -139,6 +140,7 @@ registerLocaleData(localeZhTw, "zh-TW"); MenuModule, FormFieldModule, SubmitButtonModule, + IconModule, ], exports: [ CommonModule, @@ -159,6 +161,7 @@ registerLocaleData(localeZhTw, "zh-TW"); SubmitButtonModule, PaymentComponent, TaxInfoComponent, + IconModule, ], providers: [DatePipe], bootstrap: [], diff --git a/apps/web/src/app/modules/trial-initiation/billing.component.ts b/apps/web/src/app/modules/trial-initiation/billing.component.ts index 1b881a2caa..6a57d3f6ac 100644 --- a/apps/web/src/app/modules/trial-initiation/billing.component.ts +++ b/apps/web/src/app/modules/trial-initiation/billing.component.ts @@ -13,7 +13,7 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy.service"; import { SyncService } from "@bitwarden/common/abstractions/sync.service"; import { ProductType } from "@bitwarden/common/enums/productType"; -import { OrganizationPlansComponent } from "src/app/settings/organization-plans.component"; +import { OrganizationPlansComponent } from "../../settings/organization-plans.component"; @Component({ selector: "app-billing", diff --git a/apps/web/src/app/organizations/settings/organization-subscription.component.ts b/apps/web/src/app/organizations/settings/organization-subscription.component.ts index a2208c2c59..dc1dd0a1a4 100644 --- a/apps/web/src/app/organizations/settings/organization-subscription.component.ts +++ b/apps/web/src/app/organizations/settings/organization-subscription.component.ts @@ -17,7 +17,7 @@ import { Organization } from "@bitwarden/common/models/domain/organization"; import { OrganizationConnectionResponse } from "@bitwarden/common/models/response/organizationConnectionResponse"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/models/response/organizationSubscriptionResponse"; -import { BillingSyncKeyComponent } from "src/app/settings/billing-sync-key.component"; +import { BillingSyncKeyComponent } from "../../settings/billing-sync-key.component"; import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; diff --git a/apps/web/src/app/organizations/sponsorships/accept-family-sponsorship.component.ts b/apps/web/src/app/organizations/sponsorships/accept-family-sponsorship.component.ts index 0cfcc08359..ea92159dc1 100644 --- a/apps/web/src/app/organizations/sponsorships/accept-family-sponsorship.component.ts +++ b/apps/web/src/app/organizations/sponsorships/accept-family-sponsorship.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { Params } from "@angular/router"; -import { BaseAcceptComponent } from "src/app/common/base.accept.component"; +import { BaseAcceptComponent } from "../../common/base.accept.component"; @Component({ selector: "app-accept-family-sponsorship", diff --git a/apps/web/src/app/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/organizations/sponsorships/families-for-enterprise-setup.component.ts index 2e4b505e6c..ea8c90d7ef 100644 --- a/apps/web/src/app/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -15,8 +15,8 @@ import { ProductType } from "@bitwarden/common/enums/productType"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/models/request/organization/organizationSponsorshipRedeemRequest"; -import { DeleteOrganizationComponent } from "src/app/organizations/settings/delete-organization.component"; -import { OrganizationPlansComponent } from "src/app/settings/organization-plans.component"; +import { OrganizationPlansComponent } from "../../settings/organization-plans.component"; +import { DeleteOrganizationComponent } from "../settings/delete-organization.component"; @Component({ selector: "families-for-enterprise-setup", diff --git a/apps/web/src/app/organizations/tools/exposed-passwords-report.component.ts b/apps/web/src/app/organizations/tools/exposed-passwords-report.component.ts index e059267100..d34e757d74 100644 --- a/apps/web/src/app/organizations/tools/exposed-passwords-report.component.ts +++ b/apps/web/src/app/organizations/tools/exposed-passwords-report.component.ts @@ -11,11 +11,12 @@ import { StateService } from "@bitwarden/common/abstractions/state.service"; import { Cipher } from "@bitwarden/common/models/domain/cipher"; import { CipherView } from "@bitwarden/common/models/view/cipherView"; -import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../reports/exposed-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports +import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../reports/pages/exposed-passwords-report.component"; @Component({ selector: "app-org-exposed-passwords-report", - templateUrl: "../../reports/exposed-passwords-report.component.html", + templateUrl: "../../reports/pages/exposed-passwords-report.component.html", }) export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent { manageableCiphers: Cipher[]; diff --git a/apps/web/src/app/organizations/tools/inactive-two-factor-report.component.ts b/apps/web/src/app/organizations/tools/inactive-two-factor-report.component.ts index f0ec4614f6..56e9625511 100644 --- a/apps/web/src/app/organizations/tools/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/organizations/tools/inactive-two-factor-report.component.ts @@ -10,11 +10,12 @@ import { PasswordRepromptService } from "@bitwarden/common/abstractions/password import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherView } from "@bitwarden/common/models/view/cipherView"; -import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../../reports/inactive-two-factor-report.component"; +// eslint-disable-next-line no-restricted-imports +import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../../reports/pages/inactive-two-factor-report.component"; @Component({ selector: "app-inactive-two-factor-report", - templateUrl: "../../reports/inactive-two-factor-report.component.html", + templateUrl: "../../reports/pages/inactive-two-factor-report.component.html", }) export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent { constructor( diff --git a/apps/web/src/app/organizations/tools/reused-passwords-report.component.ts b/apps/web/src/app/organizations/tools/reused-passwords-report.component.ts index e85a5fd4b4..1f97546cf3 100644 --- a/apps/web/src/app/organizations/tools/reused-passwords-report.component.ts +++ b/apps/web/src/app/organizations/tools/reused-passwords-report.component.ts @@ -10,11 +10,12 @@ import { StateService } from "@bitwarden/common/abstractions/state.service"; import { Cipher } from "@bitwarden/common/models/domain/cipher"; import { CipherView } from "@bitwarden/common/models/view/cipherView"; -import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../../reports/reused-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports +import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../../reports/pages/reused-passwords-report.component"; @Component({ selector: "app-reused-passwords-report", - templateUrl: "../../reports/reused-passwords-report.component.html", + templateUrl: "../../reports/pages/reused-passwords-report.component.html", }) export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent { manageableCiphers: Cipher[]; diff --git a/apps/web/src/app/organizations/tools/unsecured-websites-report.component.ts b/apps/web/src/app/organizations/tools/unsecured-websites-report.component.ts index 62b18ea8f7..f636303bb0 100644 --- a/apps/web/src/app/organizations/tools/unsecured-websites-report.component.ts +++ b/apps/web/src/app/organizations/tools/unsecured-websites-report.component.ts @@ -9,11 +9,12 @@ import { PasswordRepromptService } from "@bitwarden/common/abstractions/password import { StateService } from "@bitwarden/common/abstractions/state.service"; import { CipherView } from "@bitwarden/common/models/view/cipherView"; -import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../../reports/unsecured-websites-report.component"; +// eslint-disable-next-line no-restricted-imports +import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../../reports/pages/unsecured-websites-report.component"; @Component({ selector: "app-unsecured-websites-report", - templateUrl: "../../reports/unsecured-websites-report.component.html", + templateUrl: "../../reports/pages/unsecured-websites-report.component.html", }) export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent { constructor( diff --git a/apps/web/src/app/organizations/tools/weak-passwords-report.component.ts b/apps/web/src/app/organizations/tools/weak-passwords-report.component.ts index 2b65fa4292..306b861f36 100644 --- a/apps/web/src/app/organizations/tools/weak-passwords-report.component.ts +++ b/apps/web/src/app/organizations/tools/weak-passwords-report.component.ts @@ -11,11 +11,12 @@ import { StateService } from "@bitwarden/common/abstractions/state.service"; import { Cipher } from "@bitwarden/common/models/domain/cipher"; import { CipherView } from "@bitwarden/common/models/view/cipherView"; -import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../../reports/weak-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports +import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../../reports/pages/weak-passwords-report.component"; @Component({ selector: "app-weak-passwords-report", - templateUrl: "../../reports/weak-passwords-report.component.html", + templateUrl: "../../reports/pages/weak-passwords-report.component.html", }) export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent { manageableCiphers: Cipher[]; diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 76501a16f4..e94927f3ac 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -31,7 +31,7 @@ import { IndividualVaultModule } from "./modules/vault/modules/individual-vault/ import { OrganizationsRoutingModule } from "./organizations/organization-routing.module"; import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component"; import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component"; -import { ReportsRoutingModule } from "./reports/reports-routing.module"; +import { ReportsModule } from "./reports"; import { AccessComponent } from "./send/access.component"; import { SendComponent } from "./send/send.component"; import { AccountComponent } from "./settings/account.component"; @@ -238,7 +238,7 @@ const routes: Routes = [ }, { path: "reports", - loadChildren: () => ReportsRoutingModule, + loadChildren: () => ReportsModule, }, { path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent }, ], diff --git a/apps/web/src/app/reports/index.ts b/apps/web/src/app/reports/index.ts new file mode 100644 index 0000000000..0e922232d6 --- /dev/null +++ b/apps/web/src/app/reports/index.ts @@ -0,0 +1,3 @@ +export * from "./reports.module"; +export * from "./models/report-entry"; +export * from "./models/report-variant"; diff --git a/apps/web/src/app/reports/models/report-entry.ts b/apps/web/src/app/reports/models/report-entry.ts new file mode 100644 index 0000000000..08e31a8eca --- /dev/null +++ b/apps/web/src/app/reports/models/report-entry.ts @@ -0,0 +1,9 @@ +import { ReportVariant } from "./report-variant"; + +export type ReportEntry = { + title: string; + description: string; + route: string; + icon: string; + variant: ReportVariant; +}; diff --git a/apps/web/src/app/reports/models/report-variant.ts b/apps/web/src/app/reports/models/report-variant.ts new file mode 100644 index 0000000000..d011d106c6 --- /dev/null +++ b/apps/web/src/app/reports/models/report-variant.ts @@ -0,0 +1,5 @@ +export enum ReportVariant { + Enabled = "Enabled", + RequiresPremium = "RequiresPremium", + RequiresUpgrade = "RequiresUpgrade", +} diff --git a/apps/web/src/app/reports/breach-report.component.html b/apps/web/src/app/reports/pages/breach-report.component.html similarity index 100% rename from apps/web/src/app/reports/breach-report.component.html rename to apps/web/src/app/reports/pages/breach-report.component.html diff --git a/apps/web/src/app/reports/breach-report.component.ts b/apps/web/src/app/reports/pages/breach-report.component.ts similarity index 100% rename from apps/web/src/app/reports/breach-report.component.ts rename to apps/web/src/app/reports/pages/breach-report.component.ts diff --git a/apps/web/src/app/reports/cipher-report.component.ts b/apps/web/src/app/reports/pages/cipher-report.component.ts similarity index 87% rename from apps/web/src/app/reports/cipher-report.component.ts rename to apps/web/src/app/reports/pages/cipher-report.component.ts index b1c5df36b6..9179c04371 100644 --- a/apps/web/src/app/reports/cipher-report.component.ts +++ b/apps/web/src/app/reports/pages/cipher-report.component.ts @@ -8,8 +8,8 @@ import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { CipherView } from "@bitwarden/common/models/view/cipherView"; -import { AddEditComponent as OrgAddEditComponent } from "../organizations/vault/add-edit.component"; -import { AddEditComponent } from "../vault/add-edit.component"; +import { AddEditComponent as OrgAddEditComponent } from "../../organizations/vault/add-edit.component"; +import { AddEditComponent } from "../../vault/add-edit.component"; @Directive() export class CipherReportComponent { @@ -79,13 +79,6 @@ export class CipherReportComponent { this.messagingService.send("upgradeOrganization", { organizationId: this.organization.id }); return false; } - } else { - const accessPremium = await this.stateService.getCanAccessPremium(); - if (this.requiresPaid && !accessPremium) { - this.messagingService.send("premiumRequired"); - this.loading = false; - return false; - } } return true; } diff --git a/apps/web/src/app/reports/exposed-passwords-report.component.html b/apps/web/src/app/reports/pages/exposed-passwords-report.component.html similarity index 100% rename from apps/web/src/app/reports/exposed-passwords-report.component.html rename to apps/web/src/app/reports/pages/exposed-passwords-report.component.html diff --git a/apps/web/src/app/reports/exposed-passwords-report.component.ts b/apps/web/src/app/reports/pages/exposed-passwords-report.component.ts similarity index 100% rename from apps/web/src/app/reports/exposed-passwords-report.component.ts rename to apps/web/src/app/reports/pages/exposed-passwords-report.component.ts diff --git a/apps/web/src/app/reports/inactive-two-factor-report.component.html b/apps/web/src/app/reports/pages/inactive-two-factor-report.component.html similarity index 100% rename from apps/web/src/app/reports/inactive-two-factor-report.component.html rename to apps/web/src/app/reports/pages/inactive-two-factor-report.component.html diff --git a/apps/web/src/app/reports/inactive-two-factor-report.component.ts b/apps/web/src/app/reports/pages/inactive-two-factor-report.component.ts similarity index 100% rename from apps/web/src/app/reports/inactive-two-factor-report.component.ts rename to apps/web/src/app/reports/pages/inactive-two-factor-report.component.ts diff --git a/apps/web/src/app/reports/pages/reports-home.component.html b/apps/web/src/app/reports/pages/reports-home.component.html new file mode 100644 index 0000000000..31b9d03dea --- /dev/null +++ b/apps/web/src/app/reports/pages/reports-home.component.html @@ -0,0 +1,7 @@ + + +

{{ "reportsDesc" | i18n }}

+ + diff --git a/apps/web/src/app/reports/pages/reports-home.component.ts b/apps/web/src/app/reports/pages/reports-home.component.ts new file mode 100644 index 0000000000..422b945444 --- /dev/null +++ b/apps/web/src/app/reports/pages/reports-home.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit } from "@angular/core"; + +import { StateService } from "@bitwarden/common/abstractions/state.service"; + +import { ReportEntry } from "../models/report-entry"; +import { ReportVariant } from "../models/report-variant"; +import { reports, ReportType } from "../reports"; + +@Component({ + selector: "app-reports-home", + templateUrl: "reports-home.component.html", +}) +export class ReportsHomeComponent implements OnInit { + reports: ReportEntry[]; + + constructor(private stateService: StateService) {} + + async ngOnInit(): Promise { + const userHasPremium = await this.stateService.getCanAccessPremium(); + + const reportRequiresPremium = userHasPremium + ? ReportVariant.Enabled + : ReportVariant.RequiresPremium; + + this.reports = [ + { + ...reports[ReportType.ExposedPasswords], + variant: reportRequiresPremium, + }, + { + ...reports[ReportType.ReusedPasswords], + variant: reportRequiresPremium, + }, + { + ...reports[ReportType.WeakPasswords], + variant: reportRequiresPremium, + }, + { + ...reports[ReportType.UnsecuredWebsites], + variant: reportRequiresPremium, + }, + { + ...reports[ReportType.Inactive2fa], + variant: reportRequiresPremium, + }, + { + ...reports[ReportType.DataBreach], + variant: ReportVariant.Enabled, + }, + ]; + } +} diff --git a/apps/web/src/app/reports/reused-passwords-report.component.html b/apps/web/src/app/reports/pages/reused-passwords-report.component.html similarity index 100% rename from apps/web/src/app/reports/reused-passwords-report.component.html rename to apps/web/src/app/reports/pages/reused-passwords-report.component.html diff --git a/apps/web/src/app/reports/reused-passwords-report.component.ts b/apps/web/src/app/reports/pages/reused-passwords-report.component.ts similarity index 100% rename from apps/web/src/app/reports/reused-passwords-report.component.ts rename to apps/web/src/app/reports/pages/reused-passwords-report.component.ts diff --git a/apps/web/src/app/reports/unsecured-websites-report.component.html b/apps/web/src/app/reports/pages/unsecured-websites-report.component.html similarity index 100% rename from apps/web/src/app/reports/unsecured-websites-report.component.html rename to apps/web/src/app/reports/pages/unsecured-websites-report.component.html diff --git a/apps/web/src/app/reports/unsecured-websites-report.component.ts b/apps/web/src/app/reports/pages/unsecured-websites-report.component.ts similarity index 100% rename from apps/web/src/app/reports/unsecured-websites-report.component.ts rename to apps/web/src/app/reports/pages/unsecured-websites-report.component.ts diff --git a/apps/web/src/app/reports/weak-passwords-report.component.html b/apps/web/src/app/reports/pages/weak-passwords-report.component.html similarity index 100% rename from apps/web/src/app/reports/weak-passwords-report.component.html rename to apps/web/src/app/reports/pages/weak-passwords-report.component.html diff --git a/apps/web/src/app/reports/weak-passwords-report.component.ts b/apps/web/src/app/reports/pages/weak-passwords-report.component.ts similarity index 100% rename from apps/web/src/app/reports/weak-passwords-report.component.ts rename to apps/web/src/app/reports/pages/weak-passwords-report.component.ts diff --git a/apps/web/src/app/reports/report-card.component.html b/apps/web/src/app/reports/report-card/report-card.component.html similarity index 54% rename from apps/web/src/app/reports/report-card.component.html rename to apps/web/src/app/reports/report-card/report-card.component.html index 5fddd6c68c..2ea7d88cb0 100644 --- a/apps/web/src/app/reports/report-card.component.html +++ b/apps/web/src/app/reports/report-card/report-card.component.html @@ -1,25 +1,26 @@
-
+
-
-

{{ report.title | i18n }}

-

{{ report.description | i18n }}

+
+

{{ title }}

+

{{ description }}

{{ "premium" | i18n }} + {{ "premium" | i18n }} + {{ "upgrade" | i18n }} +
diff --git a/apps/web/src/app/reports/report-card/report-card.component.ts b/apps/web/src/app/reports/report-card/report-card.component.ts new file mode 100644 index 0000000000..063d52bc68 --- /dev/null +++ b/apps/web/src/app/reports/report-card/report-card.component.ts @@ -0,0 +1,23 @@ +import { Component, Input } from "@angular/core"; + +import { ReportVariant } from "../models/report-variant"; + +@Component({ + selector: "app-report-card", + templateUrl: "report-card.component.html", +}) +export class ReportCardComponent { + @Input() title: string; + @Input() description: string; + @Input() route: string; + @Input() icon: string; + @Input() variant: ReportVariant; + + protected get disabled() { + return this.variant != ReportVariant.Enabled; + } + + protected get requiresPremium() { + return this.variant == ReportVariant.RequiresPremium; + } +} diff --git a/apps/web/src/app/reports/report-card/report-card.stories.ts b/apps/web/src/app/reports/report-card/report-card.stories.ts new file mode 100644 index 0000000000..2ab9d8962e --- /dev/null +++ b/apps/web/src/app/reports/report-card/report-card.stories.ts @@ -0,0 +1,51 @@ +import { RouterTestingModule } from "@angular/router/testing"; +import { Meta, Story, moduleMetadata } from "@storybook/angular"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BadgeModule, IconModule } from "@bitwarden/components"; + +import { PremiumBadgeComponent } from "../../components/premium-badge.component"; +import { PreloadedEnglishI18nModule } from "../../tests/preloaded-english-i18n.module"; +import { ReportVariant } from "../models/report-variant"; + +import { ReportCardComponent } from "./report-card.component"; + +export default { + title: "Web/Reports/Card", + component: ReportCardComponent, + decorators: [ + moduleMetadata({ + imports: [ + JslibModule, + BadgeModule, + IconModule, + RouterTestingModule, + PreloadedEnglishI18nModule, + ], + declarations: [PremiumBadgeComponent], + }), + ], + args: { + title: "Exposed Passwords", + description: + "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins.", + icon: "reportExposedPasswords", + variant: ReportVariant.Enabled, + }, +} as Meta; + +const Template: Story = (args: ReportCardComponent) => ({ + props: args, +}); + +export const Enabled = Template.bind({}); + +export const RequiresPremium = Template.bind({}); +RequiresPremium.args = { + variant: ReportVariant.RequiresPremium, +}; + +export const RequiresUpgrade = Template.bind({}); +RequiresUpgrade.args = { + variant: ReportVariant.RequiresUpgrade, +}; diff --git a/apps/web/src/app/reports/report-list.component.html b/apps/web/src/app/reports/report-list.component.html deleted file mode 100644 index e302873119..0000000000 --- a/apps/web/src/app/reports/report-list.component.html +++ /dev/null @@ -1,11 +0,0 @@ - - -

{{ "reportsDesc" | i18n }}

- -
-
- -
-
diff --git a/apps/web/src/app/reports/report-list.component.ts b/apps/web/src/app/reports/report-list.component.ts deleted file mode 100644 index 488949842d..0000000000 --- a/apps/web/src/app/reports/report-list.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component } from "@angular/core"; - -import { ReportTypes } from "./report-card.component"; - -@Component({ - selector: "app-report-list", - templateUrl: "report-list.component.html", -}) -export class ReportListComponent { - reports = [ - ReportTypes.exposedPasswords, - ReportTypes.reusedPasswords, - ReportTypes.weakPasswords, - ReportTypes.unsecuredWebsites, - ReportTypes.inactive2fa, - ReportTypes.dataBreach, - ]; -} diff --git a/apps/web/src/app/reports/report-list/report-list.component.html b/apps/web/src/app/reports/report-list/report-list.component.html new file mode 100644 index 0000000000..809ccbabcb --- /dev/null +++ b/apps/web/src/app/reports/report-list/report-list.component.html @@ -0,0 +1,11 @@ +
+
+ +
+
diff --git a/apps/web/src/app/reports/report-list/report-list.component.ts b/apps/web/src/app/reports/report-list/report-list.component.ts new file mode 100644 index 0000000000..f2d9c3501b --- /dev/null +++ b/apps/web/src/app/reports/report-list/report-list.component.ts @@ -0,0 +1,11 @@ +import { Component, Input } from "@angular/core"; + +import { ReportEntry } from "../models/report-entry"; + +@Component({ + selector: "app-report-list", + templateUrl: "report-list.component.html", +}) +export class ReportListComponent { + @Input() reports: ReportEntry[]; +} diff --git a/apps/web/src/app/reports/report-list/report-list.stories.ts b/apps/web/src/app/reports/report-list/report-list.stories.ts new file mode 100644 index 0000000000..d52621ac62 --- /dev/null +++ b/apps/web/src/app/reports/report-list/report-list.stories.ts @@ -0,0 +1,43 @@ +import { RouterTestingModule } from "@angular/router/testing"; +import { Meta, Story, moduleMetadata } from "@storybook/angular"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BadgeModule, IconModule } from "@bitwarden/components"; + +import { PremiumBadgeComponent } from "../../components/premium-badge.component"; +import { PreloadedEnglishI18nModule } from "../../tests/preloaded-english-i18n.module"; +import { ReportVariant } from "../models/report-variant"; +import { ReportCardComponent } from "../report-card/report-card.component"; +import { reports } from "../reports"; + +import { ReportListComponent } from "./report-list.component"; + +export default { + title: "Web/Reports/List", + component: ReportListComponent, + decorators: [ + moduleMetadata({ + imports: [ + JslibModule, + BadgeModule, + RouterTestingModule, + PreloadedEnglishI18nModule, + IconModule, + ], + declarations: [PremiumBadgeComponent, ReportCardComponent], + }), + ], + args: { + reports: Object.values(reports).map((report) => ({ + ...report, + variant: + report.route == "breach-report" ? ReportVariant.Enabled : ReportVariant.RequiresPremium, + })), + }, +} as Meta; + +const Template: Story = (args: ReportListComponent) => ({ + props: args, +}); + +export const Default = Template.bind({}); diff --git a/apps/web/src/app/reports/reports.component.html b/apps/web/src/app/reports/reports-layout.component.html similarity index 100% rename from apps/web/src/app/reports/reports.component.html rename to apps/web/src/app/reports/reports-layout.component.html diff --git a/apps/web/src/app/reports/reports.component.ts b/apps/web/src/app/reports/reports-layout.component.ts similarity index 80% rename from apps/web/src/app/reports/reports.component.ts rename to apps/web/src/app/reports/reports-layout.component.ts index c0e67ac5ea..3d3fee2904 100644 --- a/apps/web/src/app/reports/reports.component.ts +++ b/apps/web/src/app/reports/reports-layout.component.ts @@ -4,10 +4,10 @@ import { Subscription } from "rxjs"; import { filter } from "rxjs/operators"; @Component({ - selector: "app-reports", - templateUrl: "reports.component.html", + selector: "app-reports-layout", + templateUrl: "reports-layout.component.html", }) -export class ReportsComponent implements OnDestroy { +export class ReportsLayoutComponent implements OnDestroy { homepage = true; subscription: Subscription; diff --git a/apps/web/src/app/reports/reports-routing.module.ts b/apps/web/src/app/reports/reports-routing.module.ts index f2029e16df..1967b1e035 100644 --- a/apps/web/src/app/reports/reports-routing.module.ts +++ b/apps/web/src/app/reports/reports-routing.module.ts @@ -3,22 +3,24 @@ import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; -import { BreachReportComponent } from "./breach-report.component"; -import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component"; -import { InactiveTwoFactorReportComponent } from "./inactive-two-factor-report.component"; -import { ReportListComponent } from "./report-list.component"; -import { ReportsComponent } from "./reports.component"; -import { ReusedPasswordsReportComponent } from "./reused-passwords-report.component"; -import { UnsecuredWebsitesReportComponent } from "./unsecured-websites-report.component"; -import { WeakPasswordsReportComponent } from "./weak-passwords-report.component"; +import { HasPremiumGuard } from "../shared/guards/has-premium.guard"; + +import { BreachReportComponent } from "./pages/breach-report.component"; +import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component"; +import { InactiveTwoFactorReportComponent } from "./pages/inactive-two-factor-report.component"; +import { ReportsHomeComponent } from "./pages/reports-home.component"; +import { ReusedPasswordsReportComponent } from "./pages/reused-passwords-report.component"; +import { UnsecuredWebsitesReportComponent } from "./pages/unsecured-websites-report.component"; +import { WeakPasswordsReportComponent } from "./pages/weak-passwords-report.component"; +import { ReportsLayoutComponent } from "./reports-layout.component"; const routes: Routes = [ { path: "", - component: ReportsComponent, + component: ReportsLayoutComponent, canActivate: [AuthGuard], children: [ - { path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } }, + { path: "", pathMatch: "full", component: ReportsHomeComponent, data: { homepage: true } }, { path: "breach-report", component: BreachReportComponent, @@ -28,26 +30,31 @@ const routes: Routes = [ path: "reused-passwords-report", component: ReusedPasswordsReportComponent, data: { titleId: "reusedPasswordsReport" }, + canActivate: [HasPremiumGuard], }, { path: "unsecured-websites-report", component: UnsecuredWebsitesReportComponent, data: { titleId: "unsecuredWebsitesReport" }, + canActivate: [HasPremiumGuard], }, { path: "weak-passwords-report", component: WeakPasswordsReportComponent, data: { titleId: "weakPasswordsReport" }, + canActivate: [HasPremiumGuard], }, { path: "exposed-passwords-report", component: ExposedPasswordsReportComponent, data: { titleId: "exposedPasswordsReport" }, + canActivate: [HasPremiumGuard], }, { path: "inactive-two-factor-report", component: InactiveTwoFactorReportComponent, data: { titleId: "inactive2faReport" }, + canActivate: [HasPremiumGuard], }, ], }, diff --git a/apps/web/src/app/reports/reports.module.ts b/apps/web/src/app/reports/reports.module.ts new file mode 100644 index 0000000000..40a865be5b --- /dev/null +++ b/apps/web/src/app/reports/reports.module.ts @@ -0,0 +1,34 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; + +import { SharedModule } from "../modules/shared.module"; + +import { BreachReportComponent } from "./pages/breach-report.component"; +import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component"; +import { InactiveTwoFactorReportComponent } from "./pages/inactive-two-factor-report.component"; +import { ReportsHomeComponent } from "./pages/reports-home.component"; +import { ReusedPasswordsReportComponent } from "./pages/reused-passwords-report.component"; +import { UnsecuredWebsitesReportComponent } from "./pages/unsecured-websites-report.component"; +import { WeakPasswordsReportComponent } from "./pages/weak-passwords-report.component"; +import { ReportCardComponent } from "./report-card/report-card.component"; +import { ReportListComponent } from "./report-list/report-list.component"; +import { ReportsLayoutComponent } from "./reports-layout.component"; +import { ReportsRoutingModule } from "./reports-routing.module"; + +@NgModule({ + imports: [CommonModule, SharedModule, ReportsRoutingModule], + declarations: [ + BreachReportComponent, + ExposedPasswordsReportComponent, + InactiveTwoFactorReportComponent, + ReportCardComponent, + ReportListComponent, + ReportsLayoutComponent, + ReportsHomeComponent, + ReusedPasswordsReportComponent, + UnsecuredWebsitesReportComponent, + WeakPasswordsReportComponent, + WeakPasswordsReportComponent, + ], +}) +export class ReportsModule {} diff --git a/apps/web/src/app/reports/reports.ts b/apps/web/src/app/reports/reports.ts new file mode 100644 index 0000000000..cd393dd113 --- /dev/null +++ b/apps/web/src/app/reports/reports.ts @@ -0,0 +1,51 @@ +import { ReportEntry } from "./models/report-entry"; + +export enum ReportType { + ExposedPasswords = "exposedPasswords", + ReusedPasswords = "reusedPasswords", + WeakPasswords = "weakPasswords", + UnsecuredWebsites = "unsecuredWebsites", + Inactive2fa = "inactive2fa", + DataBreach = "dataBreach", +} + +type ReportWithoutVariant = Omit; + +export const reports: Record = { + [ReportType.ExposedPasswords]: { + title: "exposedPasswordsReport", + description: "exposedPasswordsReportDesc", + route: "exposed-passwords-report", + icon: "reportExposedPasswords", + }, + [ReportType.ReusedPasswords]: { + title: "reusedPasswordsReport", + description: "reusedPasswordsReportDesc", + route: "reused-passwords-report", + icon: "reportReusedPasswords", + }, + [ReportType.WeakPasswords]: { + title: "weakPasswordsReport", + description: "weakPasswordsReportDesc", + route: "weak-passwords-report", + icon: "reportWeakPasswords", + }, + [ReportType.UnsecuredWebsites]: { + title: "unsecuredWebsitesReport", + description: "unsecuredWebsitesReportDesc", + route: "unsecured-websites-report", + icon: "reportUnsecuredWebsites", + }, + [ReportType.Inactive2fa]: { + title: "inactive2faReport", + description: "inactive2faReportDesc", + route: "inactive-two-factor-report", + icon: "reportInactiveTwoFactor", + }, + [ReportType.DataBreach]: { + title: "dataBreachReport", + description: "breachDesc", + route: "breach-report", + icon: "reportBreach", + }, +}; diff --git a/apps/web/src/app/settings/payment.component.ts b/apps/web/src/app/settings/payment.component.ts index 6a95a79010..a4de1521c3 100644 --- a/apps/web/src/app/settings/payment.component.ts +++ b/apps/web/src/app/settings/payment.component.ts @@ -7,7 +7,7 @@ import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PaymentMethodType } from "@bitwarden/common/enums/paymentMethodType"; import { ThemeType } from "@bitwarden/common/enums/themeType"; -import ThemeVariables from "src/scss/export.module.scss"; +import ThemeVariables from "../../scss/export.module.scss"; const lightInputColor = ThemeVariables.lightInputColor; const lightInputPlaceholderColor = ThemeVariables.lightInputPlaceholderColor; diff --git a/apps/web/src/app/shared/guards/has-premium.guard.ts b/apps/web/src/app/shared/guards/has-premium.guard.ts new file mode 100644 index 0000000000..9f0656d007 --- /dev/null +++ b/apps/web/src/app/shared/guards/has-premium.guard.ts @@ -0,0 +1,31 @@ +import { Injectable } from "@angular/core"; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router"; + +import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; +import { StateService } from "@bitwarden/common/abstractions/state.service"; + +@Injectable({ + providedIn: "root", +}) +export class HasPremiumGuard implements CanActivate { + constructor( + private router: Router, + private stateService: StateService, + private messagingService: MessagingService + ) {} + + async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) { + const userHasPremium = await this.stateService.getCanAccessPremium(); + + if (!userHasPremium) { + this.messagingService.send("premiumRequired"); + } + + // Prevent trapping the user on the login page, since that's an awful UX flow + if (!userHasPremium && this.router.url === "/login") { + return this.router.createUrlTree(["/"]); + } + + return userHasPremium; + } +} diff --git a/apps/web/src/app/tests/preloaded-english-i18n.module.ts b/apps/web/src/app/tests/preloaded-english-i18n.module.ts new file mode 100644 index 0000000000..140065cd28 --- /dev/null +++ b/apps/web/src/app/tests/preloaded-english-i18n.module.ts @@ -0,0 +1,38 @@ +import { APP_INITIALIZER, NgModule } from "@angular/core"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { I18nService as BaseI18nService } from "@bitwarden/common/services/i18n.service"; + +import * as eng from "../../locales/en/messages.json"; + +class PreloadedEnglishI18nService extends BaseI18nService { + constructor() { + super("en", "", () => { + return Promise.resolve(eng); + }); + } +} + +function i18nInitializer(i18nService: I18nService): () => Promise { + return async () => { + await (i18nService as any).init(); + }; +} + +// This is a helper I18nService implementation that loads the english `message.json` eliminating +// the need for fetching them dynamically. It should only be used within storybook. +@NgModule({ + providers: [ + { + provide: I18nService, + useClass: PreloadedEnglishI18nService, + }, + { + provide: APP_INITIALIZER, + useFactory: i18nInitializer, + deps: [I18nService], + multi: true, + }, + ], +}) +export class PreloadedEnglishI18nModule {} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 35c81e9784..fa4895114d 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "baseUrl": ".", "module": "ES2020", + "resolveJsonModule": true, "paths": { "tldjs": ["../../libs/common/src/misc/tldjs.noop"], "src/*": ["src/*"], diff --git a/libs/components/src/icon/icon.component.ts b/libs/components/src/icon/icon.component.ts new file mode 100644 index 0000000000..85e23fb9ea --- /dev/null +++ b/libs/components/src/icon/icon.component.ts @@ -0,0 +1,24 @@ +import { Component, HostBinding, Input } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; + +import { Icon, IconSvg } from "./icons"; + +@Component({ + selector: "bit-icon", + template: ``, +}) +export class BitIconComponent { + @Input() icon: Icon; + + constructor(private domSanitizer: DomSanitizer) {} + + @HostBinding("innerHtml") + protected get innerHtml() { + const svg = IconSvg[this.icon]; + if (svg == null) { + return "Unknown icon"; + } + + return this.domSanitizer.bypassSecurityTrustHtml(IconSvg[this.icon]); + } +} diff --git a/libs/components/src/icon/icon.module.ts b/libs/components/src/icon/icon.module.ts new file mode 100644 index 0000000000..32e95fd046 --- /dev/null +++ b/libs/components/src/icon/icon.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; + +import { BitIconComponent } from "./icon.component"; + +@NgModule({ + imports: [CommonModule], + declarations: [BitIconComponent], + exports: [BitIconComponent], +}) +export class IconModule {} diff --git a/libs/components/src/icon/icon.stories.ts b/libs/components/src/icon/icon.stories.ts new file mode 100644 index 0000000000..a67516f2e8 --- /dev/null +++ b/libs/components/src/icon/icon.stories.ts @@ -0,0 +1,27 @@ +import { Meta, Story } from "@storybook/angular"; + +import { BitIconComponent } from "./icon.component"; + +export default { + title: "Component Library/Icon", + component: BitIconComponent, + args: { + icon: "reportExposedPasswords", + }, +} as Meta; + +const Template: Story = (args: BitIconComponent) => ({ + props: args, + template: ` +
+ +
+ `, +}); + +export const ReportExposedPasswords = Template.bind({}); + +export const UnknownIcon = Template.bind({}); +UnknownIcon.args = { + icon: "unknown", +}; diff --git a/apps/web/src/app/reports/report-card.component.ts b/libs/components/src/icon/icons.ts similarity index 73% rename from apps/web/src/app/reports/report-card.component.ts rename to libs/components/src/icon/icons.ts index a67550d52e..c316d312e3 100644 --- a/apps/web/src/app/reports/report-card.component.ts +++ b/libs/components/src/icon/icons.ts @@ -1,32 +1,5 @@ -import { Component, Input, OnInit } from "@angular/core"; -import { DomSanitizer } from "@angular/platform-browser"; - -import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; - -export enum ReportTypes { - "exposedPasswords" = "exposedPasswords", - "reusedPasswords" = "reusedPasswords", - "weakPasswords" = "weakPasswords", - "unsecuredWebsites" = "unsecuredWebsites", - "inactive2fa" = "inactive2fa", - "dataBreach" = "dataBreach", -} - -type ReportEntry = { - title: string; - description: string; - route: string; - icon: string; - requiresPremium: boolean; -}; - -const reports: Record = { - exposedPasswords: { - title: "exposedPasswordsReport", - description: "exposedPasswordsReportDesc", - route: "exposed-passwords-report", - icon: ` +export const IconSvg = { + reportExposedPasswords: ` @@ -40,26 +13,14 @@ const reports: Record = { `, - requiresPremium: true, - }, - reusedPasswords: { - title: "reusedPasswordsReport", - description: "reusedPasswordsReportDesc", - route: "reused-passwords-report", - icon: ` + reportReusedPasswords: ` `, - requiresPremium: true, - }, - weakPasswords: { - title: "weakPasswordsReport", - description: "weakPasswordsReportDesc", - route: "weak-passwords-report", - icon: ` + reportWeakPasswords: ` @@ -67,13 +28,7 @@ const reports: Record = { `, - requiresPremium: true, - }, - unsecuredWebsites: { - title: "unsecuredWebsitesReport", - description: "unsecuredWebsitesReportDesc", - route: "unsecured-websites-report", - icon: ` + reportUnsecuredWebsites: ` @@ -83,13 +38,7 @@ const reports: Record = { `, - requiresPremium: true, - }, - inactive2fa: { - title: "inactive2faReport", - description: "inactive2faReportDesc", - route: "inactive-two-factor-report", - icon: ` + reportInactiveTwoFactor: ` @@ -97,13 +46,7 @@ const reports: Record = { `, - requiresPremium: true, - }, - dataBreach: { - title: "dataBreachReport", - description: "breachDesc", - route: "breach-report", - icon: ` + reportBreach: ` @@ -115,52 +58,6 @@ const reports: Record = { `, - requiresPremium: false, - }, }; -@Component({ - selector: "app-report-card", - templateUrl: "report-card.component.html", -}) -export class ReportCardComponent implements OnInit { - @Input() type: ReportTypes; - - report: ReportEntry; - - hasPremium: boolean; - - constructor( - private stateService: StateService, - private messagingService: MessagingService, - private sanitizer: DomSanitizer - ) {} - - async ngOnInit() { - this.report = reports[this.type]; - - this.hasPremium = await this.stateService.getCanAccessPremium(); - } - - get premium() { - return this.report.requiresPremium && !this.hasPremium; - } - - get route() { - if (this.premium) { - return null; - } - - return this.report.route; - } - - get icon() { - return this.sanitizer.bypassSecurityTrustHtml(this.report.icon); - } - - click() { - if (this.premium) { - this.messagingService.send("premiumRequired"); - } - } -} +export type Icon = keyof typeof IconSvg; diff --git a/libs/components/src/icon/index.ts b/libs/components/src/icon/index.ts new file mode 100644 index 0000000000..1ee66e5983 --- /dev/null +++ b/libs/components/src/icon/index.ts @@ -0,0 +1 @@ +export * from "./icon.module"; diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index 10553ab5c7..27da8cf6ef 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -1,11 +1,12 @@ export * from "./badge"; export * from "./banner"; export * from "./button"; -export * from "./toggle-group"; export * from "./callout"; export * from "./form-field"; +export * from "./icon"; export * from "./menu"; export * from "./modal"; -export * from "./utils/i18n-mock.service"; -export * from "./tabs"; export * from "./submit-button"; +export * from "./tabs"; +export * from "./toggle-group"; +export * from "./utils/i18n-mock.service"; diff --git a/tailwind.config.js b/tailwind.config.js index dd391db8d3..3eadeab0c7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,11 @@ /* eslint-disable */ const config = require("./libs/components/tailwind.config.base"); -config.content = ["./libs/components/src/**/*.{html,ts,mdx}", "./.storybook/preview.js"]; +config.content = [ + "./libs/components/src/**/*.{html,ts,mdx}", + "./apps/web/src/**/*.{html,ts,mdx}", + "./.storybook/preview.js", +]; config.safelist = [ { pattern: /tw-bg-(.*)/, diff --git a/tsconfig.json b/tsconfig.json index d6011a5f6f..f8b94f2a1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "declaration": false, "outDir": "dist", "baseUrl": ".", + "resolveJsonModule": true, "paths": { "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/angular/*": ["./libs/angular/src/*"], @@ -26,6 +27,6 @@ } ] }, - "include": ["apps/*/src/**/*.stories.ts", "libs/*/src/**/*"], - "exclude": ["apps/*/src/**/*.spec.ts", "libs/*/src/**/*.spec.ts"] + "include": ["apps/web/src/**/*", "libs/*/src/**/*"], + "exclude": ["apps/web/src/**/*.spec.ts", "libs/*/src/**/*.spec.ts"] }