1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

[PM-21456] Notification bar does not appear when user is adding a new login when vault is locked (#14807)

* WiP if vault is locked, prompt to unlock and save with v3 notification

* add component handling for unlock notification type

* fix header component standalone mode

* fix header copy

* render unlock notification case on save with locked vault
This commit is contained in:
Jonathan Prusik
2025-05-15 17:42:46 -04:00
committed by GitHub
parent ad7c20febc
commit 14ced25561
6 changed files with 43 additions and 10 deletions

View File

@@ -1116,6 +1116,10 @@
"message": "Update login", "message": "Update login",
"description": "Button text for updating an existing login entry." "description": "Button text for updating an existing login entry."
}, },
"unlockToSave": {
"message": "Unlock to save this login",
"description": "User prompt to take action in order to save the login they just entered."
},
"saveLogin": { "saveLogin": {
"message": "Save login", "message": "Save login",
"description": "Prompt asking the user if they want to save their login details." "description": "Prompt asking the user if they want to save their login details."

View File

@@ -134,6 +134,7 @@ export const mockI18n = {
saveLogin: "Save login", saveLogin: "Save login",
selectItemAriaLabel: "Select $ITEMTYPE$, $ITEMNAME$", selectItemAriaLabel: "Select $ITEMTYPE$, $ITEMNAME$",
typeLogin: "Login", typeLogin: "Login",
unlockToSave: "Unlock to save this login",
updateLoginAction: "Update login", updateLoginAction: "Update login",
updateLogin: "Update existing login", updateLogin: "Update existing login",
vault: "Vault", vault: "Vault",

View File

@@ -1,5 +1,5 @@
import { css } from "@emotion/css"; import { css } from "@emotion/css";
import { html } from "lit"; import { html, nothing } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
@@ -47,14 +47,13 @@ export function NotificationContainer({
type, type,
}: NotificationContainerProps) { }: NotificationContainerProps) {
const headerMessage = getHeaderMessage(i18n, type); const headerMessage = getHeaderMessage(i18n, type);
const showBody = true; const showBody = type !== NotificationTypes.Unlock;
return html` return html`
<div class=${notificationContainerStyles(theme)}> <div class=${notificationContainerStyles(theme)}>
${NotificationHeader({ ${NotificationHeader({
handleCloseNotification, handleCloseNotification,
message: headerMessage, message: headerMessage,
standalone: showBody,
theme, theme,
})} })}
${showBody ${showBody
@@ -65,7 +64,7 @@ export function NotificationContainer({
theme, theme,
i18n, i18n,
}) })
: null} : nothing}
${NotificationFooter({ ${NotificationFooter({
handleSaveAction, handleSaveAction,
collections, collections,
@@ -106,7 +105,7 @@ function getHeaderMessage(i18n: I18n, type?: NotificationType) {
case NotificationTypes.Change: case NotificationTypes.Change:
return i18n.updateLogin; return i18n.updateLogin;
case NotificationTypes.Unlock: case NotificationTypes.Unlock:
return ""; return i18n.unlockToSave;
default: default:
return undefined; return undefined;
} }

View File

@@ -34,7 +34,13 @@ export function NotificationFooter({
handleSaveAction, handleSaveAction,
}: NotificationFooterProps) { }: NotificationFooterProps) {
const isChangeNotification = notificationType === NotificationTypes.Change; const isChangeNotification = notificationType === NotificationTypes.Change;
const primaryButtonText = i18n.saveAction; const isUnlockNotification = notificationType === NotificationTypes.Unlock;
let primaryButtonText = i18n.saveAction;
if (isUnlockNotification) {
primaryButtonText = i18n.notificationUnlock;
}
return html` return html`
<div class=${notificationFooterStyles({ isChangeNotification })}> <div class=${notificationFooterStyles({ isChangeNotification })}>

View File

@@ -56,8 +56,8 @@ const notificationHeaderStyles = ({
white-space: nowrap; white-space: nowrap;
${standalone ${standalone
? css` ? css``
: css`
border-bottom: 0.5px solid ${themes[theme].secondary["300"]}; border-bottom: 0.5px solid ${themes[theme].secondary["300"]};
` `}
: css``}
`; `;

View File

@@ -19,6 +19,7 @@ import {
NotificationBarWindowMessage, NotificationBarWindowMessage,
NotificationBarIframeInitData, NotificationBarIframeInitData,
NotificationType, NotificationType,
NotificationTypes,
} from "./abstractions/notification-bar"; } from "./abstractions/notification-bar";
const logService = new ConsoleLogService(false); const logService = new ConsoleLogService(false);
@@ -85,6 +86,7 @@ function getI18n() {
saveFailureDetails: chrome.i18n.getMessage("saveFailureDetails"), saveFailureDetails: chrome.i18n.getMessage("saveFailureDetails"),
saveLogin: chrome.i18n.getMessage("saveLogin"), saveLogin: chrome.i18n.getMessage("saveLogin"),
typeLogin: chrome.i18n.getMessage("typeLogin"), typeLogin: chrome.i18n.getMessage("typeLogin"),
unlockToSave: chrome.i18n.getMessage("unlockToSave"),
updateLoginAction: chrome.i18n.getMessage("updateLoginAction"), updateLoginAction: chrome.i18n.getMessage("updateLoginAction"),
updateLogin: chrome.i18n.getMessage("updateLogin"), updateLogin: chrome.i18n.getMessage("updateLogin"),
vault: chrome.i18n.getMessage("vault"), vault: chrome.i18n.getMessage("vault"),
@@ -149,6 +151,27 @@ async function initNotificationBar(message: NotificationBarWindowMessage) {
document.body.innerHTML = ""; document.body.innerHTML = "";
// Current implementations utilize a require for scss files which creates the need to remove the node. // Current implementations utilize a require for scss files which creates the need to remove the node.
document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove()); document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove());
if (isVaultLocked) {
return render(
NotificationContainer({
...notificationBarIframeInitData,
type: NotificationTypes.Unlock,
theme: resolvedTheme,
personalVaultIsAllowed: !personalVaultDisallowed,
handleCloseNotification,
handleSaveAction: (e) => {
sendSaveCipherMessage(true);
// @TODO can't close before vault has finished decrypting, but can't leave open during long decrypt because it looks like the experience has failed
},
handleEditOrUpdateAction,
i18n,
}),
document.body,
);
}
const orgId = selectedVaultSignal.get(); const orgId = selectedVaultSignal.get();
await Promise.all([ await Promise.all([
new Promise<OrgView[]>((resolve) => new Promise<OrgView[]>((resolve) =>