1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

PM-18276-Connect confirmation UI (#13498)

* PM-18276-wip

* update typing

* dynamically retrieve messages, resolve theme in function

* five second timeout after save or update

* adjust timeout to five seconds

* negligible performance gain-revert

* sacrifice contorl for to remove event listeners-revert
This commit is contained in:
Daniel Riera
2025-02-26 16:13:27 -05:00
committed by GitHub
parent d999d91f19
commit 9aee5f16c4
3 changed files with 206 additions and 72 deletions

View File

@@ -0,0 +1,98 @@
import { css } from "@emotion/css";
import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
import {
NotificationBarIframeInitData,
NotificationTypes,
NotificationType,
} from "../../../notification/abstractions/notification-bar";
import { themes, spacing } from "../constants/styles";
import { NotificationConfirmationBody } from "./confirmation";
import {
NotificationHeader,
componentClassPrefix as notificationHeaderClassPrefix,
} from "./header";
export function NotificationConfirmationContainer({
error,
handleCloseNotification,
i18n,
theme = ThemeTypes.Light,
type,
}: NotificationBarIframeInitData & {
handleCloseNotification: (e: Event) => void;
} & {
error: string;
i18n: { [key: string]: string };
type: NotificationType;
}) {
const headerMessage = getHeaderMessage(i18n, type, error);
const confirmationMessage = getConfirmationMessage(i18n, type, error);
const buttonText = error ? i18n.newItem : i18n.view;
return html`
<div class=${notificationContainerStyles(theme)}>
${NotificationHeader({
handleCloseNotification,
message: headerMessage,
theme,
})}
${NotificationConfirmationBody({
error: error,
buttonText,
confirmationMessage,
theme,
})}
</div>
`;
}
const notificationContainerStyles = (theme: Theme) => css`
position: absolute;
right: 20px;
border: 1px solid ${themes[theme].secondary["300"]};
border-radius: ${spacing["4"]};
box-shadow: -2px 4px 6px 0px #0000001a;
background-color: ${themes[theme].background.alt};
width: 400px;
overflow: hidden;
[class*="${notificationHeaderClassPrefix}-"] {
border-radius: ${spacing["4"]} ${spacing["4"]} 0 0;
border-bottom: 0.5px solid ${themes[theme].secondary["300"]};
}
`;
function getConfirmationMessage(
i18n: { [key: string]: string },
type?: NotificationType,
error?: string,
) {
if (error) {
return i18n.saveFailureDetails;
}
return type === "add" ? i18n.loginSaveSuccessDetails : i18n.loginUpdateSuccessDetails;
}
function getHeaderMessage(
i18n: { [key: string]: string },
type?: NotificationType,
error?: string,
) {
if (error) {
return i18n.saveFailure;
}
switch (type) {
case NotificationTypes.Add:
return i18n.loginSaveSuccess;
case NotificationTypes.Change:
return i18n.loginUpdateSuccess;
case NotificationTypes.Unlock:
return "";
default:
return undefined;
}
}

View File

@@ -17,12 +17,12 @@ const { css } = createEmotion({
export function NotificationHeader({
message,
standalone,
standalone = false,
theme = ThemeTypes.Light,
handleCloseNotification,
}: {
message?: string;
standalone: boolean;
standalone?: boolean;
theme: Theme;
handleCloseNotification: (e: Event) => void;
}) {
@@ -49,7 +49,7 @@ const notificationHeaderStyles = ({
display: flex;
align-items: center;
justify-content: flex-start;
background-color: ${themes[theme].background.alt};
background-color: ${themes[theme].background};
padding: 12px 16px 8px 16px;
white-space: nowrap;

View File

@@ -7,6 +7,7 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background";
import { NotificationConfirmationContainer } from "../content/components/notification/confirmation-container";
import { NotificationContainer } from "../content/components/notification/container";
import { buildSvgDomElement } from "../utils";
import { circleCheckIcon } from "../utils/svg-icons";
@@ -22,12 +23,17 @@ const logService = new ConsoleLogService(false);
let notificationBarIframeInitData: NotificationBarIframeInitData = {};
let windowMessageOrigin: string;
let useComponentBar = false;
const notificationBarWindowMessageHandlers: NotificationBarWindowMessageHandlers = {
initNotificationBar: ({ message }) => initNotificationBar(message),
saveCipherAttemptCompleted: ({ message }) => handleSaveCipherAttemptCompletedMessage(message),
saveCipherAttemptCompleted: ({ message }) =>
useComponentBar
? handleSaveCipherConfirmation(message)
: handleSaveCipherAttemptCompletedMessage(message),
};
globalThis.addEventListener("load", load);
function load() {
setupWindowMessageListener();
sendPlatformMessage({ command: "notificationRefreshFlagValue" }, (flagValue) => {
@@ -35,7 +41,6 @@ function load() {
applyNotificationBarStyle();
});
}
function applyNotificationBarStyle() {
if (!useComponentBar) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -44,16 +49,8 @@ function applyNotificationBarStyle() {
postMessageToParent({ command: "initNotificationBar" });
}
function initNotificationBar(message: NotificationBarWindowMessage) {
const { initData } = message;
if (!initData) {
return;
}
notificationBarIframeInitData = initData;
const { isVaultLocked, theme } = notificationBarIframeInitData;
const i18n = {
function getI18n() {
return {
appName: chrome.i18n.getMessage("appName"),
close: chrome.i18n.getMessage("close"),
never: chrome.i18n.getMessage("never"),
@@ -74,20 +71,30 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
updateLoginPrompt: "Update existing login?",
loginSaveSuccess: "Login saved",
loginSaveSuccessDetails: "Login saved to Bitwarden.",
loginUpdateSuccess: "Login saved",
loginUpdateSuccess: "Login updated",
loginUpdateSuccessDetails: "Login updated in Bitwarden.",
saveFailure: "Error saving",
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details as a New item",
newItem: "New item",
view: "View",
};
}
function initNotificationBar(message: NotificationBarWindowMessage) {
const { initData } = message;
if (!initData) {
return;
}
notificationBarIframeInitData = initData;
const { isVaultLocked, theme } = notificationBarIframeInitData;
const i18n = getI18n();
const resolvedTheme = getResolvedTheme(theme);
if (useComponentBar) {
document.body.innerHTML = "";
// 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());
const themeType = getTheme(globalThis, theme);
// There are other possible passed theme values, but for now, resolve to dark or light
const resolvedTheme: Theme = themeType === ThemeTypes.Dark ? ThemeTypes.Dark : ThemeTypes.Light;
sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, (cipherData) => {
// @TODO use context to avoid prop drilling
@@ -105,8 +112,7 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
document.body,
);
});
}
} else {
setNotificationBarTheme();
(document.getElementById("logo") as HTMLImageElement).src = isVaultLocked
@@ -171,11 +177,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
globalThis.addEventListener("resize", adjustHeight);
adjustHeight();
function handleCloseNotification(e: Event) {
e.preventDefault();
sendPlatformMessage({
command: "bgCloseNotificationBar",
});
}
function handleEditOrUpdateAction(e: Event) {
const notificationType = initData.type;
@@ -183,6 +184,12 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
notificationType === "add" ? sendSaveCipherMessage(true) : sendSaveCipherMessage(false);
}
}
function handleCloseNotification(e: Event) {
e.preventDefault();
sendPlatformMessage({
command: "bgCloseNotificationBar",
});
}
function handleSaveAction(e: Event) {
e.preventDefault();
@@ -282,6 +289,27 @@ function handleSaveCipherAttemptCompletedMessage(message: NotificationBarWindowM
);
}
function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
const { theme, type } = notificationBarIframeInitData;
const { error } = message;
const i18n = getI18n();
const resolvedTheme = getResolvedTheme(theme);
globalThis.setTimeout(() => sendPlatformMessage({ command: "bgCloseNotificationBar" }), 5000);
return render(
NotificationConfirmationContainer({
...notificationBarIframeInitData,
type: type as NotificationType,
theme: resolvedTheme,
handleCloseNotification,
i18n,
error,
}),
document.body,
);
}
function handleTypeUnlock() {
setContent(document.getElementById("template-unlock") as HTMLTemplateElement);
@@ -395,6 +423,14 @@ function getTheme(globalThis: any, theme: NotificationBarIframeInitData["theme"]
return theme;
}
function getResolvedTheme(theme: Theme) {
const themeType = getTheme(globalThis, theme);
// There are other possible passed theme values, but for now, resolve to dark or light
const resolvedTheme: Theme = themeType === ThemeTypes.Dark ? ThemeTypes.Dark : ThemeTypes.Light;
return resolvedTheme;
}
function setNotificationBarTheme() {
const theme = getTheme(globalThis, notificationBarIframeInitData.theme);