diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 586e7e1f2cf..f3b85496b75 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1098,6 +1098,31 @@ "message": "Login updated", "description": "Message displayed when login details are successfully updated." }, + "loginUpdateTaskSuccess": { + "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "placeholders": { + "organization": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "loginUpdateTaskSuccessAdditional": { + "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "placeholders": { + "organization": { + "content": "$1" + }, + "task_count": { + "content": "$2" + } + }, + "description": "Shown to user after login is updated." + }, + "nextSecurityTaskAction": { + "message": "Change next password", + "description": "Message prompting user to undertake completion of another security task." + }, "saveFailure": { "message": "Error saving", "description": "Error message shown when the system fails to save login details." diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts index f0642d4233a..881b44b4785 100644 --- a/apps/browser/src/autofill/content/components/buttons/action-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts @@ -1,5 +1,5 @@ import { css } from "@emotion/css"; -import { html } from "lit"; +import { html, TemplateResult } from "lit"; import { Theme } from "@bitwarden/common/platform/enums"; @@ -11,7 +11,7 @@ export function ActionButton({ theme, handleClick, }: { - buttonText: string; + buttonText: string | TemplateResult; disabled?: boolean; theme: Theme; handleClick: (e: Event) => void; @@ -63,4 +63,9 @@ const actionButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: The color: ${themes[theme].text.contrast}; } `} + + svg { + width: fit-content; + height: 16px; + } `; diff --git a/apps/browser/src/autofill/content/components/icons/external-link.ts b/apps/browser/src/autofill/content/components/icons/external-link.ts new file mode 100644 index 00000000000..10c6d831025 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/external-link.ts @@ -0,0 +1,22 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { IconProps } from "../common-types"; +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function ExternalLink({ color, disabled, theme }: IconProps) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/index.ts b/apps/browser/src/autofill/content/components/icons/index.ts index c4769a0e69d..4b6cb7abdd8 100644 --- a/apps/browser/src/autofill/content/components/icons/index.ts +++ b/apps/browser/src/autofill/content/components/icons/index.ts @@ -4,9 +4,11 @@ export { BrandIconContainer } from "./brand-icon-container"; export { Business } from "./business"; export { Close } from "./close"; export { ExclamationTriangle } from "./exclamation-triangle"; +export { ExternalLink } from "./external-link"; export { Family } from "./family"; export { Folder } from "./folder"; export { Globe } from "./globe"; +export { Keyhole } from "./keyhole"; export { PartyHorn } from "./party-horn"; export { PencilSquare } from "./pencil-square"; export { Shield } from "./shield"; diff --git a/apps/browser/src/autofill/content/components/icons/keyhole.ts b/apps/browser/src/autofill/content/components/icons/keyhole.ts new file mode 100644 index 00000000000..0294c0c8499 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/keyhole.ts @@ -0,0 +1,222 @@ +import { html } from "lit"; + +import { ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { IconProps } from "../common-types"; + +// This icon has static multi-colors for each theme +export function Keyhole({ theme }: IconProps) { + if (theme === ThemeTypes.Dark) { + return html` + + + + + + + + + + + + + + + + + + + + + + `; + } + + return html` + + + + + + + + + + + + + + + + + + + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/lit-stories/icons/icons.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/icons/icons.lit-stories.ts index 20c88a59246..8bd87ef6674 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/icons/icons.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/icons/icons.lit-stories.ts @@ -57,9 +57,11 @@ export const BusinessIcon = createIconStory("Business"); export const BrandIcon = createIconStory("BrandIconContainer"); export const CloseIcon = createIconStory("Close"); export const ExclamationTriangleIcon = createIconStory("ExclamationTriangle"); +export const ExternalLinkIcon = createIconStory("ExternalLink"); export const FamilyIcon = createIconStory("Family"); export const FolderIcon = createIconStory("Folder"); export const GlobeIcon = createIconStory("Globe"); +export const KeyholeIcon = createIconStory("Keyhole"); export const PartyHornIcon = createIconStory("PartyHorn"); export const PencilSquareIcon = createIconStory("PencilSquare"); export const ShieldIcon = createIconStory("Shield"); diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts index e43bc08b920..32b4170d1da 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts @@ -16,7 +16,7 @@ type Args = { }; export default { - title: "Components/Notifications/Notification Body", + title: "Components/Notifications/Body", argTypes: { ciphers: { control: "object" }, theme: { control: "select", options: [...Object.values(ThemeTypes)] }, diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/body.lit-stories.ts similarity index 53% rename from apps/browser/src/autofill/content/components/lit-stories/notification/confirmation.lit-stories.ts rename to apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/body.lit-stories.ts index b3dee95efd0..4d9be06fd7e 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/body.lit-stories.ts @@ -1,29 +1,26 @@ import { Meta, StoryObj } from "@storybook/web-components"; -import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; +import { ThemeTypes } from "@bitwarden/common/platform/enums"; -import { NotificationConfirmationBody } from "../../notification/confirmation"; - -type Args = { - buttonText: string; - confirmationMessage: string; - handleOpenVault: () => void; - theme: Theme; - error: string; -}; +import { + NotificationConfirmationBody, + NotificationConfirmationBodyProps, +} from "../../../notification/confirmation/body"; export default { - title: "Components/Notifications/Notification Confirmation Body", + title: "Components/Notifications/Confirmation/Body", argTypes: { error: { control: "text" }, buttonText: { control: "text" }, confirmationMessage: { control: "text" }, + messageDetails: { control: "text" }, theme: { control: "select", options: [...Object.values(ThemeTypes)] }, }, args: { error: "", buttonText: "View", confirmationMessage: "[item name] updated in Bitwarden.", + messageDetails: "You can view it in your vault.", theme: ThemeTypes.Light, }, parameters: { @@ -32,10 +29,11 @@ export default { url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=485-20160&m=dev", }, }, -} as Meta; +} as Meta; -const Template = (args: Args) => NotificationConfirmationBody({ ...args }); +const Template = (args: NotificationConfirmationBodyProps) => + NotificationConfirmationBody({ ...args }); -export const Default: StoryObj = { +export const Default: StoryObj = { render: Template, }; diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/container.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/container.lit-stories.ts new file mode 100644 index 00000000000..ec7194004d8 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/container.lit-stories.ts @@ -0,0 +1,55 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { NotificationTypes } from "../../../../../notification/abstractions/notification-bar"; +import { + NotificationConfirmationContainer, + NotificationConfirmationContainerProps, +} from "../../../notification/confirmation/container"; + +export default { + title: "Components/Notifications/Confirmation", + argTypes: { + error: { control: "text" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + type: { control: "select", options: [NotificationTypes.Change, NotificationTypes.Add] }, + }, + args: { + error: "", + task: { + orgName: "Acme, Inc.", + remainingTasksCount: 0, + }, + handleCloseNotification: () => alert("Close notification action triggered"), + handleOpenTasks: () => alert("Open tasks action triggered"), + i18n: { + loginSaveSuccess: "Login saved", + loginUpdateSuccess: "Login updated", + loginUpdateTaskSuccessAdditional: + "Thank you for making your organization more secure. You have 3 more passwords to update.", + loginUpdateTaskSuccess: + "Great job! You took the steps to make you and your organization more secure.", + nextSecurityTaskAction: "Change next password", + saveFailure: "Error saving", + saveFailureDetails: "Oh no! We couldn't save this. Try entering the details manually.", + view: "View", + }, + type: NotificationTypes.Change, + username: "Acme, Inc. Login", + theme: ThemeTypes.Light, + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=485-20160&m=dev", + }, + }, +} as Meta; + +const Template = (args: NotificationConfirmationContainerProps) => + NotificationConfirmationContainer({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/footer.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/footer.lit-stories.ts new file mode 100644 index 00000000000..953fb90d067 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/footer.lit-stories.ts @@ -0,0 +1,36 @@ +import { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; + +import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { + NotificationConfirmationFooter, + NotificationConfirmationFooterProps, +} from "../../../notification/confirmation/footer"; + +export default { + title: "Components/Notifications/Confirmation/Footer", + argTypes: { + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + }, + args: { + handleButtonClick: () => alert("Action button triggered"), + i18n: { + nextSecurityTaskAction: "Change next password", + }, + theme: ThemeTypes.Light, + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=32-4949&m=dev", + }, + }, +} as Meta; + +const Template = (args: NotificationConfirmationFooterProps) => + html`
${NotificationConfirmationFooter({ ...args })}
`; + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/message.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/message.lit-stories.ts new file mode 100644 index 00000000000..f01503b331f --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/confirmation/message.lit-stories.ts @@ -0,0 +1,37 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { + NotificationConfirmationMessage, + NotificationConfirmationMessageProps, +} from "../../../notification/confirmation/message"; + +export default { + title: "Components/Notifications/Confirmation/Message", + argTypes: { + buttonText: { control: "text" }, + message: { control: "text" }, + messageDetails: { control: "text" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + }, + args: { + buttonText: "View", + message: "[item name] updated in Bitwarden.", + messageDetails: "It was added to your vault.", + theme: ThemeTypes.Light, + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=485-20160&m=dev", + }, + }, +} as Meta; + +const Template = (args: NotificationConfirmationMessageProps) => + NotificationConfirmationMessage({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/container.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/container.lit-stories.ts new file mode 100644 index 00000000000..351c971ec0e --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/container.lit-stories.ts @@ -0,0 +1,61 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { ThemeTypes } from "@bitwarden/common/platform/enums"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; + +import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; +import { NotificationContainer, NotificationContainerProps } from "../../notification/container"; + +export default { + title: "Components/Notifications", + argTypes: { + error: { control: "text" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + type: { control: "select", options: [...Object.values(NotificationTypes)] }, + }, + args: { + error: "", + ciphers: [ + { + id: "1", + name: "Example Cipher", + type: CipherType.Login, + favorite: false, + reprompt: CipherRepromptType.None, + icon: { + imageEnabled: true, + image: "", + fallbackImage: "https://example.com/fallback.png", + icon: "icon-class", + }, + login: { username: "user@example.com" }, + }, + ], + i18n: { + loginSaveSuccess: "Login saved", + loginUpdateSuccess: "Login updated", + saveAction: "Save", + saveAsNewLoginAction: "Save as new login", + saveFailure: "Error saving", + saveFailureDetails: "Oh no! We couldn't save this. Try entering the details manually.", + updateLoginPrompt: "Update existing login?", + view: "View", + }, + type: NotificationTypes.Change, + username: "mockUsername", + theme: ThemeTypes.Light, + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/LEhqLAcBPY8uDKRfU99n9W/Autofill-notification-redesign?node-id=485-20160&m=dev", + }, + }, +} as Meta; + +const Template = (args: NotificationContainerProps) => NotificationContainer({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts index ea2bbdc2e15..29d9955ec64 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts @@ -7,7 +7,7 @@ import { NotificationFooter, NotificationFooterProps } from "../../notification/ import { mockFolderData, mockOrganizationData } from "../mock-data"; export default { - title: "Components/Notifications/Notification Footer", + title: "Components/Notifications/Footer", argTypes: { notificationType: { control: "select", diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/header.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/header.lit-stories.ts index 49cc1e6bd8d..0857c99130e 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/notification/header.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/header.lit-stories.ts @@ -12,7 +12,7 @@ type Args = { }; export default { - title: "Components/Notifications/Notification Header", + title: "Components/Notifications/Header", argTypes: { message: { control: "text" }, standalone: { control: "boolean" }, diff --git a/apps/browser/src/autofill/content/components/notification/confirmation-message.ts b/apps/browser/src/autofill/content/components/notification/confirmation-message.ts deleted file mode 100644 index d6f7ba3024d..00000000000 --- a/apps/browser/src/autofill/content/components/notification/confirmation-message.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { css } from "@emotion/css"; -import { html } from "lit"; - -import { Theme } from "@bitwarden/common/platform/enums"; - -import { themes } from "../constants/styles"; - -export function NotificationConfirmationMessage({ - buttonText, - confirmationMessage, - handleClick, - theme, -}: { - buttonText: string; - confirmationMessage: string; - handleClick: (e: Event) => void; - theme: Theme; -}) { - return html` - ${confirmationMessage} - ${buttonText} - `; -} - -const baseTextStyles = css` - flex-grow: 1; - overflow-x: hidden; - text-align: left; - text-overflow: ellipsis; - line-height: 24px; - font-family: "DM Sans", sans-serif; - font-size: 16px; -`; - -const notificationConfirmationMessageStyles = (theme: Theme) => css` - ${baseTextStyles} - color: ${themes[theme].text.main}; - font-weight: 400; -`; - -const notificationConfirmationButtonTextStyles = (theme: Theme) => css` - ${baseTextStyles} - color: ${themes[theme].primary[600]}; - font-weight: 700; - cursor: pointer; -`; diff --git a/apps/browser/src/autofill/content/components/notification/confirmation.ts b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts similarity index 63% rename from apps/browser/src/autofill/content/components/notification/confirmation.ts rename to apps/browser/src/autofill/content/components/notification/confirmation/body.ts index 8c213a7663f..55d257b36f4 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts @@ -1,12 +1,12 @@ import createEmotion from "@emotion/css/create-instance"; -import { html } from "lit"; +import { html, nothing } from "lit"; import { Theme } from "@bitwarden/common/platform/enums"; -import { themes } from "../constants/styles"; -import { PartyHorn, Warning } from "../icons"; +import { themes } from "../../constants/styles"; +import { PartyHorn, Keyhole, Warning } from "../../icons"; -import { NotificationConfirmationMessage } from "./confirmation-message"; +import { NotificationConfirmationMessage } from "./message"; export const componentClassPrefix = "notification-confirmation-body"; @@ -14,31 +14,41 @@ const { css } = createEmotion({ key: componentClassPrefix, }); -export function NotificationConfirmationBody({ - buttonText, - error, - confirmationMessage, - theme, - handleOpenVault, -}: { - error?: string; +export type NotificationConfirmationBodyProps = { buttonText: string; confirmationMessage: string; + error?: string; + messageDetails?: string; + tasksAreComplete?: boolean; theme: Theme; handleOpenVault: (e: Event) => void; -}) { - const IconComponent = !error ? PartyHorn : Warning; +}; + +export function NotificationConfirmationBody({ + buttonText, + confirmationMessage, + error, + messageDetails, + tasksAreComplete, + theme, + handleOpenVault, +}: NotificationConfirmationBodyProps) { + const IconComponent = tasksAreComplete ? Keyhole : !error ? PartyHorn : Warning; + + const showConfirmationMessage = confirmationMessage || buttonText || messageDetails; + return html`
${IconComponent({ theme })}
- ${confirmationMessage && buttonText + ${showConfirmationMessage ? NotificationConfirmationMessage({ - handleClick: handleOpenVault, - confirmationMessage, - theme, buttonText, + message: confirmationMessage, + messageDetails, + theme, + handleClick: handleOpenVault, }) - : null} + : nothing}
`; } diff --git a/apps/browser/src/autofill/content/components/notification/confirmation-container.ts b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts similarity index 66% rename from apps/browser/src/autofill/content/components/notification/confirmation-container.ts rename to apps/browser/src/autofill/content/components/notification/confirmation/container.ts index 0666859ac44..a071338af9a 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation-container.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts @@ -1,42 +1,67 @@ import { css } from "@emotion/css"; -import { html } from "lit"; +import { html, nothing } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { NotificationBarIframeInitData, - NotificationTypes, + NotificationTaskInfo, NotificationType, -} from "../../../notification/abstractions/notification-bar"; -import { themes, spacing } from "../constants/styles"; - -import { NotificationConfirmationBody } from "./confirmation"; + NotificationTypes, +} from "../../../../notification/abstractions/notification-bar"; +import { themes, spacing } from "../../constants/styles"; import { NotificationHeader, componentClassPrefix as notificationHeaderClassPrefix, -} from "./header"; +} from "../header"; + +import { NotificationConfirmationBody } from "./body"; +import { NotificationConfirmationFooter } from "./footer"; + +export type NotificationConfirmationContainerProps = NotificationBarIframeInitData & { + handleCloseNotification: (e: Event) => void; + handleOpenVault: (e: Event) => void; + handleOpenTasks: (e: Event) => void; +} & { + error?: string; + i18n: { [key: string]: string }; + task?: NotificationTaskInfo; + type: NotificationType; + username: string; +}; export function NotificationConfirmationContainer({ error, handleCloseNotification, handleOpenVault, + handleOpenTasks, i18n, + task, theme = ThemeTypes.Light, type, username, -}: NotificationBarIframeInitData & { - handleCloseNotification: (e: Event) => void; - handleOpenVault: (e: Event) => void; -} & { - error?: string; - i18n: { [key: string]: string }; - type: NotificationType; - username: string; -}) { +}: NotificationConfirmationContainerProps) { const headerMessage = getHeaderMessage(i18n, type, error); const confirmationMessage = getConfirmationMessage(i18n, username, type, error); const buttonText = error ? i18n.newItem : i18n.view; + let messageDetails: string | undefined; + let remainingTasksCount: number | undefined; + let tasksAreComplete: boolean = false; + + if (task) { + remainingTasksCount = task.remainingTasksCount || 0; + tasksAreComplete = remainingTasksCount === 0; + + messageDetails = + remainingTasksCount > 0 + ? chrome.i18n.getMessage("loginUpdateTaskSuccessAdditional", [ + task.orgName, + `${remainingTasksCount}`, + ]) + : chrome.i18n.getMessage("loginUpdateTaskSuccess", [task.orgName]); + } + return html`
${NotificationHeader({ @@ -47,10 +72,18 @@ export function NotificationConfirmationContainer({ ${NotificationConfirmationBody({ buttonText, confirmationMessage, - error: error, - handleOpenVault, + tasksAreComplete, + messageDetails, theme, + handleOpenVault, })} + ${remainingTasksCount + ? NotificationConfirmationFooter({ + i18n, + theme, + handleButtonClick: handleOpenTasks, + }) + : nothing}
`; } diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/footer.ts b/apps/browser/src/autofill/content/components/notification/confirmation/footer.ts new file mode 100644 index 00000000000..e245d22c8e8 --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/confirmation/footer.ts @@ -0,0 +1,59 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { ActionButton } from "../../buttons/action-button"; +import { spacing, themes } from "../../constants/styles"; +import { ExternalLink } from "../../icons"; + +export type NotificationConfirmationFooterProps = { + i18n: { [key: string]: string }; + theme: Theme; + handleButtonClick: (event: Event) => void; +}; + +export function NotificationConfirmationFooter({ + i18n, + theme, + handleButtonClick, +}: NotificationConfirmationFooterProps) { + const primaryButtonText = i18n.nextSecurityTaskAction; + + return html` +
+ ${ActionButton({ + handleClick: handleButtonClick, + buttonText: AdditionalTasksButtonContent({ buttonText: primaryButtonText, theme }), + theme, + })} +
+ `; +} + +const notificationConfirmationFooterStyles = ({ theme }: { theme: Theme }) => css` + background-color: ${themes[theme].background.alt}; + padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]}; + max-width: min-content; + + :last-child { + border-radius: 0 0 ${spacing["4"]} ${spacing["4"]}; + padding-bottom: ${spacing[4]}; + } +`; + +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 new file mode 100644 index 00000000000..c018371caff --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -0,0 +1,83 @@ +import { css } from "@emotion/css"; +import { html, nothing } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { themes, typography } from "../../constants/styles"; + +export type NotificationConfirmationMessageProps = { + buttonText?: string; + message?: string; + messageDetails?: string; + handleClick: (e: Event) => void; + theme: Theme; +}; + +export function NotificationConfirmationMessage({ + buttonText, + message, + messageDetails, + handleClick, + theme, +}: NotificationConfirmationMessageProps) { + return html` +
+ ${message || buttonText + ? html` + + ${message || nothing} + ${buttonText + ? html` + + ${buttonText} + + ` + : nothing} + + ` + : nothing} + ${messageDetails + ? html`
${messageDetails}
` + : nothing} +
+ `; +} + +const baseTextStyles = css` + flex-grow: 1; + overflow-x: hidden; + text-align: left; + text-overflow: ellipsis; + line-height: 24px; + font-family: "DM Sans", sans-serif; + font-size: 16px; +`; + +const notificationConfirmationMessageStyles = (theme: Theme) => css` + ${baseTextStyles} + + color: ${themes[theme].text.main}; + font-weight: 400; +`; + +const notificationConfirmationButtonTextStyles = (theme: Theme) => css` + ${baseTextStyles} + + color: ${themes[theme].primary[600]}; + font-weight: 700; + cursor: pointer; +`; + +const AdditionalMessageStyles = ({ theme }: { theme: Theme }) => css` + ${typography.body2} + + font-size: 14px; + color: ${themes[theme].text.muted}; +`; diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index 8d80dc9fb50..c29f58e116b 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -19,6 +19,18 @@ import { componentClassPrefix as notificationHeaderClassPrefix, } from "./header"; +export type NotificationContainerProps = NotificationBarIframeInitData & { + handleCloseNotification: (e: Event) => void; + handleSaveAction: (e: Event) => void; + handleEditOrUpdateAction: (e: Event) => void; +} & { + ciphers?: NotificationCipherData[]; + folders?: FolderView[]; + i18n: { [key: string]: string }; + organizations?: OrgView[]; + type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` +}; + export function NotificationContainer({ handleCloseNotification, handleEditOrUpdateAction, @@ -29,17 +41,7 @@ export function NotificationContainer({ organizations, theme = ThemeTypes.Light, type, -}: NotificationBarIframeInitData & { - handleCloseNotification: (e: Event) => void; - handleSaveAction: (e: Event) => void; - handleEditOrUpdateAction: (e: Event) => void; -} & { - ciphers?: NotificationCipherData[]; - folders?: FolderView[]; - i18n: { [key: string]: string }; - organizations?: OrgView[]; - type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` -}) { +}: NotificationContainerProps) { const headerMessage = getHeaderMessage(i18n, type); const showBody = true; diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index c138776ed0e..7e2fdab04d3 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -11,6 +11,11 @@ const NotificationTypes = { type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]; +type NotificationTaskInfo = { + orgName: string; + remainingTasksCount: number; +}; + type NotificationBarIframeInitData = { ciphers?: NotificationCipherData[]; folders?: FolderView[]; @@ -38,6 +43,7 @@ type NotificationBarWindowMessageHandlers = { }; export { + NotificationTaskInfo, NotificationTypes, NotificationType, NotificationBarIframeInitData, diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 139b4551a24..f544e75527c 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -7,7 +7,7 @@ import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background"; import { NotificationCipherData } from "../content/components/cipher/types"; import { OrgView } from "../content/components/common-types"; -import { NotificationConfirmationContainer } from "../content/components/notification/confirmation-container"; +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"; @@ -58,6 +58,9 @@ function getI18n() { loginSaveSuccessDetails: chrome.i18n.getMessage("loginSaveSuccessDetails"), loginUpdateSuccess: chrome.i18n.getMessage("loginUpdateSuccess"), loginUpdateSuccessDetails: chrome.i18n.getMessage("loginUpdatedSuccessDetails"), + loginUpdateTaskSuccess: chrome.i18n.getMessage("loginUpdateTaskSuccess"), + loginUpdateTaskSuccessAdditional: chrome.i18n.getMessage("loginUpdateTaskSuccessAdditional"), + nextSecurityTaskAction: chrome.i18n.getMessage("nextSecurityTaskAction"), newItem: chrome.i18n.getMessage("newItem"), never: chrome.i18n.getMessage("never"), notificationAddDesc: chrome.i18n.getMessage("notificationAddDesc"), @@ -369,6 +372,7 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) { error, username: username ?? i18n.typeLogin, handleOpenVault: (e) => cipherId && openViewVaultItemPopout(e, cipherId), + handleOpenTasks: () => {}, }), document.body, );