diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index bc2448d924e..89b605ab632 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4296,5 +4296,26 @@ }, "additionalContentAvailable": { "message": "Additional content is available" + }, + "itemsInTrash": { + "message": "Items in trash" + }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "trashWarning": { + "message": "Items that have been in trash more than 30 days will automatically be deleted" + }, + "restore": { + "message": "Restore" + }, + "deleteForever": { + "message": "Delete forever" + }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" } } diff --git a/apps/browser/src/popup/app-routing.animations.ts b/apps/browser/src/popup/app-routing.animations.ts index 227ede146ba..3da6fdef196 100644 --- a/apps/browser/src/popup/app-routing.animations.ts +++ b/apps/browser/src/popup/app-routing.animations.ts @@ -199,6 +199,9 @@ export const routerTransition = trigger("routerTransition", [ transition("vault-settings => sync", inSlideLeft), transition("sync => vault-settings", outSlideRight), + transition("vault-settings => trash", inSlideLeft), + transition("trash => vault-settings", outSlideRight), + // Appearance settings transition("tabs => appearance", inSlideLeft), transition("appearance => tabs", outSlideRight), diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 455909336b3..82e673a9e54 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -91,6 +91,7 @@ import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit. import { FoldersV2Component } from "../vault/popup/settings/folders-v2.component"; import { FoldersComponent } from "../vault/popup/settings/folders.component"; import { SyncComponent } from "../vault/popup/settings/sync.component"; +import { TrashComponent } from "../vault/popup/settings/trash.component"; import { VaultSettingsV2Component } from "../vault/popup/settings/vault-settings-v2.component"; import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component"; @@ -496,6 +497,12 @@ const routes: Routes = [ component: AccountSwitcherComponent, data: { state: "account-switcher", doNotSaveUrl: true }, }, + { + path: "trash", + component: TrashComponent, + canActivate: [authGuard], + data: { state: "trash" }, + }, ]; @Injectable() diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html index 6840924fb0a..2595a6459c4 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html @@ -5,13 +5,30 @@ - - + + + + + + + + + + + + diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts new file mode 100644 index 00000000000..1ec0f52aa6d --- /dev/null +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -0,0 +1,107 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { Router } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + DialogService, + IconButtonModule, + ItemModule, + MenuModule, + SectionComponent, + SectionHeaderComponent, + ToastService, +} from "@bitwarden/components"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +@Component({ + selector: "app-trash-list-items-container", + templateUrl: "trash-list-items-container.component.html", + standalone: true, + imports: [ + CommonModule, + ItemModule, + JslibModule, + SectionComponent, + SectionHeaderComponent, + MenuModule, + IconButtonModule, + ], +}) +export class TrashListItemsContainerComponent { + /** + * The list of trashed items to display. + */ + @Input() + ciphers: CipherView[] = []; + + @Input() + headerText: string; + + constructor( + private cipherService: CipherService, + private logService: LogService, + private toastService: ToastService, + private i18nService: I18nService, + private dialogService: DialogService, + private passwordRepromptService: PasswordRepromptService, + private router: Router, + ) {} + + async restore(cipher: CipherView) { + try { + await this.cipherService.restoreWithServer(cipher.id); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItem"), + }); + } catch (e) { + this.logService.error(e); + } + } + + async delete(cipher: CipherView) { + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher); + + if (!repromptPassed) { + return; + } + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "deleteItem" }, + content: { key: "permanentlyDeleteItemConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.cipherService.deleteWithServer(cipher.id); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedItem"), + }); + } catch (e) { + this.logService.error(e); + } + } + + async onViewCipher(cipher: CipherView) { + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher); + if (!repromptPassed) { + return; + } + + await this.router.navigate(["/view-cipher"], { + queryParams: { cipherId: cipher.id, type: cipher.type }, + }); + } +} diff --git a/apps/browser/src/vault/popup/settings/trash.component.html b/apps/browser/src/vault/popup/settings/trash.component.html new file mode 100644 index 00000000000..ab3b6716504 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/trash.component.html @@ -0,0 +1,33 @@ + + + + + + + + + {{ "trashWarning" | i18n }} + + + + + + + + + + {{ "noItemsInTrash" | i18n }} + + + {{ "noItemsInTrashDesc" | i18n }} + + + + + diff --git a/apps/browser/src/vault/popup/settings/trash.component.ts b/apps/browser/src/vault/popup/settings/trash.component.ts new file mode 100644 index 00000000000..b6f77ef6a52 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/trash.component.ts @@ -0,0 +1,37 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { CalloutModule, NoItemsModule } from "@bitwarden/components"; +import { VaultIcons } from "@bitwarden/vault"; + +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { VaultListItemsContainerComponent } from "../components/vault-v2/vault-list-items-container/vault-list-items-container.component"; +import { VaultPopupItemsService } from "../services/vault-popup-items.service"; + +import { TrashListItemsContainerComponent } from "./trash-list-items-container/trash-list-items-container.component"; + +@Component({ + templateUrl: "trash.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + PopupPageComponent, + PopupHeaderComponent, + PopOutComponent, + VaultListItemsContainerComponent, + TrashListItemsContainerComponent, + CalloutModule, + NoItemsModule, + ], +}) +export class TrashComponent { + protected deletedCiphers$ = this.vaultPopupItemsService.deletedCiphers$; + + protected emptyTrashIcon = VaultIcons.EmptyTrash; + + constructor(private vaultPopupItemsService: VaultPopupItemsService) {} +} diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html index 10243bdaa9f..03dd1182fbb 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html @@ -24,6 +24,12 @@ + + + {{ "trash" | i18n }} + + +