diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index efc8c25fc5e..b50db6e08b6 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -100,13 +100,13 @@ jobs:
persist-credentials: false
- name: Install Rust
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # stable
+ uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
with:
toolchain: stable
components: rustfmt, clippy
- name: Install Rust nightly
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # stable
+ uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
with:
toolchain: nightly
components: rustfmt
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a0f783bbb36..c2fd4b7c32b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -210,7 +210,7 @@ jobs:
persist-credentials: false
- name: Install rust
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # stable
+ uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable
with:
toolchain: stable
components: llvm-tools
diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json
index 78cf90c3555..7334362d446 100644
--- a/apps/browser/src/_locales/ar/messages.json
+++ b/apps/browser/src/_locales/ar/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json
index 6a43475da32..6168f7cf2dd 100644
--- a/apps/browser/src/_locales/az/messages.json
+++ b/apps/browser/src/_locales/az/messages.json
@@ -573,13 +573,10 @@
"noItemsInArchiveDesc": {
"message": "Arxivlənmiş elementlər burada görünəcək, ümumi axtarış nəticələrindən və avto-doldurma təkliflərindən xaric ediləcək."
},
- "itemWasSentToArchive": {
- "message": "Element arxivə göndərildi"
+ "itemArchiveToast": {
+ "message": "Element arxivləndi"
},
- "itemWasUnarchived": {
- "message": "Element arxivdən çıxarıldı"
- },
- "itemUnarchived": {
+ "itemUnarchivedToast": {
"message": "Element arxivdən çıxarıldı"
},
"archiveItem": {
diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json
index 9f4a65e3072..ea569cabdf4 100644
--- a/apps/browser/src/_locales/be/messages.json
+++ b/apps/browser/src/_locales/be/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json
index a46ad75065e..e5d68bce366 100644
--- a/apps/browser/src/_locales/bg/messages.json
+++ b/apps/browser/src/_locales/bg/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Архивираните елементи ще се показват тук и ще бъдат изключени от общите резултати при търсене и от предложенията за автоматично попълване."
},
- "itemWasSentToArchive": {
- "message": "Елементът беше преместен в архива"
+ "itemArchiveToast": {
+ "message": "Елементът е преместен в архива"
},
- "itemWasUnarchived": {
- "message": "Елементът беше изваден от архива"
- },
- "itemUnarchived": {
- "message": "Елементът беше изваден от архива"
+ "itemUnarchivedToast": {
+ "message": "Елементът е изваден от архива"
},
"archiveItem": {
"message": "Архивиране на елемента"
diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json
index b46d0664231..533b12ab0a5 100644
--- a/apps/browser/src/_locales/bn/messages.json
+++ b/apps/browser/src/_locales/bn/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json
index e81fc637b5c..35c4177e5eb 100644
--- a/apps/browser/src/_locales/bs/messages.json
+++ b/apps/browser/src/_locales/bs/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json
index 2bd53876953..8e82fc34be4 100644
--- a/apps/browser/src/_locales/ca/messages.json
+++ b/apps/browser/src/_locales/ca/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json
index 1501c7d7c4a..ed1b37134e1 100644
--- a/apps/browser/src/_locales/cs/messages.json
+++ b/apps/browser/src/_locales/cs/messages.json
@@ -573,13 +573,10 @@
"noItemsInArchiveDesc": {
"message": "Zde se zobrazí archivované položky a budou vyloučeny z obecných výsledků vyhledávání a návrhů automatického vyplňování."
},
- "itemWasSentToArchive": {
- "message": "Položka byla přesunuta do archivu"
+ "itemArchiveToast": {
+ "message": "Položka archivována"
},
- "itemWasUnarchived": {
- "message": "Položka byla odebrána z archivu"
- },
- "itemUnarchived": {
+ "itemUnarchivedToast": {
"message": "Položka byla odebrána z archivu"
},
"archiveItem": {
diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json
index 6910fe2efb3..165cd05de8e 100644
--- a/apps/browser/src/_locales/cy/messages.json
+++ b/apps/browser/src/_locales/cy/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json
index faf4fc855ec..615cc6a2a0b 100644
--- a/apps/browser/src/_locales/da/messages.json
+++ b/apps/browser/src/_locales/da/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json
index ad5b45159df..8f2b023bc00 100644
--- a/apps/browser/src/_locales/de/messages.json
+++ b/apps/browser/src/_locales/de/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Vorschlägen zum automatischen Ausfüllen ausgeschlossen."
},
- "itemWasSentToArchive": {
- "message": "Eintrag wurde archiviert"
+ "itemArchiveToast": {
+ "message": "Eintrag archiviert"
},
- "itemWasUnarchived": {
- "message": "Eintrag wird nicht mehr archiviert"
- },
- "itemUnarchived": {
- "message": "Eintrag wird nicht mehr archiviert"
+ "itemUnarchivedToast": {
+ "message": "Eintrag nicht mehr archiviert"
},
"archiveItem": {
"message": "Eintrag archivieren"
@@ -5964,7 +5961,7 @@
"message": "Kartennummer"
},
"errorCannotDecrypt": {
- "message": "Error: Cannot decrypt"
+ "message": "Fehler: Entschlüsselung nicht möglich"
},
"removeMasterPasswordForOrgUserKeyConnector": {
"message": "Deine Organisation verwendet keine Master-Passwörter mehr, um sich bei Bitwarden anzumelden. Verifiziere die Organisation und Domain, um fortzufahren."
@@ -6128,7 +6125,7 @@
"message": "benutzer@bitwarden.com, benutzer@acme.com"
},
"downloadBitwardenApps": {
- "message": "Download Bitwarden apps"
+ "message": "Bitwarden-Apps herunterladen"
},
"emailProtected": {
"message": "E-Mail-Adresse geschützt"
diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json
index 59f757008f2..68f7267825d 100644
--- a/apps/browser/src/_locales/el/messages.json
+++ b/apps/browser/src/_locales/el/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Το στοιχείο στάλθηκε στην αρχειοθήκη"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Το στοιχείο επαναφέρθηκε από την αρχειοθήκη"
- },
- "itemUnarchived": {
- "message": "Το στοιχείο επαναφέρθηκε από την αρχειοθήκη"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Αρχειοθέτηση στοιχείου"
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 7944904c44a..a221dc4f338 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
@@ -3083,6 +3080,45 @@
}
}
},
+ "durationTimeHours": {
+ "message": "$HOURS$ hours",
+ "placeholders": {
+ "hours": {
+ "content": "$1",
+ "example": "5"
+ }
+ }
+ },
+ "sendCreatedDescriptionV2": {
+ "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.",
+ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
+ "placeholders": {
+ "time": {
+ "content": "$1",
+ "example": "7 days, 1 hour, 1 day"
+ }
+ }
+ },
+ "sendCreatedDescriptionPassword": {
+ "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.",
+ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
+ "placeholders": {
+ "time": {
+ "content": "$1",
+ "example": "7 days, 1 hour, 1 day"
+ }
+ }
+ },
+ "sendCreatedDescriptionEmail": {
+ "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.",
+ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
+ "placeholders": {
+ "time": {
+ "content": "$1",
+ "example": "7 days, 1 hour, 1 day"
+ }
+ }
+ },
"sendLinkCopied": {
"message": "Send link copied",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json
index e34e20844e6..d61774df145 100644
--- a/apps/browser/src/_locales/en_GB/messages.json
+++ b/apps/browser/src/_locales/en_GB/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json
index 9fd388a80d3..3622ffce241 100644
--- a/apps/browser/src/_locales/en_IN/messages.json
+++ b/apps/browser/src/_locales/en_IN/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json
index ab5fad7e3af..131263ea4d9 100644
--- a/apps/browser/src/_locales/es/messages.json
+++ b/apps/browser/src/_locales/es/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Los elementos archivados aparecerán aquí y se excluirán de los resultados de búsqueda generales y de sugerencias de autocompletado."
},
- "itemWasSentToArchive": {
- "message": "El elemento fue archivado"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "El elemento fue desarchivado"
- },
- "itemUnarchived": {
- "message": "El elemento fue desarchivado"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archivar elemento"
@@ -6113,7 +6110,7 @@
"message": "Resize side navigation"
},
"whoCanView": {
- "message": "Quien puede ver"
+ "message": "Quién puede ver"
},
"specificPeople": {
"message": "Personas específicas"
diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json
index e8efd12b1e2..cd78c444c89 100644
--- a/apps/browser/src/_locales/et/messages.json
+++ b/apps/browser/src/_locales/et/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json
index e7fcd4998e0..3e4382a3d3b 100644
--- a/apps/browser/src/_locales/eu/messages.json
+++ b/apps/browser/src/_locales/eu/messages.json
@@ -3,14 +3,14 @@
"message": "Bitwarden"
},
"appLogoLabel": {
- "message": "Bitwarden logo"
+ "message": "Bitwardenen logoa"
},
"extName": {
"message": "Bitwarden pasahitz kudeatzailea",
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
},
"extDesc": {
- "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
+ "message": "Etxean, lanean edo bidean, Bitwardenek zure pasahitz, giltz orokor edo informazio delikatua erraz gordetzen du",
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
},
"loginOrCreateNewAccount": {
@@ -23,19 +23,19 @@
"message": "Sortu kontua"
},
"newToBitwarden": {
- "message": "New to Bitwarden?"
+ "message": "Berria Bitwardenen?"
},
"logInWithPasskey": {
- "message": "Log in with passkey"
+ "message": "Sartu giltz orokorrarekin"
},
"unlockWithPasskey": {
- "message": "Unlock with passkey"
+ "message": "Ireki giltz orokorrarekin"
},
"useSingleSignOn": {
- "message": "Use single sign-on"
+ "message": "Erabili SSO"
},
"yourOrganizationRequiresSingleSignOn": {
- "message": "Your organization requires single sign-on."
+ "message": "Zure erakundeak SSO erabiltzera behartzen du."
},
"welcomeBack": {
"message": "Ongi etorri berriro ere"
@@ -71,7 +71,7 @@
"message": "Pasahitz nagusia ahazten baduzu, pista batek pasahitza gogoratzen lagunduko dizu."
},
"masterPassHintText": {
- "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.",
+ "message": "Zure pasahitza ahazten bazaizu, pasahitzaren pista emailez bidal dezakegu. Gehienez $CURRENT$/$MAXIMUM$ karaktere.",
"placeholders": {
"current": {
"content": "$1",
@@ -90,7 +90,7 @@
"message": "Pasahitz nagusirako pista (aukerakoa)"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "Pasahitzaren sendotasun puntuazioa $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -99,10 +99,10 @@
}
},
"joinOrganization": {
- "message": "Join organization"
+ "message": "Erakundearen kide egin"
},
"joinOrganizationName": {
- "message": "Join $ORGANIZATIONNAME$",
+ "message": "$ORGANIZATIONNAME$-ren kide egin",
"placeholders": {
"organizationName": {
"content": "$1",
@@ -111,7 +111,7 @@
}
},
"finishJoiningThisOrganizationBySettingAMasterPassword": {
- "message": "Finish joining this organization by setting a master password."
+ "message": "Bukatu erakunde honen kide egitea pasahitz nagusi bat ezarriz."
},
"tab": {
"message": "Fitxak"
@@ -138,7 +138,7 @@
"message": "Kopiatu pasahitza"
},
"copyPassphrase": {
- "message": "Copy passphrase"
+ "message": "Kopiatu esaldi-gakoa"
},
"copyNote": {
"message": "Kopiatu oharra"
@@ -159,28 +159,28 @@
"message": "Izena kopiatu"
},
"copyCompany": {
- "message": "Copy company"
+ "message": "Kopiatu enpresa"
},
"copySSN": {
- "message": "Copy Social Security number"
+ "message": "Kopiatu segurtasun sozialaren zenbakia"
},
"copyPassportNumber": {
- "message": "Copy passport number"
+ "message": "Kopiatu pasaporte zenbakia"
},
"copyLicenseNumber": {
- "message": "Copy license number"
+ "message": "Kopiatu lizentzia zenbakia"
},
"copyPrivateKey": {
- "message": "Copy private key"
+ "message": "Kopiatu gako pribatua"
},
"copyPublicKey": {
- "message": "Copy public key"
+ "message": "Kopiatu gako publikoa"
},
"copyFingerprint": {
- "message": "Copy fingerprint"
+ "message": "Kopiatu hatz-marka"
},
"copyCustomField": {
- "message": "Copy $FIELD$",
+ "message": "Kopiatu $FIELD$",
"placeholders": {
"field": {
"content": "$1",
@@ -189,7 +189,7 @@
}
},
"copyWebsite": {
- "message": "Copy website"
+ "message": "Kopiatu webgunea"
},
"copyNotes": {
"message": "Kopiatu oharrak"
@@ -206,7 +206,7 @@
"message": "Auto-betetzea"
},
"autoFillLogin": {
- "message": "Autofill login"
+ "message": "Saio-hasiera autobetetzea"
},
"autoFillCard": {
"message": "Auto-bete txartela"
@@ -261,16 +261,16 @@
"message": "Gehitu elementua"
},
"accountEmail": {
- "message": "Account email"
+ "message": "Kontuaren e-maila"
},
"requestHint": {
- "message": "Request hint"
+ "message": "Argibidea eskatu"
},
"requestPasswordHint": {
- "message": "Request password hint"
+ "message": "Pasahitz-laguntza eskatu"
},
"enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": {
- "message": "Enter your account email address and your password hint will be sent to you"
+ "message": "Idatzi zure kontuaren e-maila eta pasahitzaren argibidea bidaliko dizugu"
},
"getMasterPasswordHint": {
"message": "Jaso pasahitz nagusiaren pista"
@@ -297,10 +297,10 @@
"message": "Aldatu pasahitz nagusia"
},
"continueToWebApp": {
- "message": "Continue to web app?"
+ "message": "Web aplikaziora jarraitu?"
},
"continueToWebAppDesc": {
- "message": "Explore more features of your Bitwarden account on the web app."
+ "message": "Esploratu zure Bitwarden kontuaren funtzio gehiago web-aplikazioan."
},
"continueToHelpCenter": {
"message": "Continue to Help Center?"
@@ -332,7 +332,7 @@
"message": "Itxi saioa"
},
"aboutBitwarden": {
- "message": "About Bitwarden"
+ "message": "Bitwardeni buruz"
},
"about": {
"message": "Honi buruz"
@@ -398,10 +398,10 @@
}
},
"newFolder": {
- "message": "New folder"
+ "message": "Karpeta berria"
},
"folderName": {
- "message": "Folder name"
+ "message": "Karpetaren izena"
},
"folderHintText": {
"message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums"
@@ -440,7 +440,7 @@
"message": "Sinkronizatu"
},
"syncNow": {
- "message": "Sync now"
+ "message": "Sinkronizatu orain"
},
"lastSync": {
"message": "Azken sinkronizazioa:"
@@ -456,7 +456,7 @@
"message": "Automatikoki pasahitz sendo eta bakarrak sortzen ditu zure saio-hasieratarako."
},
"bitWebVaultApp": {
- "message": "Bitwarden web app"
+ "message": "Bitwarden web aplikazioa"
},
"select": {
"message": "Hautatu"
@@ -489,11 +489,11 @@
"message": "Luzera"
},
"include": {
- "message": "Include",
+ "message": "Sartu",
"description": "Card header for password generator include block"
},
"uppercaseDescription": {
- "message": "Include uppercase characters",
+ "message": "Sartu letra maiuskulak",
"description": "Tooltip for the password generator uppercase character checkbox"
},
"uppercaseLabel": {
@@ -501,7 +501,7 @@
"description": "Label for the password generator uppercase character checkbox"
},
"lowercaseDescription": {
- "message": "Include lowercase characters",
+ "message": "Sartu letra minuskulak",
"description": "Full description for the password generator lowercase character checkbox"
},
"lowercaseLabel": {
@@ -509,7 +509,7 @@
"description": "Label for the password generator lowercase character checkbox"
},
"numbersDescription": {
- "message": "Include numbers",
+ "message": "Sartu zenbakiak",
"description": "Full description for the password generator numbers checkbox"
},
"numbersLabel": {
@@ -517,7 +517,7 @@
"description": "Label for the password generator numbers checkbox"
},
"specialCharactersDescription": {
- "message": "Include special characters",
+ "message": "Sartu karaktere bereziak",
"description": "Full description for the password generator special characters checkbox"
},
"numWords": {
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
@@ -607,16 +604,16 @@
"message": "Erakutsi"
},
"viewAll": {
- "message": "View all"
+ "message": "Ikusi denak"
},
"showAll": {
- "message": "Show all"
+ "message": "Dena erakutsi"
},
"viewLess": {
"message": "View less"
},
"viewLogin": {
- "message": "View login"
+ "message": "Ikusi saio-hasiera"
},
"noItemsInList": {
"message": "Ez dago erakusteko elementurik."
@@ -946,16 +943,16 @@
"message": "Saioa amaitu da."
},
"logIn": {
- "message": "Log in"
+ "message": "Hasi saioa"
},
"logInToBitwarden": {
- "message": "Log in to Bitwarden"
+ "message": "Sartu Bitwardenera"
},
"enterTheCodeSentToYourEmail": {
- "message": "Enter the code sent to your email"
+ "message": "Sartu e-mailera bidali dizugun kodea"
},
"enterTheCodeFromYourAuthenticatorApp": {
- "message": "Enter the code from your authenticator app"
+ "message": "Sartu zure egiaztapenerako aplikazioko kodea"
},
"pressYourYubiKeyToAuthenticate": {
"message": "Press your YubiKey to authenticate"
@@ -1344,11 +1341,11 @@
"message": "Export from"
},
"exportVerb": {
- "message": "Export",
+ "message": "Esportatu",
"description": "The verb form of the word Export"
},
"exportNoun": {
- "message": "Export",
+ "message": "Esportatu",
"description": "The noun form of the word Export"
},
"importNoun": {
@@ -1768,7 +1765,7 @@
"description": "Represents the message for allowing the user to enable the autofill overlay"
},
"autofillSuggestionsSectionTitle": {
- "message": "Autofill suggestions"
+ "message": "Autobetetzeko iradokizunak"
},
"autofillSpotlightTitle": {
"message": "Easily find autofill suggestions"
@@ -2165,7 +2162,7 @@
"description": "Header for edit file send"
},
"viewItemHeaderLogin": {
- "message": "View Login",
+ "message": "Ikusi saio-hasiera",
"description": "Header for view login item type"
},
"viewItemHeaderCard": {
@@ -2203,7 +2200,7 @@
"message": "Bildumak"
},
"nCollections": {
- "message": "$COUNT$ collections",
+ "message": "$COUNT$ bilduma",
"placeholders": {
"count": {
"content": "$1",
@@ -2928,7 +2925,7 @@
}
},
"send": {
- "message": "Send",
+ "message": "Bidali",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendDetails": {
@@ -4019,7 +4016,7 @@
"message": "required"
},
"search": {
- "message": "Search"
+ "message": "Bilatu"
},
"inputMinLength": {
"message": "Input must be at least $COUNT$ characters long.",
@@ -4346,7 +4343,7 @@
"message": "Select a folder"
},
"selectImportCollection": {
- "message": "Select a collection"
+ "message": "Bilduma bat aukeratu"
},
"importTargetHintCollection": {
"message": "Select this option if you want the imported file contents moved to a collection"
@@ -4525,7 +4522,7 @@
"message": "Try again or look for an email from LastPass to verify it's you."
},
"collection": {
- "message": "Collection"
+ "message": "Bilduma"
},
"lastPassYubikeyDesc": {
"message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button."
@@ -4686,7 +4683,7 @@
"message": "Passkey removed"
},
"autofillSuggestions": {
- "message": "Autofill suggestions"
+ "message": "Autobetetzeko proposamenak"
},
"itemSuggestions": {
"message": "Suggested items"
@@ -4812,7 +4809,7 @@
"message": "No values to copy"
},
"assignToCollections": {
- "message": "Assign to collections"
+ "message": "Esleitu bildumetan"
},
"copyEmail": {
"message": "Copy email"
@@ -4904,7 +4901,7 @@
}
},
"new": {
- "message": "New"
+ "message": "Berria"
},
"removeItem": {
"message": "Remove $NAME$",
@@ -4942,10 +4939,10 @@
"message": "Additional information"
},
"itemHistory": {
- "message": "Item history"
+ "message": "Aldaketen historia"
},
"lastEdited": {
- "message": "Last edited"
+ "message": "Azken edizioa"
},
"ownerYou": {
"message": "Owner: You"
@@ -5029,7 +5026,7 @@
"message": "Filters"
},
"filterVault": {
- "message": "Filter vault"
+ "message": "Iragazi kutxa gotorra"
},
"filterApplied": {
"message": "One filter applied"
@@ -5066,13 +5063,13 @@
"description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher."
},
"loginCredentials": {
- "message": "Login credentials"
+ "message": "Saio-hasierako kredentzialak"
},
"authenticatorKey": {
"message": "Authenticator key"
},
"autofillOptions": {
- "message": "Autofill options"
+ "message": "Autobetetzeko aukerak"
},
"websiteUri": {
"message": "Website (URI)"
@@ -5866,7 +5863,7 @@
"example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent"
},
"generatorNudgeTitle": {
- "message": "Quickly create passwords"
+ "message": "Sortu pasahitzak azkar"
},
"generatorNudgeBodyOne": {
"message": "Easily create strong and unique passwords by clicking on",
@@ -5879,7 +5876,7 @@
"example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure."
},
"generatorNudgeBodyAria": {
- "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.",
+ "message": "Sortu erraz pasahitz sendo eta bakarrak Sortu pasahitza botoian klik eginez, zure saio-hasierak seguru mantentzen laguntzeko.",
"description": "Aria label for the body content of the generator nudge"
},
"aboutThisSetting": {
@@ -6107,7 +6104,7 @@
"message": "Items"
},
"searchResults": {
- "message": "Search results"
+ "message": "Bilaketaren emaitzak"
},
"resizeSideNavigation": {
"message": "Resize side navigation"
diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json
index bca4ad20d52..5bb22dc6292 100644
--- a/apps/browser/src/_locales/fa/messages.json
+++ b/apps/browser/src/_locales/fa/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json
index 2997ed6c128..2f5b1ec4932 100644
--- a/apps/browser/src/_locales/fi/messages.json
+++ b/apps/browser/src/_locales/fi/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arkistoi kohde"
diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json
index 11da450cc0f..abb06f0f19f 100644
--- a/apps/browser/src/_locales/fil/messages.json
+++ b/apps/browser/src/_locales/fil/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json
index face33e0087..596315c4d3f 100644
--- a/apps/browser/src/_locales/fr/messages.json
+++ b/apps/browser/src/_locales/fr/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Les éléments archivés apparaîtront ici et seront exclus des résultats de recherche généraux et des suggestions de remplissage automatique."
},
- "itemWasSentToArchive": {
- "message": "L'élément a été envoyé à l'archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "L'élément a été désarchivé"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archiver l'élément"
diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json
index 69ef54f78eb..e710a489f9a 100644
--- a/apps/browser/src/_locales/gl/messages.json
+++ b/apps/browser/src/_locales/gl/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json
index 22939259639..a76cbb711a9 100644
--- a/apps/browser/src/_locales/he/messages.json
+++ b/apps/browser/src/_locales/he/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "פריטים בארכיון יופיעו כאן ויוחרגו מתוצאות חיפוש כללי והצעות למילוי אוטומטי."
},
- "itemWasSentToArchive": {
- "message": "הפריט נשלח לארכיון"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "הפריט שוחזר מהארכיב"
- },
- "itemUnarchived": {
- "message": "הפריט הוסר מהארכיון"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "העבר פריט לארכיון"
diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json
index 298f0312be7..d30cbd2cc6e 100644
--- a/apps/browser/src/_locales/hi/messages.json
+++ b/apps/browser/src/_locales/hi/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json
index d7814a22da0..98fdee3b657 100644
--- a/apps/browser/src/_locales/hr/messages.json
+++ b/apps/browser/src/_locales/hr/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Arhivirane stavke biti će prikazane ovdje i biti će izuzete iz rezultata općih pretraga i preporuka auto-ispune."
},
- "itemWasSentToArchive": {
- "message": "Stavka poslana u arhivu"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Stavka vraćena iz arhive"
- },
- "itemUnarchived": {
- "message": "Stavka vraćena iz arhive"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arhiviraj stavku"
diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json
index ec4b2d405bf..e6765219f15 100644
--- a/apps/browser/src/_locales/hu/messages.json
+++ b/apps/browser/src/_locales/hu/messages.json
@@ -573,15 +573,12 @@
"noItemsInArchiveDesc": {
"message": "Az archivált elemek itt jelennek meg és kizárásra kerülnek az általános keresési eredményekből és az automatikus kitöltési javaslatokból."
},
- "itemWasSentToArchive": {
- "message": "Az elem az archivumba került."
+ "itemArchiveToast": {
+ "message": "Az elem archiválásra került."
},
- "itemWasUnarchived": {
+ "itemUnarchivedToast": {
"message": "Az elem visszavételre került az archivumból."
},
- "itemUnarchived": {
- "message": "Az elemek visszavéelre kerültek az archivumból."
- },
"archiveItem": {
"message": "Elem archiválása"
},
diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json
index f364b2f7540..ccf35569f36 100644
--- a/apps/browser/src/_locales/id/messages.json
+++ b/apps/browser/src/_locales/id/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Butir yang diarsipkan akan muncul di sini dan akan dikecualikan dari hasil pencarian umum dan saran isi otomatis."
},
- "itemWasSentToArchive": {
- "message": "Butir dikirim ke arsip"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arsipkan butir"
diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json
index 9c4ce6a0369..42efa025207 100644
--- a/apps/browser/src/_locales/it/messages.json
+++ b/apps/browser/src/_locales/it/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Gli elementi archiviati compariranno qui e saranno esclusi dai risultati di ricerca e suggerimenti di autoriempimento."
},
- "itemWasSentToArchive": {
- "message": "Elemento archiviato"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Elemento rimosso dall'archivio"
- },
- "itemUnarchived": {
- "message": "Elemento rimosso dall'archivio"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archivia elemento"
diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json
index c8a963fc744..915308cec13 100644
--- a/apps/browser/src/_locales/ja/messages.json
+++ b/apps/browser/src/_locales/ja/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "アーカイブされたアイテムはここに表示され、通常の検索結果および自動入力の候補から除外されます。"
},
- "itemWasSentToArchive": {
- "message": "アイテムはアーカイブに送信されました"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "アイテムはアーカイブから解除されました"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "アイテムをアーカイブ"
diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json
index cb6129ed2bb..791664e6eec 100644
--- a/apps/browser/src/_locales/ka/messages.json
+++ b/apps/browser/src/_locales/ka/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json
index 336e8783b75..c28007c3838 100644
--- a/apps/browser/src/_locales/km/messages.json
+++ b/apps/browser/src/_locales/km/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json
index e97ce2a95a4..faef7703a66 100644
--- a/apps/browser/src/_locales/kn/messages.json
+++ b/apps/browser/src/_locales/kn/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json
index 9f570d62abb..b4a04e75e43 100644
--- a/apps/browser/src/_locales/ko/messages.json
+++ b/apps/browser/src/_locales/ko/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "보관된 항목은 여기에 표시되며 일반 검색 결과 및 자동 완성 제안에서 제외됩니다."
},
- "itemWasSentToArchive": {
- "message": "항목이 보관함으로 이동되었습니다"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "항목이 보관 해제되었습니다"
- },
- "itemUnarchived": {
- "message": "항목 보관 해제됨"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "항목 보관"
diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json
index 6e105f044f3..68eb11aa234 100644
--- a/apps/browser/src/_locales/lt/messages.json
+++ b/apps/browser/src/_locales/lt/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json
index 8c86d7040fe..6eaf545e390 100644
--- a/apps/browser/src/_locales/lv/messages.json
+++ b/apps/browser/src/_locales/lv/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Šeit parādīsies arhivētie vienumi, un tie netiks iekļauti vispārējās meklēšanas iznākumos un automātiskās aizpildes ieteikumos."
},
- "itemWasSentToArchive": {
- "message": "Vienums tika ievietots arhīvā"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Vienums tika izņemts no arhīva"
- },
- "itemUnarchived": {
- "message": "Vienums tika izņemts no arhīva"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arhivēt vienumu"
diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json
index 61f69ffe22b..db48220ffbb 100644
--- a/apps/browser/src/_locales/ml/messages.json
+++ b/apps/browser/src/_locales/ml/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json
index 5cc614c5df7..abf2f7db968 100644
--- a/apps/browser/src/_locales/mr/messages.json
+++ b/apps/browser/src/_locales/mr/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json
index 336e8783b75..c28007c3838 100644
--- a/apps/browser/src/_locales/my/messages.json
+++ b/apps/browser/src/_locales/my/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json
index ce6c8d5a7d4..4689cb23b7a 100644
--- a/apps/browser/src/_locales/nb/messages.json
+++ b/apps/browser/src/_locales/nb/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json
index 336e8783b75..c28007c3838 100644
--- a/apps/browser/src/_locales/ne/messages.json
+++ b/apps/browser/src/_locales/ne/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json
index 44522727429..044b3cfaa64 100644
--- a/apps/browser/src/_locales/nl/messages.json
+++ b/apps/browser/src/_locales/nl/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Gearchiveerde items verschijnen hier en worden uitgesloten van algemene zoekresultaten en automatisch invulsuggesties."
},
- "itemWasSentToArchive": {
- "message": "Item naar archief verzonden"
+ "itemArchiveToast": {
+ "message": "Item gearchiveerd"
},
- "itemWasUnarchived": {
- "message": "Item uit het archief gehaald"
- },
- "itemUnarchived": {
- "message": "Item uit het archief gehaald"
+ "itemUnarchivedToast": {
+ "message": "Item gedearchiveerd"
},
"archiveItem": {
"message": "Item archiveren"
diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json
index 336e8783b75..c28007c3838 100644
--- a/apps/browser/src/_locales/nn/messages.json
+++ b/apps/browser/src/_locales/nn/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json
index 336e8783b75..c28007c3838 100644
--- a/apps/browser/src/_locales/or/messages.json
+++ b/apps/browser/src/_locales/or/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json
index 44c7d9e6d47..44c7b5fb6dd 100644
--- a/apps/browser/src/_locales/pl/messages.json
+++ b/apps/browser/src/_locales/pl/messages.json
@@ -440,7 +440,7 @@
"message": "Synchronizacja"
},
"syncNow": {
- "message": "Sync now"
+ "message": "Synchronizuj teraz"
},
"lastSync": {
"message": "Ostatnia synchronizacja:"
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Zarchiwizowane elementy pojawią się tutaj i zostaną wykluczone z wyników wyszukiwania i sugestii autouzupełniania."
},
- "itemWasSentToArchive": {
- "message": "Element został przeniesiony do archiwum"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Element został usunięty z archiwum"
- },
- "itemUnarchived": {
- "message": "Element został usunięty z archiwum"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archiwizuj element"
@@ -6128,7 +6125,7 @@
"message": "user@bitwarden.com , user@acme.com"
},
"downloadBitwardenApps": {
- "message": "Download Bitwarden apps"
+ "message": "Pobierz aplikacje Bitwarden"
},
"emailProtected": {
"message": "Email protected"
diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json
index 5ad95b480db..679173205b1 100644
--- a/apps/browser/src/_locales/pt_BR/messages.json
+++ b/apps/browser/src/_locales/pt_BR/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais de busca e das sugestões de preenchimento automático."
},
- "itemWasSentToArchive": {
- "message": "O item foi enviado para o arquivo"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "O item foi desarquivado"
- },
- "itemUnarchived": {
- "message": "O item foi desarquivado"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arquivar item"
diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json
index 604bf054707..9094e04094d 100644
--- a/apps/browser/src/_locales/pt_PT/messages.json
+++ b/apps/browser/src/_locales/pt_PT/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais da pesquisa e das sugestões de preenchimento automático."
},
- "itemWasSentToArchive": {
- "message": "O item foi movido para o arquivo"
+ "itemArchiveToast": {
+ "message": "Item arquivado"
},
- "itemWasUnarchived": {
- "message": "O item foi desarquivado"
- },
- "itemUnarchived": {
- "message": "O item foi desarquivado"
+ "itemUnarchivedToast": {
+ "message": "Item desarquivado"
},
"archiveItem": {
"message": "Arquivar item"
diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json
index 12706943e83..47f7ae9cae3 100644
--- a/apps/browser/src/_locales/ro/messages.json
+++ b/apps/browser/src/_locales/ro/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json
index dab9a22f03a..d1fb3de89a6 100644
--- a/apps/browser/src/_locales/ru/messages.json
+++ b/apps/browser/src/_locales/ru/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Архивированные элементы появятся здесь и будут исключены из общих результатов поиска и предложений автозаполнения."
},
- "itemWasSentToArchive": {
- "message": "Элемент был отправлен в архив"
+ "itemArchiveToast": {
+ "message": "Элемент архивирован"
},
- "itemWasUnarchived": {
- "message": "Элемент был разархивирован"
- },
- "itemUnarchived": {
- "message": "Элемент был разархивирован"
+ "itemUnarchivedToast": {
+ "message": "Элемент разархивирован"
},
"archiveItem": {
"message": "Архивировать элемент"
diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json
index d228cdb512a..e70c620eaf8 100644
--- a/apps/browser/src/_locales/si/messages.json
+++ b/apps/browser/src/_locales/si/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json
index db7efcd8b9f..e1886098a31 100644
--- a/apps/browser/src/_locales/sk/messages.json
+++ b/apps/browser/src/_locales/sk/messages.json
@@ -573,13 +573,10 @@
"noItemsInArchiveDesc": {
"message": "Tu sa zobrazia archivované položky, ktoré budú vylúčené zo všeobecného vyhľadávania a z návrhov automatického vypĺňania."
},
- "itemWasSentToArchive": {
+ "itemArchiveToast": {
"message": "Položka bola archivovaná"
},
- "itemWasUnarchived": {
- "message": "Položka bola odobraná z archívu"
- },
- "itemUnarchived": {
+ "itemUnarchivedToast": {
"message": "Položka bola odobraná z archívu"
},
"archiveItem": {
diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json
index 07ee84ab810..100a04a3012 100644
--- a/apps/browser/src/_locales/sl/messages.json
+++ b/apps/browser/src/_locales/sl/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json
index 0ad71788514..e91e003c8e0 100644
--- a/apps/browser/src/_locales/sr/messages.json
+++ b/apps/browser/src/_locales/sr/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Архивиране ставке ће се овде појавити и бити искључени из општих резултата претраге и сугестија о ауто-пуњењу."
},
- "itemWasSentToArchive": {
- "message": "Ставка је послата у архиву"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Ставка враћена из архиве"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Архивирај ставку"
diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json
index 08cec673d27..484817b0210 100644
--- a/apps/browser/src/_locales/sv/messages.json
+++ b/apps/browser/src/_locales/sv/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Arkiverade objekt kommer att visas här och kommer att uteslutas från allmänna sökresultat och förslag för autofyll."
},
- "itemWasSentToArchive": {
- "message": "Objektet skickades till arkivet"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Objektet har avarkiverats"
- },
- "itemUnarchived": {
- "message": "Objektet har avarkiverats"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arkivera objekt"
diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json
index 374c0968d2c..3e76c0ab0d1 100644
--- a/apps/browser/src/_locales/ta/messages.json
+++ b/apps/browser/src/_locales/ta/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "காப்பகப்படுத்தப்பட்ட உருப்படிகள் இங்கே தோன்றும், மேலும் அவை பொதுவான தேடல் முடிவுகள் மற்றும் தானியங்குநிரப்பு பரிந்துரைகளிலிருந்து விலக்கப்படும்."
},
- "itemWasSentToArchive": {
- "message": "ஆவணம் காப்பகத்திற்கு அனுப்பப்பட்டது"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "காப்பகம் மீட்டெடுக்கப்பட்டது"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "உருப்படியைக் காப்பகப்படுத்து"
diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json
index 336e8783b75..c28007c3838 100644
--- a/apps/browser/src/_locales/te/messages.json
+++ b/apps/browser/src/_locales/te/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json
index 5af1c742f45..5ec728189a8 100644
--- a/apps/browser/src/_locales/th/messages.json
+++ b/apps/browser/src/_locales/th/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "รายการที่จัดเก็บถาวรจะปรากฏที่นี่ และจะไม่ถูกรวมในผลการค้นหาทั่วไปหรือคำแนะนำการป้อนอัตโนมัติ"
},
- "itemWasSentToArchive": {
- "message": "ย้ายรายการไปที่จัดเก็บถาวรแล้ว"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
- },
- "itemUnarchived": {
- "message": "เลิกจัดเก็บถาวรรายการแล้ว"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "จัดเก็บรายการถาวร"
diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json
index 33f600fb7a7..7d5b31a9aba 100644
--- a/apps/browser/src/_locales/tr/messages.json
+++ b/apps/browser/src/_locales/tr/messages.json
@@ -573,13 +573,10 @@
"noItemsInArchiveDesc": {
"message": "Arşivlenmiş kayıtlar burada görünecek ve genel arama sonuçlarından ile otomatik doldurma önerilerinden hariç tutulacaktır."
},
- "itemWasSentToArchive": {
- "message": "Kayıt arşive gönderildi"
+ "itemArchiveToast": {
+ "message": "Kayıt arşivlendi"
},
- "itemWasUnarchived": {
- "message": "Kayıt arşivden çıkarıldı"
- },
- "itemUnarchived": {
+ "itemUnarchivedToast": {
"message": "Kayıt arşivden çıkarıldı"
},
"archiveItem": {
diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json
index b703cfeefce..49a0c9de25b 100644
--- a/apps/browser/src/_locales/uk/messages.json
+++ b/apps/browser/src/_locales/uk/messages.json
@@ -573,13 +573,10 @@
"noItemsInArchiveDesc": {
"message": "Архівовані записи з'являтимуться тут і будуть виключені з результатів звичайного пошуку та пропозицій автозаповнення."
},
- "itemWasSentToArchive": {
+ "itemArchiveToast": {
"message": "Запис архівовано"
},
- "itemWasUnarchived": {
- "message": "Запис розархівовано"
- },
- "itemUnarchived": {
+ "itemUnarchivedToast": {
"message": "Запис розархівовано"
},
"archiveItem": {
@@ -5964,7 +5961,7 @@
"message": "Номер картки"
},
"errorCannotDecrypt": {
- "message": "Error: Cannot decrypt"
+ "message": "Помилка: неможливо розшифрувати"
},
"removeMasterPasswordForOrgUserKeyConnector": {
"message": "Ваша організація більше не використовує головні паролі для входу в Bitwarden. Щоб продовжити, підтвердіть організацію та домен."
@@ -6128,7 +6125,7 @@
"message": "user@bitwarden.com , user@acme.com"
},
"downloadBitwardenApps": {
- "message": "Download Bitwarden apps"
+ "message": "Завантажити програми Bitwarden"
},
"emailProtected": {
"message": "Е-пошту захищено"
diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json
index 0082ee1ece7..4f1165835cc 100644
--- a/apps/browser/src/_locales/vi/messages.json
+++ b/apps/browser/src/_locales/vi/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "Các mục đã lưu trữ sẽ hiển thị ở đây và sẽ bị loại khỏi kết quả tìm kiếm và gợi ý tự động điền."
},
- "itemWasSentToArchive": {
- "message": "Mục đã được chuyển vào kho lưu trữ"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Mục đã được bỏ lưu trữ"
- },
- "itemUnarchived": {
- "message": "Mục đã được bỏ lưu trữ"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Lưu trữ mục"
diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json
index 860a8c09f27..c9dd30ab08e 100644
--- a/apps/browser/src/_locales/zh_CN/messages.json
+++ b/apps/browser/src/_locales/zh_CN/messages.json
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "已归档的项目将显示在此处,并将被排除在一般搜索结果和自动填充建议之外。"
},
- "itemWasSentToArchive": {
- "message": "项目已发送到归档"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "项目已取消归档"
- },
- "itemUnarchived": {
- "message": "项目已取消归档"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "归档项目"
diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json
index 3f387d935d4..8da1b2ad08f 100644
--- a/apps/browser/src/_locales/zh_TW/messages.json
+++ b/apps/browser/src/_locales/zh_TW/messages.json
@@ -228,7 +228,7 @@
"message": "複製自訂欄位名稱"
},
"noMatchingLogins": {
- "message": "沒有符合的登入資料"
+ "message": "沒有相符的登入項目"
},
"noCards": {
"message": "沒有付款卡"
@@ -252,7 +252,7 @@
"message": "登入您的密碼庫"
},
"autoFillInfo": {
- "message": "沒有可以在目前瀏覽器分頁自動填入的登入資訊。"
+ "message": "目前瀏覽器分頁沒有可自動填入的登入項目。"
},
"addLogin": {
"message": "新增登入資料"
@@ -453,10 +453,10 @@
"description": "Short for 'credential generator'."
},
"passGenInfo": {
- "message": "為您的登入資料自動產生高強度且唯一的密碼。"
+ "message": "為您的登入項目自動產生高強度且唯一的密碼。"
},
"bitWebVaultApp": {
- "message": "Bitwarden 網頁應用程式"
+ "message": "Bitwarden Web 應用程式"
},
"select": {
"message": "選擇"
@@ -468,16 +468,16 @@
"message": "產生密碼短語"
},
"passwordGenerated": {
- "message": "已產生密碼"
+ "message": "密碼已產生"
},
"passphraseGenerated": {
- "message": "已產生密碼"
+ "message": "密碼短語已產生"
},
"usernameGenerated": {
- "message": "已產生使用者名稱"
+ "message": "使用者名稱已產生"
},
"emailGenerated": {
- "message": "已產生電子郵件"
+ "message": "電子郵件已產生"
},
"regeneratePassword": {
"message": "重新產生密碼"
@@ -573,14 +573,11 @@
"noItemsInArchiveDesc": {
"message": "封存的項目會顯示在此處,且不會出現在一般搜尋結果或自動填入建議中。"
},
- "itemWasSentToArchive": {
- "message": "項目已移至封存"
+ "itemArchiveToast": {
+ "message": "項目已封存"
},
- "itemWasUnarchived": {
- "message": "已取消封存項目"
- },
- "itemUnarchived": {
- "message": "項目取消封存"
+ "itemUnarchivedToast": {
+ "message": "項目已取消封存"
},
"archiveItem": {
"message": "封存項目"
@@ -598,7 +595,7 @@
"message": "需要進階版會員才能使用封存功能。"
},
"itemRestored": {
- "message": "已還原項目"
+ "message": "項目已還原"
},
"edit": {
"message": "編輯"
@@ -1215,7 +1212,7 @@
"description": "Button text for saving login details as a new entry."
},
"updateLoginAction": {
- "message": "更新登入資料",
+ "message": "更新登入項目",
"description": "Button text for updating an existing login entry."
},
"unlockToSave": {
@@ -1223,7 +1220,7 @@
"description": "User prompt to take action in order to save the login they just entered."
},
"saveLogin": {
- "message": "儲存登入資料",
+ "message": "儲存登入項目",
"description": "Prompt asking the user if they want to save their login details."
},
"updateLogin": {
@@ -1231,7 +1228,7 @@
"description": "Prompt asking the user if they want to update an existing login entry."
},
"loginSaveSuccess": {
- "message": "登入資訊已儲存",
+ "message": "登入項目已儲存",
"description": "Message displayed when login details are successfully saved."
},
"loginUpdateSuccess": {
@@ -1305,7 +1302,7 @@
"message": "解鎖"
},
"additionalOptions": {
- "message": "額外選項"
+ "message": "其他選項"
},
"enableContextMenuItem": {
"message": "顯示內容選單選項"
@@ -1567,7 +1564,7 @@
"message": "提供密碼健全性、帳戶健康狀態及資料外洩報告,確保您的密碼庫安全。"
},
"ppremiumSignUpTotp": {
- "message": "為密碼庫中的登入資料產生 TOTP 驗證碼(2FA)。"
+ "message": "為密碼庫中的登入項目產生 TOTP 驗證碼(2FA)。"
},
"ppremiumSignUpSupport": {
"message": "優先客戶支援。"
@@ -1801,7 +1798,7 @@
"message": "Bitwarden 如何保護您的資料免於網路釣魚攻擊?"
},
"currentWebsite": {
- "message": "目網站"
+ "message": "目前網站"
},
"autofillAndAddWebsite": {
"message": "自動填充並新增此網站"
@@ -2091,7 +2088,7 @@
"message": "登入資料"
},
"typeLogins": {
- "message": "登入資料"
+ "message": "登入項目"
},
"typeSecureNote": {
"message": "安全筆記"
@@ -2227,7 +2224,7 @@
"message": "身分"
},
"logins": {
- "message": "登入資料"
+ "message": "登入項目"
},
"secureNotes": {
"message": "安全筆記"
@@ -2513,7 +2510,7 @@
"message": "項目已自動填入並且已儲存統一資源標識符(URI)"
},
"autoFillSuccess": {
- "message": "項目已自動填入 "
+ "message": "項目已自動填入"
},
"insecurePageWarning": {
"message": "警告:此為不安全的 HTTP 頁面,您送出的任何資訊都可能被他人查看並修改。此登入資料原本儲存在安全的(HTTPS)頁面上。"
@@ -2773,10 +2770,10 @@
}
},
"atRiskPassword": {
- "message": "具有風險的密碼"
+ "message": "有風險的密碼"
},
"atRiskPasswords": {
- "message": "具有風險的密碼"
+ "message": "有風險的密碼"
},
"atRiskPasswordDescSingleOrg": {
"message": "$ORGANIZATION$ 要求你變更一組有風險的密碼。",
@@ -2848,7 +2845,7 @@
"message": "更新你的設定,以便能快速自動填入密碼並產生新密碼"
},
"reviewAtRiskLogins": {
- "message": "檢視有風險的登入資訊"
+ "message": "檢視有風險的登入項目"
},
"reviewAtRiskPasswords": {
"message": "檢視有風險的密碼"
@@ -5749,7 +5746,7 @@
"message": "設定生物辨識解鎖及自動填入,不需要輸入任何字元就可以登入。"
},
"secureUser": {
- "message": "升級您的登入體驗"
+ "message": "讓您的登入項目更升級"
},
"secureUserBody": {
"message": "使用密碼產生器來建立及儲存高強度、唯一的密碼,來保護您所有的帳號。"
@@ -5964,7 +5961,7 @@
"message": "付款卡號碼"
},
"errorCannotDecrypt": {
- "message": "Error: Cannot decrypt"
+ "message": "錯誤:無法解密"
},
"removeMasterPasswordForOrgUserKeyConnector": {
"message": "您的組織已不再使用主密碼登入 Bitwarden。若要繼續,請驗證組織與網域。"
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index 585942d7537..25c7b344982 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -14,7 +14,14 @@ import {
timeout,
} from "rxjs";
-import { CollectionService, DefaultCollectionService } from "@bitwarden/admin-console/common";
+import {
+ CollectionService,
+ DefaultCollectionService,
+ DefaultOrganizationUserApiService,
+ DefaultOrganizationUserService,
+ OrganizationUserApiService,
+ OrganizationUserService,
+} from "@bitwarden/admin-console/common";
import {
AuthRequestApiServiceAbstraction,
AuthRequestService,
@@ -27,6 +34,10 @@ import {
LogoutReason,
UserDecryptionOptionsService,
} from "@bitwarden/auth/common";
+import {
+ AutomaticUserConfirmationService,
+ DefaultAutomaticUserConfirmationService,
+} from "@bitwarden/auto-confirm";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
@@ -487,6 +498,9 @@ export default class MainBackground {
onUpdatedRan: boolean;
onReplacedRan: boolean;
loginToAutoFill: CipherView = null;
+ organizationUserService: OrganizationUserService;
+ organizationUserApiService: OrganizationUserApiService;
+ autoConfirmService: AutomaticUserConfirmationService;
private commandsBackground: CommandsBackground;
private contextMenusBackground: ContextMenusBackground;
@@ -763,6 +777,15 @@ export default class MainBackground {
{ createRequest: (url, request) => new Request(url, request) },
);
+ this.organizationUserApiService = new DefaultOrganizationUserApiService(this.apiService);
+ this.organizationUserService = new DefaultOrganizationUserService(
+ this.keyService,
+ this.encryptService,
+ this.organizationUserApiService,
+ this.accountService,
+ this.i18nService,
+ );
+
this.hibpApiService = new HibpApiService(this.apiService);
this.fileUploadService = new FileUploadService(this.logService, this.apiService);
this.cipherFileUploadService = new CipherFileUploadService(
@@ -804,6 +827,16 @@ export default class MainBackground {
this.authService,
);
+ this.autoConfirmService = new DefaultAutomaticUserConfirmationService(
+ this.configService,
+ this.apiService,
+ this.organizationUserService,
+ this.stateProvider,
+ this.organizationService,
+ this.organizationUserApiService,
+ this.policyService,
+ );
+
const sdkClientFactory = flagEnabled("sdk")
? new DefaultSdkClientFactory()
: new NoopSdkClientFactory();
@@ -1219,6 +1252,7 @@ export default class MainBackground {
this.authRequestAnsweringService,
this.configService,
this.policyService,
+ this.autoConfirmService,
);
this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.authService);
diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.html b/apps/browser/src/platform/popup/layout/popup-page.component.html
index bb24fb800aa..dc07d025e60 100644
--- a/apps/browser/src/platform/popup/layout/popup-page.component.html
+++ b/apps/browser/src/platform/popup/layout/popup-page.component.html
@@ -40,7 +40,7 @@
class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center tw-text-main"
[ngClass]="{ 'tw-invisible': !loading() }"
>
-
+
diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.ts b/apps/browser/src/platform/popup/layout/popup-page.component.ts
index 7d4b7decb7f..e661bf2ca00 100644
--- a/apps/browser/src/platform/popup/layout/popup-page.component.ts
+++ b/apps/browser/src/platform/popup/layout/popup-page.component.ts
@@ -13,7 +13,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { filter, switchMap, fromEvent, startWith, map } from "rxjs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { ScrollLayoutHostDirective, ScrollLayoutService } from "@bitwarden/components";
+import { IconModule, ScrollLayoutHostDirective, ScrollLayoutService } from "@bitwarden/components";
@Component({
selector: "popup-page",
@@ -21,7 +21,7 @@ import { ScrollLayoutHostDirective, ScrollLayoutService } from "@bitwarden/compo
host: {
class: "tw-h-full tw-flex tw-flex-col tw-overflow-y-hidden",
},
- imports: [CommonModule, ScrollLayoutHostDirective],
+ imports: [CommonModule, IconModule, ScrollLayoutHostDirective],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PopupPageComponent {
diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts
index 4e14d1171fd..0d85743bba7 100644
--- a/apps/browser/src/popup/app-routing.module.ts
+++ b/apps/browser/src/popup/app-routing.module.ts
@@ -42,7 +42,7 @@ import {
TwoFactorAuthComponent,
TwoFactorAuthGuard,
} from "@bitwarden/auth/angular";
-import { canAccessAutoConfirmSettings } from "@bitwarden/auto-confirm";
+import { canAccessAutoConfirmSettings } from "@bitwarden/auto-confirm/angular";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components";
import {
LockComponent,
diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html
index 94c1df46eea..38ef7a4f1df 100644
--- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html
+++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html
@@ -20,7 +20,13 @@
{{ "createdSendSuccessfully" | i18n }}
- {{ formatExpirationDate() }}
+ @let translationKey =
+ send.authType === AuthType.Email
+ ? "sendCreatedDescriptionEmail"
+ : send.authType === AuthType.Password
+ ? "sendCreatedDescriptionPassword"
+ : "sendCreatedDescriptionV2";
+ {{ translationKey | i18n: formattedExpirationTime }}
@@ -147,16 +141,10 @@
(click)="showAccountPreferences = !showAccountPreferences"
[attr.aria-expanded]="showAccountPreferences"
>
-
-
+
{{ "accountPreferences" | i18n }}
@@ -222,16 +210,10 @@
(click)="showAppPreferences = !showAppPreferences"
[attr.aria-expanded]="showAppPreferences"
>
-
-
+
{{ "appPreferences" | i18n }}
diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts
index f2e828b95ce..7bab7db3c29 100644
--- a/apps/desktop/src/app/accounts/settings.component.ts
+++ b/apps/desktop/src/app/accounts/settings.component.ts
@@ -45,6 +45,7 @@ import {
DialogService,
FormFieldModule,
IconButtonModule,
+ IconModule,
ItemModule,
LinkModule,
SectionComponent,
@@ -89,6 +90,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man
FormsModule,
ReactiveFormsModule,
IconButtonModule,
+ IconModule,
ItemModule,
JslibModule,
LinkModule,
diff --git a/apps/desktop/src/app/layout/account-switcher.component.html b/apps/desktop/src/app/layout/account-switcher.component.html
index ef177ea1bb6..7d0ee8fac83 100644
--- a/apps/desktop/src/app/layout/account-switcher.component.html
+++ b/apps/desktop/src/app/layout/account-switcher.component.html
@@ -31,11 +31,7 @@
{{ "switchAccount" | i18n }}
-
+
)
-
+ class="bwi-2x text-muted"
+ >
0">
diff --git a/apps/desktop/src/app/layout/search/search.component.html b/apps/desktop/src/app/layout/search/search.component.html
index 515385c2076..b5bcd264897 100644
--- a/apps/desktop/src/app/layout/search/search.component.html
+++ b/apps/desktop/src/app/layout/search/search.component.html
@@ -7,5 +7,5 @@
[formControl]="searchText"
appAutofocus
/>
-
+
diff --git a/apps/desktop/src/app/shared/shared.module.ts b/apps/desktop/src/app/shared/shared.module.ts
index 6eed4a197f3..85b3b800e83 100644
--- a/apps/desktop/src/app/shared/shared.module.ts
+++ b/apps/desktop/src/app/shared/shared.module.ts
@@ -7,6 +7,7 @@ import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { IconModule } from "@bitwarden/components";
import { AvatarComponent } from "../components/avatar.component";
import { ServicesModule } from "../services/services.module";
@@ -17,6 +18,7 @@ import { ServicesModule } from "../services/services.module";
A11yModule,
DragDropModule,
FormsModule,
+ IconModule,
JslibModule,
OverlayModule,
ReactiveFormsModule,
@@ -30,6 +32,7 @@ import { ServicesModule } from "../services/services.module";
DatePipe,
DragDropModule,
FormsModule,
+ IconModule,
JslibModule,
OverlayModule,
ReactiveFormsModule,
diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts
index e5cd85aa7a3..cca0097d65e 100644
--- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts
+++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts
@@ -16,7 +16,6 @@ import {
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
-import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
import { DeviceType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
@@ -31,7 +30,6 @@ import {
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
-import { parseCredentialId } from "@bitwarden/common/platform/services/fido2/credential-id-utils";
import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils";
import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils";
import { UserId } from "@bitwarden/common/types/guid";
@@ -152,11 +150,13 @@ export class DesktopAutofillService implements OnDestroy {
passwordCredentials = cipherViews
.filter(
(cipher) =>
+ !cipher.isDeleted &&
cipher.type === CipherType.Login &&
cipher.login.uris?.length > 0 &&
cipher.login.uris.some((uri) => uri.match !== UriMatchStrategy.Never) &&
cipher.login.uris.some((uri) => !Utils.isNullOrWhitespace(uri.uri)) &&
- !Utils.isNullOrWhitespace(cipher.login.username),
+ !Utils.isNullOrWhitespace(cipher.login.username) &&
+ !Utils.isNullOrWhitespace(cipher.login.password),
)
.map((cipher) => ({
type: "password",
@@ -258,39 +258,6 @@ export class DesktopAutofillService implements OnDestroy {
const controller = new AbortController();
try {
- // For some reason the credentialId is passed as an empty array in the request, so we need to
- // get it from the cipher. For that we use the recordIdentifier, which is the cipherId.
- if (request.recordIdentifier && request.credentialId.length === 0) {
- const activeUserId = await firstValueFrom(
- this.accountService.activeAccount$.pipe(getOptionalUserId),
- );
- if (!activeUserId) {
- this.logService.error("listenPasskeyAssertion error", "Active user not found");
- callback(new Error("Active user not found"), null);
- return;
- }
-
- const cipher = await this.cipherService.get(request.recordIdentifier, activeUserId);
- if (!cipher) {
- this.logService.error("listenPasskeyAssertion error", "Cipher not found");
- callback(new Error("Cipher not found"), null);
- return;
- }
-
- const decrypted = await this.cipherService.decrypt(cipher, activeUserId);
-
- const fido2Credential = decrypted.login.fido2Credentials?.[0];
- if (!fido2Credential) {
- this.logService.error("listenPasskeyAssertion error", "Fido2Credential not found");
- callback(new Error("Fido2Credential not found"), null);
- return;
- }
-
- request.credentialId = Array.from(
- new Uint8Array(parseCredentialId(decrypted.login.fido2Credentials?.[0].credentialId)),
- );
- }
-
const response = await this.fido2AuthenticatorService.getAssertion(
this.convertAssertionRequest(request, true),
{ windowXy: normalizePosition(request.windowXy) },
diff --git a/apps/desktop/src/index.html b/apps/desktop/src/index.html
index 37eb64adf35..044d7eb0e2f 100644
--- a/apps/desktop/src/index.html
+++ b/apps/desktop/src/index.html
@@ -13,6 +13,7 @@
+
diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json
index ef221f96878..c0824c61d03 100644
--- a/apps/desktop/src/locales/af/messages.json
+++ b/apps/desktop/src/locales/af/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json
index bcac0529a8c..3e668c327b0 100644
--- a/apps/desktop/src/locales/ar/messages.json
+++ b/apps/desktop/src/locales/ar/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json
index f94ff2417cf..4e5d414eb1c 100644
--- a/apps/desktop/src/locales/az/messages.json
+++ b/apps/desktop/src/locales/az/messages.json
@@ -4387,10 +4387,10 @@
"noItemsInArchiveDesc": {
"message": "Arxivlənmiş elementlər burada görünəcək, ümumi axtarış nəticələrindən və avto-doldurma təkliflərindən xaric ediləcək."
},
- "itemWasSentToArchive": {
- "message": "Element arxivə göndərildi"
+ "itemArchiveToast": {
+ "message": "Element arxivləndi"
},
- "itemWasUnarchived": {
+ "itemUnarchivedToast": {
"message": "Element arxivdən çıxarıldı"
},
"archiveItem": {
@@ -4487,7 +4487,7 @@
"message": "Vaxt bitmə əməliyyatı"
},
"errorCannotDecrypt": {
- "message": "Error: Cannot decrypt"
+ "message": "Xəta: Şifrəsi açıla bilmir"
},
"sessionTimeoutHeader": {
"message": "Sessiya vaxt bitməsi"
diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json
index 3467fe20ae8..f2f9d0a736d 100644
--- a/apps/desktop/src/locales/be/messages.json
+++ b/apps/desktop/src/locales/be/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json
index cab21191e37..ea0355ad7f6 100644
--- a/apps/desktop/src/locales/bg/messages.json
+++ b/apps/desktop/src/locales/bg/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Архивираните елементи ще се показват тук и ще бъдат изключени от общите резултати при търсене и от предложенията за автоматично попълване."
},
- "itemWasSentToArchive": {
- "message": "Елементът беше преместен в архива"
+ "itemArchiveToast": {
+ "message": "Елементът е преместен в архива"
},
- "itemWasUnarchived": {
- "message": "Елементът беше изваден от архива"
+ "itemUnarchivedToast": {
+ "message": "Елементът е изваден от архива"
},
"archiveItem": {
"message": "Архивиране на елемента"
diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json
index 544d88a72a6..6a211c93052 100644
--- a/apps/desktop/src/locales/bn/messages.json
+++ b/apps/desktop/src/locales/bn/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json
index 289554a237f..4ca3aa8ffc2 100644
--- a/apps/desktop/src/locales/bs/messages.json
+++ b/apps/desktop/src/locales/bs/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json
index 7b8d32a798c..3b8562814fd 100644
--- a/apps/desktop/src/locales/ca/messages.json
+++ b/apps/desktop/src/locales/ca/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json
index 478343b7e7d..75136c41831 100644
--- a/apps/desktop/src/locales/cs/messages.json
+++ b/apps/desktop/src/locales/cs/messages.json
@@ -4387,10 +4387,10 @@
"noItemsInArchiveDesc": {
"message": "Zde se zobrazí archivované položky a budou vyloučeny z obecných výsledků vyhledávání a návrhů automatického vyplňování."
},
- "itemWasSentToArchive": {
- "message": "Položka byla přesunuta do archivu"
+ "itemArchiveToast": {
+ "message": "Položka archivována"
},
- "itemWasUnarchived": {
+ "itemUnarchivedToast": {
"message": "Položka byla odebrána z archivu"
},
"archiveItem": {
diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json
index 4feb0181431..46df0aca8c5 100644
--- a/apps/desktop/src/locales/cy/messages.json
+++ b/apps/desktop/src/locales/cy/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json
index bd6a6f4379a..f6abcd51740 100644
--- a/apps/desktop/src/locales/da/messages.json
+++ b/apps/desktop/src/locales/da/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json
index 205c8e95435..a2c346896ac 100644
--- a/apps/desktop/src/locales/de/messages.json
+++ b/apps/desktop/src/locales/de/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Auto-Ausfüllen-Vorschlägen ausgeschlossen."
},
- "itemWasSentToArchive": {
- "message": "Eintrag wurde archiviert"
+ "itemArchiveToast": {
+ "message": "Eintrag archiviert"
},
- "itemWasUnarchived": {
- "message": "Eintrag wird nicht mehr archiviert"
+ "itemUnarchivedToast": {
+ "message": "Eintrag nicht mehr archiviert"
},
"archiveItem": {
"message": "Eintrag archivieren"
@@ -4487,7 +4487,7 @@
"message": "Timeout-Aktion"
},
"errorCannotDecrypt": {
- "message": "Error: Cannot decrypt"
+ "message": "Fehler: Entschlüsselung nicht möglich"
},
"sessionTimeoutHeader": {
"message": "Sitzungs-Timeout"
diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json
index 97371668dca..624560f5888 100644
--- a/apps/desktop/src/locales/el/messages.json
+++ b/apps/desktop/src/locales/el/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 85742db94ab..f444265877d 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json
index 22b482ed04d..aaf1e12955c 100644
--- a/apps/desktop/src/locales/en_GB/messages.json
+++ b/apps/desktop/src/locales/en_GB/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json
index 42a63ff0db1..1dca7070bfc 100644
--- a/apps/desktop/src/locales/en_IN/messages.json
+++ b/apps/desktop/src/locales/en_IN/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json
index ae37e15d84c..cefd462e99f 100644
--- a/apps/desktop/src/locales/eo/messages.json
+++ b/apps/desktop/src/locales/eo/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json
index d6210f940b5..d9fb17907aa 100644
--- a/apps/desktop/src/locales/es/messages.json
+++ b/apps/desktop/src/locales/es/messages.json
@@ -773,7 +773,7 @@
"message": "Añadir adjunto"
},
"itemsTransferred": {
- "message": "Items transferred"
+ "message": "Elementos transferidos"
},
"fixEncryption": {
"message": "Corregir cifrado"
@@ -2089,7 +2089,7 @@
"message": "Elemento eliminado de forma permanente"
},
"archivedItemRestored": {
- "message": "Archived item restored"
+ "message": "Elemento archivado restaurado"
},
"restoredItem": {
"message": "Elemento restaurado"
@@ -4009,7 +4009,7 @@
"message": "No, no lo tengo"
},
"newDeviceVerificationNoticePageOneEmailAccessYes": {
- "message": "Yes, I can reliably access my email"
+ "message": "Sí, puedo acceder a mi correo electrónico de forma fiable"
},
"turnOnTwoStepLogin": {
"message": "Turn on two-step login"
@@ -4075,10 +4075,10 @@
"message": "This login is at-risk and missing a website. Add a website and change the password for stronger security."
},
"vulnerablePassword": {
- "message": "Vulnerable password."
+ "message": "Contraseña vulnerable."
},
"changeNow": {
- "message": "Change now"
+ "message": "Cambiar ahora"
},
"missingWebsite": {
"message": "Missing website"
@@ -4109,7 +4109,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendsTitleNoSearchResults": {
- "message": "No search results returned"
+ "message": "Ningún resultado de búsqueda devuelto"
},
"sendsBodyNoItems": {
"message": "Comparte archivos y datos de forma segura con cualquiera, en cualquier plataforma. Tu información permanecerá encriptada de extremo a extremo, limitando su exposición.",
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Los elementos archivados aparecerán aquí y se excluirán de los resultados de búsqueda generales y de sugerencias de autocompletado."
},
- "itemWasSentToArchive": {
- "message": "El elemento fue archivado"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "El elemento fue desarchivado"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archivar elemento"
@@ -4403,7 +4403,7 @@
"message": "Desarchivar y guardar"
},
"restartPremium": {
- "message": "Restart Premium"
+ "message": "Reiniciar Premium"
},
"premiumSubscriptionEnded": {
"message": "Tu suscripción Premium ha terminado"
@@ -4537,10 +4537,10 @@
"message": "Set an unlock method to change your timeout action"
},
"upgrade": {
- "message": "Upgrade"
+ "message": "Actualizar"
},
"leaveConfirmationDialogTitle": {
- "message": "Are you sure you want to leave?"
+ "message": "¿Estás seguro de que quieres salir?"
},
"leaveConfirmationDialogContentOne": {
"message": "By declining, your personal items will stay in your account, but you'll lose access to shared items and organization features."
@@ -4558,10 +4558,10 @@
}
},
"howToManageMyVault": {
- "message": "How do I manage my vault?"
+ "message": "¿Cómo gestiono mi caja fuerte?"
},
"transferItemsToOrganizationTitle": {
- "message": "Transfer items to $ORGANIZATION$",
+ "message": "Transferir elementos a $ORGANIZATION$",
"placeholders": {
"organization": {
"content": "$1",
@@ -4579,13 +4579,13 @@
}
},
"acceptTransfer": {
- "message": "Accept transfer"
+ "message": "Aceptar transferencia"
},
"declineAndLeave": {
- "message": "Decline and leave"
+ "message": "Rechazar y salir"
},
"whyAmISeeingThis": {
- "message": "Why am I seeing this?"
+ "message": "¿Por qué estoy viendo esto?"
},
"sendPasswordHelperText": {
"message": "Individuals will need to enter the password to view this Send",
@@ -4595,25 +4595,25 @@
"message": "Email protected"
},
"emails": {
- "message": "Emails"
+ "message": "Correos electrónicos"
},
"noAuth": {
- "message": "Anyone with the link"
+ "message": "Cualquiera con el enlace"
},
"anyOneWithPassword": {
"message": "Anyone with a password set by you"
},
"whoCanView": {
- "message": "Who can view"
+ "message": "Quién puede ver"
},
"specificPeople": {
- "message": "Specific people"
+ "message": "Personas específicas"
},
"emailVerificationDesc": {
"message": "After sharing this Send link, individuals will need to verify their email with a code to view this Send."
},
"enterMultipleEmailsSeparatedByComma": {
- "message": "Enter multiple emails by separating with a comma."
+ "message": "Introduce varios correos electrónicos separándolos con una coma."
},
"emailPlaceholder": {
"message": "user@bitwarden.com , user@acme.com"
diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json
index 8c9476cc69e..ba930db8961 100644
--- a/apps/desktop/src/locales/et/messages.json
+++ b/apps/desktop/src/locales/et/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json
index 75c33286fb7..e03da9ef685 100644
--- a/apps/desktop/src/locales/eu/messages.json
+++ b/apps/desktop/src/locales/eu/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json
index 4136edbde06..a443cc8c2e7 100644
--- a/apps/desktop/src/locales/fa/messages.json
+++ b/apps/desktop/src/locales/fa/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "آیتمهای بایگانیشده در اینجا نمایش داده میشوند و از نتایج جستجوی عمومی و پیشنهاد ها پر کردن خودکار حذف خواهند شد."
},
- "itemWasSentToArchive": {
- "message": "آیتم به بایگانی فرستاده شد"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "آیتم از بایگانی خارج شد"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "بایگانی آیتم"
diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json
index b04b741bd3b..c7b51def9b2 100644
--- a/apps/desktop/src/locales/fi/messages.json
+++ b/apps/desktop/src/locales/fi/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json
index c503efc39f9..5835821f526 100644
--- a/apps/desktop/src/locales/fil/messages.json
+++ b/apps/desktop/src/locales/fil/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json
index f04807aaeb9..e8d07e28d2d 100644
--- a/apps/desktop/src/locales/fr/messages.json
+++ b/apps/desktop/src/locales/fr/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Les éléments archivés apparaîtront ici et seront exclus des résultats de recherche généraux et des suggestions de remplissage automatique."
},
- "itemWasSentToArchive": {
- "message": "L'élément a été envoyé à l'archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "L'élément a été désarchivé"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archiver l'élément"
diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json
index 76904276732..6d0922bd680 100644
--- a/apps/desktop/src/locales/gl/messages.json
+++ b/apps/desktop/src/locales/gl/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json
index dbb2533e03e..763401ac6fe 100644
--- a/apps/desktop/src/locales/he/messages.json
+++ b/apps/desktop/src/locales/he/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "פריטים בארכיון יופיעו כאן ויוחרגו מתוצאות חיפוש כללי והצעות למילוי אוטומטי."
},
- "itemWasSentToArchive": {
- "message": "הפריט נשלח לארכיון"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "הפריט הוסר מהארכיון"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "העבר פריט לארכיון"
diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json
index 1bfc0674ffe..33b69ac1519 100644
--- a/apps/desktop/src/locales/hi/messages.json
+++ b/apps/desktop/src/locales/hi/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json
index 5ef663ab52b..2dc081fa3c7 100644
--- a/apps/desktop/src/locales/hr/messages.json
+++ b/apps/desktop/src/locales/hr/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Arhivirane stavke biti će prikazane ovdje i biti će izuzete iz rezultata općih pretraga i preporuka auto-ispune."
},
- "itemWasSentToArchive": {
- "message": "Stavka poslana u arhivu"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Stavka vraćena iz arhive"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arhiviraj stavku"
diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json
index 5440b53e93d..3ec097c2a7a 100644
--- a/apps/desktop/src/locales/hu/messages.json
+++ b/apps/desktop/src/locales/hu/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Az archivált elemek itt jelennek meg és kizárásra kerülnek az általános keresési eredményekből és az automatikus kitöltési javaslatokból."
},
- "itemWasSentToArchive": {
- "message": "Az elem az archivumba került."
+ "itemArchiveToast": {
+ "message": "Az elem archiválásra került."
},
- "itemWasUnarchived": {
- "message": "Az elem visszavéelre került az archivumból."
+ "itemUnarchivedToast": {
+ "message": "Az elem visszavételre került az archivumból."
},
"archiveItem": {
"message": "Elem archiválása"
diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json
index f4de0deff33..7648f4bb99b 100644
--- a/apps/desktop/src/locales/id/messages.json
+++ b/apps/desktop/src/locales/id/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json
index 9cd4783407d..eb2ade245a0 100644
--- a/apps/desktop/src/locales/it/messages.json
+++ b/apps/desktop/src/locales/it/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Gli elementi archiviati appariranno qui e saranno esclusi dai risultati di ricerca e dal riempimento automatico."
},
- "itemWasSentToArchive": {
- "message": "Elemento archiviato"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Elemento estratto dall'archivio"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archivia elemento"
diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json
index 1d46532a980..a9b05f728d8 100644
--- a/apps/desktop/src/locales/ja/messages.json
+++ b/apps/desktop/src/locales/ja/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json
index 4618fd024a9..68bba7fcb27 100644
--- a/apps/desktop/src/locales/ka/messages.json
+++ b/apps/desktop/src/locales/ka/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json
index 76904276732..6d0922bd680 100644
--- a/apps/desktop/src/locales/km/messages.json
+++ b/apps/desktop/src/locales/km/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json
index 3bb7f513701..3c6aced3a73 100644
--- a/apps/desktop/src/locales/kn/messages.json
+++ b/apps/desktop/src/locales/kn/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json
index 70ffd234941..8932f0efb48 100644
--- a/apps/desktop/src/locales/ko/messages.json
+++ b/apps/desktop/src/locales/ko/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json
index f703b1f5d53..a19856a776e 100644
--- a/apps/desktop/src/locales/lt/messages.json
+++ b/apps/desktop/src/locales/lt/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json
index e82a4b91487..8a863256ed1 100644
--- a/apps/desktop/src/locales/lv/messages.json
+++ b/apps/desktop/src/locales/lv/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Šeit parādīsies arhivētie vienumi, un tie netiks iekļauti vispārējās meklēšanas iznākumos un automātiskās aizpildes ieteikumos."
},
- "itemWasSentToArchive": {
- "message": "Vienums tika ievietots arhīvā"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Vienums tika izņemts no arhīva"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arhivēt vienumu"
diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json
index 25bb0cbc816..773a596a10d 100644
--- a/apps/desktop/src/locales/me/messages.json
+++ b/apps/desktop/src/locales/me/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json
index 43e0dc85fb0..3bddc3baa5b 100644
--- a/apps/desktop/src/locales/ml/messages.json
+++ b/apps/desktop/src/locales/ml/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json
index 76904276732..6d0922bd680 100644
--- a/apps/desktop/src/locales/mr/messages.json
+++ b/apps/desktop/src/locales/mr/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json
index ddc8bef0241..22f4a30329a 100644
--- a/apps/desktop/src/locales/my/messages.json
+++ b/apps/desktop/src/locales/my/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json
index 792c95eb1ec..b2b1631fe04 100644
--- a/apps/desktop/src/locales/nb/messages.json
+++ b/apps/desktop/src/locales/nb/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json
index 89c3d3ba231..b9eba55d8bd 100644
--- a/apps/desktop/src/locales/ne/messages.json
+++ b/apps/desktop/src/locales/ne/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json
index e4b1d6d8abc..306bf31efe2 100644
--- a/apps/desktop/src/locales/nl/messages.json
+++ b/apps/desktop/src/locales/nl/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Gearchiveerde items verschijnen hier en worden uitgesloten van algemene zoekresultaten en automatisch invulsuggesties."
},
- "itemWasSentToArchive": {
- "message": "Item naar archief verzonden"
+ "itemArchiveToast": {
+ "message": "Item gearchiveerd"
},
- "itemWasUnarchived": {
- "message": "Item uit het archief gehaald"
+ "itemUnarchivedToast": {
+ "message": "Item gedearchiveerd"
},
"archiveItem": {
"message": "Item archiveren"
diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json
index 850696f8fcf..dc62d73a236 100644
--- a/apps/desktop/src/locales/nn/messages.json
+++ b/apps/desktop/src/locales/nn/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json
index b63a970e9c2..e8ea506a873 100644
--- a/apps/desktop/src/locales/or/messages.json
+++ b/apps/desktop/src/locales/or/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json
index e2d9fbce4a2..0cee8d6683d 100644
--- a/apps/desktop/src/locales/pl/messages.json
+++ b/apps/desktop/src/locales/pl/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Zarchiwizowane elementy pojawią się tutaj i zostaną wykluczone z wyników wyszukiwania i sugestii autouzupełniania."
},
- "itemWasSentToArchive": {
- "message": "Element został przeniesiony do archiwum"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Element został usunięty z archiwum"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archiwizuj element"
@@ -4487,7 +4487,7 @@
"message": "Timeout action"
},
"errorCannotDecrypt": {
- "message": "Error: Cannot decrypt"
+ "message": "Błąd: Nie można odszyfrować"
},
"sessionTimeoutHeader": {
"message": "Session timeout"
diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json
index 3a055bdb03d..817e9de0c50 100644
--- a/apps/desktop/src/locales/pt_BR/messages.json
+++ b/apps/desktop/src/locales/pt_BR/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais de busca e das sugestões de preenchimento automático."
},
- "itemWasSentToArchive": {
- "message": "O item foi enviado para o arquivo"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "O item foi desarquivado"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arquivar item"
diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json
index bb99678bde6..3076291a4a3 100644
--- a/apps/desktop/src/locales/pt_PT/messages.json
+++ b/apps/desktop/src/locales/pt_PT/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais da pesquisa e das sugestões de preenchimento automático."
},
- "itemWasSentToArchive": {
- "message": "O item foi movido para o arquivo"
+ "itemArchiveToast": {
+ "message": "Item arquivado"
},
- "itemWasUnarchived": {
- "message": "O item foi desarquivado"
+ "itemUnarchivedToast": {
+ "message": "Item desarquivado"
},
"archiveItem": {
"message": "Arquivar item"
diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json
index 6b51cb9fecd..b8fc25b4105 100644
--- a/apps/desktop/src/locales/ro/messages.json
+++ b/apps/desktop/src/locales/ro/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json
index 389f4b37dfd..089e815fefe 100644
--- a/apps/desktop/src/locales/ru/messages.json
+++ b/apps/desktop/src/locales/ru/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Архивированные элементы появятся здесь и будут исключены из общих результатов поиска и предложений автозаполнения."
},
- "itemWasSentToArchive": {
- "message": "Элемент был отправлен в архив"
+ "itemArchiveToast": {
+ "message": "Элемент архивирован"
},
- "itemWasUnarchived": {
- "message": "Элемент был разархивирован"
+ "itemUnarchivedToast": {
+ "message": "Элемент разархивирован"
},
"archiveItem": {
"message": "Архивировать элемент"
diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json
index 9e8a727ad5d..a1a84b8ba15 100644
--- a/apps/desktop/src/locales/si/messages.json
+++ b/apps/desktop/src/locales/si/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json
index 471984785d8..2876361bbf0 100644
--- a/apps/desktop/src/locales/sk/messages.json
+++ b/apps/desktop/src/locales/sk/messages.json
@@ -4387,10 +4387,10 @@
"noItemsInArchiveDesc": {
"message": "Tu sa zobrazia archivované položky, ktoré budú vylúčené zo všeobecného vyhľadávania a z návrhov automatického vypĺňania."
},
- "itemWasSentToArchive": {
+ "itemArchiveToast": {
"message": "Položka bola archivovaná"
},
- "itemWasUnarchived": {
+ "itemUnarchivedToast": {
"message": "Položka bola odobraná z archívu"
},
"archiveItem": {
diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json
index 23d9f18fadb..82e0a20b29e 100644
--- a/apps/desktop/src/locales/sl/messages.json
+++ b/apps/desktop/src/locales/sl/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json
index 1a68810bca3..633d3123242 100644
--- a/apps/desktop/src/locales/sr/messages.json
+++ b/apps/desktop/src/locales/sr/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Архивиране ставке ће се овде појавити и бити искључени из општих резултата претраге и сугестија о ауто-пуњењу."
},
- "itemWasSentToArchive": {
- "message": "Ставка је послата у архиву"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Ставка враћена из архиве"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Архивирај ставку"
diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json
index 1dd66c409c0..ed9e62e8319 100644
--- a/apps/desktop/src/locales/sv/messages.json
+++ b/apps/desktop/src/locales/sv/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Arkiverade objekt kommer att visas här och kommer att uteslutas från allmänna sökresultat och förslag för autofyll."
},
- "itemWasSentToArchive": {
- "message": "Objektet skickades till arkivet"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Objektet har avarkiverats"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Arkivera objekt"
diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json
index 3a7fc795668..53e155874f6 100644
--- a/apps/desktop/src/locales/ta/messages.json
+++ b/apps/desktop/src/locales/ta/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json
index 76904276732..6d0922bd680 100644
--- a/apps/desktop/src/locales/te/messages.json
+++ b/apps/desktop/src/locales/te/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json
index bcff8d0849e..b7117a7ccad 100644
--- a/apps/desktop/src/locales/th/messages.json
+++ b/apps/desktop/src/locales/th/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Archived items will appear here and will be excluded from general search results and autofill suggestions."
},
- "itemWasSentToArchive": {
- "message": "Item was sent to archive"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Item was unarchived"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Archive item"
diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json
index 936a68516bd..96bb11c7a35 100644
--- a/apps/desktop/src/locales/tr/messages.json
+++ b/apps/desktop/src/locales/tr/messages.json
@@ -4387,10 +4387,10 @@
"noItemsInArchiveDesc": {
"message": "Arşivlenmiş kayıtlar burada görünecek ve genel arama sonuçları ile otomatik doldurma önerilerinden hariç tutulacaktır."
},
- "itemWasSentToArchive": {
- "message": "Kayıt arşive gönderildi"
+ "itemArchiveToast": {
+ "message": "Kayıt arşivlendi"
},
- "itemWasUnarchived": {
+ "itemUnarchivedToast": {
"message": "Kayıt arşivden çıkarıldı"
},
"archiveItem": {
diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json
index 2b4cfcb7c22..abcdbea0b1f 100644
--- a/apps/desktop/src/locales/uk/messages.json
+++ b/apps/desktop/src/locales/uk/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Архівовані записи з'являтимуться тут і будуть виключені з результатів звичайного пошуку та пропозицій автозаповнення."
},
- "itemWasSentToArchive": {
- "message": "Запис архівовано"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Запис розархівовано"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Архівувати запис"
diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json
index 56c9d9a5c6e..f06556568a1 100644
--- a/apps/desktop/src/locales/vi/messages.json
+++ b/apps/desktop/src/locales/vi/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "Các mục đã lưu trữ sẽ hiển thị ở đây và sẽ bị loại khỏi kết quả tìm kiếm và gợi ý tự động điền."
},
- "itemWasSentToArchive": {
- "message": "Mục đã được chuyển vào kho lưu trữ"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "Mục đã được bỏ lưu trữ"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "Lưu trữ mục"
diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json
index cf42adba294..9941e296da3 100644
--- a/apps/desktop/src/locales/zh_CN/messages.json
+++ b/apps/desktop/src/locales/zh_CN/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "已归档的项目将显示在此处,并将被排除在一般搜索结果和自动填充建议之外。"
},
- "itemWasSentToArchive": {
- "message": "项目已发送到归档"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "项目已取消归档"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "归档项目"
diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json
index 3157e929e8f..461eb031068 100644
--- a/apps/desktop/src/locales/zh_TW/messages.json
+++ b/apps/desktop/src/locales/zh_TW/messages.json
@@ -4387,11 +4387,11 @@
"noItemsInArchiveDesc": {
"message": "封存的項目會顯示在此處,且不會出現在一般搜尋結果或自動填入建議中。"
},
- "itemWasSentToArchive": {
- "message": "項目已移至封存"
+ "itemArchiveToast": {
+ "message": "Item archived"
},
- "itemWasUnarchived": {
- "message": "項目取消封存"
+ "itemUnarchivedToast": {
+ "message": "Item unarchived"
},
"archiveItem": {
"message": "封存項目"
diff --git a/apps/web/src/app/admin-console/common/base-members.component.ts b/apps/web/src/app/admin-console/common/base-members.component.ts
deleted file mode 100644
index 5ecf4269a1a..00000000000
--- a/apps/web/src/app/admin-console/common/base-members.component.ts
+++ /dev/null
@@ -1,245 +0,0 @@
-import { Directive } from "@angular/core";
-import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
-import { FormControl } from "@angular/forms";
-import { firstValueFrom, lastValueFrom, debounceTime, combineLatest, BehaviorSubject } from "rxjs";
-
-import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
-import {
- OrganizationUserStatusType,
- OrganizationUserType,
- ProviderUserStatusType,
- ProviderUserType,
-} from "@bitwarden/common/admin-console/enums";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
-import { ListResponse } from "@bitwarden/common/models/response/list.response";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
-import { Utils } from "@bitwarden/common/platform/misc/utils";
-import { DialogService, ToastService } from "@bitwarden/components";
-import { KeyService } from "@bitwarden/key-management";
-
-import { OrganizationUserView } from "../organizations/core/views/organization-user.view";
-import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
-import { MemberActionResult } from "../organizations/members/services/member-actions/member-actions.service";
-
-import { PeopleTableDataSource, peopleFilter } from "./people-table-data-source";
-
-export type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
-export type UserViewTypes = ProviderUserUserDetailsResponse | OrganizationUserView;
-
-/**
- * A refactored copy of BasePeopleComponent, using the component library table and other modern features.
- * This will replace BasePeopleComponent once all subclasses have been changed over to use this class.
- */
-@Directive()
-export abstract class BaseMembersComponent {
- /**
- * Shows a banner alerting the admin that users need to be confirmed.
- */
- get showConfirmUsers(): boolean {
- return (
- this.dataSource.activeUserCount > 1 &&
- this.dataSource.confirmedUserCount > 0 &&
- this.dataSource.confirmedUserCount < 3 &&
- this.dataSource.acceptedUserCount > 0
- );
- }
-
- get showBulkConfirmUsers(): boolean {
- return this.dataSource
- .getCheckedUsers()
- .every((member) => member.status == this.userStatusType.Accepted);
- }
-
- get showBulkReinviteUsers(): boolean {
- return this.dataSource
- .getCheckedUsers()
- .every((member) => member.status == this.userStatusType.Invited);
- }
-
- abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
- abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
-
- protected abstract dataSource: PeopleTableDataSource;
-
- firstLoaded: boolean = false;
-
- /**
- * The currently selected status filter, or undefined to show all active users.
- */
- status?: StatusType;
-
- /**
- * The currently executing promise - used to avoid multiple user actions executing at once.
- */
- actionPromise?: Promise;
-
- protected searchControl = new FormControl("", { nonNullable: true });
- protected statusToggle = new BehaviorSubject(undefined);
-
- constructor(
- protected apiService: ApiService,
- protected i18nService: I18nService,
- protected keyService: KeyService,
- protected validationService: ValidationService,
- protected logService: LogService,
- protected userNamePipe: UserNamePipe,
- protected dialogService: DialogService,
- protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
- protected toastService: ToastService,
- ) {
- // Connect the search input and status toggles to the table dataSource filter
- combineLatest([this.searchControl.valueChanges.pipe(debounceTime(200)), this.statusToggle])
- .pipe(takeUntilDestroyed())
- .subscribe(
- ([searchText, status]) => (this.dataSource.filter = peopleFilter(searchText, status)),
- );
- }
-
- abstract edit(user: UserView, organization?: Organization): void;
- abstract getUsers(organization?: Organization): Promise | UserView[]>;
- abstract removeUser(id: string, organization?: Organization): Promise;
- abstract reinviteUser(id: string, organization?: Organization): Promise;
- abstract confirmUser(
- user: UserView,
- publicKey: Uint8Array,
- organization?: Organization,
- ): Promise;
- abstract invite(organization?: Organization): void;
-
- async load(organization?: Organization) {
- // Load new users from the server
- const response = await this.getUsers(organization);
-
- // GetUsers can return a ListResponse or an Array
- if (response instanceof ListResponse) {
- this.dataSource.data = response.data != null && response.data.length > 0 ? response.data : [];
- } else if (Array.isArray(response)) {
- this.dataSource.data = response;
- }
-
- this.firstLoaded = true;
- }
-
- protected async removeUserConfirmationDialog(user: UserView) {
- return this.dialogService.openSimpleDialog({
- title: this.userNamePipe.transform(user),
- content: { key: "removeUserConfirmation" },
- type: "warning",
- });
- }
-
- async remove(user: UserView, organization?: Organization) {
- const confirmed = await this.removeUserConfirmationDialog(user);
- if (!confirmed) {
- return false;
- }
-
- this.actionPromise = this.removeUser(user.id, organization);
- try {
- const result = await this.actionPromise;
- if (result.success) {
- this.toastService.showToast({
- variant: "success",
- message: this.i18nService.t("removedUserId", this.userNamePipe.transform(user)),
- });
- this.dataSource.removeUser(user);
- } else {
- throw new Error(result.error);
- }
- } catch (e) {
- this.validationService.showError(e);
- }
- this.actionPromise = undefined;
- }
-
- async reinvite(user: UserView, organization?: Organization) {
- if (this.actionPromise != null) {
- return;
- }
-
- this.actionPromise = this.reinviteUser(user.id, organization);
- try {
- const result = await this.actionPromise;
- if (result.success) {
- this.toastService.showToast({
- variant: "success",
- message: this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user)),
- });
- } else {
- throw new Error(result.error);
- }
- } catch (e) {
- this.validationService.showError(e);
- }
- this.actionPromise = undefined;
- }
-
- async confirm(user: UserView, organization?: Organization) {
- const confirmUser = async (publicKey: Uint8Array) => {
- try {
- this.actionPromise = this.confirmUser(user, publicKey, organization);
- const result = await this.actionPromise;
- if (result.success) {
- user.status = this.userStatusType.Confirmed;
- this.dataSource.replaceUser(user);
-
- this.toastService.showToast({
- variant: "success",
- message: this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user)),
- });
- } else {
- throw new Error(result.error);
- }
- } catch (e) {
- this.validationService.showError(e);
- throw e;
- } finally {
- this.actionPromise = undefined;
- }
- };
-
- if (this.actionPromise != null) {
- return;
- }
-
- try {
- const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
- const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
-
- const autoConfirm = await firstValueFrom(
- this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
- );
- if (user == null) {
- throw new Error("Cannot confirm null user.");
- }
- if (autoConfirm == null || !autoConfirm) {
- const dialogRef = UserConfirmComponent.open(this.dialogService, {
- data: {
- name: this.userNamePipe.transform(user),
- userId: user.userId,
- publicKey: publicKey,
- confirmUser: () => confirmUser(publicKey),
- },
- });
- await lastValueFrom(dialogRef.closed);
-
- return;
- }
-
- try {
- const fingerprint = await this.keyService.getFingerprint(user.userId, publicKey);
- this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
- } catch (e) {
- this.logService.error(e);
- }
- await confirmUser(publicKey);
- } catch (e) {
- this.logService.error(`Handled exception: ${e}`);
- }
- }
-}
diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts
index 073d73f6a50..a641116f4de 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts
+++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts
@@ -472,7 +472,7 @@ export class VaultComponent implements OnInit, OnDestroy {
collections,
filter.collectionId,
);
- searchableCollectionNodes = selectedCollection.children ?? [];
+ searchableCollectionNodes = selectedCollection?.children ?? [];
}
let collectionsToReturn: CollectionAdminView[] = [];
@@ -962,10 +962,10 @@ export class VaultComponent implements OnInit, OnDestroy {
await this.editCipher(cipher, true);
}
- restore = async (c: CipherViewLike): Promise => {
+ restore = async (c: CipherViewLike): Promise => {
const organization = await firstValueFrom(this.organization$);
if (!CipherViewLikeUtils.isDeleted(c)) {
- return false;
+ return;
}
if (
@@ -974,11 +974,11 @@ export class VaultComponent implements OnInit, OnDestroy {
!organization.allowAdminAccessToAllCollectionItems
) {
this.showMissingPermissionsError();
- return false;
+ return;
}
if (!(await this.repromptCipher([c]))) {
- return false;
+ return;
}
// Allow restore of an Unassigned Item
@@ -996,10 +996,10 @@ export class VaultComponent implements OnInit, OnDestroy {
message: this.i18nService.t("restoredItem"),
});
this.refresh();
- return true;
+ return;
} catch (e) {
this.logService.error(e);
- return false;
+ return;
}
};
diff --git a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts
index 03130d0b946..788d01695b0 100644
--- a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts
+++ b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts
@@ -2,11 +2,8 @@
// @ts-strict-ignore
import { Component, Inject, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
-import { firstValueFrom } from "rxjs";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
@@ -17,8 +14,6 @@ export type UserConfirmDialogData = {
name: string;
userId: string;
publicKey: Uint8Array;
- // @TODO remove this when doing feature flag cleanup for members component refactor.
- confirmUser?: (publicKey: Uint8Array) => Promise;
};
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
@@ -46,7 +41,6 @@ export class UserConfirmComponent implements OnInit {
private keyService: KeyService,
private logService: LogService,
private organizationManagementPreferencesService: OrganizationManagementPreferencesService,
- private configService: ConfigService,
) {
this.name = data.name;
this.userId = data.userId;
@@ -76,13 +70,6 @@ export class UserConfirmComponent implements OnInit {
await this.organizationManagementPreferencesService.autoConfirmFingerPrints.set(true);
}
- const membersComponentRefactorEnabled = await firstValueFrom(
- this.configService.getFeatureFlag$(FeatureFlag.MembersComponentRefactor),
- );
- if (!membersComponentRefactorEnabled) {
- await this.data.confirmUser(this.publicKey);
- }
-
this.dialogRef.close(true);
};
diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts
index 5c9bf919ed4..cfddb17627a 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts
@@ -9,7 +9,6 @@ import {
} from "@bitwarden/common/admin-console/enums";
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
-import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components";
@@ -34,7 +33,7 @@ type BulkStatusEntry = {
type BulkStatusDialogData = {
users: Array;
filteredUsers: Array;
- request: Promise>;
+ request: Promise;
successfulMessage: string;
};
@@ -63,7 +62,7 @@ export class BulkStatusComponent implements OnInit {
async showBulkStatus(data: BulkStatusDialogData) {
try {
const response = await data.request;
- const keyedErrors: any = response.data
+ const keyedErrors: any = (response ?? [])
.filter((r) => r.error !== "")
.reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
const keyedFilteredUsers: any = data.filteredUsers.reduce(
diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts
index 6848f76286f..43520449535 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts
@@ -195,9 +195,9 @@ export class MemberDialogComponent implements OnDestroy {
private accountService: AccountService,
organizationService: OrganizationService,
private toastService: ToastService,
- private configService: ConfigService,
private deleteManagedMemberWarningService: DeleteManagedMemberWarningService,
private organizationUserService: OrganizationUserService,
+ private configService: ConfigService,
) {
this.organization$ = accountService.activeAccount$.pipe(
getUserId,
diff --git a/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.html b/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.html
deleted file mode 100644
index 65bab31c728..00000000000
--- a/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.html
+++ /dev/null
@@ -1,495 +0,0 @@
-@let organization = this.organization();
-@if (organization) {
-
-
-
-
-
-
-
-
-
-
-
- {{ "all" | i18n }}
- {{
- allCount
- }}
-
-
-
- {{ "invited" | i18n }}
- {{
- invitedCount
- }}
-
-
-
- {{ "needsConfirmation" | i18n }}
- {{
- acceptedUserCount
- }}
-
-
-
- {{ "revoked" | i18n }}
- {{
- revokedCount
- }}
-
-
-
-
-
- {{ "loading" | i18n }}
-
-
- {{ "noMembersInList" | i18n }}
-
-
- {{ "usersNeedConfirmed" | i18n }}
-
-
-
-
-
-
- |
-
-
- |
- {{ "name" | i18n }} |
- {{ (organization.useGroups ? "groups" : "collections") | i18n }} |
- {{ "role" | i18n }} |
- {{ "policies" | i18n }} |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
- |
-
- |
-
-
-
-
-
-
-
-
- {{ "invited" | i18n }}
-
-
- {{ "needsConfirmation" | i18n }}
-
-
- {{ "revoked" | i18n }}
-
-
-
- {{ u.email }}
-
-
-
- |
-
-
-
-
-
-
-
- {{ u.name ?? u.email }}
-
- {{ "invited" | i18n }}
-
-
- {{ "needsConfirmation" | i18n }}
-
-
- {{ "revoked" | i18n }}
-
-
-
- {{ u.email }}
-
-
-
- |
-
-
-
-
-
- |
-
-
-
-
- |
-
-
-
-
- {{ u.type | userType }}
- |
-
-
-
- {{ u.type | userType }}
- |
-
-
-
-
-
- {{ "userUsingTwoStep" | i18n }}
-
- @let resetPasswordPolicyEnabled = resetPasswordPolicyEnabled$ | async;
-
-
- {{ "enrolledAccountRecovery" | i18n }}
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-}
diff --git a/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.ts b/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.ts
deleted file mode 100644
index dae9bafbcfe..00000000000
--- a/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.ts
+++ /dev/null
@@ -1,624 +0,0 @@
-import { Component, computed, Signal } from "@angular/core";
-import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
-import { ActivatedRoute } from "@angular/router";
-import {
- combineLatest,
- concatMap,
- filter,
- firstValueFrom,
- from,
- map,
- merge,
- Observable,
- shareReplay,
- switchMap,
- take,
-} from "rxjs";
-
-import { OrganizationUserUserDetailsResponse } from "@bitwarden/admin-console/common";
-import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
-import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
-import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
-import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
-import {
- OrganizationUserStatusType,
- OrganizationUserType,
- PolicyType,
-} from "@bitwarden/common/admin-console/enums";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { getUserId } from "@bitwarden/common/auth/services/account.service";
-import { OrganizationMetadataServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-metadata.service.abstraction";
-import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
-import { getById } from "@bitwarden/common/platform/misc";
-import { DialogService, ToastService } from "@bitwarden/components";
-import { KeyService } from "@bitwarden/key-management";
-import { UserId } from "@bitwarden/user-core";
-import { BillingConstraintService } from "@bitwarden/web-vault/app/billing/members/billing-constraint/billing-constraint.service";
-import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
-
-import { BaseMembersComponent } from "../../common/base-members.component";
-import {
- CloudBulkReinviteLimit,
- MaxCheckedCount,
- PeopleTableDataSource,
-} from "../../common/people-table-data-source";
-import { OrganizationUserView } from "../core/views/organization-user.view";
-
-import { AccountRecoveryDialogResultType } from "./components/account-recovery/account-recovery-dialog.component";
-import { MemberDialogResult, MemberDialogTab } from "./components/member-dialog";
-import {
- MemberDialogManagerService,
- MemberExportService,
- OrganizationMembersService,
-} from "./services";
-import { DeleteManagedMemberWarningService } from "./services/delete-managed-member/delete-managed-member-warning.service";
-import {
- MemberActionsService,
- MemberActionResult,
-} from "./services/member-actions/member-actions.service";
-
-class MembersTableDataSource extends PeopleTableDataSource {
- protected statusType = OrganizationUserStatusType;
-}
-
-// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
-// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
-@Component({
- templateUrl: "deprecated_members.component.html",
- standalone: false,
-})
-export class MembersComponent extends BaseMembersComponent {
- userType = OrganizationUserType;
- userStatusType = OrganizationUserStatusType;
- memberTab = MemberDialogTab;
- protected dataSource: MembersTableDataSource;
-
- readonly organization: Signal;
- status: OrganizationUserStatusType | undefined;
-
- private userId$: Observable = this.accountService.activeAccount$.pipe(getUserId);
-
- resetPasswordPolicyEnabled$: Observable;
-
- protected readonly canUseSecretsManager: Signal = computed(
- () => this.organization()?.useSecretsManager ?? false,
- );
- protected readonly showUserManagementControls: Signal = computed(
- () => this.organization()?.canManageUsers ?? false,
- );
- protected billingMetadata$: Observable;
-
- // Fixed sizes used for cdkVirtualScroll
- protected rowHeight = 66;
- protected rowHeightClass = `tw-h-[66px]`;
-
- constructor(
- apiService: ApiService,
- i18nService: I18nService,
- organizationManagementPreferencesService: OrganizationManagementPreferencesService,
- keyService: KeyService,
- validationService: ValidationService,
- logService: LogService,
- userNamePipe: UserNamePipe,
- dialogService: DialogService,
- toastService: ToastService,
- private route: ActivatedRoute,
- protected deleteManagedMemberWarningService: DeleteManagedMemberWarningService,
- private organizationWarningsService: OrganizationWarningsService,
- private memberActionsService: MemberActionsService,
- private memberDialogManager: MemberDialogManagerService,
- protected billingConstraint: BillingConstraintService,
- protected memberService: OrganizationMembersService,
- private organizationService: OrganizationService,
- private accountService: AccountService,
- private policyService: PolicyService,
- private policyApiService: PolicyApiServiceAbstraction,
- private organizationMetadataService: OrganizationMetadataServiceAbstraction,
- private memberExportService: MemberExportService,
- private environmentService: EnvironmentService,
- ) {
- super(
- apiService,
- i18nService,
- keyService,
- validationService,
- logService,
- userNamePipe,
- dialogService,
- organizationManagementPreferencesService,
- toastService,
- );
-
- this.dataSource = new MembersTableDataSource(this.environmentService);
-
- const organization$ = this.route.params.pipe(
- concatMap((params) =>
- this.userId$.pipe(
- switchMap((userId) =>
- this.organizationService.organizations$(userId).pipe(getById(params.organizationId)),
- ),
- filter((organization): organization is Organization => organization != null),
- shareReplay({ refCount: true, bufferSize: 1 }),
- ),
- ),
- );
-
- this.organization = toSignal(organization$);
-
- const policies$ = combineLatest([this.userId$, organization$]).pipe(
- switchMap(([userId, organization]) =>
- organization.isProviderUser
- ? from(this.policyApiService.getPolicies(organization.id)).pipe(
- map((response) => Policy.fromListResponse(response)),
- )
- : this.policyService.policies$(userId),
- ),
- );
-
- this.resetPasswordPolicyEnabled$ = combineLatest([organization$, policies$]).pipe(
- map(
- ([organization, policies]) =>
- policies
- .filter((policy) => policy.type === PolicyType.ResetPassword)
- .find((p) => p.organizationId === organization.id)?.enabled ?? false,
- ),
- );
-
- combineLatest([this.route.queryParams, organization$])
- .pipe(
- concatMap(async ([qParams, organization]) => {
- await this.load(organization!);
-
- this.searchControl.setValue(qParams.search);
-
- if (qParams.viewEvents != null) {
- const user = this.dataSource.data.filter((u) => u.id === qParams.viewEvents);
- if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
- this.openEventsDialog(user[0], organization!);
- }
- }
- }),
- takeUntilDestroyed(),
- )
- .subscribe();
-
- organization$
- .pipe(
- switchMap((organization) =>
- merge(
- this.organizationWarningsService.showInactiveSubscriptionDialog$(organization),
- this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
- ),
- ),
- takeUntilDestroyed(),
- )
- .subscribe();
-
- this.billingMetadata$ = organization$.pipe(
- switchMap((organization) =>
- this.organizationMetadataService.getOrganizationMetadata$(organization.id),
- ),
- shareReplay({ bufferSize: 1, refCount: false }),
- );
-
- // Stripe is slow, so kick this off in the background but without blocking page load.
- // Anyone who needs it will still await the first emission.
- this.billingMetadata$.pipe(take(1), takeUntilDestroyed()).subscribe();
- }
-
- override async load(organization: Organization) {
- await super.load(organization);
- }
-
- async getUsers(organization: Organization): Promise {
- return await this.memberService.loadUsers(organization);
- }
-
- async removeUser(id: string, organization: Organization): Promise {
- return await this.memberActionsService.removeUser(organization, id);
- }
-
- async revokeUser(id: string, organization: Organization): Promise {
- return await this.memberActionsService.revokeUser(organization, id);
- }
-
- async restoreUser(id: string, organization: Organization): Promise {
- return await this.memberActionsService.restoreUser(organization, id);
- }
-
- async reinviteUser(id: string, organization: Organization): Promise {
- return await this.memberActionsService.reinviteUser(organization, id);
- }
-
- async confirmUser(
- user: OrganizationUserView,
- publicKey: Uint8Array,
- organization: Organization,
- ): Promise {
- return await this.memberActionsService.confirmUser(user, publicKey, organization);
- }
-
- async revoke(user: OrganizationUserView, organization: Organization) {
- const confirmed = await this.revokeUserConfirmationDialog(user);
-
- if (!confirmed) {
- return false;
- }
-
- this.actionPromise = this.revokeUser(user.id, organization);
- try {
- const result = await this.actionPromise;
- if (result.success) {
- this.toastService.showToast({
- variant: "success",
- message: this.i18nService.t("revokedUserId", this.userNamePipe.transform(user)),
- });
- await this.load(organization);
- } else {
- throw new Error(result.error);
- }
- } catch (e) {
- this.validationService.showError(e);
- }
- this.actionPromise = undefined;
- }
-
- async restore(user: OrganizationUserView, organization: Organization) {
- this.actionPromise = this.restoreUser(user.id, organization);
- try {
- const result = await this.actionPromise;
- if (result.success) {
- this.toastService.showToast({
- variant: "success",
- message: this.i18nService.t("restoredUserId", this.userNamePipe.transform(user)),
- });
- await this.load(organization);
- } else {
- throw new Error(result.error);
- }
- } catch (e) {
- this.validationService.showError(e);
- }
- this.actionPromise = undefined;
- }
-
- allowResetPassword(
- orgUser: OrganizationUserView,
- organization: Organization,
- orgResetPasswordPolicyEnabled: boolean,
- ): boolean {
- return this.memberActionsService.allowResetPassword(
- orgUser,
- organization,
- orgResetPasswordPolicyEnabled,
- );
- }
-
- showEnrolledStatus(
- orgUser: OrganizationUserUserDetailsResponse,
- organization: Organization,
- orgResetPasswordPolicyEnabled: boolean,
- ): boolean {
- return (
- organization.useResetPassword &&
- orgUser.resetPasswordEnrolled &&
- orgResetPasswordPolicyEnabled
- );
- }
-
- private async handleInviteDialog(organization: Organization) {
- const billingMetadata = await firstValueFrom(this.billingMetadata$);
- const allUserEmails = this.dataSource.data?.map((user) => user.email) ?? [];
-
- const result = await this.memberDialogManager.openInviteDialog(
- organization,
- billingMetadata,
- allUserEmails,
- );
-
- if (result === MemberDialogResult.Saved) {
- await this.load(organization);
- }
- }
-
- async invite(organization: Organization) {
- const billingMetadata = await firstValueFrom(this.billingMetadata$);
- const seatLimitResult = this.billingConstraint.checkSeatLimit(organization, billingMetadata);
- if (!(await this.billingConstraint.seatLimitReached(seatLimitResult, organization))) {
- await this.handleInviteDialog(organization);
- this.organizationMetadataService.refreshMetadataCache();
- }
- }
-
- async edit(
- user: OrganizationUserView,
- organization: Organization,
- initialTab: MemberDialogTab = MemberDialogTab.Role,
- ) {
- const billingMetadata = await firstValueFrom(this.billingMetadata$);
-
- const result = await this.memberDialogManager.openEditDialog(
- user,
- organization,
- billingMetadata,
- initialTab,
- );
-
- switch (result) {
- case MemberDialogResult.Deleted:
- this.dataSource.removeUser(user);
- break;
- case MemberDialogResult.Saved:
- case MemberDialogResult.Revoked:
- case MemberDialogResult.Restored:
- await this.load(organization);
- break;
- }
- }
-
- async bulkRemove(organization: Organization) {
- if (this.actionPromise != null) {
- return;
- }
-
- const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount);
-
- await this.memberDialogManager.openBulkRemoveDialog(organization, users);
- this.organizationMetadataService.refreshMetadataCache();
- await this.load(organization);
- }
-
- async bulkDelete(organization: Organization) {
- if (this.actionPromise != null) {
- return;
- }
-
- const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount);
-
- await this.memberDialogManager.openBulkDeleteDialog(organization, users);
- await this.load(organization);
- }
-
- async bulkRevoke(organization: Organization) {
- await this.bulkRevokeOrRestore(true, organization);
- }
-
- async bulkRestore(organization: Organization) {
- await this.bulkRevokeOrRestore(false, organization);
- }
-
- async bulkRevokeOrRestore(isRevoking: boolean, organization: Organization) {
- if (this.actionPromise != null) {
- return;
- }
-
- const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount);
-
- await this.memberDialogManager.openBulkRestoreRevokeDialog(organization, users, isRevoking);
- await this.load(organization);
- }
-
- async bulkReinvite(organization: Organization) {
- if (this.actionPromise != null) {
- return;
- }
-
- let users: OrganizationUserView[];
- if (this.dataSource.isIncreasedBulkLimitEnabled()) {
- users = this.dataSource.getCheckedUsersInVisibleOrder();
- } else {
- users = this.dataSource.getCheckedUsers();
- }
-
- const allInvitedUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited);
-
- // Capture the original count BEFORE enforcing the limit
- const originalInvitedCount = allInvitedUsers.length;
-
- // When feature flag is enabled, limit invited users and uncheck the excess
- let filteredUsers: OrganizationUserView[];
- if (this.dataSource.isIncreasedBulkLimitEnabled()) {
- filteredUsers = this.dataSource.limitAndUncheckExcess(
- allInvitedUsers,
- CloudBulkReinviteLimit,
- );
- } else {
- filteredUsers = allInvitedUsers;
- }
-
- if (filteredUsers.length <= 0) {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("noSelectedUsersApplicable"),
- });
- return;
- }
-
- try {
- const result = await this.memberActionsService.bulkReinvite(organization, filteredUsers);
-
- if (!result.successful) {
- throw new Error();
- }
-
- // When feature flag is enabled, show toast instead of dialog
- if (this.dataSource.isIncreasedBulkLimitEnabled()) {
- const selectedCount = originalInvitedCount;
- const invitedCount = filteredUsers.length;
-
- if (selectedCount > CloudBulkReinviteLimit) {
- const excludedCount = selectedCount - CloudBulkReinviteLimit;
- this.toastService.showToast({
- variant: "success",
- message: this.i18nService.t(
- "bulkReinviteLimitedSuccessToast",
- CloudBulkReinviteLimit.toLocaleString(),
- selectedCount.toLocaleString(),
- excludedCount.toLocaleString(),
- ),
- });
- } else {
- this.toastService.showToast({
- variant: "success",
- message:
- invitedCount === 1
- ? this.i18nService.t("reinviteSuccessToast")
- : this.i18nService.t("bulkReinviteSentToast", invitedCount.toString()),
- });
- }
- } else {
- // Feature flag disabled - show legacy dialog
- await this.memberDialogManager.openBulkStatusDialog(
- users,
- filteredUsers,
- Promise.resolve(result.successful),
- this.i18nService.t("bulkReinviteMessage"),
- );
- }
- } catch (e) {
- this.validationService.showError(e);
- }
- this.actionPromise = undefined;
- }
-
- async bulkConfirm(organization: Organization) {
- if (this.actionPromise != null) {
- return;
- }
-
- const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount);
-
- await this.memberDialogManager.openBulkConfirmDialog(organization, users);
- await this.load(organization);
- }
-
- async bulkEnableSM(organization: Organization) {
- const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount);
-
- await this.memberDialogManager.openBulkEnableSecretsManagerDialog(organization, users);
-
- this.dataSource.uncheckAllUsers();
- await this.load(organization);
- }
-
- openEventsDialog(user: OrganizationUserView, organization: Organization) {
- this.memberDialogManager.openEventsDialog(user, organization);
- }
-
- async resetPassword(user: OrganizationUserView, organization: Organization) {
- if (!user || !user.email || !user.id) {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("orgUserDetailsNotFound"),
- });
- this.logService.error("Org user details not found when attempting account recovery");
-
- return;
- }
-
- const result = await this.memberDialogManager.openAccountRecoveryDialog(user, organization);
- if (result === AccountRecoveryDialogResultType.Ok) {
- await this.load(organization);
- }
-
- return;
- }
-
- protected async removeUserConfirmationDialog(user: OrganizationUserView) {
- return await this.memberDialogManager.openRemoveUserConfirmationDialog(user);
- }
-
- protected async revokeUserConfirmationDialog(user: OrganizationUserView) {
- return await this.memberDialogManager.openRevokeUserConfirmationDialog(user);
- }
-
- async deleteUser(user: OrganizationUserView, organization: Organization) {
- const confirmed = await this.memberDialogManager.openDeleteUserConfirmationDialog(
- user,
- organization,
- );
-
- if (!confirmed) {
- return false;
- }
-
- this.actionPromise = this.memberActionsService.deleteUser(organization, user.id);
- try {
- const result = await this.actionPromise;
- if (!result.success) {
- throw new Error(result.error);
- }
- this.toastService.showToast({
- variant: "success",
- message: this.i18nService.t("organizationUserDeleted", this.userNamePipe.transform(user)),
- });
- this.dataSource.removeUser(user);
- } catch (e) {
- this.validationService.showError(e);
- }
- this.actionPromise = undefined;
- }
-
- get showBulkRestoreUsers(): boolean {
- return this.dataSource
- .getCheckedUsers()
- .every((member) => member.status == this.userStatusType.Revoked);
- }
-
- get showBulkRevokeUsers(): boolean {
- return this.dataSource
- .getCheckedUsers()
- .every((member) => member.status != this.userStatusType.Revoked);
- }
-
- get showBulkRemoveUsers(): boolean {
- return this.dataSource.getCheckedUsers().every((member) => !member.managedByOrganization);
- }
-
- get showBulkDeleteUsers(): boolean {
- const validStatuses = [
- this.userStatusType.Accepted,
- this.userStatusType.Confirmed,
- this.userStatusType.Revoked,
- ];
-
- return this.dataSource
- .getCheckedUsers()
- .every((member) => member.managedByOrganization && validStatuses.includes(member.status));
- }
-
- get selectedInvitedCount(): number {
- return this.dataSource
- .getCheckedUsers()
- .filter((member) => member.status === this.userStatusType.Invited).length;
- }
-
- get isSingleInvite(): boolean {
- return this.selectedInvitedCount === 1;
- }
-
- exportMembers = () => {
- const result = this.memberExportService.getMemberExport(this.dataSource.data);
- if (result.success) {
- this.toastService.showToast({
- variant: "success",
- title: undefined,
- message: this.i18nService.t("dataExportSuccess"),
- });
- }
-
- if (result.error != null) {
- this.validationService.showError(result.error.message);
- }
- };
-}
diff --git a/apps/web/src/app/admin-console/organizations/members/members-routing.module.ts b/apps/web/src/app/admin-console/organizations/members/members-routing.module.ts
index 2f22b9871b7..153a2f3a956 100644
--- a/apps/web/src/app/admin-console/organizations/members/members-routing.module.ts
+++ b/apps/web/src/app/admin-console/organizations/members/members-routing.module.ts
@@ -1,30 +1,23 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
-import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
import { canAccessMembersTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { FreeBitwardenFamiliesComponent } from "../../../billing/members/free-bitwarden-families.component";
import { organizationPermissionsGuard } from "../guards/org-permissions.guard";
import { canAccessSponsoredFamilies } from "./../../../billing/guards/can-access-sponsored-families.guard";
-import { MembersComponent } from "./deprecated_members.component";
-import { vNextMembersComponent } from "./members.component";
+import { MembersComponent } from "./members.component";
const routes: Routes = [
- ...featureFlaggedRoute({
- defaultComponent: MembersComponent,
- flaggedComponent: vNextMembersComponent,
- featureFlag: FeatureFlag.MembersComponentRefactor,
- routeOptions: {
- path: "",
- canActivate: [organizationPermissionsGuard(canAccessMembersTab)],
- data: {
- titleId: "members",
- },
+ {
+ path: "",
+ component: MembersComponent,
+ canActivate: [organizationPermissionsGuard(canAccessMembersTab)],
+ data: {
+ titleId: "members",
},
- }),
+ },
{
path: "sponsored-families",
component: FreeBitwardenFamiliesComponent,
diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.spec.ts b/apps/web/src/app/admin-console/organizations/members/members.component.spec.ts
index 1cd90989b12..72c12fd4d79 100644
--- a/apps/web/src/app/admin-console/organizations/members/members.component.spec.ts
+++ b/apps/web/src/app/admin-console/organizations/members/members.component.spec.ts
@@ -36,7 +36,7 @@ import { OrganizationUserView } from "../core/views/organization-user.view";
import { AccountRecoveryDialogResultType } from "./components/account-recovery/account-recovery-dialog.component";
import { MemberDialogResult } from "./components/member-dialog";
-import { vNextMembersComponent } from "./members.component";
+import { MembersComponent } from "./members.component";
import {
MemberDialogManagerService,
MemberExportService,
@@ -48,9 +48,9 @@ import {
MemberActionResult,
} from "./services/member-actions/member-actions.service";
-describe("vNextMembersComponent", () => {
- let component: vNextMembersComponent;
- let fixture: ComponentFixture;
+describe("MembersComponent", () => {
+ let component: MembersComponent;
+ let fixture: ComponentFixture;
let mockApiService: MockProxy;
let mockI18nService: MockProxy;
@@ -172,7 +172,7 @@ describe("vNextMembersComponent", () => {
mockFileDownloadService = mock();
await TestBed.configureTestingModule({
- declarations: [vNextMembersComponent],
+ declarations: [MembersComponent],
providers: [
{ provide: ApiService, useValue: mockApiService },
{ provide: I18nService, useValue: mockI18nService },
@@ -211,13 +211,13 @@ describe("vNextMembersComponent", () => {
],
schemas: [NO_ERRORS_SCHEMA],
})
- .overrideComponent(vNextMembersComponent, {
+ .overrideComponent(MembersComponent, {
remove: { imports: [] },
add: { template: "" },
})
.compileComponents();
- fixture = TestBed.createComponent(vNextMembersComponent);
+ fixture = TestBed.createComponent(MembersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -515,7 +515,7 @@ describe("vNextMembersComponent", () => {
};
jest.spyOn(component["dataSource"](), "isIncreasedBulkLimitEnabled").mockReturnValue(false);
jest.spyOn(component["dataSource"](), "getCheckedUsers").mockReturnValue([invitedUser]);
- mockMemberActionsService.bulkReinvite.mockResolvedValue({ successful: true });
+ mockMemberActionsService.bulkReinvite.mockResolvedValue({ successful: [{}], failed: [] });
await component.bulkReinvite(mockOrg);
@@ -549,7 +549,7 @@ describe("vNextMembersComponent", () => {
jest.spyOn(component["dataSource"](), "isIncreasedBulkLimitEnabled").mockReturnValue(false);
jest.spyOn(component["dataSource"](), "getCheckedUsers").mockReturnValue([invitedUser]);
const error = new Error("Bulk reinvite failed");
- mockMemberActionsService.bulkReinvite.mockResolvedValue({ successful: false, failed: error });
+ mockMemberActionsService.bulkReinvite.mockResolvedValue({ successful: [], failed: error });
await component.bulkReinvite(mockOrg);
diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts
index 6139c5f07a5..6b93edc8c6b 100644
--- a/apps/web/src/app/admin-console/organizations/members/members.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts
@@ -82,7 +82,7 @@ interface BulkMemberFlags {
templateUrl: "members.component.html",
standalone: false,
})
-export class vNextMembersComponent {
+export class MembersComponent {
protected i18nService = inject(I18nService);
protected validationService = inject(ValidationService);
protected logService = inject(LogService);
@@ -426,7 +426,7 @@ export class vNextMembersComponent {
const result = await this.memberActionsService.bulkReinvite(organization, filteredUsers);
- if (!result.successful) {
+ if (result.successful.length === 0) {
this.validationService.showError(result.failed);
}
diff --git a/apps/web/src/app/admin-console/organizations/members/members.module.ts b/apps/web/src/app/admin-console/organizations/members/members.module.ts
index 54e2d1b6373..92ae71123cc 100644
--- a/apps/web/src/app/admin-console/organizations/members/members.module.ts
+++ b/apps/web/src/app/admin-console/organizations/members/members.module.ts
@@ -19,9 +19,8 @@ import { BulkRemoveDialogComponent } from "./components/bulk/bulk-remove-dialog.
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
import { UserDialogModule } from "./components/member-dialog";
-import { MembersComponent } from "./deprecated_members.component";
import { MembersRoutingModule } from "./members-routing.module";
-import { vNextMembersComponent } from "./members.component";
+import { MembersComponent } from "./members.component";
import { UserStatusPipe } from "./pipes";
import {
OrganizationMembersService,
@@ -52,7 +51,6 @@ import {
BulkProgressDialogComponent,
BulkReinviteFailureDialogComponent,
MembersComponent,
- vNextMembersComponent,
BulkDeleteDialogComponent,
UserStatusPipe,
],
diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts
index 688c7ed77ce..1ba056a24f6 100644
--- a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts
+++ b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.spec.ts
@@ -507,7 +507,7 @@ describe("MemberActionsService", () => {
const result = await service.bulkReinvite(mockOrganization, users);
- expect(result.successful).toBeUndefined();
+ expect(result.successful).toHaveLength(0);
expect(result.failed).toHaveLength(totalUsers);
expect(result.failed.every((f) => f.error === errorMessage)).toBe(true);
expect(organizationUserApiService.postManyOrganizationUserReinvite).toHaveBeenCalledTimes(2);
diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts
index e5f8c0c6673..7d573c8eeef 100644
--- a/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts
+++ b/apps/web/src/app/admin-console/organizations/members/services/member-actions/member-actions.service.ts
@@ -37,11 +37,7 @@ export interface MemberActionResult {
}
export class BulkActionResult {
- constructor() {
- this.failed = [];
- }
-
- successful?: OrganizationUserBulkResponse[];
+ successful: OrganizationUserBulkResponse[] = [];
failed: { id: string; error: string }[] = [];
}
@@ -316,7 +312,7 @@ export class MemberActionsService {
}
return {
- successful: allSuccessful.length > 0 ? allSuccessful : undefined,
+ successful: allSuccessful,
failed: allFailed,
};
}
diff --git a/apps/web/src/app/admin-console/organizations/members/services/member-dialog-manager/member-dialog-manager.service.ts b/apps/web/src/app/admin-console/organizations/members/services/member-dialog-manager/member-dialog-manager.service.ts
index 18106031fd0..6c367692376 100644
--- a/apps/web/src/app/admin-console/organizations/members/services/member-dialog-manager/member-dialog-manager.service.ts
+++ b/apps/web/src/app/admin-console/organizations/members/services/member-dialog-manager/member-dialog-manager.service.ts
@@ -1,8 +1,10 @@
import { Injectable, WritableSignal } from "@angular/core";
import { firstValueFrom, lastValueFrom } from "rxjs";
+import { OrganizationUserBulkResponse } from "@bitwarden/admin-console/common";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -197,7 +199,7 @@ export class MemberDialogManagerService {
async openBulkStatusDialog(
users: OrganizationUserView[],
filteredUsers: OrganizationUserView[],
- request: Promise,
+ request: Promise,
successMessage: string,
): Promise {
const dialogRef = BulkStatusComponent.open(this.dialogService, {
diff --git a/apps/web/src/app/billing/clients/account-billing.client.ts b/apps/web/src/app/billing/clients/account-billing.client.ts
index 1334ff643dd..6864e1de981 100644
--- a/apps/web/src/app/billing/clients/account-billing.client.ts
+++ b/apps/web/src/app/billing/clients/account-billing.client.ts
@@ -4,6 +4,8 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { BitwardenSubscriptionResponse } from "@bitwarden/common/billing/models/response/bitwarden-subscription.response";
import { SubscriptionCadence } from "@bitwarden/common/billing/types/subscription-pricing-tier";
+import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
+import { Maybe } from "@bitwarden/pricing";
import { BitwardenSubscription } from "@bitwarden/subscription";
import {
@@ -23,11 +25,18 @@ export class AccountBillingClient {
return this.apiService.send("GET", path, null, true, true);
};
- getSubscription = async (): Promise => {
+ getSubscription = async (): Promise> => {
const path = `${this.endpoint}/subscription`;
- const json = await this.apiService.send("GET", path, null, true, true);
- const response = new BitwardenSubscriptionResponse(json);
- return response.toDomain();
+ try {
+ const json = await this.apiService.send("GET", path, null, true, true);
+ const response = new BitwardenSubscriptionResponse(json);
+ return response.toDomain();
+ } catch (error: any) {
+ if (error instanceof ErrorResponse && error.statusCode === 404) {
+ return null;
+ }
+ throw error;
+ }
};
purchaseSubscription = async (
diff --git a/apps/web/src/app/billing/individual/individual-billing-routing.module.ts b/apps/web/src/app/billing/individual/individual-billing-routing.module.ts
index f85dab54fe7..8d9c999caec 100644
--- a/apps/web/src/app/billing/individual/individual-billing-routing.module.ts
+++ b/apps/web/src/app/billing/individual/individual-billing-routing.module.ts
@@ -19,7 +19,7 @@ const routes: Routes = [
component: SubscriptionComponent,
data: { titleId: "subscription" },
children: [
- { path: "", pathMatch: "full", redirectTo: "premium" },
+ { path: "", pathMatch: "full", redirectTo: "user-subscription" },
...featureFlaggedRoute({
defaultComponent: UserSubscriptionComponent,
flaggedComponent: AccountSubscriptionComponent,
diff --git a/apps/web/src/app/billing/individual/subscription.component.ts b/apps/web/src/app/billing/individual/subscription.component.ts
index 37fb2baf3a6..4f52f3c2ea2 100644
--- a/apps/web/src/app/billing/individual/subscription.component.ts
+++ b/apps/web/src/app/billing/individual/subscription.component.ts
@@ -1,17 +1,22 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
-import { Observable, switchMap } from "rxjs";
+import { combineLatest, from, map, Observable, switchMap } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { AccountBillingClient } from "../clients/account-billing.client";
+
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
templateUrl: "subscription.component.html",
standalone: false,
+ providers: [AccountBillingClient],
})
export class SubscriptionComponent implements OnInit {
hasPremium$: Observable;
@@ -21,9 +26,21 @@ export class SubscriptionComponent implements OnInit {
private platformUtilsService: PlatformUtilsService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
+ configService: ConfigService,
+ private accountBillingClient: AccountBillingClient,
) {
- this.hasPremium$ = accountService.activeAccount$.pipe(
- switchMap((account) => billingAccountProfileStateService.hasPremiumPersonally$(account.id)),
+ this.hasPremium$ = combineLatest([
+ configService.getFeatureFlag$(FeatureFlag.PM29594_UpdateIndividualSubscriptionPage),
+ accountService.activeAccount$,
+ ]).pipe(
+ switchMap(([isFeatureFlagEnabled, account]) => {
+ if (isFeatureFlagEnabled) {
+ return from(accountBillingClient.getSubscription()).pipe(
+ map((subscription) => !!subscription),
+ );
+ }
+ return billingAccountProfileStateService.hasPremiumPersonally$(account.id);
+ }),
);
}
diff --git a/apps/web/src/app/billing/individual/subscription/account-subscription.component.ts b/apps/web/src/app/billing/individual/subscription/account-subscription.component.ts
index d8e25de7965..7fdc830effd 100644
--- a/apps/web/src/app/billing/individual/subscription/account-subscription.component.ts
+++ b/apps/web/src/app/billing/individual/subscription/account-subscription.component.ts
@@ -34,6 +34,11 @@ import {
AdjustAccountSubscriptionStorageDialogComponent,
AdjustAccountSubscriptionStorageDialogParams,
} from "@bitwarden/web-vault/app/billing/individual/subscription/adjust-account-subscription-storage-dialog.component";
+import {
+ UnifiedUpgradeDialogComponent,
+ UnifiedUpgradeDialogStatus,
+ UnifiedUpgradeDialogStep,
+} from "@bitwarden/web-vault/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component";
import {
OffboardingSurveyDialogResultType,
openOffboardingSurvey,
@@ -93,10 +98,11 @@ export class AccountSubscriptionComponent {
if (!this.account()) {
return await redirectToPremiumPage();
}
- if (!this.hasPremiumPersonally()) {
+ const subscription = await this.accountBillingClient.getSubscription();
+ if (!subscription) {
return await redirectToPremiumPage();
}
- return await this.accountBillingClient.getSubscription();
+ return subscription;
},
});
@@ -106,6 +112,7 @@ export class AccountSubscriptionComponent {
const subscription = this.subscription.value();
if (subscription) {
return (
+ subscription.status === SubscriptionStatuses.Incomplete ||
subscription.status === SubscriptionStatuses.IncompleteExpired ||
subscription.status === SubscriptionStatuses.Canceled ||
subscription.status === SubscriptionStatuses.Unpaid
@@ -230,6 +237,27 @@ export class AccountSubscriptionComponent {
case SubscriptionCardActions.UpdatePayment:
await this.router.navigate(["../payment-details"], { relativeTo: this.activatedRoute });
break;
+ case SubscriptionCardActions.Resubscribe: {
+ const account = this.account();
+ if (!account) {
+ return;
+ }
+
+ const dialogRef = UnifiedUpgradeDialogComponent.open(this.dialogService, {
+ data: {
+ account,
+ initialStep: UnifiedUpgradeDialogStep.Payment,
+ selectedPlan: PersonalSubscriptionPricingTierIds.Premium,
+ },
+ });
+
+ const result = await lastValueFrom(dialogRef.closed);
+
+ if (result?.status === UnifiedUpgradeDialogStatus.UpgradedToPremium) {
+ this.subscription.reload();
+ }
+ break;
+ }
case SubscriptionCardActions.UpgradePlan:
await this.openUpgradeDialog();
break;
diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.html b/apps/web/src/app/components/environment-selector/environment-selector.component.html
index 9862f62c2e2..44039bfe605 100644
--- a/apps/web/src/app/components/environment-selector/environment-selector.component.html
+++ b/apps/web/src/app/components/environment-selector/environment-selector.component.html
@@ -7,11 +7,11 @@
region == currentRegion ? 'javascript:void(0)' : region.urls.webVault + routeAndParams
"
>
-
+ >
{{ region.domain }}
@@ -19,7 +19,7 @@
{{ "accessing" | i18n }}:
{{ currentRegion?.domain }}
-
+
diff --git a/apps/web/src/app/layouts/header/web-header.component.html b/apps/web/src/app/layouts/header/web-header.component.html
index 995169e3dc1..9288c96237e 100644
--- a/apps/web/src/app/layouts/header/web-header.component.html
+++ b/apps/web/src/app/layouts/header/web-header.component.html
@@ -60,11 +60,11 @@
-
+
{{ "accountSettings" | i18n }}
-
+
{{ "getHelp" | i18n }}
-
+
{{ "getApps" | i18n }}
diff --git a/apps/web/src/app/layouts/org-switcher/org-switcher.component.html b/apps/web/src/app/layouts/org-switcher/org-switcher.component.html
index a9acddeb0b8..b8f7c5ab0c0 100644
--- a/apps/web/src/app/layouts/org-switcher/org-switcher.component.html
+++ b/apps/web/src/app/layouts/org-switcher/org-switcher.component.html
@@ -7,13 +7,14 @@
[routerLinkActiveOptions]="{ exact: true }"
[(open)]="open"
>
-
+ >
-
+ >
-
{{ fingerprint }}
diff --git a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.html b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.html
index f0c0b01e06e..e52771a282b 100644
--- a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.html
+++ b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.html
@@ -1,10 +1,14 @@
- {{ title }}{{ title }}
diff --git a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts
index 277a4d2d26e..47a618a1269 100644
--- a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts
+++ b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts
@@ -2,6 +2,8 @@
// @ts-strict-ignore
import { Component, Input } from "@angular/core";
+import { BitwardenIcon } from "@bitwarden/components";
+
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
@@ -21,7 +23,7 @@ export class OnboardingTaskComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input()
- icon = "bwi-info-circle";
+ icon: BitwardenIcon = "bwi-info-circle";
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
diff --git a/apps/web/src/app/shared/components/onboarding/onboarding.component.html b/apps/web/src/app/shared/components/onboarding/onboarding.component.html
index 2433ec51fcc..ca98ceb8fbf 100644
--- a/apps/web/src/app/shared/components/onboarding/onboarding.component.html
+++ b/apps/web/src/app/shared/components/onboarding/onboarding.component.html
@@ -6,11 +6,7 @@
0; else spinner">
{{ "complete" | i18n: amountCompleted : tasks.length }}
-
+
@@ -24,5 +20,5 @@
-
+
diff --git a/apps/web/src/app/shared/components/onboarding/onboarding.stories.ts b/apps/web/src/app/shared/components/onboarding/onboarding.stories.ts
index 6873700e2bc..26c951fb11f 100644
--- a/apps/web/src/app/shared/components/onboarding/onboarding.stories.ts
+++ b/apps/web/src/app/shared/components/onboarding/onboarding.stories.ts
@@ -4,7 +4,7 @@ import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/an
import { delay, of, startWith } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
-import { LinkModule, SvgModule, ProgressModule } from "@bitwarden/components";
+import { LinkModule, SvgModule, ProgressModule, IconModule } from "@bitwarden/components";
import { PreloadedEnglishI18nModule } from "../../../core/tests";
@@ -16,7 +16,7 @@ export default {
component: OnboardingComponent,
decorators: [
moduleMetadata({
- imports: [JslibModule, RouterModule, LinkModule, SvgModule, ProgressModule],
+ imports: [JslibModule, RouterModule, LinkModule, IconModule, SvgModule, ProgressModule],
declarations: [OnboardingTaskComponent],
}),
applicationConfig({
diff --git a/apps/web/src/app/shared/shared.module.ts b/apps/web/src/app/shared/shared.module.ts
index b83555fd84e..729238e0b0d 100644
--- a/apps/web/src/app/shared/shared.module.ts
+++ b/apps/web/src/app/shared/shared.module.ts
@@ -18,6 +18,7 @@ import {
DialogModule,
FormFieldModule,
IconButtonModule,
+ IconModule,
SvgModule,
LinkModule,
MenuModule,
@@ -63,6 +64,7 @@ import {
DialogModule,
FormFieldModule,
IconButtonModule,
+ IconModule,
SvgModule,
LinkModule,
MenuModule,
@@ -99,6 +101,7 @@ import {
DialogModule,
FormFieldModule,
IconButtonModule,
+ IconModule,
SvgModule,
LinkModule,
MenuModule,
diff --git a/apps/web/src/app/tools/send/send-access/send-auth.component.ts b/apps/web/src/app/tools/send/send-access/send-auth.component.ts
index 9ed8106ad40..8c630ce5315 100644
--- a/apps/web/src/app/tools/send/send-access/send-auth.component.ts
+++ b/apps/web/src/app/tools/send/send-access/send-auth.component.ts
@@ -26,7 +26,7 @@ import { SendAccessResponse } from "@bitwarden/common/tools/send/models/response
import { SEND_KDF_ITERATIONS } from "@bitwarden/common/tools/send/send-kdf";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { AuthType } from "@bitwarden/common/tools/send/types/auth-type";
-import { ToastService } from "@bitwarden/components";
+import { AnonLayoutWrapperDataService, ToastService } from "@bitwarden/components";
import { SharedModule } from "../../../shared";
@@ -69,6 +69,7 @@ export class SendAuthComponent implements OnInit {
private formBuilder: FormBuilder,
private configService: ConfigService,
private sendTokenService: SendTokenService,
+ private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,
) {}
ngOnInit() {
@@ -160,8 +161,10 @@ export class SendAuthComponent implements OnInit {
this.expiredAuthAttempts = 0;
if (emailRequired(response.error)) {
this.sendAuthType.set(AuthType.Email);
+ this.updatePageTitle();
} else if (emailAndOtpRequired(response.error)) {
this.enterOtp.set(true);
+ this.updatePageTitle();
} else if (otpInvalid(response.error)) {
this.toastService.showToast({
variant: "error",
@@ -170,6 +173,7 @@ export class SendAuthComponent implements OnInit {
});
} else if (passwordHashB64Required(response.error)) {
this.sendAuthType.set(AuthType.Password);
+ this.updatePageTitle();
} else if (passwordHashB64Invalid(response.error)) {
this.toastService.showToast({
variant: "error",
@@ -207,4 +211,24 @@ export class SendAuthComponent implements OnInit {
);
return Utils.fromBufferToB64(passwordHash) as SendHashedPasswordB64;
}
+
+ private updatePageTitle(): void {
+ const authType = this.sendAuthType();
+
+ if (authType === AuthType.Email) {
+ if (this.enterOtp()) {
+ this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
+ pageTitle: { key: "enterTheCodeSentToYourEmail" },
+ });
+ } else {
+ this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
+ pageTitle: { key: "verifyYourEmailToViewThisSend" },
+ });
+ }
+ } else if (authType === AuthType.Password) {
+ this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
+ pageTitle: { key: "sendAccessPasswordTitle" },
+ });
+ }
+ }
}
diff --git a/apps/web/src/app/tools/send/send-access/send-view.component.ts b/apps/web/src/app/tools/send/send-access/send-view.component.ts
index 1ab9a121ace..923a749db92 100644
--- a/apps/web/src/app/tools/send/send-access/send-view.component.ts
+++ b/apps/web/src/app/tools/send/send-access/send-view.component.ts
@@ -69,6 +69,9 @@ export class SendViewComponent implements OnInit {
) {}
ngOnInit() {
+ this.layoutWrapperDataService.setAnonLayoutWrapperData({
+ pageTitle: { key: "sendAccessContentTitle" },
+ });
void this.load();
}
diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts
index 4da2d05f12b..4c6efdee167 100644
--- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts
+++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts
@@ -100,7 +100,7 @@ export interface VaultItemDialogParams {
/**
* Function to restore a cipher from the trash.
*/
- restore?: (c: CipherViewLike) => Promise;
+ restore?: (c: CipherViewLike) => Promise;
}
export const VaultItemDialogResult = {
@@ -616,7 +616,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
this.toastService.showToast({
variant: "success",
- message: this.i18nService.t("itemWasSentToArchive"),
+ message: this.i18nService.t("itemArchiveToast"),
});
} catch {
this.toastService.showToast({
@@ -638,7 +638,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
this.toastService.showToast({
variant: "success",
- message: this.i18nService.t("itemWasUnarchived"),
+ message: this.i18nService.t("itemUnarchivedToast"),
});
} catch {
this.toastService.showToast({
diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts
index 4b9d2ed59ee..5ff72b0d147 100644
--- a/apps/web/src/app/vault/individual-vault/vault.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault.component.ts
@@ -1,15 +1,6 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
+import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, viewChild } from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
-import {
- BehaviorSubject,
- combineLatest,
- firstValueFrom,
- lastValueFrom,
- Observable,
- Subject,
-} from "rxjs";
+import { combineLatest, firstValueFrom, lastValueFrom, Observable, of, Subject } from "rxjs";
import {
concatMap,
debounceTime,
@@ -18,6 +9,7 @@ import {
first,
map,
shareReplay,
+ startWith,
switchMap,
take,
takeUntil,
@@ -89,7 +81,6 @@ import { CipherListView } from "@bitwarden/sdk-internal";
import {
AddEditFolderDialogComponent,
AddEditFolderDialogResult,
- AttachmentDialogCloseResult,
AttachmentDialogResult,
AttachmentsV2Component,
CipherFormConfig,
@@ -179,14 +170,10 @@ type EmptyStateMap = Record;
],
})
export class VaultComponent implements OnInit, OnDestroy {
- // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
- // eslint-disable-next-line @angular-eslint/prefer-signals
- @ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
- // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
- // eslint-disable-next-line @angular-eslint/prefer-signals
- @ViewChild("vaultItems", { static: false }) vaultItemsComponent: VaultItemsComponent;
+ readonly filterComponent = viewChild(VaultFilterComponent);
+ readonly vaultItemsComponent = viewChild(VaultItemsComponent);
- trashCleanupWarning: string = null;
+ trashCleanupWarning: string = "";
activeFilter: VaultFilter = new VaultFilter();
protected deactivatedOrgIcon = DeactivatedOrg;
@@ -198,20 +185,20 @@ export class VaultComponent implements OnInit, OnDestr
protected refreshing = false;
protected processingEvent = false;
protected filter: RoutedVaultFilterModel = {};
- protected showBulkMove: boolean;
- protected canAccessPremium: boolean;
- protected allCollections: CollectionView[];
+ protected showBulkMove: boolean = false;
+ protected canAccessPremium: boolean = false;
+ protected allCollections: CollectionView[] = [];
protected allOrganizations: Organization[] = [];
- protected ciphers: C[];
- protected collections: CollectionView[];
- protected isEmpty: boolean;
+ protected ciphers: C[] = [];
+ protected collections: CollectionView[] = [];
+ protected isEmpty: boolean = false;
protected selectedCollection: TreeNode | undefined;
protected canCreateCollections = false;
protected currentSearchText$: Observable = this.route.queryParams.pipe(
map((queryParams) => queryParams.search),
);
private searchText$ = new Subject();
- private refresh$ = new BehaviorSubject(null);
+ private refresh$ = new Subject();
private destroy$ = new Subject();
private vaultItemDialogRef?: DialogRef | undefined;
@@ -220,7 +207,7 @@ export class VaultComponent implements OnInit, OnDestr
organizations$ = this.accountService.activeAccount$
.pipe(map((a) => a?.id))
- .pipe(switchMap((id) => this.organizationService.organizations$(id)));
+ .pipe(switchMap((id) => (id ? this.organizationService.organizations$(id) : of([]))));
emptyState$ = combineLatest([
this.currentSearchText$,
@@ -228,7 +215,7 @@ export class VaultComponent implements OnInit, OnDestr
this.organizations$,
]).pipe(
map(([searchText, filter, organizations]) => {
- const selectedOrg = organizations?.find((org) => org.id === filter.organizationId);
+ const selectedOrg = organizations.find((org) => org.id === filter.organizationId);
const isOrgDisabled = selectedOrg && !selectedOrg.enabled;
if (isOrgDisabled) {
@@ -586,7 +573,7 @@ export class VaultComponent implements OnInit, OnDestr
firstSetup$
.pipe(
- switchMap(() => this.refresh$),
+ switchMap(() => this.refresh$.pipe(startWith(undefined))),
tap(() => (this.refreshing = true)),
switchMap(() =>
combineLatest([
@@ -712,7 +699,6 @@ export class VaultComponent implements OnInit, OnDestr
async handleUnknownCipher() {
this.toastService.showToast({
variant: "error",
- title: null,
message: this.i18nService.t("unknownCipher"),
});
await this.router.navigate([], {
@@ -744,7 +730,7 @@ export class VaultComponent implements OnInit, OnDestr
await this.cipherArchiveService.archiveWithServer(cipher.id as CipherId, activeUserId);
this.toastService.showToast({
variant: "success",
- message: this.i18nService.t("itemWasSentToArchive"),
+ message: this.i18nService.t("itemArchiveToast"),
});
this.refresh();
} catch (e) {
@@ -801,7 +787,7 @@ export class VaultComponent implements OnInit, OnDestr
this.toastService.showToast({
variant: "success",
- message: this.i18nService.t("itemUnarchived"),
+ message: this.i18nService.t("itemUnarchivedToast"),
});
this.refresh();
@@ -842,9 +828,13 @@ export class VaultComponent implements OnInit, OnDestr
if (orgId == null) {
orgId = "MyVault";
}
- const orgs = await firstValueFrom(this.filterComponent.filters.organizationFilter.data$);
+ const data = this.filterComponent()?.filters?.organizationFilter?.data$;
+ if (data == undefined) {
+ return;
+ }
+ const orgs = await firstValueFrom(data);
const orgNode = ServiceUtils.getTreeNodeObject(orgs, orgId) as TreeNode;
- await this.filterComponent.filters?.organizationFilter?.action(orgNode);
+ await this.filterComponent()?.filters?.organizationFilter?.action(orgNode);
}
addFolder = (): void => {
@@ -912,7 +902,10 @@ export class VaultComponent implements OnInit, OnDestr
canEditCipher: cipher.edit,
});
- const result: AttachmentDialogCloseResult = await lastValueFrom(dialogRef.closed);
+ const result = await lastValueFrom(dialogRef.closed);
+ if (result === undefined) {
+ return;
+ }
if (
result.action === AttachmentDialogResult.Uploaded ||
@@ -966,7 +959,7 @@ export class VaultComponent implements OnInit, OnDestr
*/
async addCipher(cipherType?: CipherType) {
const type = cipherType ?? this.activeFilter.cipherType;
- const cipherFormConfig = await this.cipherFormConfigService.buildConfig("add", null, type);
+ const cipherFormConfig = await this.cipherFormConfigService.buildConfig("add", undefined, type);
const collectionId =
this.activeFilter.collectionId !== "AllCollections" && this.activeFilter.collectionId != null
? this.activeFilter.collectionId
@@ -994,7 +987,7 @@ export class VaultComponent implements OnInit, OnDestr
}
async editCipher(cipher: CipherView | CipherListView, cloneMode?: boolean) {
- return this.editCipherId(uuidAsString(cipher?.id), cloneMode);
+ return this.editCipherId(uuidAsString(cipher.id), cloneMode);
}
/**
@@ -1088,6 +1081,9 @@ export class VaultComponent implements OnInit, OnDestr
},
});
const result = await lastValueFrom(dialog.closed);
+ if (result === undefined) {
+ return;
+ }
if (result.action === CollectionDialogAction.Saved) {
if (result.collection) {
// Update CollectionService with the new collection
@@ -1104,7 +1100,7 @@ export class VaultComponent implements OnInit, OnDestr
async editCollection(c: CollectionView, tab: CollectionDialogTabType): Promise {
const dialog = openCollectionDialog(this.dialogService, {
data: {
- collectionId: c?.id,
+ collectionId: c.id,
organizationId: c.organizationId,
initialTab: tab,
limitNestedCollections: true,
@@ -1112,6 +1108,9 @@ export class VaultComponent implements OnInit, OnDestr
});
const result = await lastValueFrom(dialog.closed);
+ if (result === undefined) {
+ return;
+ }
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
if (result.action === CollectionDialogAction.Saved) {
if (result.collection) {
@@ -1163,7 +1162,6 @@ export class VaultComponent implements OnInit, OnDestr
this.toastService.showToast({
variant: "success",
- title: null,
message: this.i18nService.t("deletedCollectionId", collection.name),
});
if (navigateAway) {
@@ -1196,12 +1194,12 @@ export class VaultComponent implements OnInit, OnDestr
let availableCollections: CollectionView[] = [];
const orgId =
this.activeFilter.organizationId ||
- ciphers.find((c) => c.organizationId !== null)?.organizationId;
+ ciphers.find((c) => c.organizationId !== undefined)?.organizationId;
if (orgId && orgId !== "MyVault") {
const organization = this.allOrganizations.find((o) => o.id === orgId);
availableCollections = this.allCollections.filter(
- (c) => c.organizationId === organization.id,
+ (c) => c.organizationId === organization?.id,
);
}
@@ -1229,7 +1227,7 @@ export class VaultComponent implements OnInit, OnDestr
ciphers: ciphersToAssign,
organizationId: orgId as OrganizationId,
availableCollections,
- activeCollection: this.activeFilter?.selectedCollectionNode?.node,
+ activeCollection: this.activeFilter.selectedCollectionNode?.node,
},
});
@@ -1255,7 +1253,7 @@ export class VaultComponent implements OnInit, OnDestr
await this.editCipher(cipher, true);
}
- restore = async (c: C): Promise => {
+ restore = async (c: CipherViewLike) => {
let toastMessage;
if (!CipherViewLikeUtils.isDeleted(c)) {
return;
@@ -1281,13 +1279,14 @@ export class VaultComponent implements OnInit, OnDestr
await this.cipherService.restoreWithServer(uuidAsString(c.id), activeUserId);
this.toastService.showToast({
variant: "success",
- title: null,
message: toastMessage,
});
this.refresh();
} catch (e) {
this.logService.error(e);
+ return;
}
+ return;
};
async bulkRestore(ciphers: C[]) {
@@ -1311,7 +1310,6 @@ export class VaultComponent implements OnInit, OnDestr
if (selectedCipherIds.length === 0) {
this.toastService.showToast({
variant: "error",
- title: null,
message: this.i18nService.t("nothingSelected"),
});
return;
@@ -1321,23 +1319,24 @@ export class VaultComponent implements OnInit, OnDestr
await this.cipherService.restoreManyWithServer(selectedCipherIds, activeUserId);
this.toastService.showToast({
variant: "success",
- title: null,
message: toastMessage,
});
this.refresh();
}
private async handleDeleteEvent(items: VaultItem[]) {
- const ciphers: C[] = items.filter((i) => i.collection === undefined).map((i) => i.cipher);
- const collections = items.filter((i) => i.cipher === undefined).map((i) => i.collection);
+ const ciphers = items
+ .filter((i) => i.collection === undefined && i.cipher !== undefined)
+ .map((i) => i.cipher as C);
+ const collections = items
+ .filter((i) => i.collection !== undefined)
+ .map((i) => i.collection as CollectionView);
if (ciphers.length === 1 && collections.length === 0) {
await this.deleteCipher(ciphers[0]);
} else if (ciphers.length === 0 && collections.length === 1) {
await this.deleteCollection(collections[0]);
} else {
- const orgIds = items
- .filter((i) => i.cipher === undefined)
- .map((i) => i.collection.organizationId);
+ const orgIds = collections.map((c) => c.organizationId);
const orgs = await firstValueFrom(
this.organizations$.pipe(map((orgs) => orgs.filter((o) => orgIds.includes(o.id)))),
);
@@ -1345,7 +1344,7 @@ export class VaultComponent implements OnInit, OnDestr
}
}
- async deleteCipher(c: C): Promise {
+ async deleteCipher(c: C) {
if (!(await this.repromptCipher([c]))) {
return;
}
@@ -1364,7 +1363,7 @@ export class VaultComponent implements OnInit, OnDestr
});
if (!confirmed) {
- return false;
+ return;
}
try {
@@ -1373,7 +1372,6 @@ export class VaultComponent implements OnInit, OnDestr
this.toastService.showToast({
variant: "success",
- title: null,
message: this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"),
});
this.refresh();
@@ -1390,7 +1388,6 @@ export class VaultComponent implements OnInit, OnDestr
if (ciphers.length === 0 && collections.length === 0) {
this.toastService.showToast({
variant: "error",
- title: null,
message: this.i18nService.t("nothingSelected"),
});
return;
@@ -1430,7 +1427,6 @@ export class VaultComponent implements OnInit, OnDestr
if (selectedCipherIds.length === 0) {
this.toastService.showToast({
variant: "error",
- title: null,
message: this.i18nService.t("nothingSelected"),
});
return;
@@ -1454,11 +1450,8 @@ export class VaultComponent implements OnInit, OnDestr
const login = CipherViewLikeUtils.getLogin(cipher);
if (!login) {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("unexpectedError"),
- });
+ this.showErrorToast();
+ return;
}
if (field === "username") {
@@ -1471,15 +1464,15 @@ export class VaultComponent implements OnInit, OnDestr
typeI18nKey = "password";
} else if (field === "totp") {
aType = "TOTP";
+ if (!login.totp) {
+ this.showErrorToast();
+ return;
+ }
const totpResponse = await firstValueFrom(this.totpService.getCode$(login.totp));
value = totpResponse.code;
typeI18nKey = "verificationCodeTotp";
} else {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("unexpectedError"),
- });
+ this.showErrorToast();
return;
}
@@ -1494,10 +1487,13 @@ export class VaultComponent implements OnInit, OnDestr
return;
}
+ if (!value) {
+ this.showErrorToast();
+ return;
+ }
this.platformUtilsService.copyToClipboard(value, { window: window });
this.toastService.showToast({
variant: "info",
- title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)),
});
@@ -1514,6 +1510,13 @@ export class VaultComponent implements OnInit, OnDestr
}
}
+ showErrorToast() {
+ this.toastService.showToast({
+ variant: "error",
+ message: this.i18nService.t("unexpectedError"),
+ });
+ }
+
/**
* Toggles the favorite status of the cipher and updates it on the server.
*/
@@ -1525,7 +1528,6 @@ export class VaultComponent implements OnInit, OnDestr
this.toastService.showToast({
variant: "success",
- title: null,
message: this.i18nService.t(
cipherFullView.favorite ? "itemAddedToFavorites" : "itemRemovedFromFavorites",
),
@@ -1540,15 +1542,15 @@ export class VaultComponent implements OnInit, OnDestr
: this.cipherService.softDeleteWithServer(id, userId);
}
- protected async repromptCipher(ciphers: C[]) {
+ protected async repromptCipher(ciphers: CipherViewLike[]) {
const notProtected = !ciphers.find((cipher) => cipher.reprompt !== CipherRepromptType.None);
return notProtected || (await this.passwordRepromptService.showPasswordPrompt());
}
private refresh() {
- this.refresh$.next();
- this.vaultItemsComponent?.clearSelection();
+ this.refresh$.next(undefined);
+ this.vaultItemsComponent()?.clearSelection();
}
private async go(queryParams: any = null) {
@@ -1573,7 +1575,6 @@ export class VaultComponent implements OnInit, OnDestr
private showMissingPermissionsError() {
this.toastService.showToast({
variant: "error",
- title: null,
message: this.i18nService.t("missingPermissions"),
});
}
@@ -1584,13 +1585,13 @@ export class VaultComponent implements OnInit, OnDestr
*/
private async getPasswordFromCipherViewLike(cipher: C): Promise {
if (!CipherViewLikeUtils.isCipherListView(cipher)) {
- return Promise.resolve(cipher.login?.password);
+ return Promise.resolve(cipher?.login?.password);
}
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const _cipher = await this.cipherService.get(uuidAsString(cipher.id), activeUserId);
const cipherView = await this.cipherService.decrypt(_cipher, activeUserId);
- return cipherView.login?.password;
+ return cipherView.login.password;
}
}
diff --git a/apps/web/src/connectors/platform/proxy-cookie-redirect.html b/apps/web/src/connectors/platform/proxy-cookie-redirect.html
index 1daa6d2e412..1918fcd771c 100644
--- a/apps/web/src/connectors/platform/proxy-cookie-redirect.html
+++ b/apps/web/src/connectors/platform/proxy-cookie-redirect.html
@@ -18,6 +18,7 @@
+
+
-
-
-
-
-
-
-
-
- {{ "all" | i18n }}
-
- {{ allCount }}
-
-
-
- {{ "invited" | i18n }}
-
- {{ invitedCount }}
-
-
-
- {{ "needsConfirmation" | i18n }}
-
- {{ acceptedCount }}
-
-
-
-
-
-
-
-
- {{ "loading" | i18n }}
-
-
-
- {{ "noMembersInList" | i18n }}
-
-
- {{ "providerUsersNeedConfirmed" | i18n }}
-
-
-
-
-
- |
-
-
- |
- {{ "name" | i18n }} |
- {{ "role" | i18n }} |
-
-
-
-
-
-
-
- |
-
-
-
-
- |
-
- |
-
-
-
-
-
-
-
- {{ "invited" | i18n }}
-
-
- {{ "needsConfirmation" | i18n }}
-
-
- {{ "revoked" | i18n }}
-
-
-
- {{ user.email }}
-
-
-
- |
-
- {{ "providerAdmin" | i18n }}
- {{ "serviceUser" | i18n }}
- |
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.ts
deleted file mode 100644
index 1b1ae25c027..00000000000
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.ts
+++ /dev/null
@@ -1,349 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Component } from "@angular/core";
-import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
-import { ActivatedRoute, Router } from "@angular/router";
-import { combineLatest, firstValueFrom, lastValueFrom, switchMap } from "rxjs";
-import { first, map } from "rxjs/operators";
-
-import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
-import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
-import { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
-import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request";
-import { ProviderUserConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-confirm.request";
-import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { getUserId } from "@bitwarden/common/auth/services/account.service";
-import { assertNonNullish } from "@bitwarden/common/auth/utils";
-import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
-import { ListResponse } from "@bitwarden/common/models/response/list.response";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
-import { ProviderId } from "@bitwarden/common/types/guid";
-import { DialogRef, DialogService, ToastService } from "@bitwarden/components";
-import { KeyService } from "@bitwarden/key-management";
-import { BaseMembersComponent } from "@bitwarden/web-vault/app/admin-console/common/base-members.component";
-import {
- CloudBulkReinviteLimit,
- MaxCheckedCount,
- peopleFilter,
- PeopleTableDataSource,
-} from "@bitwarden/web-vault/app/admin-console/common/people-table-data-source";
-import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component";
-import { BulkStatusComponent } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/bulk-status.component";
-import { MemberActionResult } from "@bitwarden/web-vault/app/admin-console/organizations/members/services/member-actions/member-actions.service";
-
-import {
- AddEditMemberDialogComponent,
- AddEditMemberDialogParams,
- AddEditMemberDialogResultType,
-} from "./dialogs/add-edit-member-dialog.component";
-import { BulkConfirmDialogComponent } from "./dialogs/bulk-confirm-dialog.component";
-import { BulkRemoveDialogComponent } from "./dialogs/bulk-remove-dialog.component";
-
-type ProviderUser = ProviderUserUserDetailsResponse;
-
-class MembersTableDataSource extends PeopleTableDataSource {
- protected statusType = ProviderUserStatusType;
-}
-
-// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
-// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
-@Component({
- templateUrl: "deprecated_members.component.html",
- standalone: false,
-})
-export class MembersComponent extends BaseMembersComponent {
- accessEvents = false;
- dataSource: MembersTableDataSource;
- loading = true;
- providerId: string;
- rowHeight = 70;
- rowHeightClass = `tw-h-[70px]`;
- status: ProviderUserStatusType = null;
-
- userStatusType = ProviderUserStatusType;
- userType = ProviderUserType;
-
- constructor(
- apiService: ApiService,
- keyService: KeyService,
- dialogService: DialogService,
- i18nService: I18nService,
- logService: LogService,
- organizationManagementPreferencesService: OrganizationManagementPreferencesService,
- toastService: ToastService,
- userNamePipe: UserNamePipe,
- validationService: ValidationService,
- private encryptService: EncryptService,
- private activatedRoute: ActivatedRoute,
- private providerService: ProviderService,
- private router: Router,
- private accountService: AccountService,
- private environmentService: EnvironmentService,
- ) {
- super(
- apiService,
- i18nService,
- keyService,
- validationService,
- logService,
- userNamePipe,
- dialogService,
- organizationManagementPreferencesService,
- toastService,
- );
-
- this.dataSource = new MembersTableDataSource(this.environmentService);
-
- combineLatest([
- this.activatedRoute.parent.params,
- this.activatedRoute.queryParams.pipe(first()),
- ])
- .pipe(
- switchMap(async ([urlParams, queryParams]) => {
- this.searchControl.setValue(queryParams.search);
- this.dataSource.filter = peopleFilter(queryParams.search, null);
-
- this.providerId = urlParams.providerId;
- const provider = await firstValueFrom(
- this.accountService.activeAccount$.pipe(
- getUserId,
- switchMap((userId) => this.providerService.get$(this.providerId, userId)),
- ),
- );
-
- if (!provider || !provider.canManageUsers) {
- return await this.router.navigate(["../"], { relativeTo: this.activatedRoute });
- }
- this.accessEvents = provider.useEvents;
- await this.load();
-
- if (queryParams.viewEvents != null) {
- const user = this.dataSource.data.find((user) => user.id === queryParams.viewEvents);
- if (user && user.status === ProviderUserStatusType.Confirmed) {
- this.openEventsDialog(user);
- }
- }
- }),
- takeUntilDestroyed(),
- )
- .subscribe();
- }
-
- async bulkConfirm(): Promise {
- if (this.actionPromise != null) {
- return;
- }
-
- const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount);
-
- const dialogRef = BulkConfirmDialogComponent.open(this.dialogService, {
- data: {
- providerId: this.providerId,
- users: users,
- },
- });
-
- await lastValueFrom(dialogRef.closed);
- await this.load();
- }
-
- async bulkReinvite(): Promise {
- if (this.actionPromise != null) {
- return;
- }
-
- let users: ProviderUser[];
- if (this.dataSource.isIncreasedBulkLimitEnabled()) {
- users = this.dataSource.getCheckedUsersInVisibleOrder();
- } else {
- users = this.dataSource.getCheckedUsers();
- }
-
- const allInvitedUsers = users.filter((user) => user.status === ProviderUserStatusType.Invited);
-
- // Capture the original count BEFORE enforcing the limit
- const originalInvitedCount = allInvitedUsers.length;
-
- // When feature flag is enabled, limit invited users and uncheck the excess
- let checkedInvitedUsers: ProviderUser[];
- if (this.dataSource.isIncreasedBulkLimitEnabled()) {
- checkedInvitedUsers = this.dataSource.limitAndUncheckExcess(
- allInvitedUsers,
- CloudBulkReinviteLimit,
- );
- } else {
- checkedInvitedUsers = allInvitedUsers;
- }
-
- if (checkedInvitedUsers.length <= 0) {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("noSelectedUsersApplicable"),
- });
- return;
- }
-
- try {
- // When feature flag is enabled, show toast instead of dialog
- if (this.dataSource.isIncreasedBulkLimitEnabled()) {
- await this.apiService.postManyProviderUserReinvite(
- this.providerId,
- new ProviderUserBulkRequest(checkedInvitedUsers.map((user) => user.id)),
- );
-
- const selectedCount = originalInvitedCount;
- const invitedCount = checkedInvitedUsers.length;
-
- if (selectedCount > CloudBulkReinviteLimit) {
- const excludedCount = selectedCount - CloudBulkReinviteLimit;
- this.toastService.showToast({
- variant: "success",
- message: this.i18nService.t(
- "bulkReinviteLimitedSuccessToast",
- CloudBulkReinviteLimit.toLocaleString(),
- selectedCount.toLocaleString(),
- excludedCount.toLocaleString(),
- ),
- });
- } else {
- this.toastService.showToast({
- variant: "success",
- message:
- invitedCount === 1
- ? this.i18nService.t("reinviteSuccessToast")
- : this.i18nService.t("bulkReinviteSentToast", invitedCount.toString()),
- });
- }
- } else {
- // Feature flag disabled - show legacy dialog
- const request = this.apiService.postManyProviderUserReinvite(
- this.providerId,
- new ProviderUserBulkRequest(checkedInvitedUsers.map((user) => user.id)),
- );
-
- const dialogRef = BulkStatusComponent.open(this.dialogService, {
- data: {
- users: users,
- filteredUsers: checkedInvitedUsers,
- request,
- successfulMessage: this.i18nService.t("bulkReinviteMessage"),
- },
- });
- await lastValueFrom(dialogRef.closed);
- }
- } catch (error) {
- this.validationService.showError(error);
- }
- }
-
- async invite() {
- await this.edit(null);
- }
-
- async bulkRemove(): Promise {
- if (this.actionPromise != null) {
- return;
- }
-
- const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount);
-
- const dialogRef = BulkRemoveDialogComponent.open(this.dialogService, {
- data: {
- providerId: this.providerId,
- users: users,
- },
- });
-
- await lastValueFrom(dialogRef.closed);
- await this.load();
- }
-
- async confirmUser(user: ProviderUser, publicKey: Uint8Array): Promise {
- try {
- const providerKey = await firstValueFrom(
- this.accountService.activeAccount$.pipe(
- getUserId,
- switchMap((userId) => this.keyService.providerKeys$(userId)),
- map((providerKeys) => providerKeys?.[this.providerId as ProviderId] ?? null),
- ),
- );
- assertNonNullish(providerKey, "Provider key not found");
-
- const key = await this.encryptService.encapsulateKeyUnsigned(providerKey, publicKey);
- const request = new ProviderUserConfirmRequest(key.encryptedString);
- await this.apiService.postProviderUserConfirm(this.providerId, user.id, request);
- return { success: true };
- } catch (error) {
- return { success: false, error: error.message };
- }
- }
-
- removeUser = async (id: string): Promise => {
- try {
- await this.apiService.deleteProviderUser(this.providerId, id);
- return { success: true };
- } catch (error) {
- return { success: false, error: error.message };
- }
- };
-
- edit = async (user: ProviderUser | null): Promise => {
- const data: AddEditMemberDialogParams = {
- providerId: this.providerId,
- user,
- };
-
- const dialogRef = AddEditMemberDialogComponent.open(this.dialogService, {
- data,
- });
-
- const result = await lastValueFrom(dialogRef.closed);
-
- switch (result) {
- case AddEditMemberDialogResultType.Saved:
- case AddEditMemberDialogResultType.Deleted:
- await this.load();
- break;
- }
- };
-
- openEventsDialog = (user: ProviderUser): DialogRef =>
- openEntityEventsDialog(this.dialogService, {
- data: {
- name: this.userNamePipe.transform(user),
- providerId: this.providerId,
- entityId: user.id,
- showUser: false,
- entity: "user",
- },
- });
-
- getUsers = (): Promise> =>
- this.apiService.getProviderUsers(this.providerId);
-
- reinviteUser = async (id: string): Promise => {
- try {
- await this.apiService.postProviderUserReinvite(this.providerId, id);
- return { success: true };
- } catch (error) {
- return { success: false, error: error.message };
- }
- };
-
- get selectedInvitedCount(): number {
- return this.dataSource
- .getCheckedUsers()
- .filter((member) => member.status === this.userStatusType.Invited).length;
- }
-
- get isSingleInvite(): boolean {
- return this.selectedInvitedCount === 1;
- }
-}
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts
index c63bda449c5..d4a6ba92451 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts
@@ -61,7 +61,7 @@ interface BulkProviderFlags {
templateUrl: "members.component.html",
standalone: false,
})
-export class vNextMembersComponent {
+export class MembersComponent {
protected apiService = inject(ApiService);
protected dialogService = inject(DialogService);
protected i18nService = inject(I18nService);
@@ -236,10 +236,12 @@ export class vNextMembersComponent {
}
} else {
// In self-hosted environments, show legacy dialog
- const request = this.apiService.postManyProviderUserReinvite(
- providerId,
- new ProviderUserBulkRequest(checkedInvitedUsers.map((user) => user.id)),
- );
+ const request = this.apiService
+ .postManyProviderUserReinvite(
+ providerId,
+ new ProviderUserBulkRequest(checkedInvitedUsers.map((user) => user.id)),
+ )
+ .then((response) => response.data);
const dialogRef = BulkStatusComponent.open(this.dialogService, {
data: {
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts
index 447481a8bcb..5fadc935644 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts
@@ -2,9 +2,7 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { authGuard } from "@bitwarden/angular/auth/guards";
-import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AnonLayoutWrapperComponent } from "@bitwarden/components";
import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component";
import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component";
@@ -17,9 +15,8 @@ import { ProviderSubscriptionComponent } from "../../billing/providers/subscript
import { ManageClientsComponent } from "./clients/manage-clients.component";
import { providerPermissionsGuard } from "./guards/provider-permissions.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
-import { MembersComponent } from "./manage/deprecated_members.component";
import { EventsComponent } from "./manage/events.component";
-import { vNextMembersComponent } from "./manage/members.component";
+import { MembersComponent } from "./manage/members.component";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersComponent } from "./providers.component";
import { AccountComponent } from "./settings/account.component";
@@ -95,20 +92,16 @@ const routes: Routes = [
pathMatch: "full",
redirectTo: "members",
},
- ...featureFlaggedRoute({
- defaultComponent: MembersComponent,
- flaggedComponent: vNextMembersComponent,
- featureFlag: FeatureFlag.MembersComponentRefactor,
- routeOptions: {
- path: "members",
- canActivate: [
- providerPermissionsGuard((provider: Provider) => provider.canManageUsers),
- ],
- data: {
- titleId: "members",
- },
+ {
+ path: "members",
+ component: MembersComponent,
+ canActivate: [
+ providerPermissionsGuard((provider: Provider) => provider.canManageUsers),
+ ],
+ data: {
+ titleId: "members",
},
- }),
+ },
{
path: "events",
component: EventsComponent,
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
index 44e2e51637f..abdd35c5e61 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
@@ -27,12 +27,11 @@ import { CreateClientDialogComponent } from "./clients/create-client-dialog.comp
import { ManageClientNameDialogComponent } from "./clients/manage-client-name-dialog.component";
import { ManageClientSubscriptionDialogComponent } from "./clients/manage-client-subscription-dialog.component";
import { AcceptProviderComponent } from "./manage/accept-provider.component";
-import { MembersComponent } from "./manage/deprecated_members.component";
import { AddEditMemberDialogComponent } from "./manage/dialogs/add-edit-member-dialog.component";
import { BulkConfirmDialogComponent } from "./manage/dialogs/bulk-confirm-dialog.component";
import { BulkRemoveDialogComponent } from "./manage/dialogs/bulk-remove-dialog.component";
import { EventsComponent } from "./manage/events.component";
-import { vNextMembersComponent } from "./manage/members.component";
+import { MembersComponent } from "./manage/members.component";
import { ProviderActionsService } from "./manage/services/provider-actions/provider-actions.service";
import { ProvidersLayoutComponent } from "./providers-layout.component";
import { ProvidersRoutingModule } from "./providers-routing.module";
@@ -67,7 +66,6 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr
BulkConfirmDialogComponent,
BulkRemoveDialogComponent,
EventsComponent,
- vNextMembersComponent,
MembersComponent,
SetupComponent,
SetupProviderComponent,
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts
index 65a31896341..2307eab04fe 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts
@@ -1,8 +1,10 @@
import { BehaviorSubject, combineLatest, Observable } from "rxjs";
import { map, shareReplay } from "rxjs/operators";
-import { RiskInsightsDataService } from "@bitwarden/bit-common/dirt/reports/risk-insights";
-import { SecurityTasksApiService } from "@bitwarden/bit-common/dirt/reports/risk-insights";
+import {
+ RiskInsightsDataService,
+ SecurityTasksApiService,
+} from "@bitwarden/bit-common/dirt/reports/risk-insights";
import { CipherId, OrganizationId } from "@bitwarden/common/types/guid";
import { SecurityTask, SecurityTaskStatus, SecurityTaskType } from "@bitwarden/common/vault/tasks";
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 0f857e67247..2fbf55bf6c5 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -56,6 +56,7 @@ import {
UserDecryptionOptionsService,
UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common";
+import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
@@ -1079,6 +1080,7 @@ const safeProviders: SafeProvider[] = [
AuthRequestAnsweringService,
ConfigService,
InternalPolicyService,
+ AutomaticUserConfirmationService,
],
}),
safeProvider({
diff --git a/libs/auto-confirm/src/abstractions/auto-confirm.service.abstraction.ts b/libs/auto-confirm/src/abstractions/auto-confirm.service.abstraction.ts
index 9ce6cb9c1a4..1ef3be4ff4e 100644
--- a/libs/auto-confirm/src/abstractions/auto-confirm.service.abstraction.ts
+++ b/libs/auto-confirm/src/abstractions/auto-confirm.service.abstraction.ts
@@ -1,6 +1,6 @@
import { Observable } from "rxjs";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+import { OrganizationId } from "@bitwarden/common/types/guid";
import { UserId } from "@bitwarden/user-core";
import { AutoConfirmState } from "../models/auto-confirm-state.model";
@@ -27,12 +27,12 @@ export abstract class AutomaticUserConfirmationService {
/**
* Calls the API endpoint to initiate automatic user confirmation.
* @param userId The userId of the logged in admin performing auto confirmation. This is neccesary to perform the key exchange and for permissions checks.
- * @param confirmingUserId The userId of the user being confirmed.
- * @param organization the organization the user is being auto confirmed to.
+ * @param confirmedUserId The userId of the member being confirmed.
+ * @param organization the organization the member is being auto confirmed to.
**/
abstract autoConfirmUser(
userId: UserId,
- confirmingUserId: UserId,
- organization: Organization,
+ confirmedUserId: UserId,
+ organization: OrganizationId,
): Promise;
}
diff --git a/libs/auto-confirm/src/components/auto-confirm-extension-dialog.component.ts b/libs/auto-confirm/src/angular/components/auto-confirm-extension-dialog.component.ts
similarity index 100%
rename from libs/auto-confirm/src/components/auto-confirm-extension-dialog.component.ts
rename to libs/auto-confirm/src/angular/components/auto-confirm-extension-dialog.component.ts
diff --git a/libs/auto-confirm/src/components/auto-confirm-warning-dialog.component.html b/libs/auto-confirm/src/angular/components/auto-confirm-warning-dialog.component.html
similarity index 100%
rename from libs/auto-confirm/src/components/auto-confirm-warning-dialog.component.html
rename to libs/auto-confirm/src/angular/components/auto-confirm-warning-dialog.component.html
diff --git a/libs/auto-confirm/src/components/auto-confirm-warning-dialog.component.ts b/libs/auto-confirm/src/angular/components/auto-confirm-warning-dialog.component.ts
similarity index 100%
rename from libs/auto-confirm/src/components/auto-confirm-warning-dialog.component.ts
rename to libs/auto-confirm/src/angular/components/auto-confirm-warning-dialog.component.ts
diff --git a/libs/auto-confirm/src/components/index.ts b/libs/auto-confirm/src/angular/components/index.ts
similarity index 100%
rename from libs/auto-confirm/src/components/index.ts
rename to libs/auto-confirm/src/angular/components/index.ts
diff --git a/libs/auto-confirm/src/guards/automatic-user-confirmation-settings.guard.spec.ts b/libs/auto-confirm/src/angular/guards/automatic-user-confirmation-settings.guard.spec.ts
similarity index 97%
rename from libs/auto-confirm/src/guards/automatic-user-confirmation-settings.guard.spec.ts
rename to libs/auto-confirm/src/angular/guards/automatic-user-confirmation-settings.guard.spec.ts
index aca51edb8dc..0261a1a86dc 100644
--- a/libs/auto-confirm/src/guards/automatic-user-confirmation-settings.guard.spec.ts
+++ b/libs/auto-confirm/src/angular/guards/automatic-user-confirmation-settings.guard.spec.ts
@@ -3,14 +3,13 @@ import { Router, UrlTree } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
+import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { newGuid } from "@bitwarden/guid";
-import { AutomaticUserConfirmationService } from "../abstractions";
-
import { canAccessAutoConfirmSettings } from "./automatic-user-confirmation-settings.guard";
describe("canAccessAutoConfirmSettings", () => {
diff --git a/libs/auto-confirm/src/guards/automatic-user-confirmation-settings.guard.ts b/libs/auto-confirm/src/angular/guards/automatic-user-confirmation-settings.guard.ts
similarity index 94%
rename from libs/auto-confirm/src/guards/automatic-user-confirmation-settings.guard.ts
rename to libs/auto-confirm/src/angular/guards/automatic-user-confirmation-settings.guard.ts
index 77f01ba2801..3ae6b5b4c52 100644
--- a/libs/auto-confirm/src/guards/automatic-user-confirmation-settings.guard.ts
+++ b/libs/auto-confirm/src/angular/guards/automatic-user-confirmation-settings.guard.ts
@@ -2,13 +2,12 @@ import { inject } from "@angular/core";
import { CanActivateFn, Router } from "@angular/router";
import { map, switchMap } from "rxjs";
+import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities";
import { ToastService } from "@bitwarden/components";
-import { AutomaticUserConfirmationService } from "../abstractions";
-
export const canAccessAutoConfirmSettings: CanActivateFn = () => {
const accountService = inject(AccountService);
const autoConfirmService = inject(AutomaticUserConfirmationService);
diff --git a/libs/auto-confirm/src/guards/index.ts b/libs/auto-confirm/src/angular/guards/index.ts
similarity index 100%
rename from libs/auto-confirm/src/guards/index.ts
rename to libs/auto-confirm/src/angular/guards/index.ts
diff --git a/libs/auto-confirm/src/angular/index.ts b/libs/auto-confirm/src/angular/index.ts
new file mode 100644
index 00000000000..ff2d69248b4
--- /dev/null
+++ b/libs/auto-confirm/src/angular/index.ts
@@ -0,0 +1,8 @@
+// Re-export core auto-confirm functionality for convenience
+export * from "../abstractions";
+export * from "../models";
+export * from "../services";
+
+// Angular-specific exports
+export * from "./components";
+export * from "./guards";
diff --git a/libs/auto-confirm/src/index.ts b/libs/auto-confirm/src/index.ts
index 56b9d0b0285..9187ccd39cf 100644
--- a/libs/auto-confirm/src/index.ts
+++ b/libs/auto-confirm/src/index.ts
@@ -1,5 +1,3 @@
export * from "./abstractions";
-export * from "./components";
-export * from "./guards";
export * from "./models";
export * from "./services";
diff --git a/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts b/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts
index 1d37378b96c..0ea3ca9c23a 100644
--- a/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts
+++ b/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts
@@ -377,48 +377,70 @@ describe("DefaultAutomaticUserConfirmationService", () => {
defaultUserCollectionName: "encrypted-collection",
} as OrganizationUserConfirmRequest;
- beforeEach(() => {
+ beforeEach(async () => {
const organizations$ = new BehaviorSubject([mockOrganization]);
organizationService.organizations$.mockReturnValue(organizations$);
configService.getFeatureFlag$.mockReturnValue(of(true));
policyService.policyAppliesToUser$.mockReturnValue(of(true));
+ // Enable auto-confirm configuration for the user
+ const enabledConfig = new AutoConfirmState();
+ enabledConfig.enabled = true;
+ await stateProvider.setUserState(
+ AUTO_CONFIRM_STATE,
+ { [mockUserId]: enabledConfig },
+ mockUserId,
+ );
+
apiService.getUserPublicKey.mockResolvedValue({
publicKey: mockPublicKey,
} as UserKeyResponse);
jest.spyOn(Utils, "fromB64ToArray").mockReturnValue(mockPublicKeyArray);
organizationUserService.buildConfirmRequest.mockReturnValue(of(mockConfirmRequest));
- organizationUserApiService.postOrganizationUserConfirm.mockResolvedValue(undefined);
+ organizationUserApiService.postOrganizationUserAutoConfirm.mockResolvedValue(undefined);
});
- it("should successfully auto-confirm a user", async () => {
- await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization);
+ it("should successfully auto-confirm a user with organizationId", async () => {
+ await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId);
expect(apiService.getUserPublicKey).toHaveBeenCalledWith(mockUserId);
expect(organizationUserService.buildConfirmRequest).toHaveBeenCalledWith(
mockOrganization,
mockPublicKeyArray,
);
- expect(organizationUserApiService.postOrganizationUserConfirm).toHaveBeenCalledWith(
+ expect(organizationUserApiService.postOrganizationUserAutoConfirm).toHaveBeenCalledWith(
mockOrganizationId,
mockConfirmingUserId,
mockConfirmRequest,
);
});
- it("should not confirm user when canManageAutoConfirm returns false", async () => {
+ it("should return early when canManageAutoConfirm returns false", async () => {
configService.getFeatureFlag$.mockReturnValue(of(false));
- await expect(
- service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization),
- ).rejects.toThrow("Cannot automatically confirm user (insufficient permissions)");
+ await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId);
expect(apiService.getUserPublicKey).not.toHaveBeenCalled();
- expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
+ expect(organizationUserApiService.postOrganizationUserAutoConfirm).not.toHaveBeenCalled();
+ });
+
+ it("should return early when auto-confirm is disabled in configuration", async () => {
+ const disabledConfig = new AutoConfirmState();
+ disabledConfig.enabled = false;
+ await stateProvider.setUserState(
+ AUTO_CONFIRM_STATE,
+ { [mockUserId]: disabledConfig },
+ mockUserId,
+ );
+
+ await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId);
+
+ expect(apiService.getUserPublicKey).not.toHaveBeenCalled();
+ expect(organizationUserApiService.postOrganizationUserAutoConfirm).not.toHaveBeenCalled();
});
it("should build confirm request with organization and public key", async () => {
- await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization);
+ await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId);
expect(organizationUserService.buildConfirmRequest).toHaveBeenCalledWith(
mockOrganization,
@@ -427,10 +449,10 @@ describe("DefaultAutomaticUserConfirmationService", () => {
});
it("should call API with correct parameters", async () => {
- await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization);
+ await service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId);
- expect(organizationUserApiService.postOrganizationUserConfirm).toHaveBeenCalledWith(
- mockOrganization.id,
+ expect(organizationUserApiService.postOrganizationUserAutoConfirm).toHaveBeenCalledWith(
+ mockOrganizationId,
mockConfirmingUserId,
mockConfirmRequest,
);
@@ -441,10 +463,10 @@ describe("DefaultAutomaticUserConfirmationService", () => {
apiService.getUserPublicKey.mockRejectedValue(apiError);
await expect(
- service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization),
+ service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId),
).rejects.toThrow("API Error");
- expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
+ expect(organizationUserApiService.postOrganizationUserAutoConfirm).not.toHaveBeenCalled();
});
it("should handle buildConfirmRequest errors gracefully", async () => {
@@ -452,10 +474,10 @@ describe("DefaultAutomaticUserConfirmationService", () => {
organizationUserService.buildConfirmRequest.mockReturnValue(throwError(() => buildError));
await expect(
- service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganization),
+ service.autoConfirmUser(mockUserId, mockConfirmingUserId, mockOrganizationId),
).rejects.toThrow("Build Error");
- expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
+ expect(organizationUserApiService.postOrganizationUserAutoConfirm).not.toHaveBeenCalled();
});
});
});
diff --git a/libs/auto-confirm/src/services/default-auto-confirm.service.ts b/libs/auto-confirm/src/services/default-auto-confirm.service.ts
index 109ccb6c9db..821340a0a9c 100644
--- a/libs/auto-confirm/src/services/default-auto-confirm.service.ts
+++ b/libs/auto-confirm/src/services/default-auto-confirm.service.ts
@@ -8,10 +8,11 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
+import { getById } from "@bitwarden/common/platform/misc";
import { Utils } from "@bitwarden/common/platform/misc/utils";
+import { OrganizationId } from "@bitwarden/common/types/guid";
import { StateProvider } from "@bitwarden/state";
import { UserId } from "@bitwarden/user-core";
@@ -66,26 +67,44 @@ export class DefaultAutomaticUserConfirmationService implements AutomaticUserCon
async autoConfirmUser(
userId: UserId,
- confirmingUserId: UserId,
- organization: Organization,
+ confirmedUserId: UserId,
+ organizationId: OrganizationId,
): Promise {
+ const canManage = await firstValueFrom(this.canManageAutoConfirm$(userId));
+
+ if (!canManage) {
+ return;
+ }
+
+ // Only initiate auto confirmation if the local client setting has been turned on
+ const autoConfirmEnabled = await firstValueFrom(
+ this.configuration$(userId).pipe(map((state) => state.enabled)),
+ );
+
+ if (!autoConfirmEnabled) {
+ return;
+ }
+
+ const organization$ = this.organizationService.organizations$(userId).pipe(
+ getById(organizationId),
+ map((organization) => {
+ if (organization == null) {
+ throw new Error("Organization not found");
+ }
+ return organization;
+ }),
+ );
+
+ const publicKeyResponse = await this.apiService.getUserPublicKey(userId);
+ const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
+
await firstValueFrom(
- this.canManageAutoConfirm$(userId).pipe(
- map((canManage) => {
- if (!canManage) {
- throw new Error("Cannot automatically confirm user (insufficient permissions)");
- }
- return canManage;
- }),
- switchMap(() => this.apiService.getUserPublicKey(userId)),
- map((publicKeyResponse) => Utils.fromB64ToArray(publicKeyResponse.publicKey)),
- switchMap((publicKey) =>
- this.organizationUserService.buildConfirmRequest(organization, publicKey),
- ),
+ organization$.pipe(
+ switchMap((org) => this.organizationUserService.buildConfirmRequest(org, publicKey)),
switchMap((request) =>
- this.organizationUserApiService.postOrganizationUserConfirm(
- organization.id,
- confirmingUserId,
+ this.organizationUserApiService.postOrganizationUserAutoConfirm(
+ organizationId,
+ confirmedUserId,
request,
),
),
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index 5298ed34eda..96196fbc8e1 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -13,7 +13,6 @@ export enum FeatureFlag {
/* Admin Console Team */
AutoConfirm = "pm-19934-auto-confirm-organization-users",
DefaultUserCollectionRestore = "pm-30883-my-items-restored-users",
- MembersComponentRefactor = "pm-29503-refactor-members-inheritance",
BulkReinviteUI = "pm-28416-bulk-reinvite-ux-improvements",
/* Auth */
@@ -110,7 +109,6 @@ export const DefaultFeatureFlagValue = {
/* Admin Console Team */
[FeatureFlag.AutoConfirm]: FALSE,
[FeatureFlag.DefaultUserCollectionRestore]: FALSE,
- [FeatureFlag.MembersComponentRefactor]: FALSE,
[FeatureFlag.BulkReinviteUI]: FALSE,
/* Autofill */
diff --git a/libs/common/src/enums/notification-type.enum.ts b/libs/common/src/enums/notification-type.enum.ts
index a10e6bf4448..d323dda4d74 100644
--- a/libs/common/src/enums/notification-type.enum.ts
+++ b/libs/common/src/enums/notification-type.enum.ts
@@ -35,4 +35,5 @@ export enum NotificationType {
ProviderBankAccountVerified = 24,
SyncPolicy = 25,
+ AutoConfirmMember = 26,
}
diff --git a/libs/common/src/models/response/notification.response.spec.ts b/libs/common/src/models/response/notification.response.spec.ts
new file mode 100644
index 00000000000..91a1390bfdb
--- /dev/null
+++ b/libs/common/src/models/response/notification.response.spec.ts
@@ -0,0 +1,63 @@
+import { NotificationType } from "../../enums";
+
+import { AutoConfirmMemberNotification, NotificationResponse } from "./notification.response";
+
+describe("NotificationResponse", () => {
+ describe("AutoConfirmMemberNotification", () => {
+ it("should parse AutoConfirmMemberNotification payload", () => {
+ const response = {
+ ContextId: "context-123",
+ Type: NotificationType.AutoConfirmMember,
+ Payload: {
+ TargetUserId: "target-user-id",
+ UserId: "user-id",
+ OrganizationId: "org-id",
+ },
+ };
+
+ const notification = new NotificationResponse(response);
+
+ expect(notification.type).toBe(NotificationType.AutoConfirmMember);
+ expect(notification.payload).toBeInstanceOf(AutoConfirmMemberNotification);
+ expect(notification.payload.targetUserId).toBe("target-user-id");
+ expect(notification.payload.userId).toBe("user-id");
+ expect(notification.payload.organizationId).toBe("org-id");
+ });
+
+ it("should handle stringified JSON payload", () => {
+ const response = {
+ ContextId: "context-123",
+ Type: NotificationType.AutoConfirmMember,
+ Payload: JSON.stringify({
+ TargetUserId: "target-user-id-2",
+ UserId: "user-id-2",
+ OrganizationId: "org-id-2",
+ }),
+ };
+
+ const notification = new NotificationResponse(response);
+
+ expect(notification.type).toBe(NotificationType.AutoConfirmMember);
+ expect(notification.payload).toBeInstanceOf(AutoConfirmMemberNotification);
+ expect(notification.payload.targetUserId).toBe("target-user-id-2");
+ expect(notification.payload.userId).toBe("user-id-2");
+ expect(notification.payload.organizationId).toBe("org-id-2");
+ });
+ });
+
+ describe("AutoConfirmMemberNotification constructor", () => {
+ it("should extract all properties from response", () => {
+ const response = {
+ TargetUserId: "target-user-id",
+ UserId: "user-id",
+ OrganizationId: "org-id",
+ };
+
+ const notification = new AutoConfirmMemberNotification(response);
+
+ expect(notification.targetUserId).toBe("target-user-id");
+ expect(notification.userId).toBe("user-id");
+ expect(notification.organizationId).toBe("org-id");
+ });
+ });
+});
diff --git a/libs/common/src/models/response/notification.response.ts b/libs/common/src/models/response/notification.response.ts
index 2c0c0aae3f1..27232696d2e 100644
--- a/libs/common/src/models/response/notification.response.ts
+++ b/libs/common/src/models/response/notification.response.ts
@@ -75,6 +75,9 @@ export class NotificationResponse extends BaseResponse {
case NotificationType.SyncPolicy:
this.payload = new SyncPolicyNotification(payload);
break;
+ case NotificationType.AutoConfirmMember:
+ this.payload = new AutoConfirmMemberNotification(payload);
+ break;
default:
break;
}
@@ -210,3 +213,16 @@ export class LogOutNotification extends BaseResponse {
this.reason = this.getResponseProperty("Reason");
}
}
+
+export class AutoConfirmMemberNotification extends BaseResponse {
+ userId: string;
+ targetUserId: string;
+ organizationId: string;
+
+ constructor(response: any) {
+ super(response);
+ this.targetUserId = this.getResponseProperty("TargetUserId");
+ this.userId = this.getResponseProperty("UserId");
+ this.organizationId = this.getResponseProperty("OrganizationId");
+ }
+}
diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts
index 2795e4c3003..70b93c77f1c 100644
--- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts
+++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts
@@ -3,6 +3,7 @@ import { BehaviorSubject, bufferCount, firstValueFrom, Subject, ObservedValueOf
// eslint-disable-next-line no-restricted-imports
import { LogoutReason } from "@bitwarden/auth/common";
+import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
@@ -36,6 +37,7 @@ describe("DefaultServerNotificationsService (multi-user)", () => {
let authRequestAnsweringService: MockProxy;
let configService: MockProxy;
let policyService: MockProxy;
+ let autoConfirmService: MockProxy;
let activeUserAccount$: BehaviorSubject>;
let userAccounts$: BehaviorSubject>;
@@ -131,6 +133,8 @@ describe("DefaultServerNotificationsService (multi-user)", () => {
policyService = mock();
+ autoConfirmService = mock();
+
defaultServerNotificationsService = new DefaultServerNotificationsService(
mock(),
syncService,
@@ -145,6 +149,7 @@ describe("DefaultServerNotificationsService (multi-user)", () => {
authRequestAnsweringService,
configService,
policyService,
+ autoConfirmService,
);
});
diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts
index f058e8794ac..a54509925ef 100644
--- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts
+++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts
@@ -4,6 +4,7 @@ import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, of, Subj
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { LogoutReason } from "@bitwarden/auth/common";
+import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
@@ -45,6 +46,7 @@ describe("NotificationsService", () => {
let authRequestAnsweringService: MockProxy;
let configService: MockProxy;
let policyService: MockProxy;
+ let autoConfirmService: MockProxy;
let activeAccount: BehaviorSubject>;
let accounts: BehaviorSubject>;
@@ -75,6 +77,7 @@ describe("NotificationsService", () => {
authRequestAnsweringService = mock();
configService = mock();
policyService = mock();
+ autoConfirmService = mock();
// For these tests, use the active-user implementation (feature flag disabled)
configService.getFeatureFlag$.mockImplementation(() => of(true));
@@ -128,6 +131,7 @@ describe("NotificationsService", () => {
authRequestAnsweringService,
configService,
policyService,
+ autoConfirmService,
);
});
@@ -507,5 +511,29 @@ describe("NotificationsService", () => {
});
});
});
+
+ describe("NotificationType.AutoConfirmMember", () => {
+ it("should call autoConfirmService.autoConfirmUser with correct parameters", async () => {
+ autoConfirmService.autoConfirmUser.mockResolvedValue();
+
+ const notification = new NotificationResponse({
+ type: NotificationType.AutoConfirmMember,
+ payload: {
+ UserId: mockUser1,
+ TargetUserId: "target-user-id",
+ OrganizationId: "org-id",
+ },
+ contextId: "different-app-id",
+ });
+
+ await sut["processNotification"](notification, mockUser1);
+
+ expect(autoConfirmService.autoConfirmUser).toHaveBeenCalledWith(
+ mockUser1,
+ "target-user-id",
+ "org-id",
+ );
+ });
+ });
});
});
diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts
index 83ea12bf154..1a43c0edb09 100644
--- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts
+++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts
@@ -15,6 +15,7 @@ import {
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { LogoutReason } from "@bitwarden/auth/common";
+import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
@@ -49,6 +50,7 @@ export const DISABLED_NOTIFICATIONS_URL = "http://-";
export const AllowedMultiUserNotificationTypes = new Set([
NotificationType.AuthRequest,
+ NotificationType.AutoConfirmMember,
]);
export class DefaultServerNotificationsService implements ServerNotificationsService {
@@ -70,6 +72,7 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
private readonly authRequestAnsweringService: AuthRequestAnsweringService,
private readonly configService: ConfigService,
private readonly policyService: InternalPolicyService,
+ private autoConfirmService: AutomaticUserConfirmationService,
) {
this.notifications$ = this.accountService.accounts$.pipe(
map((accounts: Record): Set => {
@@ -292,6 +295,13 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
case NotificationType.SyncPolicy:
await this.policyService.syncPolicy(PolicyData.fromPolicy(notification.payload.policy));
break;
+ case NotificationType.AutoConfirmMember:
+ await this.autoConfirmService.autoConfirmUser(
+ notification.payload.userId,
+ notification.payload.targetUserId,
+ notification.payload.organizationId,
+ );
+ break;
default:
break;
}
diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts
index 68c03503e8d..a25b1b3c210 100644
--- a/libs/common/src/platform/sync/default-sync.service.ts
+++ b/libs/common/src/platform/sync/default-sync.service.ts
@@ -183,6 +183,8 @@ export class DefaultSyncService extends CoreSyncService {
const response = await this.inFlightApiCalls.sync;
+ await this.cipherService.clear(response.profile.id);
+
await this.syncUserDecryption(response.profile.id, response.userDecryption);
await this.syncProfile(response.profile);
await this.syncFolders(response.folders, response.profile.id);
diff --git a/libs/common/src/tools/send/services/send-api.service.ts b/libs/common/src/tools/send/services/send-api.service.ts
index 57004b6ff0e..fdbbd579c3f 100644
--- a/libs/common/src/tools/send/services/send-api.service.ts
+++ b/libs/common/src/tools/send/services/send-api.service.ts
@@ -105,7 +105,7 @@ export class SendApiService implements SendApiServiceAbstraction {
"POST",
"/sends/access/file/" + send.file.id,
null,
- true,
+ false,
true,
apiUrl,
setAuthTokenHeader,
diff --git a/libs/common/src/vault/abstractions/search.service.ts b/libs/common/src/vault/abstractions/search.service.ts
index 29575ec3af9..b4dfc015efe 100644
--- a/libs/common/src/vault/abstractions/search.service.ts
+++ b/libs/common/src/vault/abstractions/search.service.ts
@@ -2,7 +2,6 @@ import { Observable } from "rxjs";
import { SendView } from "../../tools/send/models/view/send.view";
import { IndexedEntityId, UserId } from "../../types/guid";
-import { CipherView } from "../models/view/cipher.view";
import { CipherViewLike } from "../utils/cipher-view-like-utils";
export abstract class SearchService {
@@ -20,7 +19,7 @@ export abstract class SearchService {
abstract isSearchable(userId: UserId, query: string | null): Promise;
abstract indexCiphers(
userId: UserId,
- ciphersToIndex: CipherView[],
+ ciphersToIndex: CipherViewLike[],
indexedEntityGuid?: string,
): Promise;
abstract searchCiphers(
diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts
index 06c6628f158..e4c4f892b4a 100644
--- a/libs/common/src/vault/services/cipher.service.ts
+++ b/libs/common/src/vault/services/cipher.service.ts
@@ -173,13 +173,14 @@ export class CipherService implements CipherServiceAbstraction {
decryptStartTime = performance.now();
}),
switchMap(async (ciphers) => {
- const [decrypted, failures] = await this.decryptCiphersWithSdk(ciphers, userId, false);
- void this.setFailedDecryptedCiphers(failures, userId);
- // Trigger full decryption and indexing in background
- void this.getAllDecrypted(userId);
- return decrypted;
+ return await this.decryptCiphersWithSdk(ciphers, userId, false);
}),
- tap((decrypted) => {
+ tap(([decrypted, failures]) => {
+ void Promise.all([
+ this.setFailedDecryptedCiphers(failures, userId),
+ this.searchService.indexCiphers(userId, decrypted),
+ ]);
+
this.logService.measure(
decryptStartTime,
"Vault",
@@ -188,10 +189,11 @@ export class CipherService implements CipherServiceAbstraction {
[["Items", decrypted.length]],
);
}),
+ map(([decrypted]) => decrypted),
);
}),
);
- });
+ }, this.clearCipherViewsForUser$);
/**
* Observable that emits an array of decrypted ciphers for the active user.
@@ -530,6 +532,10 @@ export class CipherService implements CipherServiceAbstraction {
ciphers: Cipher[],
userId: UserId,
): Promise<[CipherView[], CipherView[]] | null> {
+ if (ciphers.length === 0) {
+ return [[], []];
+ }
+
if (await this.configService.getFeatureFlag(FeatureFlag.PM19941MigrateCipherDomainToSdk)) {
const decryptStartTime = performance.now();
@@ -2401,6 +2407,12 @@ export class CipherService implements CipherServiceAbstraction {
userId: UserId,
fullDecryption: boolean = true,
): Promise<[CipherViewLike[], CipherView[]]> {
+ // Short-circuit if there are no ciphers to decrypt
+ // Observables reacting to key changes may attempt to decrypt with a stale SDK reference.
+ if (ciphers.length === 0) {
+ return [[], []];
+ }
+
if (fullDecryption) {
const [decryptedViews, failedViews] = await this.cipherEncryptionService.decryptManyLegacy(
ciphers,
diff --git a/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts b/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts
index a0ca4833b92..98b554b5762 100644
--- a/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts
+++ b/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts
@@ -95,6 +95,7 @@ describe("DefaultCipherEncryptionService", () => {
vault: jest.fn().mockReturnValue({
ciphers: jest.fn().mockReturnValue({
encrypt: jest.fn(),
+ encrypt_list: jest.fn(),
encrypt_cipher_for_rotation: jest.fn(),
set_fido2_credentials: jest.fn(),
decrypt: jest.fn(),
@@ -280,10 +281,23 @@ describe("DefaultCipherEncryptionService", () => {
name: "encrypted-name-3",
} as unknown as Cipher;
- mockSdkClient.vault().ciphers().encrypt.mockReturnValue({
- cipher: sdkCipher,
- encryptedFor: userId,
- });
+ mockSdkClient
+ .vault()
+ .ciphers()
+ .encrypt_list.mockReturnValue([
+ {
+ cipher: sdkCipher,
+ encryptedFor: userId,
+ },
+ {
+ cipher: sdkCipher,
+ encryptedFor: userId,
+ },
+ {
+ cipher: sdkCipher,
+ encryptedFor: userId,
+ },
+ ]);
jest
.spyOn(Cipher, "fromSdkCipher")
@@ -299,7 +313,8 @@ describe("DefaultCipherEncryptionService", () => {
expect(results[1].cipher).toEqual(expectedCipher2);
expect(results[2].cipher).toEqual(expectedCipher3);
- expect(mockSdkClient.vault().ciphers().encrypt).toHaveBeenCalledTimes(3);
+ expect(mockSdkClient.vault().ciphers().encrypt_list).toHaveBeenCalledTimes(1);
+ expect(mockSdkClient.vault().ciphers().encrypt).not.toHaveBeenCalled();
expect(results[0].encryptedFor).toBe(userId);
expect(results[1].encryptedFor).toBe(userId);
@@ -311,7 +326,7 @@ describe("DefaultCipherEncryptionService", () => {
expect(results).toBeDefined();
expect(results.length).toBe(0);
- expect(mockSdkClient.vault().ciphers().encrypt).not.toHaveBeenCalled();
+ expect(mockSdkClient.vault().ciphers().encrypt_list).not.toHaveBeenCalled();
});
});
diff --git a/libs/common/src/vault/services/default-cipher-encryption.service.ts b/libs/common/src/vault/services/default-cipher-encryption.service.ts
index 588265846e0..45542091618 100644
--- a/libs/common/src/vault/services/default-cipher-encryption.service.ts
+++ b/libs/common/src/vault/services/default-cipher-encryption.service.ts
@@ -65,21 +65,14 @@ export class DefaultCipherEncryptionService implements CipherEncryptionService {
using ref = sdk.take();
- const results: EncryptionContext[] = [];
-
- // TODO: https://bitwarden.atlassian.net/browse/PM-30580
- // Replace this loop with a native SDK encryptMany method for better performance.
- for (const model of models) {
- const sdkCipherView = this.toSdkCipherView(model, ref.value);
- const encryptionContext = ref.value.vault().ciphers().encrypt(sdkCipherView);
-
- results.push({
+ return ref.value
+ .vault()
+ .ciphers()
+ .encrypt_list(models.map((model) => this.toSdkCipherView(model, ref.value)))
+ .map((encryptionContext) => ({
cipher: Cipher.fromSdkCipher(encryptionContext.cipher)!,
encryptedFor: uuidAsString(encryptionContext.encryptedFor) as UserId,
- });
- }
-
- return results;
+ }));
}),
catchError((error: unknown) => {
this.logService.error(`Failed to encrypt ciphers in batch: ${error}`);
diff --git a/libs/common/src/vault/services/search.service.ts b/libs/common/src/vault/services/search.service.ts
index feb6a7494b5..e14a66aad6f 100644
--- a/libs/common/src/vault/services/search.service.ts
+++ b/libs/common/src/vault/services/search.service.ts
@@ -21,7 +21,6 @@ import { IndexedEntityId, UserId } from "../../types/guid";
import { SearchService as SearchServiceAbstraction } from "../abstractions/search.service";
import { FieldType } from "../enums";
import { CipherType } from "../enums/cipher-type";
-import { CipherView } from "../models/view/cipher.view";
import { CipherViewLike, CipherViewLikeUtils } from "../utils/cipher-view-like-utils";
// Time to wait before performing a search after the user stops typing.
@@ -169,7 +168,7 @@ export class SearchService implements SearchServiceAbstraction {
async indexCiphers(
userId: UserId,
- ciphers: CipherView[],
+ ciphers: CipherViewLike[],
indexedEntityId?: string,
): Promise {
if (await this.getIsIndexing(userId)) {
@@ -182,34 +181,47 @@ export class SearchService implements SearchServiceAbstraction {
const builder = new lunr.Builder();
builder.pipeline.add(this.normalizeAccentsPipelineFunction);
builder.ref("id");
- builder.field("shortid", { boost: 100, extractor: (c: CipherView) => c.id.substr(0, 8) });
+ builder.field("shortid", {
+ boost: 100,
+ extractor: (c: CipherViewLike) => uuidAsString(c.id).substr(0, 8),
+ });
builder.field("name", {
boost: 10,
});
builder.field("subtitle", {
boost: 5,
- extractor: (c: CipherView) => {
- if (c.subTitle != null && c.type === CipherType.Card) {
- return c.subTitle.replace(/\*/g, "");
+ extractor: (c: CipherViewLike) => {
+ const subtitle = CipherViewLikeUtils.subtitle(c);
+ if (subtitle != null && CipherViewLikeUtils.getType(c) === CipherType.Card) {
+ return subtitle.replace(/\*/g, "");
}
- return c.subTitle;
+ return subtitle;
},
});
- builder.field("notes");
+ builder.field("notes", { extractor: (c: CipherViewLike) => CipherViewLikeUtils.getNotes(c) });
builder.field("login.username", {
- extractor: (c: CipherView) =>
- c.type === CipherType.Login && c.login != null ? c.login.username : null,
+ extractor: (c: CipherViewLike) => {
+ const login = CipherViewLikeUtils.getLogin(c);
+ return login?.username ?? null;
+ },
+ });
+ builder.field("login.uris", {
+ boost: 2,
+ extractor: (c: CipherViewLike) => this.uriExtractor(c),
+ });
+ builder.field("fields", {
+ extractor: (c: CipherViewLike) => this.fieldExtractor(c, false),
+ });
+ builder.field("fields_joined", {
+ extractor: (c: CipherViewLike) => this.fieldExtractor(c, true),
});
- builder.field("login.uris", { boost: 2, extractor: (c: CipherView) => this.uriExtractor(c) });
- builder.field("fields", { extractor: (c: CipherView) => this.fieldExtractor(c, false) });
- builder.field("fields_joined", { extractor: (c: CipherView) => this.fieldExtractor(c, true) });
builder.field("attachments", {
- extractor: (c: CipherView) => this.attachmentExtractor(c, false),
+ extractor: (c: CipherViewLike) => this.attachmentExtractor(c, false),
});
builder.field("attachments_joined", {
- extractor: (c: CipherView) => this.attachmentExtractor(c, true),
+ extractor: (c: CipherViewLike) => this.attachmentExtractor(c, true),
});
- builder.field("organizationid", { extractor: (c: CipherView) => c.organizationId });
+ builder.field("organizationid", { extractor: (c: CipherViewLike) => c.organizationId });
ciphers = ciphers || [];
ciphers.forEach((c) => builder.add(c));
const index = builder.build();
@@ -400,37 +412,44 @@ export class SearchService implements SearchServiceAbstraction {
return await firstValueFrom(this.searchIsIndexing$(userId));
}
- private fieldExtractor(c: CipherView, joined: boolean) {
- if (!c.hasFields) {
+ private fieldExtractor(c: CipherViewLike, joined: boolean) {
+ const fields = CipherViewLikeUtils.getFields(c);
+ if (!fields || fields.length === 0) {
return null;
}
- let fields: string[] = [];
- c.fields.forEach((f) => {
+ let fieldStrings: string[] = [];
+ fields.forEach((f) => {
if (f.name != null) {
- fields.push(f.name);
+ fieldStrings.push(f.name);
}
- if (f.type === FieldType.Text && f.value != null) {
- fields.push(f.value);
+ // For CipherListView, value is only populated for Text fields
+ // For CipherView, we check the type explicitly
+ if (f.value != null) {
+ const fieldType = (f as { type?: FieldType }).type;
+ if (fieldType === undefined || fieldType === FieldType.Text) {
+ fieldStrings.push(f.value);
+ }
}
});
- fields = fields.filter((f) => f.trim() !== "");
- if (fields.length === 0) {
+ fieldStrings = fieldStrings.filter((f) => f.trim() !== "");
+ if (fieldStrings.length === 0) {
return null;
}
- return joined ? fields.join(" ") : fields;
+ return joined ? fieldStrings.join(" ") : fieldStrings;
}
- private attachmentExtractor(c: CipherView, joined: boolean) {
- if (!c.hasAttachments) {
+ private attachmentExtractor(c: CipherViewLike, joined: boolean) {
+ const attachmentNames = CipherViewLikeUtils.getAttachmentNames(c);
+ if (!attachmentNames || attachmentNames.length === 0) {
return null;
}
let attachments: string[] = [];
- c.attachments.forEach((a) => {
- if (a != null && a.fileName != null) {
- if (joined && a.fileName.indexOf(".") > -1) {
- attachments.push(a.fileName.substr(0, a.fileName.lastIndexOf(".")));
+ attachmentNames.forEach((fileName) => {
+ if (fileName != null) {
+ if (joined && fileName.indexOf(".") > -1) {
+ attachments.push(fileName.substring(0, fileName.lastIndexOf(".")));
} else {
- attachments.push(a.fileName);
+ attachments.push(fileName);
}
}
});
@@ -441,43 +460,39 @@ export class SearchService implements SearchServiceAbstraction {
return joined ? attachments.join(" ") : attachments;
}
- private uriExtractor(c: CipherView) {
- if (c.type !== CipherType.Login || c.login == null || !c.login.hasUris) {
+ private uriExtractor(c: CipherViewLike) {
+ if (CipherViewLikeUtils.getType(c) !== CipherType.Login) {
+ return null;
+ }
+ const login = CipherViewLikeUtils.getLogin(c);
+ if (!login?.uris?.length) {
return null;
}
const uris: string[] = [];
- c.login.uris.forEach((u) => {
+ login.uris.forEach((u) => {
if (u.uri == null || u.uri === "") {
return;
}
- // Match ports
+ // Extract port from URI
const portMatch = u.uri.match(/:(\d+)(?:[/?#]|$)/);
const port = portMatch?.[1];
- let uri = u.uri;
-
- if (u.hostname !== null) {
- uris.push(u.hostname);
+ const hostname = CipherViewLikeUtils.getUriHostname(u);
+ if (hostname !== undefined) {
+ uris.push(hostname);
if (port) {
- uris.push(`${u.hostname}:${port}`);
- uris.push(port);
- }
- return;
- } else {
- const slash = uri.indexOf("/");
- const hostPart = slash > -1 ? uri.substring(0, slash) : uri;
- uris.push(hostPart);
- if (port) {
- uris.push(`${hostPart}`);
+ uris.push(`${hostname}:${port}`);
uris.push(port);
}
}
+ // Add processed URI (strip protocol and query params for non-regex matches)
+ let uri = u.uri;
if (u.match !== UriMatchStrategy.RegularExpression) {
const protocolIndex = uri.indexOf("://");
if (protocolIndex > -1) {
- uri = uri.substr(protocolIndex + 3);
+ uri = uri.substring(protocolIndex + 3);
}
const queryIndex = uri.search(/\?|&|#/);
if (queryIndex > -1) {
@@ -486,6 +501,7 @@ export class SearchService implements SearchServiceAbstraction {
}
uris.push(uri);
});
+
return uris.length > 0 ? uris : null;
}
diff --git a/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts b/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts
index 56b94fcf3ce..2a7bfac2970 100644
--- a/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts
+++ b/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts
@@ -651,4 +651,198 @@ describe("CipherViewLikeUtils", () => {
expect(CipherViewLikeUtils.decryptionFailure(cipherListView)).toBe(false);
});
});
+
+ describe("getNotes", () => {
+ describe("CipherView", () => {
+ it("returns notes when present", () => {
+ const cipherView = createCipherView();
+ cipherView.notes = "This is a test note";
+
+ expect(CipherViewLikeUtils.getNotes(cipherView)).toBe("This is a test note");
+ });
+
+ it("returns undefined when notes are not present", () => {
+ const cipherView = createCipherView();
+ cipherView.notes = undefined;
+
+ expect(CipherViewLikeUtils.getNotes(cipherView)).toBeUndefined();
+ });
+ });
+
+ describe("CipherListView", () => {
+ it("returns notes when present", () => {
+ const cipherListView = {
+ type: "secureNote",
+ notes: "List view notes",
+ } as CipherListView;
+
+ expect(CipherViewLikeUtils.getNotes(cipherListView)).toBe("List view notes");
+ });
+
+ it("returns undefined when notes are not present", () => {
+ const cipherListView = {
+ type: "secureNote",
+ } as CipherListView;
+
+ expect(CipherViewLikeUtils.getNotes(cipherListView)).toBeUndefined();
+ });
+ });
+ });
+
+ describe("getFields", () => {
+ describe("CipherView", () => {
+ it("returns fields when present", () => {
+ const cipherView = createCipherView();
+ cipherView.fields = [
+ { name: "Field1", value: "Value1" } as any,
+ { name: "Field2", value: "Value2" } as any,
+ ];
+
+ const fields = CipherViewLikeUtils.getFields(cipherView);
+
+ expect(fields).toHaveLength(2);
+ expect(fields?.[0].name).toBe("Field1");
+ expect(fields?.[0].value).toBe("Value1");
+ expect(fields?.[1].name).toBe("Field2");
+ expect(fields?.[1].value).toBe("Value2");
+ });
+
+ it("returns empty array when fields array is empty", () => {
+ const cipherView = createCipherView();
+ cipherView.fields = [];
+
+ expect(CipherViewLikeUtils.getFields(cipherView)).toEqual([]);
+ });
+ });
+
+ describe("CipherListView", () => {
+ it("returns fields when present", () => {
+ const cipherListView = {
+ type: { login: {} },
+ fields: [
+ { name: "Username", value: "user@example.com" },
+ { name: "API Key", value: "abc123" },
+ ],
+ } as CipherListView;
+
+ const fields = CipherViewLikeUtils.getFields(cipherListView);
+
+ expect(fields).toHaveLength(2);
+ expect(fields?.[0].name).toBe("Username");
+ expect(fields?.[0].value).toBe("user@example.com");
+ expect(fields?.[1].name).toBe("API Key");
+ expect(fields?.[1].value).toBe("abc123");
+ });
+
+ it("returns empty array when fields array is empty", () => {
+ const cipherListView = {
+ type: "secureNote",
+ fields: [],
+ } as unknown as CipherListView;
+
+ expect(CipherViewLikeUtils.getFields(cipherListView)).toEqual([]);
+ });
+
+ it("returns undefined when fields are not present", () => {
+ const cipherListView = {
+ type: "secureNote",
+ } as CipherListView;
+
+ expect(CipherViewLikeUtils.getFields(cipherListView)).toBeUndefined();
+ });
+ });
+ });
+
+ describe("getAttachmentNames", () => {
+ describe("CipherView", () => {
+ it("returns attachment filenames when present", () => {
+ const cipherView = createCipherView();
+ const attachment1 = new AttachmentView();
+ attachment1.id = "1";
+ attachment1.fileName = "document.pdf";
+ const attachment2 = new AttachmentView();
+ attachment2.id = "2";
+ attachment2.fileName = "image.png";
+ const attachment3 = new AttachmentView();
+ attachment3.id = "3";
+ attachment3.fileName = "spreadsheet.xlsx";
+ cipherView.attachments = [attachment1, attachment2, attachment3];
+
+ const attachmentNames = CipherViewLikeUtils.getAttachmentNames(cipherView);
+
+ expect(attachmentNames).toEqual(["document.pdf", "image.png", "spreadsheet.xlsx"]);
+ });
+
+ it("filters out null and undefined filenames", () => {
+ const cipherView = createCipherView();
+ const attachment1 = new AttachmentView();
+ attachment1.id = "1";
+ attachment1.fileName = "valid.pdf";
+ const attachment2 = new AttachmentView();
+ attachment2.id = "2";
+ attachment2.fileName = null as any;
+ const attachment3 = new AttachmentView();
+ attachment3.id = "3";
+ attachment3.fileName = undefined;
+ const attachment4 = new AttachmentView();
+ attachment4.id = "4";
+ attachment4.fileName = "another.txt";
+ cipherView.attachments = [attachment1, attachment2, attachment3, attachment4];
+
+ const attachmentNames = CipherViewLikeUtils.getAttachmentNames(cipherView);
+
+ expect(attachmentNames).toEqual(["valid.pdf", "another.txt"]);
+ });
+
+ it("returns empty array when attachments have no filenames", () => {
+ const cipherView = createCipherView();
+ const attachment1 = new AttachmentView();
+ attachment1.id = "1";
+ const attachment2 = new AttachmentView();
+ attachment2.id = "2";
+ cipherView.attachments = [attachment1, attachment2];
+
+ const attachmentNames = CipherViewLikeUtils.getAttachmentNames(cipherView);
+
+ expect(attachmentNames).toEqual([]);
+ });
+
+ it("returns empty array for empty attachments array", () => {
+ const cipherView = createCipherView();
+ cipherView.attachments = [];
+
+ expect(CipherViewLikeUtils.getAttachmentNames(cipherView)).toEqual([]);
+ });
+ });
+
+ describe("CipherListView", () => {
+ it("returns attachment names when present", () => {
+ const cipherListView = {
+ type: "secureNote",
+ attachmentNames: ["report.pdf", "photo.jpg", "data.csv"],
+ } as CipherListView;
+
+ const attachmentNames = CipherViewLikeUtils.getAttachmentNames(cipherListView);
+
+ expect(attachmentNames).toEqual(["report.pdf", "photo.jpg", "data.csv"]);
+ });
+
+ it("returns empty array when attachmentNames is empty", () => {
+ const cipherListView = {
+ type: "secureNote",
+ attachmentNames: [],
+ } as unknown as CipherListView;
+
+ expect(CipherViewLikeUtils.getAttachmentNames(cipherListView)).toEqual([]);
+ });
+
+ it("returns undefined when attachmentNames is not present", () => {
+ const cipherListView = {
+ type: "secureNote",
+ } as CipherListView;
+
+ expect(CipherViewLikeUtils.getAttachmentNames(cipherListView)).toBeUndefined();
+ });
+ });
+ });
});
diff --git a/libs/common/src/vault/utils/cipher-view-like-utils.ts b/libs/common/src/vault/utils/cipher-view-like-utils.ts
index 04adb8d4832..5359bfb958f 100644
--- a/libs/common/src/vault/utils/cipher-view-like-utils.ts
+++ b/libs/common/src/vault/utils/cipher-view-like-utils.ts
@@ -10,6 +10,7 @@ import {
LoginUriView as LoginListUriView,
} from "@bitwarden/sdk-internal";
+import { Utils } from "../../platform/misc/utils";
import { CipherType } from "../enums";
import { Cipher } from "../models/domain/cipher";
import { CardView } from "../models/view/card.view";
@@ -290,6 +291,71 @@ export class CipherViewLikeUtils {
static decryptionFailure = (cipher: CipherViewLike): boolean => {
return "decryptionFailure" in cipher ? cipher.decryptionFailure : false;
};
+
+ /**
+ * Returns the notes from the cipher.
+ *
+ * @param cipher - The cipher to extract notes from (either `CipherView` or `CipherListView`)
+ * @returns The notes string if present, or `undefined` if not set
+ */
+ static getNotes = (cipher: CipherViewLike): string | undefined => {
+ return cipher.notes;
+ };
+
+ /**
+ * Returns the fields from the cipher.
+ *
+ * @param cipher - The cipher to extract fields from (either `CipherView` or `CipherListView`)
+ * @returns Array of field objects with `name` and `value` properties, `undefined` if not set
+ */
+ static getFields = (
+ cipher: CipherViewLike,
+ ): { name?: string | null; value?: string | undefined }[] | undefined => {
+ if (this.isCipherListView(cipher)) {
+ return cipher.fields;
+ }
+ return cipher.fields;
+ };
+
+ /**
+ * Returns attachment filenames from the cipher.
+ *
+ * @param cipher - The cipher to extract attachment names from (either `CipherView` or `CipherListView`)
+ * @returns Array of attachment filenames, `undefined` if attachments are not present
+ */
+ static getAttachmentNames = (cipher: CipherViewLike): string[] | undefined => {
+ if (this.isCipherListView(cipher)) {
+ return cipher.attachmentNames;
+ }
+
+ return cipher.attachments
+ ?.map((a) => a.fileName)
+ .filter((name): name is string => name != null);
+ };
+
+ /**
+ * Extracts hostname from a login URI.
+ *
+ * @param uri - The URI object (either `LoginUriView` class or `LoginListUriView`)
+ * @returns The hostname if available, `undefined` otherwise
+ *
+ * @remarks
+ * - For `LoginUriView` (CipherView): Uses the built-in `hostname` getter
+ * - For `LoginListUriView` (CipherListView): Computes hostname using `Utils.getHostname()`
+ * - Returns `undefined` for RegularExpression match types or when hostname cannot be extracted
+ */
+ static getUriHostname = (uri: LoginListUriView | LoginUriView): string | undefined => {
+ if ("hostname" in uri && typeof uri.hostname !== "undefined") {
+ return uri.hostname ?? undefined;
+ }
+
+ if (uri.match !== UriMatchStrategy.RegularExpression && uri.uri) {
+ const hostname = Utils.getHostname(uri.uri);
+ return hostname === "" ? undefined : hostname;
+ }
+
+ return undefined;
+ };
}
/**
diff --git a/libs/subscription/src/components/subscription-card/subscription-card.component.mdx b/libs/subscription/src/components/subscription-card/subscription-card.component.mdx
index c9cc6df7263..d3bad6583f5 100644
--- a/libs/subscription/src/components/subscription-card/subscription-card.component.mdx
+++ b/libs/subscription/src/components/subscription-card/subscription-card.component.mdx
@@ -78,6 +78,7 @@ type SubscriptionCardAction =
| "contact-support"
| "manage-invoices"
| "reinstate-subscription"
+ | "resubscribe"
| "update-payment"
| "upgrade-plan";
```
@@ -279,7 +280,7 @@ Payment issue expired, subscription has been suspended:
```
-**Actions available:** Contact Support
+**Actions available:** Resubscribe
### Past Due
@@ -370,7 +371,7 @@ Subscription that has been canceled:
```
-**Note:** Canceled subscriptions display no callout or actions.
+**Actions available:** Resubscribe
### Enterprise
diff --git a/libs/subscription/src/components/subscription-card/subscription-card.component.spec.ts b/libs/subscription/src/components/subscription-card/subscription-card.component.spec.ts
index cdb85360c74..f524c4b5c26 100644
--- a/libs/subscription/src/components/subscription-card/subscription-card.component.spec.ts
+++ b/libs/subscription/src/components/subscription-card/subscription-card.component.spec.ts
@@ -44,9 +44,11 @@ describe("SubscriptionCardComponent", () => {
unpaid: "Unpaid",
weCouldNotProcessYourPayment: "We could not process your payment",
contactSupportShort: "Contact support",
- yourSubscriptionHasExpired: "Your subscription has expired",
+ yourSubscriptionIsExpired: "Your subscription is expired",
+ yourSubscriptionIsCanceled: "Your subscription is canceled",
yourSubscriptionIsScheduledToCancel: `Your subscription is scheduled to cancel on ${params[0]}`,
reinstateSubscription: "Reinstate subscription",
+ resubscribe: "Resubscribe",
upgradeYourPlan: "Upgrade your plan",
premiumShareEvenMore: "Premium share even more",
upgradeNow: "Upgrade now",
@@ -253,7 +255,7 @@ describe("SubscriptionCardComponent", () => {
expect(buttons[1].nativeElement.textContent.trim()).toBe("Contact support");
});
- it("should display incomplete_expired callout with contact support action", () => {
+ it("should display incomplete_expired callout with resubscribe action", () => {
setupComponent({
...baseSubscription,
status: "incomplete_expired",
@@ -265,18 +267,18 @@ describe("SubscriptionCardComponent", () => {
expect(calloutData).toBeTruthy();
expect(calloutData!.type).toBe("danger");
expect(calloutData!.title).toBe("Expired");
- expect(calloutData!.description).toContain("Your subscription has expired");
+ expect(calloutData!.description).toContain("Your subscription is expired");
expect(calloutData!.callsToAction?.length).toBe(1);
const callout = fixture.debugElement.query(By.css("bit-callout"));
expect(callout).toBeTruthy();
const description = callout.query(By.css("p"));
- expect(description.nativeElement.textContent).toContain("Your subscription has expired");
+ expect(description.nativeElement.textContent).toContain("Your subscription is expired");
const buttons = callout.queryAll(By.css("button"));
expect(buttons.length).toBe(1);
- expect(buttons[0].nativeElement.textContent.trim()).toBe("Contact support");
+ expect(buttons[0].nativeElement.textContent.trim()).toBe("Resubscribe");
});
it("should display pending cancellation callout for active status with cancelAt", () => {
@@ -364,15 +366,29 @@ describe("SubscriptionCardComponent", () => {
expect(buttons[0].nativeElement.textContent.trim()).toBe("Manage invoices");
});
- it("should not display callout for canceled status", () => {
+ it("should display canceled callout with resubscribe action", () => {
setupComponent({
...baseSubscription,
status: "canceled",
canceled: new Date("2025-01-15"),
});
+ const calloutData = component.callout();
+ expect(calloutData).toBeTruthy();
+ expect(calloutData!.type).toBe("danger");
+ expect(calloutData!.title).toBe("Canceled");
+ expect(calloutData!.description).toContain("Your subscription is canceled");
+ expect(calloutData!.callsToAction?.length).toBe(1);
+
const callout = fixture.debugElement.query(By.css("bit-callout"));
- expect(callout).toBeFalsy();
+ expect(callout).toBeTruthy();
+
+ const description = callout.query(By.css("p"));
+ expect(description.nativeElement.textContent).toContain("Your subscription is canceled");
+
+ const buttons = callout.queryAll(By.css("button"));
+ expect(buttons.length).toBe(1);
+ expect(buttons[0].nativeElement.textContent.trim()).toBe("Resubscribe");
});
it("should display unpaid callout with manage invoices action", () => {
@@ -489,6 +505,39 @@ describe("SubscriptionCardComponent", () => {
expect(emitSpy).toHaveBeenCalledWith("manage-invoices");
});
+
+ it("should emit resubscribe action when button is clicked for incomplete_expired status", () => {
+ setupComponent({
+ ...baseSubscription,
+ status: "incomplete_expired",
+ suspension: new Date("2025-01-15"),
+ gracePeriod: 7,
+ });
+
+ const emitSpy = jest.spyOn(component.callToActionClicked, "emit");
+
+ const button = fixture.debugElement.query(By.css("bit-callout button"));
+ button.triggerEventHandler("click", { button: 0 });
+ fixture.detectChanges();
+
+ expect(emitSpy).toHaveBeenCalledWith("resubscribe");
+ });
+
+ it("should emit resubscribe action when button is clicked for canceled status", () => {
+ setupComponent({
+ ...baseSubscription,
+ status: "canceled",
+ canceled: new Date("2025-01-15"),
+ });
+
+ const emitSpy = jest.spyOn(component.callToActionClicked, "emit");
+
+ const button = fixture.debugElement.query(By.css("bit-callout button"));
+ button.triggerEventHandler("click", { button: 0 });
+ fixture.detectChanges();
+
+ expect(emitSpy).toHaveBeenCalledWith("resubscribe");
+ });
});
describe("Cart summary header content", () => {
diff --git a/libs/subscription/src/components/subscription-card/subscription-card.component.stories.ts b/libs/subscription/src/components/subscription-card/subscription-card.component.stories.ts
index 32976c89cc2..3d99ded2e5c 100644
--- a/libs/subscription/src/components/subscription-card/subscription-card.component.stories.ts
+++ b/libs/subscription/src/components/subscription-card/subscription-card.component.stories.ts
@@ -51,10 +51,13 @@ export default {
weCouldNotProcessYourPayment:
"We could not process your payment. Please update your payment method or contact the support team for assistance.",
contactSupportShort: "Contact Support",
- yourSubscriptionHasExpired:
- "Your subscription has expired. Please contact the support team for assistance.",
+ yourSubscriptionIsExpired:
+ "Your subscription is expired. Please resubscribe to continue using premium features.",
+ yourSubscriptionIsCanceled:
+ "Your subscription is canceled. Please resubscribe to continue using premium features.",
yourSubscriptionIsScheduledToCancel: `Your subscription is scheduled to cancel on ${args[0]}. You can reinstate it anytime before then.`,
reinstateSubscription: "Reinstate subscription",
+ resubscribe: "Resubscribe",
upgradeYourPlan: "Upgrade your plan",
premiumShareEvenMore:
"Share even more with Families, or get powerful, trusted password security with Teams or Enterprise.",
diff --git a/libs/subscription/src/components/subscription-card/subscription-card.component.ts b/libs/subscription/src/components/subscription-card/subscription-card.component.ts
index ebfb41df6c2..78d2c40eb3e 100644
--- a/libs/subscription/src/components/subscription-card/subscription-card.component.ts
+++ b/libs/subscription/src/components/subscription-card/subscription-card.component.ts
@@ -20,6 +20,7 @@ export const SubscriptionCardActions = {
ContactSupport: "contact-support",
ManageInvoices: "manage-invoices",
ReinstateSubscription: "reinstate-subscription",
+ Resubscribe: "resubscribe",
UpdatePayment: "update-payment",
UpgradePlan: "upgrade-plan",
} as const;
@@ -154,12 +155,12 @@ export class SubscriptionCardComponent {
return {
title: this.i18nService.t("expired"),
type: "danger",
- description: this.i18nService.t("yourSubscriptionHasExpired"),
+ description: this.i18nService.t("yourSubscriptionIsExpired"),
callsToAction: [
{
- text: this.i18nService.t("contactSupportShort"),
+ text: this.i18nService.t("resubscribe"),
buttonType: "unstyled",
- action: SubscriptionCardActions.ContactSupport,
+ action: SubscriptionCardActions.Resubscribe,
},
],
};
@@ -218,7 +219,18 @@ export class SubscriptionCardComponent {
};
}
case SubscriptionStatuses.Canceled: {
- return null;
+ return {
+ title: this.i18nService.t("canceled"),
+ type: "danger",
+ description: this.i18nService.t("yourSubscriptionIsCanceled"),
+ callsToAction: [
+ {
+ text: this.i18nService.t("resubscribe"),
+ buttonType: "unstyled",
+ action: SubscriptionCardActions.Resubscribe,
+ },
+ ],
+ };
}
case SubscriptionStatuses.Unpaid: {
return {
diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
index dc1894b0935..62fa65ae1e0 100644
--- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
+++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
@@ -21,20 +21,6 @@
[originalSendView]="originalSendView"
>
-
- {{ "sendLink" | i18n }}
-
-
-
-
{{ "deletionDate" | i18n }}
{{ "enterMultipleEmailsSeparatedByComma" | i18n }}
}
+
+
+ {{ "sendLink" | i18n }}
+
+
+
diff --git a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html
index 17f1233d70e..62417dfb577 100644
--- a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html
+++ b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html
@@ -1,4 +1,4 @@
-