diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index f1f5a15ac5..e8a9e96924 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -62,6 +62,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { DialogService, ToastOptions, ToastService } from "@bitwarden/components"; +import { CredentialGeneratorHistoryDialogComponent } from "@bitwarden/generator-components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KeyService, BiometricStateService } from "@bitwarden/key-management"; @@ -324,10 +325,7 @@ export class AppComponent implements OnInit, OnDestroy { await this.deleteAccount(); break; case "openPasswordHistory": - await this.openModal( - PasswordGeneratorHistoryComponent, - this.passwordHistoryRef, - ); + await this.openGeneratorHistory(); break; case "showToast": this.toastService._showToast(message); @@ -558,6 +556,21 @@ export class AppComponent implements OnInit, OnDestroy { }); } + async openGeneratorHistory() { + const isGeneratorSwapEnabled = await this.configService.getFeatureFlag( + FeatureFlag.GeneratorToolsModernization, + ); + if (isGeneratorSwapEnabled) { + await this.dialogService.open(CredentialGeneratorHistoryDialogComponent); + return; + } + + await this.openModal( + PasswordGeneratorHistoryComponent, + this.passwordHistoryRef, + ); + } + private async updateAppMenu() { let updateRequest: MenuUpdateRequest; const stateAccounts = await firstValueFrom(this.accountService.accounts$); diff --git a/apps/desktop/src/app/tools/generator/credential-generator.component.html b/apps/desktop/src/app/tools/generator/credential-generator.component.html index 423c7119ea..37d677472d 100644 --- a/apps/desktop/src/app/tools/generator/credential-generator.component.html +++ b/apps/desktop/src/app/tools/generator/credential-generator.component.html @@ -2,6 +2,18 @@ {{ "generator" | i18n }} + + + + diff --git a/apps/web/src/app/tools/credential-generator/credential-generator.component.ts b/apps/web/src/app/tools/credential-generator/credential-generator.component.ts index 9eb4b0a081..f252796d06 100644 --- a/apps/web/src/app/tools/credential-generator/credential-generator.component.ts +++ b/apps/web/src/app/tools/credential-generator/credential-generator.component.ts @@ -1,6 +1,10 @@ import { Component } from "@angular/core"; -import { GeneratorModule } from "@bitwarden/generator-components"; +import { ButtonModule, DialogService, ItemModule, LinkModule } from "@bitwarden/components"; +import { + CredentialGeneratorHistoryDialogComponent, + GeneratorModule, +} from "@bitwarden/generator-components"; import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; @@ -9,6 +13,12 @@ import { SharedModule } from "../../shared"; standalone: true, selector: "credential-generator", templateUrl: "credential-generator.component.html", - imports: [SharedModule, HeaderModule, GeneratorModule], + imports: [SharedModule, HeaderModule, GeneratorModule, ItemModule, ButtonModule, LinkModule], }) -export class CredentialGeneratorComponent {} +export class CredentialGeneratorComponent { + constructor(private dialogService: DialogService) {} + + openHistoryDialog = () => { + this.dialogService.open(CredentialGeneratorHistoryDialogComponent); + }; +} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index a6642d9d11..0c1558a35d 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1642,9 +1642,27 @@ "passwordHistory": { "message": "Password history" }, + "generatorHistory": { + "message": "Generator history" + }, + "clearGeneratorHistoryTitle": { + "message": "Clear generator history" + }, + "cleargGeneratorHistoryDescription": { + "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + }, "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "nothingToShow": { + "message": "Nothing to show" + }, + "nothingGeneratedRecently": { + "message": "You haven't generated anything recently" + }, "clear": { "message": "Clear", "description": "To clear something out. Example: To clear browser history." diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.html b/libs/tools/generator/components/src/credential-generator-history-dialog.component.html new file mode 100644 index 0000000000..b07eb62ae9 --- /dev/null +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.component.html @@ -0,0 +1,18 @@ + + {{ "generatorHistory" | i18n }} + + + + + + + + diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts new file mode 100644 index 0000000000..af1221e9d4 --- /dev/null +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.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, DialogModule, DialogService } from "@bitwarden/components"; +import { GeneratorHistoryService } from "@bitwarden/generator-history"; + +import { CredentialGeneratorHistoryComponent as CredentialGeneratorHistoryToolsComponent } from "./credential-generator-history.component"; +import { EmptyCredentialHistoryComponent } from "./empty-credential-history.component"; + +@Component({ + templateUrl: "credential-generator-history-dialog.component.html", + standalone: true, + imports: [ + ButtonModule, + CommonModule, + JslibModule, + DialogModule, + CredentialGeneratorHistoryToolsComponent, + EmptyCredentialHistoryComponent, + ], +}) +export class CredentialGeneratorHistoryDialogComponent { + protected readonly hasHistory$ = new BehaviorSubject(false); + protected readonly userId$ = new BehaviorSubject(null); + + constructor( + private accountService: AccountService, + private history: GeneratorHistoryService, + private dialogService: DialogService, + ) { + 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 () => { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "clearGeneratorHistoryTitle" }, + content: { key: "cleargGeneratorHistoryDescription" }, + type: "warning", + acceptButtonText: { key: "clearHistory" }, + cancelButtonText: { key: "cancel" }, + }); + + if (confirmed) { + await this.history.clear(await firstValueFrom(this.userId$)); + } + }; +} diff --git a/libs/tools/generator/components/src/index.ts b/libs/tools/generator/components/src/index.ts index 213461174f..3850e8a3be 100644 --- a/libs/tools/generator/components/src/index.ts +++ b/libs/tools/generator/components/src/index.ts @@ -1,3 +1,4 @@ export { CredentialGeneratorHistoryComponent } from "./credential-generator-history.component"; +export { CredentialGeneratorHistoryDialogComponent } from "./credential-generator-history-dialog.component"; export { EmptyCredentialHistoryComponent } from "./empty-credential-history.component"; export { GeneratorModule } from "./generator.module";