diff --git a/apps/browser/src/autofill/background/overlay-notifications.background.ts b/apps/browser/src/autofill/background/overlay-notifications.background.ts
index 6186ffaf128..e3ca6258590 100644
--- a/apps/browser/src/autofill/background/overlay-notifications.background.ts
+++ b/apps/browser/src/autofill/background/overlay-notifications.background.ts
@@ -444,7 +444,9 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg
{ tab },
);
this.clearCompletedWebRequest(requestId, tab);
+ return;
}
+
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(getOptionalUserId),
);
diff --git a/apps/browser/src/autofill/content/components/buttons/additional-tasks/button-content.ts b/apps/browser/src/autofill/content/components/buttons/additional-tasks/button-content.ts
new file mode 100644
index 00000000000..2357da4e785
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/buttons/additional-tasks/button-content.ts
@@ -0,0 +1,29 @@
+import { css } from "@emotion/css";
+import { html } from "lit";
+
+import { Theme } from "@bitwarden/common/platform/enums";
+
+import { spacing, themes } from "../../constants/styles";
+import { ExternalLink } from "../../icons";
+
+export function AdditionalTasksButtonContent({
+ buttonText,
+ theme,
+}: {
+ buttonText: string;
+ theme: Theme;
+}) {
+ return html`
+
+ ${buttonText}
+ ${ExternalLink({ theme, color: themes[theme].text.contrast })}
+
+ `;
+}
+
+export const additionalTasksButtonContentStyles = ({ theme }: { theme: Theme }) => css`
+ gap: ${spacing[2]};
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+`;
diff --git a/apps/browser/src/autofill/content/components/notification/at-risk-password/body.ts b/apps/browser/src/autofill/content/components/notification/at-risk-password/body.ts
new file mode 100644
index 00000000000..a1b0ff91e7e
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/notification/at-risk-password/body.ts
@@ -0,0 +1,55 @@
+import { html, nothing } from "lit";
+
+import { Theme } from "@bitwarden/common/platform/enums";
+
+import { Celebrate, Keyhole, Warning } from "../../illustrations";
+import { iconContainerStyles, notificationConfirmationBodyStyles } from "../confirmation/body";
+
+import { AtRiskNotificationMessage } from "./message";
+
+export const componentClassPrefix = "notification-confirmation-body";
+
+export type AtRiskNotificationBodyProps = {
+ buttonAria: string;
+ buttonText: string;
+ confirmationMessage: string;
+ error?: string;
+ itemName?: string;
+ messageDetails?: string;
+ tasksAreComplete?: boolean;
+ theme: Theme;
+ handleOpenVault: (e: Event) => void;
+};
+
+export function AtRiskNotificationBody({
+ buttonAria,
+ buttonText,
+ confirmationMessage,
+ error,
+ itemName,
+ messageDetails,
+ tasksAreComplete,
+ theme,
+ handleOpenVault,
+}: AtRiskNotificationBodyProps) {
+ const IconComponent = tasksAreComplete ? Keyhole : !error ? Celebrate : Warning;
+
+ const showConfirmationMessage = confirmationMessage || buttonText || messageDetails;
+
+ return html`
+
+
${IconComponent({ theme })}
+ ${showConfirmationMessage
+ ? AtRiskNotificationMessage({
+ buttonAria,
+ buttonText,
+ itemName,
+ message: confirmationMessage,
+ messageDetails,
+ theme,
+ handleClick: handleOpenVault,
+ })
+ : nothing}
+
+ `;
+}
diff --git a/apps/browser/src/autofill/content/components/notification/at-risk-password/container.ts b/apps/browser/src/autofill/content/components/notification/at-risk-password/container.ts
new file mode 100644
index 00000000000..24564dc37b8
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/notification/at-risk-password/container.ts
@@ -0,0 +1,76 @@
+import { html } from "lit";
+
+import { ThemeTypes } from "@bitwarden/common/platform/enums";
+
+import {
+ AtRiskPasswordNotificationParams,
+ NotificationBarIframeInitData,
+ NotificationType,
+ NotificationTypes,
+} from "../../../../notification/abstractions/notification-bar";
+import { I18n } from "../../common-types";
+import { notificationContainerStyles } from "../confirmation/container";
+import { NotificationHeader } from "../header";
+
+import { AtRiskNotificationBody } from "./body";
+import { AtRiskNotificationFooter } from "./footer";
+
+export type AtRiskNotificationProps = NotificationBarIframeInitData & {
+ handleCloseNotification: (e: Event) => void;
+ handleOpenVault: (e: Event) => void;
+ handleOpenTasks: (e: Event) => void;
+} & {
+ error?: string;
+ i18n: I18n;
+ itemName: string;
+ type: NotificationType;
+ params: AtRiskPasswordNotificationParams;
+};
+
+export function AtRiskNotification({
+ error,
+ handleCloseNotification,
+ i18n,
+ theme = ThemeTypes.Light,
+ type,
+ params,
+}: AtRiskNotificationProps) {
+ const headerMessage = getHeaderMessage(i18n, type, error);
+ const { passwordChangeUri, organizationName } = params;
+
+ return html`
+
+ ${NotificationHeader({
+ handleCloseNotification,
+ message: headerMessage,
+ theme,
+ })}
+ ${AtRiskNotificationBody({
+ buttonAria: "",
+ error: "At risk password",
+ theme,
+ tasksAreComplete: false,
+ itemName: "",
+ handleOpenVault: () => {},
+ buttonText: "",
+ confirmationMessage: chrome.i18n.getMessage(
+ passwordChangeUri ? "atRiskChangePrompt" : "atRiskNavigatePrompt",
+ organizationName,
+ ),
+ })};
+ ${AtRiskNotificationFooter({
+ i18n,
+ theme,
+ passwordChangeUri: params?.passwordChangeUri,
+ })}
+
+ `;
+}
+
+function getHeaderMessage(i18n: I18n, type?: NotificationType, error?: string) {
+ if (error) {
+ return i18n.saveFailure;
+ }
+
+ return type === NotificationTypes.AtRiskPassword ? i18n.changePassword : undefined;
+}
diff --git a/apps/browser/src/autofill/content/components/notification/at-risk-password/footer.ts b/apps/browser/src/autofill/content/components/notification/at-risk-password/footer.ts
new file mode 100644
index 00000000000..8848922242b
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/notification/at-risk-password/footer.ts
@@ -0,0 +1,33 @@
+import { html } from "lit";
+
+import { Theme } from "@bitwarden/common/platform/enums";
+
+import { ActionButton } from "../../buttons/action-button";
+import { AdditionalTasksButtonContent } from "../../buttons/additional-tasks/button-content";
+import { I18n } from "../../common-types";
+// Utilizes default notification styles, not confirmation.
+import { notificationFooterStyles } from "../footer";
+
+export type AtRiskNotificationFooterProps = {
+ i18n: I18n;
+ theme: Theme;
+ passwordChangeUri: string;
+};
+
+export function AtRiskNotificationFooter({
+ i18n,
+ theme,
+ passwordChangeUri,
+}: AtRiskNotificationFooterProps) {
+ return html``;
+}
diff --git a/apps/browser/src/autofill/content/components/notification/at-risk-password/message.ts b/apps/browser/src/autofill/content/components/notification/at-risk-password/message.ts
new file mode 100644
index 00000000000..e3c9e2a3af5
--- /dev/null
+++ b/apps/browser/src/autofill/content/components/notification/at-risk-password/message.ts
@@ -0,0 +1,68 @@
+import { html, nothing } from "lit";
+
+import { Theme } from "@bitwarden/common/platform/enums";
+
+import {
+ AdditionalMessageStyles,
+ notificationConfirmationButtonTextStyles,
+ notificationConfirmationMessageStyles,
+} from "../confirmation/message";
+
+export type AtRiskNotificationMessageProps = {
+ buttonAria?: string;
+ buttonText?: string;
+ itemName?: string;
+ message?: string;
+ messageDetails?: string;
+ handleClick: (e: Event) => void;
+ theme: Theme;
+};
+
+export function AtRiskNotificationMessage({
+ buttonAria,
+ buttonText,
+ message,
+ messageDetails,
+ handleClick,
+ theme,
+}: AtRiskNotificationMessageProps) {
+ return html`
+
+ `;
+}
+
+function handleButtonKeyDown(event: KeyboardEvent, handleClick: () => void) {
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault();
+ handleClick();
+ }
+}
diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts
index f509f08946e..4d8019b0a55 100644
--- a/apps/browser/src/autofill/content/components/notification/body.ts
+++ b/apps/browser/src/autofill/content/components/notification/body.ts
@@ -3,19 +3,13 @@ import { html } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
-import {
- NotificationMessageParams,
- NotificationType,
- NotificationTypes,
-} from "../../../notification/abstractions/notification-bar";
+import { NotificationType } from "../../../notification/abstractions/notification-bar";
import { CipherItem } from "../cipher";
import { NotificationCipherData } from "../cipher/types";
import { I18n } from "../common-types";
import { scrollbarStyles, spacing, themes, typography } from "../constants/styles";
import { ItemRow } from "../rows/item-row";
-import { NotificationConfirmationBody } from "./confirmation/body";
-
export const componentClassPrefix = "notification-body";
const { css } = createEmotion({
@@ -28,7 +22,6 @@ export type NotificationBodyProps = {
notificationType?: NotificationType;
theme: Theme;
handleEditOrUpdateAction: (e: Event) => void;
- params?: NotificationMessageParams;
};
export function NotificationBody({
@@ -37,45 +30,26 @@ export function NotificationBody({
notificationType,
theme = ThemeTypes.Light,
handleEditOrUpdateAction,
- params,
}: NotificationBodyProps) {
// @TODO get client vendor from context
const isSafari = false;
- const { passwordChangeUri, organizationName } = params;
- switch (notificationType) {
- case NotificationTypes.AtRiskPassword:
- return NotificationConfirmationBody({
- buttonAria: "",
- error: "At risk password",
- theme,
- tasksAreComplete: false,
- itemName: "",
- handleOpenVault: () => {},
- buttonText: "",
- confirmationMessage: chrome.i18n.getMessage(
- passwordChangeUri ? "atRiskChangePrompt" : "atRiskNavigatePrompt",
- organizationName,
- ),
- });
- default:
- return html`
-
- ${ciphers.map((cipher) =>
- ItemRow({
- theme,
- children: CipherItem({
- cipher,
- i18n,
- notificationType,
- theme,
- handleAction: handleEditOrUpdateAction,
- }),
- }),
- )}
-
- `;
- }
+ return html`
+
+ ${ciphers.map((cipher) =>
+ ItemRow({
+ theme,
+ children: CipherItem({
+ cipher,
+ i18n,
+ notificationType,
+ theme,
+ handleAction: handleEditOrUpdateAction,
+ }),
+ }),
+ )}
+
+ `;
}
const notificationBodyStyles = ({ isSafari, theme }: { isSafari: boolean; theme: Theme }) => css`
diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/body.ts b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts
index 1c744be66ac..98cc9a7ef7d 100644
--- a/apps/browser/src/autofill/content/components/notification/confirmation/body.ts
+++ b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts
@@ -59,13 +59,14 @@ export function NotificationConfirmationBody({
`;
}
-const iconContainerStyles = (error?: string) => css`
+// Allow sharing of styles between notifications (@TODO isolate structural/presentational component layer)
+export const iconContainerStyles = (error?: string) => css`
> svg {
width: ${!error ? "50px" : "40px"};
height: fit-content;
}
`;
-const notificationConfirmationBodyStyles = ({ theme }: { theme: Theme }) => css`
+export const notificationConfirmationBodyStyles = ({ theme }: { theme: Theme }) => css`
gap: 16px;
display: flex;
align-items: center;
diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts
index 9778f0e6b81..a077ec0bbef 100644
--- a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts
+++ b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts
@@ -93,7 +93,7 @@ export function NotificationConfirmationContainer({
`;
}
-const notificationContainerStyles = (theme: Theme) => css`
+export const notificationContainerStyles = (theme: Theme) => css`
position: absolute;
right: 20px;
border: 1px solid ${themes[theme].secondary["300"]};
diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/footer.ts b/apps/browser/src/autofill/content/components/notification/confirmation/footer.ts
index c04a971b650..5fb6724a0ef 100644
--- a/apps/browser/src/autofill/content/components/notification/confirmation/footer.ts
+++ b/apps/browser/src/autofill/content/components/notification/confirmation/footer.ts
@@ -4,9 +4,9 @@ import { html } from "lit";
import { Theme } from "@bitwarden/common/platform/enums";
import { ActionButton } from "../../buttons/action-button";
+import { AdditionalTasksButtonContent } from "../../buttons/additional-tasks/button-content";
import { I18n } from "../../common-types";
-import { spacing, themes } from "../../constants/styles";
-import { ExternalLink } from "../../icons";
+import { notificationFooterStyles } from "../footer";
export type NotificationConfirmationFooterProps = {
i18n: I18n;
@@ -22,7 +22,7 @@ export function NotificationConfirmationFooter({
const primaryButtonText = i18n.nextSecurityTaskAction;
return html`
-