diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 32215013ac..05525be6ff 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 7af8831684..50f1aef6c0 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -53,6 +53,7 @@ import { PremiumV2Component } from "../billing/popup/settings/premium-v2.compone import { PremiumComponent } from "../billing/popup/settings/premium.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; +import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component"; import { CredentialGeneratorComponent } from "../tools/popup/generator/credential-generator.component"; import { GeneratorComponent } from "../tools/popup/generator/generator.component"; import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component"; @@ -276,12 +277,11 @@ const routes: Routes = [ canActivate: [authGuard], data: { state: "generator" }, }, - { + ...extensionRefreshSwap(PasswordGeneratorHistoryComponent, CredentialGeneratorHistoryComponent, { path: "generator-history", - component: PasswordGeneratorHistoryComponent, canActivate: [authGuard], data: { state: "generator-history" }, - }, + }), ...extensionRefreshSwap(ImportBrowserComponent, ImportBrowserV2Component, { path: "import", canActivate: [authGuard], diff --git a/apps/browser/src/tools/popup/generator/credential-generator-history.component.html b/apps/browser/src/tools/popup/generator/credential-generator-history.component.html new file mode 100644 index 0000000000..48f0479d8d --- /dev/null +++ b/apps/browser/src/tools/popup/generator/credential-generator-history.component.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts b/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts new file mode 100644 index 0000000000..0de71cf5b9 --- /dev/null +++ b/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts @@ -0,0 +1,66 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { BehaviorSubject, distinctUntilChanged, firstValueFrom, map, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { ButtonModule, ContainerComponent } from "@bitwarden/components"; +import { + CredentialGeneratorHistoryComponent as CredentialGeneratorHistoryToolsComponent, + EmptyCredentialHistoryComponent, +} from "@bitwarden/generator-components"; +import { GeneratorHistoryService } from "@bitwarden/generator-history"; + +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; + +@Component({ + selector: "app-credential-generator-history", + templateUrl: "credential-generator-history.component.html", + standalone: true, + imports: [ + ButtonModule, + CommonModule, + ContainerComponent, + JslibModule, + PopOutComponent, + PopupHeaderComponent, + PopupPageComponent, + CredentialGeneratorHistoryToolsComponent, + EmptyCredentialHistoryComponent, + PopupFooterComponent, + ], +}) +export class CredentialGeneratorHistoryComponent { + protected readonly hasHistory$ = new BehaviorSubject(false); + protected readonly userId$ = new BehaviorSubject(null); + + constructor( + private accountService: AccountService, + private history: GeneratorHistoryService, + ) { + this.accountService.activeAccount$ + .pipe( + takeUntilDestroyed(), + map(({ id }) => id), + distinctUntilChanged(), + ) + .subscribe(this.userId$); + + this.userId$ + .pipe( + takeUntilDestroyed(), + switchMap((id) => id && this.history.credentials$(id)), + map((credentials) => credentials.length > 0), + ) + .subscribe(this.hasHistory$); + } + + clear = async () => { + await this.history.clear(await firstValueFrom(this.userId$)); + }; +} diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 49f11fabc8..5ccba9ead3 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -243,6 +243,10 @@ import { FolderService } from "@bitwarden/common/vault/services/folder/folder.se import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service"; import { ToastService } from "@bitwarden/components"; +import { + GeneratorHistoryService, + LocalGeneratorHistoryService, +} from "@bitwarden/generator-history"; import { legacyPasswordGenerationServiceFactory, legacyUsernameGenerationServiceFactory, @@ -592,6 +596,11 @@ const safeProviders: SafeProvider[] = [ StateProvider, ], }), + safeProvider({ + provide: GeneratorHistoryService, + useClass: LocalGeneratorHistoryService, + deps: [EncryptService, CryptoServiceAbstraction, StateProvider], + }), safeProvider({ provide: UsernameGenerationServiceAbstraction, useFactory: legacyUsernameGenerationServiceFactory, diff --git a/libs/tools/generator/components/src/credential-generator-history.component.html b/libs/tools/generator/components/src/credential-generator-history.component.html new file mode 100644 index 0000000000..2b8802b932 --- /dev/null +++ b/libs/tools/generator/components/src/credential-generator-history.component.html @@ -0,0 +1,20 @@ + +
+
+

{{ credential.credential }}

+ {{ + credential.generationDate | date: "medium" + }} +
+ +
+
diff --git a/libs/tools/generator/components/src/credential-generator-history.component.ts b/libs/tools/generator/components/src/credential-generator-history.component.ts new file mode 100644 index 0000000000..2f76027a94 --- /dev/null +++ b/libs/tools/generator/components/src/credential-generator-history.component.ts @@ -0,0 +1,58 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { RouterLink } from "@angular/router"; +import { BehaviorSubject, distinctUntilChanged, map, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { + CardComponent, + IconButtonModule, + NoItemsModule, + SectionComponent, + SectionHeaderComponent, +} from "@bitwarden/components"; +import { GeneratedCredential, GeneratorHistoryService } from "@bitwarden/generator-history"; + +@Component({ + standalone: true, + selector: "bit-credential-generator-history", + templateUrl: "credential-generator-history.component.html", + imports: [ + CommonModule, + IconButtonModule, + NoItemsModule, + JslibModule, + RouterLink, + CardComponent, + SectionComponent, + SectionHeaderComponent, + ], +}) +export class CredentialGeneratorHistoryComponent { + protected readonly userId$ = new BehaviorSubject(null); + protected readonly credentials$ = new BehaviorSubject([]); + + constructor( + private accountService: AccountService, + private history: GeneratorHistoryService, + ) { + this.accountService.activeAccount$ + .pipe( + takeUntilDestroyed(), + map(({ id }) => id), + distinctUntilChanged(), + ) + .subscribe(this.userId$); + + this.userId$ + .pipe( + takeUntilDestroyed(), + switchMap((id) => id && this.history.credentials$(id)), + map((credentials) => credentials), + ) + .subscribe(this.credentials$); + } +} diff --git a/libs/tools/generator/components/src/empty-credential-history.component.html b/libs/tools/generator/components/src/empty-credential-history.component.html new file mode 100644 index 0000000000..ee28dc5895 --- /dev/null +++ b/libs/tools/generator/components/src/empty-credential-history.component.html @@ -0,0 +1,7 @@ +
+
+ +

{{ "noPasswordsToShow" | i18n }}

+
{{ "noRecentlyGeneratedPassword" | i18n }}
+
+
diff --git a/libs/tools/generator/components/src/empty-credential-history.component.ts b/libs/tools/generator/components/src/empty-credential-history.component.ts new file mode 100644 index 0000000000..1e23adf0bb --- /dev/null +++ b/libs/tools/generator/components/src/empty-credential-history.component.ts @@ -0,0 +1,18 @@ +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { IconModule, TypographyModule } from "@bitwarden/components"; + +import { NoCredentialsIcon } from "./icons/no-credentials.icon"; + +@Component({ + standalone: true, + selector: "bit-empty-credential-history", + templateUrl: "empty-credential-history.component.html", + imports: [JslibModule, IconModule, TypographyModule], +}) +export class EmptyCredentialHistoryComponent { + noCredentialsIcon = NoCredentialsIcon; + + constructor() {} +} diff --git a/libs/tools/generator/components/src/icons/no-credentials.icon.ts b/libs/tools/generator/components/src/icons/no-credentials.icon.ts new file mode 100644 index 0000000000..63843faccc --- /dev/null +++ b/libs/tools/generator/components/src/icons/no-credentials.icon.ts @@ -0,0 +1,27 @@ +import { svgIcon } from "@bitwarden/components"; + +export const NoCredentialsIcon = svgIcon` + + + + + + + + + + + + + + + + + + + + + + + + `; diff --git a/libs/tools/generator/components/src/index.ts b/libs/tools/generator/components/src/index.ts index 0fc0655a02..5915c5d59f 100644 --- a/libs/tools/generator/components/src/index.ts +++ b/libs/tools/generator/components/src/index.ts @@ -1,2 +1,4 @@ export { PassphraseSettingsComponent } from "./passphrase-settings.component"; +export { CredentialGeneratorHistoryComponent } from "./credential-generator-history.component"; +export { EmptyCredentialHistoryComponent } from "./empty-credential-history.component"; export { PasswordSettingsComponent } from "./password-settings.component";