diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts
index 65ac83b59fd..b98dd0a8398 100644
--- a/apps/desktop/src/app/app.component.ts
+++ b/apps/desktop/src/app/app.component.ts
@@ -55,6 +55,7 @@ import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.compo
import { SettingsComponent } from "./accounts/settings.component";
import { ExportComponent } from "./tools/export/export.component";
import { GeneratorComponent } from "./tools/generator.component";
+import { ImportDesktopComponent } from "./tools/import/import-desktop.component";
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
const BroadcasterSubscriptionId = "AppComponent";
@@ -328,6 +329,9 @@ export class AppComponent implements OnInit, OnDestroy {
}
this.messagingService.send("scheduleNextSync");
break;
+ case "importVault":
+ await this.dialogService.open(ImportDesktopComponent);
+ break;
case "exportVault":
await this.openExportVault();
break;
diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.html b/apps/desktop/src/app/tools/import/import-desktop.component.html
new file mode 100644
index 00000000000..74d4098255b
--- /dev/null
+++ b/apps/desktop/src/app/tools/import/import-desktop.component.html
@@ -0,0 +1,26 @@
+
+ {{ "importData" | i18n }}
+
+
+
+
+
+
+
+
diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.ts b/apps/desktop/src/app/tools/import/import-desktop.component.ts
new file mode 100644
index 00000000000..62fc007731d
--- /dev/null
+++ b/apps/desktop/src/app/tools/import/import-desktop.component.ts
@@ -0,0 +1,33 @@
+import { DialogRef } from "@angular/cdk/dialog";
+import { CommonModule } from "@angular/common";
+import { Component } from "@angular/core";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components";
+import { ImportComponent } from "@bitwarden/importer/ui";
+
+@Component({
+ templateUrl: "import-desktop.component.html",
+ standalone: true,
+ imports: [
+ CommonModule,
+ JslibModule,
+ DialogModule,
+ AsyncActionsModule,
+ ButtonModule,
+ ImportComponent,
+ ],
+})
+export class ImportDesktopComponent {
+ protected disabled = false;
+ protected loading = false;
+
+ constructor(public dialogRef: DialogRef) {}
+
+ /**
+ * Callback that is called after a successful import.
+ */
+ protected async onSuccessfulImport(organizationId: string): Promise {
+ this.dialogRef.close();
+ }
+}
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 52fdbf1b560..537be07da97 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -1662,6 +1662,9 @@
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
},
+ "personalOwnershipPolicyInEffectImports": {
+ "message": "An organization policy has blocked importing items into your individual vault."
+ },
"allSends": {
"message": "All Sends",
"description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
@@ -2427,5 +2430,113 @@
},
"aliasDomain": {
"message": "Alias domain"
+ },
+ "importData": {
+ "message": "Import data",
+ "description": "Used for the desktop menu item and the header of the import dialog"
+ },
+ "importError": {
+ "message": "Import error"
+ },
+ "importErrorDesc": {
+ "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again."
+ },
+ "resolveTheErrorsBelowAndTryAgain": {
+ "message": "Resolve the errors below and try again."
+ },
+ "description": {
+ "message": "Description"
+ },
+ "importSuccess": {
+ "message": "Data successfully imported"
+ },
+ "importSuccessNumberOfItems": {
+ "message": "A total of $AMOUNT$ items were imported.",
+ "placeholders": {
+ "amount": {
+ "content": "$1",
+ "example": "2"
+ }
+ }
+ },
+ "total": {
+ "message": "Total"
+ },
+ "importWarning": {
+ "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?",
+ "placeholders": {
+ "organization": {
+ "content": "$1",
+ "example": "My Org Name"
+ }
+ }
+ },
+ "importFormatError": {
+ "message": "Data is not formatted correctly. Please check your import file and try again."
+ },
+ "importNothingError": {
+ "message": "Nothing was imported."
+ },
+ "importEncKeyError": {
+ "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data."
+ },
+ "importDestination": {
+ "message": "Import destination"
+ },
+ "learnAboutImportOptions": {
+ "message": "Learn about your import options"
+ },
+ "selectImportFolder": {
+ "message": "Select a folder"
+ },
+ "selectImportCollection": {
+ "message": "Select a collection"
+ },
+ "importTargetHint": {
+ "message": "Select this option if you want the imported file contents moved to a $DESTINATION$",
+ "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.",
+ "placeholders": {
+ "destination": {
+ "content": "$1",
+ "example": "folder or collection"
+ }
+ }
+ },
+ "importUnassignedItemsError": {
+ "message": "File contains unassigned items."
+ },
+ "selectFormat": {
+ "message": "Select the format of the import file"
+ },
+ "selectImportFile": {
+ "message": "Select the import file"
+ },
+ "chooseFile": {
+ "message": "Choose File"
+ },
+ "noFileChosen": {
+ "message": "No file chosen"
+ },
+ "orCopyPasteFileContents": {
+ "message": "or copy/paste the import file contents"
+ },
+ "instructionsFor": {
+ "message": "$NAME$ Instructions",
+ "description": "The title for the import tool instructions.",
+ "placeholders": {
+ "name": {
+ "content": "$1",
+ "example": "LastPass (csv)"
+ }
+ }
+ },
+ "confirmVaultImport": {
+ "message": "Confirm vault import"
+ },
+ "confirmVaultImportDesc": {
+ "message": "This file is password-protected. Please enter the file password to import data."
+ },
+ "confirmFilePassword": {
+ "message": "Confirm file password"
}
}
diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts
index 173b6066aba..0782039d7c2 100644
--- a/apps/desktop/src/main/menu/menu.file.ts
+++ b/apps/desktop/src/main/menu/menu.file.ts
@@ -24,6 +24,7 @@ export class FileMenu extends FirstMenu implements IMenubarMenu {
this.addNewFolder,
this.separator,
this.syncVault,
+ this.importVault,
this.exportVault,
];
@@ -123,6 +124,15 @@ export class FileMenu extends FirstMenu implements IMenubarMenu {
};
}
+ private get importVault(): MenuItemConstructorOptions {
+ return {
+ id: "importVault",
+ label: this.localize("importData"),
+ click: () => this.sendMessage("importVault"),
+ enabled: !this._isLocked,
+ };
+ }
+
private get exportVault(): MenuItemConstructorOptions {
return {
id: "exportVault",