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: `
`,
- 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: `
`,
- requiresPremium: true,
- },
- unsecuredWebsites: {
- title: "unsecuredWebsitesReport",
- description: "unsecuredWebsitesReportDesc",
- route: "unsecured-websites-report",
- icon: `
+ reportUnsecuredWebsites: `
`,
- requiresPremium: true,
- },
- inactive2fa: {
- title: "inactive2faReport",
- description: "inactive2faReportDesc",
- route: "inactive-two-factor-report",
- icon: `
+ reportInactiveTwoFactor: `
`,
- requiresPremium: true,
- },
- dataBreach: {
- title: "dataBreachReport",
- description: "breachDesc",
- route: "breach-report",
- icon: `
+ reportBreach: `
`,
- 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"]
}