From ee60c190878c5aba3f45c5a1cf8c08335e979c92 Mon Sep 17 00:00:00 2001 From: Miles Blackwood Date: Thu, 8 May 2025 11:02:54 -0400 Subject: [PATCH] Create new at-risk-password dir specific to this notification. --- .../overlay-notifications.background.ts | 2 + .../additional-tasks/button-content.ts | 29 +++++++ .../notification/at-risk-password/body.ts | 55 ++++++++++++++ .../at-risk-password/container.ts | 76 +++++++++++++++++++ .../notification/at-risk-password/footer.ts | 33 ++++++++ .../notification/at-risk-password/message.ts | 68 +++++++++++++++++ .../content/components/notification/body.ts | 60 +++++---------- .../notification/confirmation/body.ts | 5 +- .../notification/confirmation/container.ts | 2 +- .../notification/confirmation/footer.ts | 37 +-------- .../notification/confirmation/message.ts | 8 +- .../components/notification/container.ts | 4 - .../content/components/notification/footer.ts | 9 ++- .../abstractions/notification-bar.ts | 6 +- 14 files changed, 300 insertions(+), 94 deletions(-) create mode 100644 apps/browser/src/autofill/content/components/buttons/additional-tasks/button-content.ts create mode 100644 apps/browser/src/autofill/content/components/notification/at-risk-password/body.ts create mode 100644 apps/browser/src/autofill/content/components/notification/at-risk-password/container.ts create mode 100644 apps/browser/src/autofill/content/components/notification/at-risk-password/footer.ts create mode 100644 apps/browser/src/autofill/content/components/notification/at-risk-password/message.ts 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`
+ ${passwordChangeUri && + ActionButton({ + handleClick: () => { + open("https://" + passwordChangeUri, "_blank"); + }, + buttonText: AdditionalTasksButtonContent({ buttonText: i18n.changePassword, theme }), + theme, + fullWidth: false, + })} +
`; +} 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` +
+ ${message || buttonText + ? html` + + ${message || nothing} + ${buttonText + ? html` + handleButtonKeyDown(e, () => handleClick(e))} + aria-label=${buttonAria} + tabindex="0" + role="button" + > + ${buttonText} + + ` + : nothing} + + ` + : nothing} + ${messageDetails + ? html`
${messageDetails}
` + : nothing} +
+ `; +} + +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` -
+
${ActionButton({ handleClick: handleButtonClick, buttonText: AdditionalTasksButtonContent({ buttonText: primaryButtonText, theme }), @@ -32,35 +32,6 @@ export function NotificationConfirmationFooter({ `; } -const notificationConfirmationFooterStyles = ({ theme }: { theme: Theme }) => css` - background-color: ${themes[theme].background.alt}; - padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]}; +const maxWidthMinContent = () => css` max-width: min-content; - - :last-child { - border-radius: 0 0 ${spacing["4"]} ${spacing["4"]}; - padding-bottom: ${spacing[4]}; - } -`; - -export function AdditionalTasksButtonContent({ - buttonText, - theme, -}: { - buttonText: string; - theme: Theme; -}) { - return html` -
- ${buttonText} - ${ExternalLink({ theme, color: themes[theme].text.contrast })} -
- `; -} - -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/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 8f6e817ba77..65f7223fc0e 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -57,7 +57,7 @@ export function NotificationConfirmationMessage({ `; } -const baseTextStyles = css` +export const baseTextStyles = css` flex-grow: 1; overflow-x: hidden; text-align: left; @@ -67,14 +67,14 @@ const baseTextStyles = css` font-size: 16px; `; -const notificationConfirmationMessageStyles = (theme: Theme) => css` +export const notificationConfirmationMessageStyles = (theme: Theme) => css` ${baseTextStyles} color: ${themes[theme].text.main}; font-weight: 400; `; -const notificationConfirmationButtonTextStyles = (theme: Theme) => css` +export const notificationConfirmationButtonTextStyles = (theme: Theme) => css` ${baseTextStyles} color: ${themes[theme].primary[600]}; @@ -82,7 +82,7 @@ const notificationConfirmationButtonTextStyles = (theme: Theme) => css` cursor: pointer; `; -const AdditionalMessageStyles = ({ theme }: { theme: Theme }) => css` +export const AdditionalMessageStyles = ({ theme }: { theme: Theme }) => css` ${typography.body2} font-size: 14px; diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index 447a97db06d..01676af7876 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -31,7 +31,6 @@ export type NotificationContainerProps = NotificationBarIframeInitData & { organizations?: OrgView[]; personalVaultIsAllowed?: boolean; type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` - params: object; }; export function NotificationContainer({ @@ -46,7 +45,6 @@ export function NotificationContainer({ personalVaultIsAllowed = true, theme = ThemeTypes.Light, type, - params, }: NotificationContainerProps) { const headerMessage = getHeaderMessage(i18n, type); const showBody = true; @@ -66,7 +64,6 @@ export function NotificationContainer({ notificationType: type, theme, i18n, - params, }) : null} ${NotificationFooter({ @@ -78,7 +75,6 @@ export function NotificationContainer({ organizations, personalVaultIsAllowed, theme, - passwordChangeUri: params?.passwordChangeUri, })}
`; diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts index 02f08da6aa2..d8bfa7dd9f0 100644 --- a/apps/browser/src/autofill/content/components/notification/footer.ts +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -8,11 +8,11 @@ import { NotificationTypes, } from "../../../notification/abstractions/notification-bar"; import { ActionButton } from "../buttons/action-button"; +import { AdditionalTasksButtonContent } from "../buttons/additional-tasks/button-content"; import { OrgView, FolderView, I18n, CollectionView } from "../common-types"; import { spacing, themes } from "../constants/styles"; import { NotificationButtonRow } from "./button-row"; -import { AdditionalTasksButtonContent } from "./confirmation/footer"; export type NotificationFooterProps = { collections?: CollectionView[]; @@ -55,7 +55,7 @@ export function NotificationFooter({ } return html` -
+
${!isChangeNotification ? NotificationButtonRow({ collections, @@ -74,8 +74,11 @@ export function NotificationFooter({ `; } -const notificationFooterStyles = ({ theme }: { theme: Theme }) => css` +export const displayFlex = () => css` display: flex; +`; + +export const notificationFooterStyles = ({ theme }: { theme: Theme }) => css` background-color: ${themes[theme].background.alt}; padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]}; diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index 9009e0a9ea4..8d17077268e 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -53,15 +53,13 @@ type NotificationBarWindowMessageHandlers = { saveCipherAttemptCompleted: ({ message }: { message: NotificationBarWindowMessage }) => void; }; -type NotificationMessageParamsAtRiskPasswordType = { +type AtRiskPasswordNotificationParams = { passwordChangeUri?: string; organizationName: string; }; -type NotificationMessageParams = NotificationMessageParamsAtRiskPasswordType | any; - export { - NotificationMessageParams, + AtRiskPasswordNotificationParams, NotificationTaskInfo, NotificationTypes, NotificationType,