mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
Merge branch 'PM-12985-Reports' of github.com:bitwarden/clients into PM-12985-Reports
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { border, themes, typography, spacing } from "../constants/styles";
|
||||||
|
|
||||||
|
export function ActionButton({
|
||||||
|
buttonAction,
|
||||||
|
buttonText,
|
||||||
|
disabled = false,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
buttonAction: (e: Event) => void;
|
||||||
|
buttonText: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const handleButtonClick = (event: Event) => {
|
||||||
|
if (!disabled) {
|
||||||
|
buttonAction(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title=${buttonText}
|
||||||
|
class=${actionButtonStyles({ disabled, theme })}
|
||||||
|
@click=${handleButtonClick}
|
||||||
|
>
|
||||||
|
${buttonText}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css`
|
||||||
|
${typography.body2}
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: ${border.radius.full};
|
||||||
|
padding: ${spacing["1"]} ${spacing["3"]};
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
${disabled
|
||||||
|
? `
|
||||||
|
background-color: ${themes[theme].secondary["300"]};
|
||||||
|
color: ${themes[theme].text.muted};
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
background-color: ${themes[theme].primary["600"]};
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${themes[theme].text.contrast};
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
border-color: ${themes[theme].primary["700"]};
|
||||||
|
background-color: ${themes[theme].primary["700"]};
|
||||||
|
color: ${themes[theme].text.contrast};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { border, themes, typography, spacing } from "../constants/styles";
|
||||||
|
|
||||||
|
export function BadgeButton({
|
||||||
|
buttonAction,
|
||||||
|
buttonText,
|
||||||
|
disabled = false,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
buttonAction: (e: Event) => void;
|
||||||
|
buttonText: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const handleButtonClick = (event: Event) => {
|
||||||
|
if (!disabled) {
|
||||||
|
buttonAction(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title=${buttonText}
|
||||||
|
class=${badgeButtonStyles({ disabled, theme })}
|
||||||
|
@click=${handleButtonClick}
|
||||||
|
>
|
||||||
|
${buttonText}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const badgeButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css`
|
||||||
|
${typography.helperMedium}
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
border-radius: ${border.radius.full};
|
||||||
|
padding: ${spacing["1"]} ${spacing["2"]};
|
||||||
|
max-height: fit-content;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
${disabled
|
||||||
|
? `
|
||||||
|
border: 0.5px solid ${themes[theme].secondary["300"]};
|
||||||
|
background-color: ${themes[theme].secondary["300"]};
|
||||||
|
color: ${themes[theme].text.muted};
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
border: 0.5px solid ${themes[theme].primary["700"]};
|
||||||
|
background-color: ${themes[theme].primary["100"]};
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${themes[theme].primary["700"]};
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
border-color: ${themes[theme].primary["600"]};
|
||||||
|
background-color: ${themes[theme].primary["600"]};
|
||||||
|
color: ${themes[theme].text.contrast};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { spacing, themes } from "../constants/styles";
|
||||||
|
import { Close as CloseIcon } from "../icons";
|
||||||
|
|
||||||
|
export function CloseButton({
|
||||||
|
handleCloseNotification,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
handleCloseNotification: (e: Event) => void;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
return html`
|
||||||
|
<button type="button" class=${closeButtonStyles(theme)} @click=${handleCloseNotification}>
|
||||||
|
${CloseIcon({ theme })}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeButtonStyles = (theme: Theme) => css`
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: ${spacing["1"]};
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
border: 1px solid ${themes[theme].primary["600"]};
|
||||||
|
}
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { themes, typography, spacing } from "../constants/styles";
|
||||||
|
import { PencilSquare } from "../icons";
|
||||||
|
|
||||||
|
export function EditButton({
|
||||||
|
buttonAction,
|
||||||
|
buttonText,
|
||||||
|
disabled = false,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
buttonAction: (e: Event) => void;
|
||||||
|
buttonText: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title=${buttonText}
|
||||||
|
class=${editButtonStyles({ disabled, theme })}
|
||||||
|
@click=${(event: Event) => {
|
||||||
|
!disabled && buttonAction(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${PencilSquare({ disabled, theme })}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editButtonStyles = ({ disabled, theme }: { disabled?: boolean; theme: Theme }) => css`
|
||||||
|
${typography.helperMedium}
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: ${spacing["1"]};
|
||||||
|
background-color: transparent;
|
||||||
|
padding: ${spacing["1"]};
|
||||||
|
max-height: fit-content;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
${!disabled
|
||||||
|
? `
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
border-color: ${themes[theme].primary["600"]};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
width: 16px;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { BadgeButton } from "../../../content/components/buttons/badge-button";
|
||||||
|
import { EditButton } from "../../../content/components/buttons/edit-button";
|
||||||
|
import { NotificationTypes } from "../../../notification/abstractions/notification-bar";
|
||||||
|
|
||||||
|
export function CipherAction({
|
||||||
|
handleAction = () => {
|
||||||
|
/* no-op */
|
||||||
|
},
|
||||||
|
notificationType,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
handleAction?: (e: Event) => void;
|
||||||
|
notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
return notificationType === NotificationTypes.Change
|
||||||
|
? BadgeButton({
|
||||||
|
buttonAction: handleAction,
|
||||||
|
// @TODO localize
|
||||||
|
buttonText: "Update item",
|
||||||
|
theme,
|
||||||
|
})
|
||||||
|
: EditButton({
|
||||||
|
buttonAction: handleAction,
|
||||||
|
// @TODO localize
|
||||||
|
buttonText: "Edit item",
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { Globe } from "../../../content/components/icons";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} props.color contextual color override if no icon URI is available
|
||||||
|
* @param {string} props.size valid CSS `width` value, represents the width-basis of the graphic, with height maintaining original aspect-ratio
|
||||||
|
*/
|
||||||
|
export function CipherIcon({
|
||||||
|
color,
|
||||||
|
size,
|
||||||
|
theme,
|
||||||
|
uri,
|
||||||
|
}: {
|
||||||
|
color: string;
|
||||||
|
size: string;
|
||||||
|
theme: Theme;
|
||||||
|
uri?: string;
|
||||||
|
}) {
|
||||||
|
const iconClass = cipherIconStyle({ width: size });
|
||||||
|
|
||||||
|
return uri
|
||||||
|
? html`<img class=${iconClass} src=${uri} />`
|
||||||
|
: html`<span class=${iconClass}>${Globe({ color, theme })}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipherIconStyle = ({ width }: { width: string }) => css`
|
||||||
|
width: ${width};
|
||||||
|
height: fit-content;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { themes } from "../../../content/components/constants/styles";
|
||||||
|
import { Business, Family } from "../../../content/components/icons";
|
||||||
|
|
||||||
|
// @TODO connect data source to icon checks
|
||||||
|
// @TODO support other indicator types (attachments, etc)
|
||||||
|
export function CipherInfoIndicatorIcons({
|
||||||
|
isBusinessOrg,
|
||||||
|
isFamilyOrg,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
isBusinessOrg?: boolean;
|
||||||
|
isFamilyOrg?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const indicatorIcons = [
|
||||||
|
...(isBusinessOrg ? [Business({ color: themes[theme].text.muted, theme })] : []),
|
||||||
|
...(isFamilyOrg ? [Family({ color: themes[theme].text.muted, theme })] : []),
|
||||||
|
];
|
||||||
|
|
||||||
|
return indicatorIcons.length
|
||||||
|
? html` <span class=${cipherInfoIndicatorIconsStyles}> ${indicatorIcons} </span> `
|
||||||
|
: null; // @TODO null case should be handled by parent
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipherInfoIndicatorIconsStyles = css`
|
||||||
|
> svg {
|
||||||
|
width: fit-content;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { themes, typography } from "../../../content/components/constants/styles";
|
||||||
|
|
||||||
|
import { CipherInfoIndicatorIcons } from "./cipher-indicator-icons";
|
||||||
|
import { CipherData } from "./types";
|
||||||
|
|
||||||
|
// @TODO support other cipher types (card, identity, notes, etc)
|
||||||
|
export function CipherInfo({ cipher, theme }: { cipher: CipherData; theme: Theme }) {
|
||||||
|
const { name, login } = cipher;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div>
|
||||||
|
<span class=${cipherInfoPrimaryTextStyles(theme)}>
|
||||||
|
${[name, CipherInfoIndicatorIcons({ theme })]}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
${login?.username
|
||||||
|
? html`<span class=${cipherInfoSecondaryTextStyles(theme)}>${login.username}</span>`
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipherInfoPrimaryTextStyles = (theme: Theme) => css`
|
||||||
|
${typography.body2}
|
||||||
|
|
||||||
|
gap: 2px;
|
||||||
|
display: flex;
|
||||||
|
display: block;
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: ${themes[theme].text.main};
|
||||||
|
font-weight: 500;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const cipherInfoSecondaryTextStyles = (theme: Theme) => css`
|
||||||
|
${typography.helperMedium}
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: ${themes[theme].text.muted};
|
||||||
|
font-weight: 400;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { spacing, themes } from "../../../content/components/constants/styles";
|
||||||
|
import {
|
||||||
|
NotificationType,
|
||||||
|
NotificationTypes,
|
||||||
|
} from "../../../notification/abstractions/notification-bar";
|
||||||
|
|
||||||
|
import { CipherAction } from "./cipher-action";
|
||||||
|
import { CipherIcon } from "./cipher-icon";
|
||||||
|
import { CipherInfo } from "./cipher-info";
|
||||||
|
import { CipherData } from "./types";
|
||||||
|
|
||||||
|
const cipherIconWidth = "24px";
|
||||||
|
|
||||||
|
export function CipherItem({
|
||||||
|
cipher,
|
||||||
|
handleAction,
|
||||||
|
notificationType,
|
||||||
|
theme = ThemeTypes.Light,
|
||||||
|
}: {
|
||||||
|
cipher: CipherData;
|
||||||
|
handleAction?: (e: Event) => void;
|
||||||
|
notificationType?: NotificationType;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const { icon } = cipher;
|
||||||
|
const uri = (icon.imageEnabled && icon.image) || undefined;
|
||||||
|
|
||||||
|
let cipherActionButton = null;
|
||||||
|
|
||||||
|
if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) {
|
||||||
|
cipherActionButton = html`<div>
|
||||||
|
${CipherAction({ handleAction, notificationType, theme })}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class=${cipherItemStyles}>
|
||||||
|
${CipherIcon({ color: themes[theme].text.muted, size: cipherIconWidth, theme, uri })}
|
||||||
|
${CipherInfo({ theme, cipher })}
|
||||||
|
</div>
|
||||||
|
${cipherActionButton}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipherItemStyles = css`
|
||||||
|
gap: ${spacing["2"]};
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
|
||||||
|
> img,
|
||||||
|
> span {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
max-width: calc(100% - ${cipherIconWidth} - ${spacing["2"]});
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export * from "./cipher-action";
|
||||||
|
export * from "./cipher-icon";
|
||||||
|
export * from "./cipher-indicator-icons";
|
||||||
|
export * from "./cipher-info";
|
||||||
|
export * from "./cipher-item";
|
||||||
44
apps/browser/src/autofill/content/components/cipher/types.ts
Normal file
44
apps/browser/src/autofill/content/components/cipher/types.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const CipherTypes = {
|
||||||
|
Login: 1,
|
||||||
|
SecureNote: 2,
|
||||||
|
Card: 3,
|
||||||
|
Identity: 4,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type CipherType = (typeof CipherTypes)[keyof typeof CipherTypes];
|
||||||
|
|
||||||
|
const CipherRepromptTypes = {
|
||||||
|
None: 0,
|
||||||
|
Password: 1,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type CipherRepromptType = (typeof CipherRepromptTypes)[keyof typeof CipherRepromptTypes];
|
||||||
|
|
||||||
|
export type WebsiteIconData = {
|
||||||
|
imageEnabled: boolean;
|
||||||
|
image: string;
|
||||||
|
fallbackImage: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CipherData = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: CipherType;
|
||||||
|
reprompt: CipherRepromptType;
|
||||||
|
favorite: boolean;
|
||||||
|
icon: WebsiteIconData;
|
||||||
|
accountCreationFieldType?: string;
|
||||||
|
login?: {
|
||||||
|
username: string;
|
||||||
|
passkey: {
|
||||||
|
rpName: string;
|
||||||
|
userName: string;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
card?: string;
|
||||||
|
identity?: {
|
||||||
|
fullName: string;
|
||||||
|
username?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
206
apps/browser/src/autofill/content/components/constants/styles.ts
Normal file
206
apps/browser/src/autofill/content/components/constants/styles.ts
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
const lightTheme = {
|
||||||
|
transparent: {
|
||||||
|
hover: `rgb(0 0 0 / 0.02)`,
|
||||||
|
},
|
||||||
|
shadow: `rgba(168 179 200)`,
|
||||||
|
primary: {
|
||||||
|
100: `rgba(219, 229, 246)`,
|
||||||
|
300: `rgba(121, 161, 233)`,
|
||||||
|
600: `rgba(23, 93, 220)`,
|
||||||
|
700: `rgba(26, 65, 172)`,
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
100: `rgba(230, 233, 239)`,
|
||||||
|
300: `rgba(168, 179, 200)`,
|
||||||
|
500: `rgba(90, 109, 145)`,
|
||||||
|
600: `rgba(83, 99, 131)`,
|
||||||
|
700: `rgba(63, 75, 99)`,
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
100: `rgba(219, 229, 246)`,
|
||||||
|
600: `rgba(121, 161, 233)`,
|
||||||
|
700: `rgba(26, 65, 172)`,
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
100: `rgba(255, 236, 239)`,
|
||||||
|
600: `rgba(203, 38, 58)`,
|
||||||
|
700: `rgba(149, 27, 42)`,
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
100: `rgba(255, 248, 228)`,
|
||||||
|
600: `rgba(255, 191, 0)`,
|
||||||
|
700: `rgba(172, 88, 0)`,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
100: `rgba(219, 229, 246)`,
|
||||||
|
600: `rgba(121, 161, 233)`,
|
||||||
|
700: `rgba(26, 65, 172)`,
|
||||||
|
},
|
||||||
|
art: {
|
||||||
|
primary: `rgba(2, 15, 102)`,
|
||||||
|
accent: `rgba(44, 221, 223)`,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
main: `rgba(27, 32, 41)`,
|
||||||
|
muted: `rgba(90, 109, 145)`,
|
||||||
|
contrast: `rgba(255, 255, 255)`,
|
||||||
|
alt2: `rgba(255, 255, 255)`,
|
||||||
|
code: `rgba(192, 17, 118)`,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
DEFAULT: `rgba(255, 255, 255)`,
|
||||||
|
alt: `rgba(243, 246, 249)`,
|
||||||
|
alt2: `rgba(23, 92, 219)`,
|
||||||
|
alt3: `rgba(26, 65, 172)`,
|
||||||
|
alt4: `rgba(2, 15, 102)`,
|
||||||
|
},
|
||||||
|
brandLogo: `rgba(23, 93, 220)`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const darkTheme = {
|
||||||
|
transparent: {
|
||||||
|
hover: `rgb(255 255 255 / 0.02)`,
|
||||||
|
},
|
||||||
|
shadow: `rgba(0, 0, 0)`,
|
||||||
|
primary: {
|
||||||
|
100: `rgba(26, 39, 78)`,
|
||||||
|
300: `rgba(26, 65, 172)`,
|
||||||
|
600: `rgba(101, 171, 255)`,
|
||||||
|
700: `rgba(170, 195, 239)`,
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
100: `rgba(48, 57, 70)`,
|
||||||
|
300: `rgba(82, 91, 106)`,
|
||||||
|
500: `rgba(121, 128, 142)`,
|
||||||
|
600: `rgba(143, 152, 166)`,
|
||||||
|
700: `rgba(158, 167, 181)`,
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
100: `rgba(11, 111, 21)`,
|
||||||
|
600: `rgba(107, 241, 120)`,
|
||||||
|
700: `rgba(191, 236, 195)`,
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
100: `rgba(149, 27, 42)`,
|
||||||
|
600: `rgba(255, 78, 99)`,
|
||||||
|
700: `rgba(255, 236, 239)`,
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
100: `rgba(172, 88, 0)`,
|
||||||
|
600: `rgba(255, 191, 0)`,
|
||||||
|
700: `rgba(255, 248, 228)`,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
100: `rgba(26, 65, 172)`,
|
||||||
|
600: `rgba(121, 161, 233)`,
|
||||||
|
700: `rgba(219, 229, 246)`,
|
||||||
|
},
|
||||||
|
art: {
|
||||||
|
primary: `rgba(243, 246, 249)`,
|
||||||
|
accent: `rgba(44, 221, 233)`,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
main: `rgba(243, 246, 249)`,
|
||||||
|
muted: `rgba(136, 152, 181)`,
|
||||||
|
contrast: `rgba(18 26 39)`,
|
||||||
|
alt2: `rgba(255, 255, 255)`,
|
||||||
|
code: `rgba(255, 143, 208)`,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
DEFAULT: `rgba(32, 39, 51)`,
|
||||||
|
alt: `rgba(18, 26, 39)`,
|
||||||
|
alt2: `rgba(47, 52, 61)`,
|
||||||
|
alt3: `rgba(48, 57, 70)`,
|
||||||
|
alt4: `rgba(18, 26, 39)`,
|
||||||
|
},
|
||||||
|
brandLogo: `rgba(255, 255, 255)`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const themes = {
|
||||||
|
light: lightTheme,
|
||||||
|
dark: darkTheme,
|
||||||
|
|
||||||
|
// For compatibility
|
||||||
|
system: lightTheme,
|
||||||
|
nord: lightTheme,
|
||||||
|
solarizedDark: darkTheme,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const spacing = {
|
||||||
|
4: `16px`,
|
||||||
|
3: `12px`,
|
||||||
|
2: `8px`,
|
||||||
|
"1.5": `6px`,
|
||||||
|
1: `4px`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const border = {
|
||||||
|
radius: {
|
||||||
|
large: `8px`,
|
||||||
|
full: `9999px`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const typography = {
|
||||||
|
body1: `
|
||||||
|
line-height: 24px;
|
||||||
|
font-family: "DM Sans", sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
`,
|
||||||
|
body2: `
|
||||||
|
line-height: 20px;
|
||||||
|
font-family: "DM Sans", sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
`,
|
||||||
|
helperMedium: `
|
||||||
|
line-height: 16px;
|
||||||
|
font-family: "DM Sans", sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ruleNames = {
|
||||||
|
fill: "fill",
|
||||||
|
stroke: "stroke",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type RuleName = (typeof ruleNames)[keyof typeof ruleNames];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `color` is an intentionally generic name here, since either fill or stroke may apply, due to
|
||||||
|
* inconsistent SVG construction. This consequently precludes dynamic multi-colored icons here.
|
||||||
|
*/
|
||||||
|
export const buildIconColorRule = (color: string, rule: RuleName = ruleNames.fill) => `
|
||||||
|
${rule}: ${color};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function scrollbarStyles(theme: Theme) {
|
||||||
|
return {
|
||||||
|
default: `
|
||||||
|
/* FireFox & Chrome support */
|
||||||
|
scrollbar-color: ${themes[theme].secondary["500"]} ${themes[theme].background.alt};
|
||||||
|
`,
|
||||||
|
safari: `
|
||||||
|
/* Safari Support */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-width: 4px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border-color: transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
background-color: ${themes[theme].secondary["500"]};
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
${themes[theme].background.alt};
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
${themes[theme].secondary["600"]};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
||||||
121
apps/browser/src/autofill/content/components/dropdown-menu.ts
Normal file
121
apps/browser/src/autofill/content/components/dropdown-menu.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html, TemplateResult } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { border, themes, typography, spacing } from "./constants/styles";
|
||||||
|
import { AngleDown } from "./icons";
|
||||||
|
|
||||||
|
export function DropdownMenu({
|
||||||
|
buttonText,
|
||||||
|
icon,
|
||||||
|
disabled = false,
|
||||||
|
selectAction,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
selectAction?: (e: Event) => void;
|
||||||
|
buttonText: string;
|
||||||
|
icon?: TemplateResult;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
// @TODO placeholder/will not work; make stateful
|
||||||
|
const showDropdown = false;
|
||||||
|
const handleButtonClick = (event: Event) => {
|
||||||
|
// if (!disabled) {
|
||||||
|
// // show dropdown
|
||||||
|
// showDropdown = !showDropdown;
|
||||||
|
// this.requestUpdate();
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
const dropdownMenuItems: TemplateResult[] = [];
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class=${dropdownContainerStyles}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title=${buttonText}
|
||||||
|
class=${dropdownButtonStyles({ disabled, theme })}
|
||||||
|
@click=${handleButtonClick}
|
||||||
|
>
|
||||||
|
${icon ?? null}
|
||||||
|
<span class=${dropdownButtonTextStyles}>${buttonText}</span>
|
||||||
|
${AngleDown({ color: themes[theme].text.muted, theme })}
|
||||||
|
</button>
|
||||||
|
${showDropdown
|
||||||
|
? html` <div class=${dropdownMenuStyles({ theme })}>${dropdownMenuItems}</div> `
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconSize = "15px";
|
||||||
|
|
||||||
|
const dropdownContainerStyles = css`
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div,
|
||||||
|
> button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const dropdownButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css`
|
||||||
|
${typography.body2}
|
||||||
|
|
||||||
|
font-weight: 400;
|
||||||
|
gap: ${spacing["1.5"]};
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: ${border.radius.full};
|
||||||
|
padding: ${spacing["1"]} ${spacing["2"]};
|
||||||
|
max-height: fit-content;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
max-width: ${iconSize};
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
${disabled
|
||||||
|
? `
|
||||||
|
border: 1px solid ${themes[theme].secondary["300"]};
|
||||||
|
background-color: ${themes[theme].secondary["300"]};
|
||||||
|
color: ${themes[theme].text.muted};
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
border: 1px solid ${themes[theme].text.muted};
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${themes[theme].text.muted};
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
border-color: ${themes[theme].secondary["700"]};
|
||||||
|
background-color: ${themes[theme].secondary["100"]};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const dropdownButtonTextStyles = css`
|
||||||
|
max-width: calc(100% - ${iconSize} - ${iconSize});
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const dropdownMenuStyles = ({ theme }: { theme: Theme }) => css`
|
||||||
|
color: ${themes[theme].text.main};
|
||||||
|
border: 1px solid ${themes[theme].secondary["500"]};
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-clip: padding-box;
|
||||||
|
background-color: ${themes[theme].background.DEFAULT};
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
position: absolute;
|
||||||
|
overflow-y: auto;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function AngleDown({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 12" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
d="M12.004 11.244a2.705 2.705 0 0 1-1.75-.644L.266 2.154a.76.76 0 0 1-.263-.51.75.75 0 0 1 1.233-.637l9.99 8.445a1.186 1.186 0 0 0 1.565 0l10-8.54a.751.751 0 0 1 .973 1.141l-10 8.538a2.703 2.703 0 0 1-1.76.653Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { Shield } from "./shield";
|
||||||
|
|
||||||
|
export function BrandIconContainer({ iconLink, theme }: { iconLink?: URL; theme: Theme }) {
|
||||||
|
const Icon = html`<div class=${brandIconContainerStyles}>${Shield({ theme })}</div>`;
|
||||||
|
|
||||||
|
return iconLink ? html`<a href="${iconLink}" target="_blank" rel="noreferrer">${Icon}</a>` : Icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
const brandIconContainerStyles = css`
|
||||||
|
> svg {
|
||||||
|
width: 20px;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function Business({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M6.015 16.482a3.007 3.007 0 1 0 0-6.015 3.007 3.007 0 0 0 0 6.015Zm0 1.504a4.51 4.51 0 1 0 0-9.022 4.51 4.51 0 0 0 0 9.022Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M10.439 22.497c-.548-2.805-2.51-4.511-4.427-4.511-1.917 0-3.879 1.706-4.426 4.51h8.853Zm-8.934.525v-.002.002ZM.659 24h10.466c.645 0 .984-.424.888-1.18-.457-3.591-2.97-6.338-6-6.338-3.032 0-5.544 2.747-6.001 6.339-.066.511.143 1.18.647 1.18Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M7.46 1.387v7.577a.694.694 0 1 1-1.387 0V.97c0-.536.434-.971.97-.971H23.03c.536 0 .971.435.971.971v20.496a.971.971 0 0 1-.971.971h-11a.694.694 0 0 1 0-1.387h10.584V1.387H7.46Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.stroke))}
|
||||||
|
stroke-linecap="round"
|
||||||
|
d="M14.033 3.953h2.007M9.522 3.953h2.007M18.544 3.953h2.007M14.033 8.464h2.007M9.522 8.464h2.007M18.544 8.464h2.007M14.033 12.975h2.007M9.522 12.975h2.007M18.544 12.975h2.007M14.033 17.485h2.007M18.544 17.485h2.007"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
27
apps/browser/src/autofill/content/components/icons/close.ts
Normal file
27
apps/browser/src/autofill/content/components/icons/close.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function Close({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
d="m19.809 19.21-8.487-8.226a.592.592 0 0 1 0-.852l8.382-8.13a.594.594 0 0 0 .175-.423.593.593 0 0 0-.182-.42.632.632 0 0 0-.872-.007l-8.383 8.126a.634.634 0 0 1-.88 0l-8.41-8.135a.642.642 0 0 0-.887-.008.602.602 0 0 0-.182.431.588.588 0 0 0 .19.428l8.41 8.139a.592.592 0 0 1 0 .852l-8.5 8.225a.605.605 0 0 0-.183.427c0 .16.066.313.183.426a.635.635 0 0 0 .88-.001l8.5-8.226a.635.635 0 0 1 .88 0l8.488 8.226a.64.64 0 0 0 .887.008.605.605 0 0 0 .182-.43.591.591 0 0 0-.19-.429h-.001Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function ExclamationTriangle({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 22" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
d="M21.627 21.877H2.373a2.28 2.28 0 0 1-1.195-.326 2.394 2.394 0 0 1-.869-.908 2.433 2.433 0 0 1 .015-2.404L9.951 1.33c.211-.368.511-.672.87-.883a2.322 2.322 0 0 1 2.357 0c.36.211.66.515.871.882l9.627 16.911a2.442 2.442 0 0 1 .015 2.404 2.39 2.39 0 0 1-.87.908c-.362.217-.775.33-1.194.326ZM12 1.677a.844.844 0 0 0-.436.115.883.883 0 0 0-.322.326l-9.625 16.91a.846.846 0 0 0 0 .844.876.876 0 0 0 .322.334.84.84 0 0 0 .44.117h19.248a.837.837 0 0 0 .44-.117.882.882 0 0 0 .322-.334.846.846 0 0 0 0-.843L12.758 2.118a.89.89 0 0 0-.322-.326.837.837 0 0 0-.436-.114Zm0 13.309a.735.735 0 0 1-.53-.228.794.794 0 0 1-.22-.55V7.105a.79.79 0 0 1 .22-.55.735.735 0 0 1 1.06 0c.14.146.22.344.22.55v7.105a.79.79 0 0 1-.22.55.74.74 0 0 1-.53.227Zm0 3.84c.491 0 .89-.412.89-.92 0-.51-.399-.922-.89-.922s-.89.412-.89.921c0 .51.399.922.89.922Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
27
apps/browser/src/autofill/content/components/icons/family.ts
Normal file
27
apps/browser/src/autofill/content/components/icons/family.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function Family({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 18" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
d="M20.535 8.219a4.5 4.5 0 1 0-5.07 0c-.34.187-.657.414-.945.675a3 3 0 0 0-5.04 0 5.745 5.745 0 0 0-.945-.675 4.5 4.5 0 1 0-5.07 0A7.5 7.5 0 0 0 0 13.829C0 14.34.135 15 .645 15H8.07a6.6 6.6 0 0 0-.57 2.055c0 .405.105.945.48.945h7.83c.48 0 .735-.345.66-.945A7.503 7.503 0 0 0 15.93 15h7.17c.645 0 .975-.42.885-1.17a7.5 7.5 0 0 0-3.45-5.61ZM15 4.499a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm-3 4.5a1.5 1.5 0 0 1 1.5 1.11c.016.13.016.26 0 .39a1.5 1.5 0 0 1-.99 1.395 1.29 1.29 0 0 1-1.02 0 1.5 1.5 0 0 1-.99-1.395 1.778 1.778 0 0 1 0-.39A1.5 1.5 0 0 1 12 9Zm-9-4.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm-1.425 9C2.13 10.71 4.08 9 6.075 9A4.035 4.035 0 0 1 9 10.499a3 3 0 0 0 .945 2.145A4.499 4.499 0 0 0 9 13.5H1.575Zm13.29 3h-5.73A5.07 5.07 0 0 1 9.75 15 2.865 2.865 0 0 1 12 13.5h.15a2.82 2.82 0 0 1 2.16 1.5c.27.465.457.972.555 1.5Zm.135-3a4.5 4.5 0 0 0-.945-.825A3 3 0 0 0 15 10.5 4.08 4.08 0 0 1 18 9a5.01 5.01 0 0 1 4.41 4.5H15Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
27
apps/browser/src/autofill/content/components/icons/folder.ts
Normal file
27
apps/browser/src/autofill/content/components/icons/folder.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function Folder({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 22" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
d="M21.78 3.823h-7.97a.823.823 0 0 1-.584-.265.872.872 0 0 1-.23-.61v-.396A2.321 2.321 0 0 0 12.348.93a2.214 2.214 0 0 0-1.577-.681H2.22A2.214 2.214 0 0 0 .645.93 2.321 2.321 0 0 0 0 2.552v16.88c-.003.608.23 1.191.647 1.624.417.432.984.677 1.576.681l19.554.016c.288 0 .574-.058.84-.171.267-.113.51-.278.714-.487a2.31 2.31 0 0 0 .497-.756c.115-.284.174-.588.172-.894V6.129c0-.606-.233-1.189-.648-1.62a2.223 2.223 0 0 0-1.572-.686ZM2.223 1.678h8.552c.22.006.43.101.582.265a.865.865 0 0 1 .23.61v.396c0 .606.234 1.189.65 1.62.416.432.983.677 1.576.684h7.97c.22.006.43.1.582.264a.86.86 0 0 1 .23.607v1.707a.56.56 0 0 1-.16.389.535.535 0 0 1-.38.159H1.951a.531.531 0 0 1-.381-.16.558.558 0 0 1-.16-.389V2.551a.867.867 0 0 1 .23-.609.82.82 0 0 1 .582-.265ZM22.34 20.08a.779.779 0 0 1-.558.238l-19.558-.014a.823.823 0 0 1-.582-.264.864.864 0 0 1-.23-.608v-9.065a.566.566 0 0 1 .16-.39.547.547 0 0 1 .38-.16h20.104c.143-.001.28.057.382.16a.561.561 0 0 1 .16.39v9.083a.903.903 0 0 1-.259.63h.001Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
28
apps/browser/src/autofill/content/components/icons/globe.ts
Normal file
28
apps/browser/src/autofill/content/components/icons/globe.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function Globe({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M19.958 18.85A10.476 10.476 0 0 1 12 22.5c-1.674 0-3.256-.392-4.66-1.088l.084-.195c.03-.07.06-.135.084-.194l.086-.2c.11-.262.23-.548.372-.832.18-.36.374-.67.58-.875.035-.035.137-.096.372-.146.225-.049.512-.076.855-.095.187-.01.392-.018.607-.026.542-.02 1.15-.043 1.72-.118.426-.057.769-.169 1.025-.349.27-.19.422-.44.477-.713a1.531 1.531 0 0 0-.04-.697 3.99 3.99 0 0 0-.162-.458l-.014-.037a5.335 5.335 0 0 0-.198-.425c-.17-.34-.321-.64-.39-1.395-.04-.442.122-.939.34-1.463l.121-.283c.069-.158.137-.317.191-.456.086-.22.174-.482.174-.728 0-.242-.077-.5-.18-.731a3.271 3.271 0 0 0-.434-.704c-.335-.416-.863-.86-1.485-.86-.597 0-1.367.217-2.02.473-.334.13-.652.278-.922.425-.262.143-.507.3-.67.46-1.262 1.236-2.01 1.593-2.458 1.619-.376.021-.649-.194-.999-.676-.085-.117-.168-.24-.257-.373l-.01-.014c-.084-.126-.174-.26-.268-.39-.192-.269-.42-.55-.708-.771a1.946 1.946 0 0 0-1.085-.416 2.206 2.206 0 0 0-.393.011 10.477 10.477 0 0 1 2.7-5.06c.074.44.198.804.369 1.1.311.54.762.822 1.258.921.55.11 1.163-.08 1.711-.277.17-.062.337-.126.505-.19.42-.16.84-.321 1.287-.439.35-.092.788-.073 1.3.017.36.062.72.151 1.088.242.16.04.323.08.488.119.515.12 1.063.228 1.542.19.51-.041.996-.254 1.27-.802.171-.343.168-.67.03-.966-.117-.253-.324-.457-.475-.604-.352-.344-.558-.55-.558-.881 0-.161.05-.258.122-.338.086-.095.219-.18.411-.272.08-.039.162-.075.25-.114l.048-.02c.102-.046.214-.097.32-.153.064-.033.133-.072.202-.119 2.628.96 4.765 2.941 5.932 5.462a12.186 12.186 0 0 0-.043-.005c-.514-.06-1.002-.07-1.442.08-.463.158-.82.472-1.111.951a9.428 9.428 0 0 1-.628.862 82.02 82.02 0 0 0-.13.165c-.281.362-.585.765-.788 1.182-.204.42-.332.908-.191 1.418.14.512.524.95 1.136 1.332.134.085.304.145.427.188l.027.01c.146.052.264.095.363.15a.422.422 0 0 1 .17.147c.021.038.05.112.029.264a7.15 7.15 0 0 0-.07 1.33c.022.375.096.759.295 1.04.16.224.162.542.147 1.01v.014c-.007.198-.015.447.029.666.03.149.089.312.203.45Zm1.843.075A11.945 11.945 0 0 0 24 12c0-6.627-5.373-12-12-12S0 5.373 0 12c0 4.495 2.471 8.413 6.13 10.468v.12h.218A11.946 11.946 0 0 0 12 24c4.011 0 7.563-1.968 9.742-4.991a.73.73 0 0 0 .065-.08l-.006-.004Zm-15.253 2.05.059-.135.074-.17.078-.185c.11-.262.246-.584.403-.896.193-.387.439-.803.749-1.111.222-.221.532-.327.818-.388a6.64 6.64 0 0 1 .994-.114c.22-.012.445-.02.672-.029.525-.02 1.062-.039 1.587-.108.346-.046.532-.127.626-.193.081-.057.103-.108.112-.155a.653.653 0 0 0-.027-.285c-.032-.12-.078-.234-.128-.358l-.014-.035a3.953 3.953 0 0 0-.141-.298c-.183-.362-.422-.837-.508-1.776-.063-.685.187-1.366.406-1.892l.137-.32c.062-.143.117-.27.166-.398.085-.218.113-.34.113-.402 0-.066-.025-.192-.102-.364a2.38 2.38 0 0 0-.314-.507c-.283-.352-.58-.524-.783-.524-.427 0-1.07.167-1.692.41-.303.12-.587.251-.82.378-.24.131-.397.242-.47.314-1.271 1.244-2.23 1.827-3.036 1.873-.878.05-1.406-.532-1.78-1.045-.095-.132-.188-.27-.275-.4l-.006-.01c-.087-.13-.17-.252-.255-.371-.176-.245-.344-.442-.527-.583a1.047 1.047 0 0 0-.592-.23 1.54 1.54 0 0 0-.495.058 10.494 10.494 0 0 0 4.971 10.25Zm14.072-2.978c0-.07.002-.15.005-.247l.002-.059c.014-.392.036-1.01-.314-1.502-.05-.07-.111-.244-.13-.572a6.267 6.267 0 0 1 .063-1.158c.042-.314-.005-.595-.142-.833a1.308 1.308 0 0 0-.51-.481 3.197 3.197 0 0 0-.497-.213l-.004-.001a1.74 1.74 0 0 1-.278-.112c-.501-.314-.686-.592-.746-.809-.06-.217-.02-.468.134-.786.156-.321.403-.656.689-1.022l.119-.152.001-.002c.242-.308.5-.636.696-.958.206-.339.41-.491.632-.567.244-.084.566-.094 1.048-.038.192.023.372.05.537.08.373 1.077.575 2.232.575 3.435 0 2.23-.695 4.297-1.88 5.997ZM14.33 1.759C13.58 1.59 12.8 1.5 12 1.5c-2.567 0-4.918.92-6.742 2.45.033.693.163 1.14.325 1.42.177.306.401.438.655.489.272.054.657-.035 1.228-.242.14-.05.29-.107.447-.167.434-.166.925-.354 1.422-.485.541-.143 1.133-.096 1.685 0 .392.068.802.17 1.184.264.153.038.301.074.442.107.52.122.942.195 1.265.17.294-.024.443-.12.537-.307.053-.107.037-.147.02-.185-.038-.08-.12-.175-.289-.34a12.25 12.25 0 0 0-.059-.056c-.295-.285-.77-.743-.77-1.468 0-.393.138-.704.356-.944.186-.204.418-.343.624-.447Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
12
apps/browser/src/autofill/content/components/icons/index.ts
Normal file
12
apps/browser/src/autofill/content/components/icons/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export { AngleDown } from "./angle-down";
|
||||||
|
export { BrandIconContainer } from "./brand-icon-container";
|
||||||
|
export { Business } from "./business";
|
||||||
|
export { Close } from "./close";
|
||||||
|
export { ExclamationTriangle } from "./exclamation-triangle";
|
||||||
|
export { Family } from "./family";
|
||||||
|
export { Folder } from "./folder";
|
||||||
|
export { Globe } from "./globe";
|
||||||
|
export { PartyHorn } from "./party-horn";
|
||||||
|
export { PencilSquare } from "./pencil-square";
|
||||||
|
export { Shield } from "./shield";
|
||||||
|
export { User } from "./user";
|
||||||
174
apps/browser/src/autofill/content/components/icons/party-horn.ts
Normal file
174
apps/browser/src/autofill/content/components/icons/party-horn.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
// This icon has static multi-colors for each theme
|
||||||
|
export function PartyHorn({ theme }: { theme: Theme }) {
|
||||||
|
if (theme === ThemeTypes.Dark) {
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" fill="none">
|
||||||
|
<path
|
||||||
|
fill="#030C1B"
|
||||||
|
d="M33.044 37.587 5.716 48.972a2.275 2.275 0 0 1-2.975-2.975L14.127 18.67a4.31 4.31 0 0 1 5.217-2.47l.77.23a22.625 22.625 0 0 1 15.17 15.17l.23.77a4.31 4.31 0 0 1-2.47 5.217Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#FFBF00"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M15.947 44.71a47.282 47.282 0 0 1-4.768-4.175 47.292 47.292 0 0 1-4.175-4.768l.48-1.154a45.963 45.963 0 0 0 4.457 5.16 45.967 45.967 0 0 0 5.16 4.456l-1.154.481ZM11.4 46.604a56.295 56.295 0 0 1-6.292-6.291l-.463 1.112a57.493 57.493 0 0 0 5.642 5.643l1.113-.464Zm10.65-4.437c-2.38-1.42-4.765-3.271-7-5.505-2.233-2.234-4.084-4.62-5.504-6.999l.52-1.25c1.414 2.52 3.347 5.087 5.747 7.487 2.4 2.4 4.966 4.332 7.486 5.746l-1.25.52Zm-9.634-19.393c.894 3.259 3.09 6.93 6.342 10.181 3.251 3.252 6.922 5.448 10.18 6.341l1.747-.727a12.588 12.588 0 0 1-1.756-.396c-2.975-.885-6.364-2.934-9.41-5.98-3.045-3.045-5.094-6.434-5.98-9.41a12.584 12.584 0 0 1-.395-1.755l-.728 1.746Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#0E3377"
|
||||||
|
stroke="#91B4F2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
d="M27.985 23.73c2.174 2.174 3.79 4.462 4.653 6.387.432.965.663 1.811.699 2.491.035.68-.125 1.13-.4 1.406-.276.275-.726.436-1.406.4-.68-.035-1.526-.267-2.49-.699-1.926-.863-4.214-2.478-6.389-4.653-2.175-2.175-3.79-4.463-4.653-6.388-.432-.965-.664-1.811-.7-2.49-.035-.68.126-1.131.401-1.407.275-.275.726-.436 1.406-.4.68.036 1.526.267 2.49.7 1.926.862 4.214 2.478 6.389 4.652Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke="#91B4F2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
d="M33.044 37.587 5.716 48.972a2.275 2.275 0 0 1-2.975-2.975L14.127 18.67a4.31 4.31 0 0 1 5.217-2.47l.77.23a22.625 22.625 0 0 1 15.17 15.17l.23.77a4.31 4.31 0 0 1-2.47 5.217Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#91B4F2"
|
||||||
|
d="M25.46 2.47a.407.407 0 0 1 .793 0l.02.086a3.232 3.232 0 0 0 2.415 2.414l.086.02a.407.407 0 0 1 0 .793l-.086.02a3.232 3.232 0 0 0-2.414 2.415l-.02.086a.407.407 0 0 1-.794 0l-.02-.086a3.232 3.232 0 0 0-2.414-2.414l-.087-.02a.407.407 0 0 1 0-.794l.087-.02a3.232 3.232 0 0 0 2.414-2.414l.02-.086ZM45.93 10.55a.407.407 0 0 1 .794 0l.02.086a3.232 3.232 0 0 0 2.414 2.415l.086.02a.407.407 0 0 1 0 .793l-.086.02a3.232 3.232 0 0 0-2.414 2.414l-.02.087a.407.407 0 0 1-.794 0l-.02-.087a3.232 3.232 0 0 0-2.414-2.414l-.086-.02a.407.407 0 0 1 0-.793l.086-.02a3.232 3.232 0 0 0 2.414-2.415l.02-.086ZM38.928 43.41a.407.407 0 0 1 .793 0l.02.086a3.232 3.232 0 0 0 2.414 2.414l.086.02a.407.407 0 0 1 0 .794l-.086.02a3.232 3.232 0 0 0-2.414 2.414l-.02.086a.407.407 0 0 1-.793 0l-.02-.086a3.232 3.232 0 0 0-2.415-2.414l-.086-.02a.407.407 0 0 1 0-.793l.086-.02a3.232 3.232 0 0 0 2.414-2.415l.02-.086Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke="#91B4F2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
d="M37.708 27.827a4.31 4.31 0 0 1 6.095 0M36.63 24.873a6.95 6.95 0 0 1 9.495-2.544M17.238 13.467a4.31 4.31 0 0 0-4.31-4.31M18.583 10.392a4.31 4.31 0 0 0-2.533-5.544"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="32.86"
|
||||||
|
cy="11.313"
|
||||||
|
r="1.616"
|
||||||
|
fill="#0E3377"
|
||||||
|
stroke="#91B4F2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="36.631"
|
||||||
|
cy="17.777"
|
||||||
|
r="1.077"
|
||||||
|
fill="#030C1B"
|
||||||
|
stroke="#91B4F2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="30.705"
|
||||||
|
cy="44.172"
|
||||||
|
r="1.077"
|
||||||
|
fill="#030C1B"
|
||||||
|
stroke="#91B4F2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="44.711"
|
||||||
|
cy="34.476"
|
||||||
|
r="2.155"
|
||||||
|
fill="#FFBF00"
|
||||||
|
stroke="#91B4F2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="41.479"
|
||||||
|
cy="5.926"
|
||||||
|
r="3.232"
|
||||||
|
fill="#202733"
|
||||||
|
stroke="#91B4F2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" fill="none">
|
||||||
|
<path
|
||||||
|
fill="#DFE9FB"
|
||||||
|
d="M33.044 37.587 5.716 48.972a2.275 2.275 0 0 1-2.975-2.975L14.127 18.67a4.31 4.31 0 0 1 5.217-2.47l.77.23a22.625 22.625 0 0 1 15.17 15.17l.23.77a4.31 4.31 0 0 1-2.47 5.217Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#FFBF00"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M15.947 44.71a47.27 47.27 0 0 1-4.768-4.175 47.284 47.284 0 0 1-4.175-4.768l.48-1.154a45.953 45.953 0 0 0 4.457 5.16 45.958 45.958 0 0 0 5.16 4.456l-1.154.481ZM11.4 46.604a56.295 56.295 0 0 1-6.292-6.291l-.463 1.112a57.493 57.493 0 0 0 5.642 5.643l1.113-.464Zm10.65-4.437c-2.38-1.42-4.765-3.271-7-5.505-2.233-2.234-4.084-4.62-5.504-6.999l.52-1.25c1.414 2.52 3.347 5.087 5.747 7.487 2.4 2.4 4.966 4.332 7.486 5.746l-1.25.52Zm-9.634-19.393c.894 3.259 3.09 6.93 6.342 10.181 3.251 3.252 6.922 5.448 10.18 6.341l1.747-.727a12.586 12.586 0 0 1-1.756-.396c-2.975-.885-6.364-2.934-9.41-5.98-3.045-3.045-5.094-6.434-5.98-9.41a12.585 12.585 0 0 1-.395-1.755l-.728 1.746Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#99BAF4"
|
||||||
|
stroke="#0E3781"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
d="M27.985 23.73c2.175 2.174 3.79 4.462 4.653 6.387.432.965.663 1.811.699 2.491.036.68-.125 1.13-.4 1.406-.276.275-.726.436-1.406.4-.68-.035-1.526-.267-2.49-.699-1.926-.863-4.214-2.478-6.389-4.653-2.175-2.175-3.79-4.463-4.653-6.388-.432-.965-.664-1.811-.7-2.49-.035-.68.126-1.131.401-1.407.275-.275.726-.436 1.406-.4.68.036 1.526.267 2.49.7 1.926.862 4.214 2.478 6.389 4.652Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke="#0E3781"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
d="M33.044 37.587 5.716 48.972a2.275 2.275 0 0 1-2.975-2.975L14.127 18.67a4.31 4.31 0 0 1 5.217-2.47l.77.23a22.625 22.625 0 0 1 15.17 15.17l.23.77a4.31 4.31 0 0 1-2.47 5.217Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#0E3781"
|
||||||
|
d="M25.46 2.47a.407.407 0 0 1 .793 0l.02.086a3.232 3.232 0 0 0 2.415 2.414l.086.02a.407.407 0 0 1 0 .793l-.086.02a3.232 3.232 0 0 0-2.414 2.415l-.02.086a.407.407 0 0 1-.794 0l-.02-.086a3.232 3.232 0 0 0-2.414-2.414l-.086-.02a.407.407 0 0 1 0-.794l.086-.02a3.232 3.232 0 0 0 2.414-2.414l.02-.086ZM45.93 10.55a.407.407 0 0 1 .794 0l.02.086a3.232 3.232 0 0 0 2.414 2.415l.086.02a.407.407 0 0 1 0 .793l-.086.02a3.232 3.232 0 0 0-2.414 2.414l-.02.087a.407.407 0 0 1-.794 0l-.02-.087a3.232 3.232 0 0 0-2.414-2.414l-.086-.02a.407.407 0 0 1 0-.793l.086-.02a3.232 3.232 0 0 0 2.414-2.415l.02-.086ZM38.928 43.41a.407.407 0 0 1 .793 0l.02.086a3.232 3.232 0 0 0 2.414 2.414l.086.02a.407.407 0 0 1 0 .794l-.086.02a3.232 3.232 0 0 0-2.414 2.414l-.02.086a.407.407 0 0 1-.793 0l-.02-.086a3.232 3.232 0 0 0-2.415-2.414l-.086-.02a.407.407 0 0 1 0-.793l.086-.02a3.232 3.232 0 0 0 2.414-2.415l.02-.086Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke="#0E3781"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
d="M37.708 27.827a4.31 4.31 0 0 1 6.095 0M36.63 24.873a6.95 6.95 0 0 1 9.495-2.544M17.238 13.467a4.31 4.31 0 0 0-4.31-4.31M18.583 10.392a4.31 4.31 0 0 0-2.533-5.544"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="32.86"
|
||||||
|
cy="11.313"
|
||||||
|
r="1.616"
|
||||||
|
fill="#99BAF4"
|
||||||
|
stroke="#0E3781"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="36.631"
|
||||||
|
cy="17.777"
|
||||||
|
r="1.077"
|
||||||
|
fill="#DFE9FB"
|
||||||
|
stroke="#0E3781"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="30.705"
|
||||||
|
cy="44.172"
|
||||||
|
r="1.077"
|
||||||
|
fill="#DFE9FB"
|
||||||
|
stroke="#0E3781"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="44.711"
|
||||||
|
cy="34.476"
|
||||||
|
r="2.155"
|
||||||
|
fill="#FFBF00"
|
||||||
|
stroke="#0E3781"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="41.479"
|
||||||
|
cy="5.926"
|
||||||
|
r="3.232"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#0E3781"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1.077"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function PencilSquare({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
d="M17.799 24H2.709a2.422 2.422 0 0 1-1.729-.735 2.533 2.533 0 0 1-.715-1.768V6.03c0-.663.257-1.299.715-1.769a2.416 2.416 0 0 1 1.728-.734h7.996c.216 0 .424.088.577.244a.846.846 0 0 1 0 1.18.808.808 0 0 1-.577.245H2.708a.809.809 0 0 0-.576.244.844.844 0 0 0-.238.59v15.467c0 .221.085.433.238.59.153.156.36.244.576.244h15.09a.809.809 0 0 0 .577-.244.843.843 0 0 0 .238-.59v-6.754a.832.832 0 0 1 .494-.801.796.796 0 0 1 .64 0 .82.82 0 0 1 .442.472.836.836 0 0 1 .052.33v6.753a2.53 2.53 0 0 1-.715 1.768c-.458.47-1.08.734-1.727.735ZM9.24 15.417a.812.812 0 0 1-.677-.373.852.852 0 0 1-.074-.783l1.32-3.239c.121-.297.297-.567.52-.795L19.615.714A2.394 2.394 0 0 1 21.325 0c.638.002 1.25.263 1.703.726.452.463.706 1.09.707 1.744a2.502 2.502 0 0 1-.7 1.746l-9.229 9.455c-.274.28-.609.489-.977.61l-3.34 1.09a.801.801 0 0 1-.248.047Zm12.084-13.76a.771.771 0 0 0-.558.235l-9.282 9.514a.828.828 0 0 0-.17.26l-.642 1.572 1.663-.543a.778.778 0 0 0 .317-.198l9.231-9.455a.812.812 0 0 0 .172-.88.805.805 0 0 0-.29-.363.78.78 0 0 0-.44-.136v-.006h-.001Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
19
apps/browser/src/autofill/content/components/icons/shield.ts
Normal file
19
apps/browser/src/autofill/content/components/icons/shield.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function Shield({ color, theme }: { color?: string; theme: Theme }) {
|
||||||
|
const shapeColor = color || themes[theme].brandLogo;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 24" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
d="M19.703.3A.97.97 0 0 0 19 0H1a.958.958 0 0 0-.702.3.962.962 0 0 0-.3.702v12c.004.913.18 1.818.522 2.665a9.95 9.95 0 0 0 1.297 2.345c.552.72 1.169 1.387 1.844 1.993a21.721 21.721 0 0 0 1.975 1.61c.6.426 1.23.83 1.89 1.21.66.381 1.126.639 1.398.773.275.135.497.241.662.312.129.062.27.093.414.09a.87.87 0 0 0 .406-.095c.168-.073.387-.177.665-.312.277-.135.75-.393 1.398-.772.648-.38 1.285-.785 1.89-1.21.69-.499 1.35-1.036 1.978-1.61a14.458 14.458 0 0 0 1.844-1.994c.535-.72.972-1.508 1.297-2.344a7.185 7.185 0 0 0 .522-2.666v-12A.944.944 0 0 0 19.703.3Zm-2.32 12.811c0 4.35-7.382 8.087-7.382 8.087V2.57h7.381v10.54Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
27
apps/browser/src/autofill/content/components/icons/user.ts
Normal file
27
apps/browser/src/autofill/content/components/icons/user.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function User({
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
color?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
|
||||||
|
d="M23.16 20.895a11.343 11.343 0 0 0-7.756-8.425 6.624 6.624 0 0 0 3.374-5.74 6.73 6.73 0 1 0-13.46 0 6.624 6.624 0 0 0 3.343 5.722 11.334 11.334 0 0 0-7.82 8.443A2.57 2.57 0 0 0 3.362 24h17.274a2.573 2.573 0 0 0 2.523-3.106v.001ZM6.933 6.73a5.12 5.12 0 0 1 3.12-4.766 5.115 5.115 0 0 1 4.845 8.962 5.114 5.114 0 0 1-2.848.866A5.097 5.097 0 0 1 6.933 6.73v.001ZM21.38 22.053a.94.94 0 0 1-.748.35H3.363a.938.938 0 0 1-.74-.35.986.986 0 0 1-.204-.833A9.812 9.812 0 0 1 12 13.516a9.807 9.807 0 0 1 9.581 7.704.98.98 0 0 1-.202.833Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import createEmotion from "@emotion/css/create-instance";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { NotificationType } from "../../../notification/abstractions/notification-bar";
|
||||||
|
import { CipherItem } from "../cipher";
|
||||||
|
import { CipherData } from "../cipher/types";
|
||||||
|
import { scrollbarStyles, spacing, themes, typography } from "../constants/styles";
|
||||||
|
import { ItemRow } from "../rows/item-row";
|
||||||
|
|
||||||
|
export const componentClassPrefix = "notification-body";
|
||||||
|
|
||||||
|
const { css } = createEmotion({
|
||||||
|
key: componentClassPrefix,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function NotificationBody({
|
||||||
|
ciphers,
|
||||||
|
notificationType,
|
||||||
|
theme = ThemeTypes.Light,
|
||||||
|
}: {
|
||||||
|
ciphers: CipherData[];
|
||||||
|
customClasses?: string[];
|
||||||
|
notificationType?: NotificationType;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
// @TODO get client vendor from context
|
||||||
|
const isSafari = false;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class=${notificationBodyStyles({ isSafari, theme })}>
|
||||||
|
${ciphers.map((cipher) =>
|
||||||
|
ItemRow({
|
||||||
|
theme,
|
||||||
|
children: CipherItem({
|
||||||
|
cipher,
|
||||||
|
notificationType,
|
||||||
|
theme,
|
||||||
|
handleAction: () => {
|
||||||
|
// @TODO connect update or edit actions to handler
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationBodyStyles = ({ isSafari, theme }: { isSafari: boolean; theme: Theme }) => css`
|
||||||
|
${typography.body1}
|
||||||
|
|
||||||
|
gap: ${spacing["1.5"]};
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
background-color: ${themes[theme].background.alt};
|
||||||
|
max-height: 123px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: ${themes[theme].text.main};
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
:last-child {
|
||||||
|
border-radius: 0 0 ${spacing["4"]} ${spacing["4"]};
|
||||||
|
}
|
||||||
|
|
||||||
|
${isSafari ? scrollbarStyles(theme).safari : scrollbarStyles(theme).default}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
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 { createAutofillOverlayCipherDataMock } from "../../../spec/autofill-mocks";
|
||||||
|
import { CipherData } from "../cipher/types";
|
||||||
|
import { themes, spacing } from "../constants/styles";
|
||||||
|
|
||||||
|
import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body";
|
||||||
|
import { NotificationFooter } from "./footer";
|
||||||
|
import {
|
||||||
|
NotificationHeader,
|
||||||
|
componentClassPrefix as notificationHeaderClassPrefix,
|
||||||
|
} from "./header";
|
||||||
|
|
||||||
|
export function NotificationContainer({
|
||||||
|
handleCloseNotification,
|
||||||
|
i18n,
|
||||||
|
theme = ThemeTypes.Light,
|
||||||
|
type,
|
||||||
|
}: NotificationBarIframeInitData & { handleCloseNotification: (e: Event) => void } & {
|
||||||
|
i18n: { [key: string]: string };
|
||||||
|
type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type`
|
||||||
|
}) {
|
||||||
|
const headerMessage = getHeaderMessage(i18n, type);
|
||||||
|
const showBody = true;
|
||||||
|
|
||||||
|
// @TODO remove mock ciphers for development
|
||||||
|
const ciphers = [
|
||||||
|
createAutofillOverlayCipherDataMock(1),
|
||||||
|
{ ...createAutofillOverlayCipherDataMock(2), icon: { imageEnabled: false } },
|
||||||
|
{
|
||||||
|
...createAutofillOverlayCipherDataMock(3),
|
||||||
|
icon: { imageEnabled: true, image: "https://localhost:8443/icons/webtests.dev/icon.png" },
|
||||||
|
},
|
||||||
|
] as CipherData[];
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class=${notificationContainerStyles(theme)}>
|
||||||
|
${NotificationHeader({
|
||||||
|
handleCloseNotification,
|
||||||
|
standalone: showBody,
|
||||||
|
message: headerMessage,
|
||||||
|
theme,
|
||||||
|
})}
|
||||||
|
${showBody
|
||||||
|
? NotificationBody({
|
||||||
|
ciphers,
|
||||||
|
notificationType: type,
|
||||||
|
theme,
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
${NotificationFooter({
|
||||||
|
theme,
|
||||||
|
notificationType: type,
|
||||||
|
})}
|
||||||
|
</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;
|
||||||
|
|
||||||
|
[class*="${notificationHeaderClassPrefix}-"] {
|
||||||
|
border-radius: ${spacing["4"]} ${spacing["4"]} 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="${notificationBodyClassPrefix}-"] {
|
||||||
|
margin: ${spacing["3"]} 0 ${spacing["1.5"]} ${spacing["3"]};
|
||||||
|
padding-right: ${spacing["3"]};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function getHeaderMessage(i18n: { [key: string]: string }, type?: NotificationType) {
|
||||||
|
switch (type) {
|
||||||
|
case NotificationTypes.Add:
|
||||||
|
return i18n.saveAsNewLoginAction;
|
||||||
|
case NotificationTypes.Change:
|
||||||
|
return i18n.updateLoginPrompt;
|
||||||
|
case NotificationTypes.Unlock:
|
||||||
|
return "";
|
||||||
|
case NotificationTypes.FilelessImport:
|
||||||
|
return "";
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import {
|
||||||
|
NotificationType,
|
||||||
|
NotificationTypes,
|
||||||
|
} from "../../../notification/abstractions/notification-bar";
|
||||||
|
import { spacing, themes } from "../constants/styles";
|
||||||
|
import { ActionRow } from "../rows/action-row";
|
||||||
|
import { ButtonRow } from "../rows/button-row";
|
||||||
|
|
||||||
|
export function NotificationFooter({
|
||||||
|
notificationType,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
notificationType?: NotificationType;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
const isChangeNotification = notificationType === NotificationTypes.Change;
|
||||||
|
// @TODO localize
|
||||||
|
const saveNewItemText = "Save as new login";
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class=${notificationFooterStyles({ theme })}>
|
||||||
|
${isChangeNotification
|
||||||
|
? ActionRow({ itemText: saveNewItemText, handleAction: () => {}, theme })
|
||||||
|
: ButtonRow({ theme })}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationFooterStyles = ({ theme }: { theme: Theme }) => css`
|
||||||
|
display: flex;
|
||||||
|
background-color: ${themes[theme].background.alt};
|
||||||
|
padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]};
|
||||||
|
|
||||||
|
:last-child {
|
||||||
|
border-radius: 0 0 ${spacing["4"]} ${spacing["4"]};
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { themes } from "../constants/styles";
|
||||||
|
|
||||||
|
export function NotificationHeaderMessage({ message, theme }: { message: string; theme: Theme }) {
|
||||||
|
return html`
|
||||||
|
<span title=${message} class=${notificationHeaderMessageStyles(theme)}>${message}</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationHeaderMessageStyles = (theme: Theme) => css`
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-align: left;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
line-height: 28px;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: ${themes[theme].text.main};
|
||||||
|
font-family: "DM Sans", sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import createEmotion from "@emotion/css/create-instance";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { CloseButton } from "../buttons/close-button";
|
||||||
|
import { themes } from "../constants/styles";
|
||||||
|
import { BrandIconContainer } from "../icons/brand-icon-container";
|
||||||
|
|
||||||
|
import { NotificationHeaderMessage } from "./header-message";
|
||||||
|
|
||||||
|
export const componentClassPrefix = "notification-header";
|
||||||
|
|
||||||
|
const { css } = createEmotion({
|
||||||
|
key: componentClassPrefix,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function NotificationHeader({
|
||||||
|
message,
|
||||||
|
standalone,
|
||||||
|
theme = ThemeTypes.Light,
|
||||||
|
handleCloseNotification,
|
||||||
|
}: {
|
||||||
|
message?: string;
|
||||||
|
standalone: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
handleCloseNotification: (e: Event) => void;
|
||||||
|
}) {
|
||||||
|
const showIcon = true;
|
||||||
|
const isDismissable = true;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class=${notificationHeaderStyles({ standalone, theme })}>
|
||||||
|
${showIcon ? BrandIconContainer({ theme }) : null}
|
||||||
|
${message ? NotificationHeaderMessage({ message, theme }) : null}
|
||||||
|
${isDismissable ? CloseButton({ handleCloseNotification, theme }) : null}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationHeaderStyles = ({
|
||||||
|
standalone,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
standalone: boolean;
|
||||||
|
theme: Theme;
|
||||||
|
}) => css`
|
||||||
|
gap: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
background-color: ${themes[theme].background.alt};
|
||||||
|
padding: 12px 16px 8px 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
${standalone
|
||||||
|
? css`
|
||||||
|
border-bottom: 0.5px solid ${themes[theme].secondary["300"]};
|
||||||
|
`
|
||||||
|
: css``}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { spacing, themes, typography } from "../../../content/components/constants/styles";
|
||||||
|
|
||||||
|
export function ActionRow({
|
||||||
|
handleAction,
|
||||||
|
itemText,
|
||||||
|
theme = ThemeTypes.Light,
|
||||||
|
}: {
|
||||||
|
itemText: string;
|
||||||
|
handleAction?: (e: Event) => void;
|
||||||
|
theme: Theme;
|
||||||
|
}) {
|
||||||
|
return html`
|
||||||
|
<button type="button" @click=${handleAction} class=${actionRowStyles(theme)} title=${itemText}>
|
||||||
|
<span>${itemText}</span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionRowStyles = (theme: Theme) => css`
|
||||||
|
${typography.body2}
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
border-width: 0 0 0.5px 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: ${spacing["2"]};
|
||||||
|
border-color: ${themes[theme].secondary["300"]};
|
||||||
|
background-color: ${themes[theme].background.DEFAULT};
|
||||||
|
cursor: pointer;
|
||||||
|
padding: ${spacing["2"]} ${spacing["3"]};
|
||||||
|
width: 100%;
|
||||||
|
min-height: 40px;
|
||||||
|
text-align: left;
|
||||||
|
color: ${themes[theme].primary["600"]};
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 5px);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
background-color: ${themes[theme].primary["100"]};
|
||||||
|
color: ${themes[theme].primary["600"]};
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html, TemplateResult } from "lit";
|
||||||
|
|
||||||
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { ActionButton } from "../../../content/components/buttons/action-button";
|
||||||
|
import { spacing, themes } from "../../../content/components/constants/styles";
|
||||||
|
import { Folder, User } from "../../../content/components/icons";
|
||||||
|
import { DropdownMenu } from "../dropdown-menu";
|
||||||
|
|
||||||
|
export function ButtonRow({ theme }: { theme: Theme }) {
|
||||||
|
return html`
|
||||||
|
<div class=${buttonRowStyles}>
|
||||||
|
${[
|
||||||
|
ActionButton({
|
||||||
|
buttonAction: () => {},
|
||||||
|
buttonText: "Action Button",
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
DropdownContainer({
|
||||||
|
children: [
|
||||||
|
DropdownMenu({
|
||||||
|
buttonText: "You",
|
||||||
|
icon: User({ color: themes[theme].text.muted, theme }),
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
DropdownMenu({
|
||||||
|
buttonText: "Folder",
|
||||||
|
icon: Folder({ color: themes[theme].text.muted, theme }),
|
||||||
|
disabled: true,
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownContainer({ children }: { children: TemplateResult[] }) {
|
||||||
|
return html` <div class=${dropdownContainerStyles}>${children}</div> `;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonRowStyles = css`
|
||||||
|
gap: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 52px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
max-width: min-content;
|
||||||
|
flex: 1 1 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex: 1 1 min-content;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const dropdownContainerStyles = css`
|
||||||
|
gap: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
min-width: calc(50% - ${spacing["1.5"]});
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { html, TemplateResult } from "lit";
|
||||||
|
|
||||||
|
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
import { spacing, themes, typography } from "../../../content/components/constants/styles";
|
||||||
|
|
||||||
|
export function ItemRow({
|
||||||
|
theme = ThemeTypes.Light,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
theme: Theme;
|
||||||
|
children: TemplateResult | TemplateResult[];
|
||||||
|
}) {
|
||||||
|
return html` <div class=${itemRowStyles({ theme })}>${children}</div> `;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const itemRowStyles = ({ theme }: { theme: Theme }) => css`
|
||||||
|
${typography.body1}
|
||||||
|
|
||||||
|
gap: ${spacing["2"]};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-width: 0 0 0.5px 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: ${spacing["2"]};
|
||||||
|
border-color: ${themes[theme].secondary["300"]};
|
||||||
|
background-color: ${themes[theme].background.DEFAULT};
|
||||||
|
padding: ${spacing["2"]} ${spacing["3"]};
|
||||||
|
min-height: min-content;
|
||||||
|
max-height: 52px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: ${themes[theme].text.main};
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
:first-child {
|
||||||
|
flex: 3 3 75%;
|
||||||
|
min-width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(:first-child) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
max-width: 25%;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
max-width: min-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
import { Theme } from "@bitwarden/common/platform/enums";
|
import { Theme } from "@bitwarden/common/platform/enums";
|
||||||
|
|
||||||
|
const NotificationTypes = {
|
||||||
|
Add: "add",
|
||||||
|
Change: "change",
|
||||||
|
Unlock: "unlock",
|
||||||
|
FilelessImport: "fileless-import",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes];
|
||||||
|
|
||||||
type NotificationBarIframeInitData = {
|
type NotificationBarIframeInitData = {
|
||||||
type?: string;
|
type?: string; // @TODO use `NotificationType`
|
||||||
isVaultLocked?: boolean;
|
isVaultLocked?: boolean;
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
removeIndividualVault?: boolean;
|
removeIndividualVault?: boolean;
|
||||||
@@ -24,6 +33,8 @@ type NotificationBarWindowMessageHandlers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
NotificationTypes,
|
||||||
|
NotificationType,
|
||||||
NotificationBarIframeInitData,
|
NotificationBarIframeInitData,
|
||||||
NotificationBarWindowMessage,
|
NotificationBarWindowMessage,
|
||||||
NotificationBarWindowMessageHandlers,
|
NotificationBarWindowMessageHandlers,
|
||||||
|
|||||||
@@ -769,7 +769,10 @@ export default class MainBackground {
|
|||||||
this.configService,
|
this.configService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.devicesService = new DevicesServiceImplementation(this.devicesApiService);
|
this.devicesService = new DevicesServiceImplementation(
|
||||||
|
this.devicesApiService,
|
||||||
|
this.appIdService,
|
||||||
|
);
|
||||||
|
|
||||||
this.authRequestService = new AuthRequestService(
|
this.authRequestService = new AuthRequestService(
|
||||||
this.appIdService,
|
this.appIdService,
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<bit-container>
|
||||||
|
<div class="tabbed-header">
|
||||||
|
<div class="tw-flex tw-items-center tw-gap-2">
|
||||||
|
<h1>{{ "devices" | i18n }}</h1>
|
||||||
|
<button
|
||||||
|
[bitPopoverTriggerFor]="infoPopover"
|
||||||
|
type="button"
|
||||||
|
class="tw-border-none tw-bg-transparent tw-text-primary-600 tw-flex tw-items-center tw-h-4 tw-w-4"
|
||||||
|
[position]="'right-start'"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<bit-popover [title]="'whatIsADevice' | i18n" #infoPopover>
|
||||||
|
<p>{{ "aDeviceIs" | i18n }}</p>
|
||||||
|
</bit-popover>
|
||||||
|
<i
|
||||||
|
*ngIf="asyncActionLoading"
|
||||||
|
class="bwi bwi-spinner bwi-spin tw-flex tw-items-center tw-h-4 tw-w-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>{{ "deviceListDescription" | i18n }}</p>
|
||||||
|
|
||||||
|
<div *ngIf="loading" class="tw-flex tw-justify-center tw-items-center tw-p-4">
|
||||||
|
<i class="bwi bwi-spinner bwi-spin tw-text-2xl" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bit-table-scroll *ngIf="!loading" [dataSource]="dataSource" [rowSize]="50">
|
||||||
|
<ng-container header>
|
||||||
|
<th
|
||||||
|
*ngFor="let col of columnConfig"
|
||||||
|
[class]="col.headerClass"
|
||||||
|
bitCell
|
||||||
|
[bitSortable]="col.sortable ? col.name : null"
|
||||||
|
[default]="col.name === 'loginStatus' ? 'desc' : null"
|
||||||
|
scope="col"
|
||||||
|
role="columnheader"
|
||||||
|
>
|
||||||
|
{{ col.title }}
|
||||||
|
</th>
|
||||||
|
<th bitCell scope="col" role="columnheader"></th>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template bitRowDef let-row>
|
||||||
|
<td bitCell class="tw-flex tw-gap-2">
|
||||||
|
<div class="tw-flex tw-items-center tw-justify-center tw-w-10">
|
||||||
|
<i [class]="getDeviceIcon(row.type)" class="bwi-lg" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ row.displayName }}
|
||||||
|
<span *ngIf="row.trusted" class="tw-text-sm tw-text-muted tw-block">
|
||||||
|
{{ "trusted" | i18n }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td bitCell>
|
||||||
|
<span *ngIf="isCurrentDevice(row)" bitBadge variant="primary">{{
|
||||||
|
"currentSession" | i18n
|
||||||
|
}}</span>
|
||||||
|
<span *ngIf="hasPendingAuthRequest(row)" bitBadge variant="warning">{{
|
||||||
|
"requestPending" | i18n
|
||||||
|
}}</span>
|
||||||
|
</td>
|
||||||
|
<td bitCell>{{ row.firstLogin | date: "medium" }}</td>
|
||||||
|
<td bitCell>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitIconButton="bwi-ellipsis-v"
|
||||||
|
[bitMenuTriggerFor]="optionsMenu"
|
||||||
|
></button>
|
||||||
|
<bit-menu #optionsMenu>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitMenuItem
|
||||||
|
(click)="removeDevice(row)"
|
||||||
|
[disabled]="isCurrentDevice(row)"
|
||||||
|
>
|
||||||
|
<span [class]="isCurrentDevice(row) ? 'tw-text-muted' : 'tw-text-danger'">
|
||||||
|
<i class="bwi bwi-trash" aria-hidden="true"></i>
|
||||||
|
{{ "removeDevice" | i18n }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</bit-menu>
|
||||||
|
</td>
|
||||||
|
</ng-template>
|
||||||
|
</bit-table-scroll>
|
||||||
|
</bit-container>
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
import { switchMap } from "rxjs/operators";
|
||||||
|
|
||||||
|
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||||
|
import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view";
|
||||||
|
import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
|
import {
|
||||||
|
DialogService,
|
||||||
|
ToastService,
|
||||||
|
TableDataSource,
|
||||||
|
TableModule,
|
||||||
|
PopoverModule,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { SharedModule } from "../../../shared";
|
||||||
|
|
||||||
|
interface DeviceTableData {
|
||||||
|
id: string;
|
||||||
|
type: DeviceType;
|
||||||
|
displayName: string;
|
||||||
|
loginStatus: string;
|
||||||
|
firstLogin: Date;
|
||||||
|
trusted: boolean;
|
||||||
|
devicePendingAuthRequest: object | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a table of devices and allows the user to log out, approve or remove a device
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: "app-device-management",
|
||||||
|
templateUrl: "./device-management.component.html",
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, SharedModule, TableModule, PopoverModule],
|
||||||
|
})
|
||||||
|
export class DeviceManagementComponent {
|
||||||
|
protected readonly tableId = "device-management-table";
|
||||||
|
protected dataSource = new TableDataSource<DeviceTableData>();
|
||||||
|
protected currentDevice: DeviceView | undefined;
|
||||||
|
protected loading = true;
|
||||||
|
protected asyncActionLoading = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private devicesService: DevicesServiceAbstraction,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private validationService: ValidationService,
|
||||||
|
) {
|
||||||
|
this.devicesService
|
||||||
|
.getCurrentDevice$()
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(),
|
||||||
|
switchMap((currentDevice) => {
|
||||||
|
this.currentDevice = new DeviceView(currentDevice);
|
||||||
|
return this.devicesService.getDevices$();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (devices) => {
|
||||||
|
this.dataSource.data = devices.map((device) => {
|
||||||
|
return {
|
||||||
|
id: device.id,
|
||||||
|
type: device.type,
|
||||||
|
displayName: this.getHumanReadableDeviceType(device.type),
|
||||||
|
loginStatus: this.getLoginStatus(device),
|
||||||
|
devicePendingAuthRequest: device.response.devicePendingAuthRequest,
|
||||||
|
firstLogin: new Date(device.creationDate),
|
||||||
|
trusted: device.response.isTrusted,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column configuration for the table
|
||||||
|
*/
|
||||||
|
protected readonly columnConfig = [
|
||||||
|
{
|
||||||
|
name: "displayName",
|
||||||
|
title: this.i18nService.t("device"),
|
||||||
|
headerClass: "tw-w-1/3",
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "loginStatus",
|
||||||
|
title: this.i18nService.t("loginStatus"),
|
||||||
|
headerClass: "tw-w-1/3",
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "firstLogin",
|
||||||
|
title: this.i18nService.t("firstLogin"),
|
||||||
|
headerClass: "tw-w-1/3",
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon for a device type
|
||||||
|
* @param type - The device type
|
||||||
|
* @returns The icon for the device type
|
||||||
|
*/
|
||||||
|
getDeviceIcon(type: DeviceType): string {
|
||||||
|
const defaultIcon = "bwi bwi-desktop";
|
||||||
|
const categoryIconMap: Record<string, string> = {
|
||||||
|
webVault: "bwi bwi-browser",
|
||||||
|
desktop: "bwi bwi-desktop",
|
||||||
|
mobile: "bwi bwi-mobile",
|
||||||
|
cli: "bwi bwi-cli",
|
||||||
|
extension: "bwi bwi-puzzle",
|
||||||
|
sdk: "bwi bwi-desktop",
|
||||||
|
};
|
||||||
|
|
||||||
|
const metadata = DeviceTypeMetadata[type];
|
||||||
|
return metadata ? (categoryIconMap[metadata.category] ?? defaultIcon) : defaultIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the login status of a device
|
||||||
|
* It will return the current session if the device is the current device
|
||||||
|
* It will return the date of the pending auth request when available
|
||||||
|
* @param device - The device
|
||||||
|
* @returns The login status
|
||||||
|
*/
|
||||||
|
private getLoginStatus(device: DeviceView): string {
|
||||||
|
if (this.isCurrentDevice(device)) {
|
||||||
|
return this.i18nService.t("currentSession");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.response.devicePendingAuthRequest?.creationDate) {
|
||||||
|
return this.i18nService.t("requestPending");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a human readable device type from the DeviceType enum
|
||||||
|
* @param type - The device type
|
||||||
|
* @returns The human readable device type
|
||||||
|
*/
|
||||||
|
private getHumanReadableDeviceType(type: DeviceType): string {
|
||||||
|
const metadata = DeviceTypeMetadata[type];
|
||||||
|
if (!metadata) {
|
||||||
|
return this.i18nService.t("unknownDevice");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the platform is "Unknown" translate it since it is not a proper noun
|
||||||
|
const platform =
|
||||||
|
metadata.platform === "Unknown" ? this.i18nService.t("unknown") : metadata.platform;
|
||||||
|
const category = this.i18nService.t(metadata.category);
|
||||||
|
return platform ? `${category} - ${platform}` : category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a device is the current device
|
||||||
|
* @param device - The device or device table data
|
||||||
|
* @returns True if the device is the current device, false otherwise
|
||||||
|
*/
|
||||||
|
protected isCurrentDevice(device: DeviceView | DeviceTableData): boolean {
|
||||||
|
return "response" in device
|
||||||
|
? device.id === this.currentDevice?.id
|
||||||
|
: device.id === this.currentDevice?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a device has a pending auth request
|
||||||
|
* @param device - The device
|
||||||
|
* @returns True if the device has a pending auth request, false otherwise
|
||||||
|
*/
|
||||||
|
protected hasPendingAuthRequest(device: DeviceTableData): boolean {
|
||||||
|
return (
|
||||||
|
device.devicePendingAuthRequest !== undefined && device.devicePendingAuthRequest !== null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a device
|
||||||
|
* @param device - The device
|
||||||
|
*/
|
||||||
|
protected async removeDevice(device: DeviceTableData) {
|
||||||
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
|
title: { key: "removeDevice" },
|
||||||
|
content: { key: "removeDeviceConfirmation" },
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.asyncActionLoading = true;
|
||||||
|
await firstValueFrom(this.devicesService.deactivateDevice$(device.id));
|
||||||
|
this.asyncActionLoading = false;
|
||||||
|
|
||||||
|
// Remove the device from the data source
|
||||||
|
this.dataSource.data = this.dataSource.data.filter((d) => d.id !== device.id);
|
||||||
|
|
||||||
|
this.toastService.showToast({
|
||||||
|
title: "",
|
||||||
|
message: this.i18nService.t("deviceRemoved"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.validationService.showError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { RouterModule, Routes } from "@angular/router";
|
|||||||
import { ChangePasswordComponent } from "../change-password.component";
|
import { ChangePasswordComponent } from "../change-password.component";
|
||||||
import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component";
|
import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component";
|
||||||
|
|
||||||
|
import { DeviceManagementComponent } from "./device-management.component";
|
||||||
import { SecurityKeysComponent } from "./security-keys.component";
|
import { SecurityKeysComponent } from "./security-keys.component";
|
||||||
import { SecurityComponent } from "./security.component";
|
import { SecurityComponent } from "./security.component";
|
||||||
|
|
||||||
@@ -29,6 +30,11 @@ const routes: Routes = [
|
|||||||
component: SecurityKeysComponent,
|
component: SecurityKeysComponent,
|
||||||
data: { titleId: "keys" },
|
data: { titleId: "keys" },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "device-management",
|
||||||
|
component: DeviceManagementComponent,
|
||||||
|
data: { titleId: "devices" },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<bit-tab-link route="change-password">{{ "masterPassword" | i18n }}</bit-tab-link>
|
<bit-tab-link route="change-password">{{ "masterPassword" | i18n }}</bit-tab-link>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<bit-tab-link route="two-factor">{{ "twoStepLogin" | i18n }}</bit-tab-link>
|
<bit-tab-link route="two-factor">{{ "twoStepLogin" | i18n }}</bit-tab-link>
|
||||||
|
<bit-tab-link route="device-management">{{ "devices" | i18n }}</bit-tab-link>
|
||||||
<bit-tab-link route="security-keys">{{ "keys" | i18n }}</bit-tab-link>
|
<bit-tab-link route="security-keys">{{ "keys" | i18n }}</bit-tab-link>
|
||||||
</bit-tab-nav-bar>
|
</bit-tab-nav-bar>
|
||||||
</app-header>
|
</app-header>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-security",
|
selector: "app-security",
|
||||||
@@ -9,7 +10,10 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
|
|||||||
export class SecurityComponent implements OnInit {
|
export class SecurityComponent implements OnInit {
|
||||||
showChangePassword = true;
|
showChangePassword = true;
|
||||||
|
|
||||||
constructor(private userVerificationService: UserVerificationService) {}
|
constructor(
|
||||||
|
private userVerificationService: UserVerificationService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.showChangePassword = await this.userVerificationService.hasMasterPassword();
|
this.showChangePassword = await this.userVerificationService.hasMasterPassword();
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||||
<bit-dialog dialogSize="large" [loading]="loading">
|
<bit-dialog dialogSize="large" [loading]="loading">
|
||||||
<span bitDialogTitle class="tw-font-semibold">
|
<span bitDialogTitle class="tw-font-semibold">
|
||||||
{{ "upgradeFreeOrganization" | i18n: currentPlanName }}
|
{{ dialogHeaderName }}
|
||||||
</span>
|
</span>
|
||||||
<div bitDialogContent>
|
<div bitDialogContent>
|
||||||
<p>{{ "upgradePlans" | i18n }}</p>
|
<p>{{ "upgradePlans" | i18n }}</p>
|
||||||
<div class="tw-mb-3 tw-flex tw-justify-between">
|
<div class="tw-mb-3 tw-flex tw-justify-between">
|
||||||
<span class="tw-text-lg tw-pr-1 tw-font-bold">{{ "selectAPlan" | i18n }}</span>
|
<span [hidden]="isSubscriptionCanceled" class="tw-text-lg tw-pr-1 tw-font-bold">{{
|
||||||
|
"selectAPlan" | i18n
|
||||||
|
}}</span>
|
||||||
<!-- Discount Badge -->
|
<!-- Discount Badge -->
|
||||||
<div class="tw-flex tw-items-center tw-gap-2">
|
<div class="tw-flex tw-items-center tw-gap-2">
|
||||||
<span
|
<span
|
||||||
class="tw-mr-1"
|
class="tw-mr-1"
|
||||||
|
[hidden]="isSubscriptionCanceled"
|
||||||
*ngIf="
|
*ngIf="
|
||||||
this.discountPercentageFromSub > 0
|
this.discountPercentageFromSub > 0
|
||||||
? discountPercentageFromSub
|
? discountPercentageFromSub
|
||||||
@@ -69,7 +72,10 @@
|
|||||||
>
|
>
|
||||||
<div class="tw-relative">
|
<div class="tw-relative">
|
||||||
<div
|
<div
|
||||||
*ngIf="selectableProduct.productTier === productTypes.Enterprise"
|
*ngIf="
|
||||||
|
selectableProduct.productTier === productTypes.Enterprise &&
|
||||||
|
!isSubscriptionCanceled
|
||||||
|
"
|
||||||
class="tw-bg-secondary-100 tw-text-center !tw-border-0 tw-text-sm tw-font-bold tw-py-1"
|
class="tw-bg-secondary-100 tw-text-center !tw-border-0 tw-text-sm tw-font-bold tw-py-1"
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'tw-bg-primary-700 !tw-text-contrast': selectableProduct === selectedPlan,
|
'tw-bg-primary-700 !tw-text-contrast': selectableProduct === selectedPlan,
|
||||||
@@ -330,9 +336,15 @@
|
|||||||
<br />
|
<br />
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- Payment info -->
|
<!-- Payment info -->
|
||||||
<ng-container *ngIf="formGroup.value.productTier !== productTypes.Free">
|
<ng-container
|
||||||
|
*ngIf="formGroup.value.productTier !== productTypes.Free || isSubscriptionCanceled"
|
||||||
|
>
|
||||||
<h2 bitTypography="h4">{{ "paymentMethod" | i18n }}</h2>
|
<h2 bitTypography="h4">{{ "paymentMethod" | i18n }}</h2>
|
||||||
<p *ngIf="!showPayment && (paymentSource || billing?.paymentSource)">
|
<p
|
||||||
|
*ngIf="
|
||||||
|
!showPayment && (paymentSource || billing?.paymentSource) && !isSubscriptionCanceled
|
||||||
|
"
|
||||||
|
>
|
||||||
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
|
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
|
||||||
{{
|
{{
|
||||||
deprecateStripeSourcesAPI
|
deprecateStripeSourcesAPI
|
||||||
|
|||||||
@@ -24,7 +24,14 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
|||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
||||||
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
|
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
import {
|
||||||
|
BillingApiServiceAbstraction,
|
||||||
|
BillingInformation,
|
||||||
|
OrganizationInformation,
|
||||||
|
PaymentInformation,
|
||||||
|
PlanInformation,
|
||||||
|
OrganizationBillingServiceAbstraction as OrganizationBillingService,
|
||||||
|
} from "@bitwarden/common/billing/abstractions";
|
||||||
import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
|
import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
|
||||||
import {
|
import {
|
||||||
PaymentMethodType,
|
PaymentMethodType,
|
||||||
@@ -49,6 +56,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
|||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
|
import { BillingSharedModule } from "../shared/billing-shared.module";
|
||||||
import { PaymentV2Component } from "../shared/payment/payment-v2.component";
|
import { PaymentV2Component } from "../shared/payment/payment-v2.component";
|
||||||
import { PaymentComponent } from "../shared/payment/payment.component";
|
import { PaymentComponent } from "../shared/payment/payment.component";
|
||||||
|
|
||||||
@@ -89,6 +97,8 @@ interface OnSuccessArgs {
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./change-plan-dialog.component.html",
|
templateUrl: "./change-plan-dialog.component.html",
|
||||||
|
standalone: true,
|
||||||
|
imports: [BillingSharedModule],
|
||||||
})
|
})
|
||||||
export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||||
@@ -163,6 +173,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
organization: Organization;
|
organization: Organization;
|
||||||
sub: OrganizationSubscriptionResponse;
|
sub: OrganizationSubscriptionResponse;
|
||||||
billing: BillingResponse;
|
billing: BillingResponse;
|
||||||
|
dialogHeaderName: string;
|
||||||
currentPlanName: string;
|
currentPlanName: string;
|
||||||
showPayment: boolean = false;
|
showPayment: boolean = false;
|
||||||
totalOpened: boolean = false;
|
totalOpened: boolean = false;
|
||||||
@@ -174,6 +185,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
paymentSource?: PaymentSourceResponse;
|
paymentSource?: PaymentSourceResponse;
|
||||||
|
|
||||||
deprecateStripeSourcesAPI: boolean;
|
deprecateStripeSourcesAPI: boolean;
|
||||||
|
isSubscriptionCanceled: boolean = false;
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
@@ -196,6 +208,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private billingApiService: BillingApiServiceAbstraction,
|
private billingApiService: BillingApiServiceAbstraction,
|
||||||
private taxService: TaxServiceAbstraction,
|
private taxService: TaxServiceAbstraction,
|
||||||
|
private organizationBillingService: OrganizationBillingService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
@@ -208,6 +221,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.sub =
|
this.sub =
|
||||||
this.dialogParams.subscription ??
|
this.dialogParams.subscription ??
|
||||||
(await this.organizationApiService.getSubscription(this.dialogParams.organizationId));
|
(await this.organizationApiService.getSubscription(this.dialogParams.organizationId));
|
||||||
|
this.dialogHeaderName = this.resolveHeaderName(this.sub);
|
||||||
this.organizationId = this.dialogParams.organizationId;
|
this.organizationId = this.dialogParams.organizationId;
|
||||||
this.currentPlan = this.sub?.plan;
|
this.currentPlan = this.sub?.plan;
|
||||||
this.selectedPlan = this.sub?.plan;
|
this.selectedPlan = this.sub?.plan;
|
||||||
@@ -281,6 +295,20 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.refreshSalesTax();
|
this.refreshSalesTax();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolveHeaderName(subscription: OrganizationSubscriptionResponse): string {
|
||||||
|
if (subscription.subscription != null) {
|
||||||
|
this.isSubscriptionCanceled = subscription.subscription.cancelled;
|
||||||
|
if (subscription.subscription.cancelled) {
|
||||||
|
return this.i18nService.t("restartSubscription");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.i18nService.t(
|
||||||
|
"upgradeFreeOrganization",
|
||||||
|
this.resolvePlanName(this.dialogParams.productTierType),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setInitialPlanSelection() {
|
setInitialPlanSelection() {
|
||||||
this.focusedIndex = this.selectableProducts.length - 1;
|
this.focusedIndex = this.selectableProducts.length - 1;
|
||||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||||
@@ -388,6 +416,19 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
case PlanCardState.Disabled: {
|
case PlanCardState.Disabled: {
|
||||||
|
if (this.isSubscriptionCanceled) {
|
||||||
|
return [
|
||||||
|
"tw-cursor-not-allowed",
|
||||||
|
"tw-bg-secondary-100",
|
||||||
|
"tw-font-normal",
|
||||||
|
"tw-bg-blur",
|
||||||
|
"tw-text-muted",
|
||||||
|
"tw-block",
|
||||||
|
"tw-rounded",
|
||||||
|
"tw-w-80",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"tw-cursor-not-allowed",
|
"tw-cursor-not-allowed",
|
||||||
"tw-bg-secondary-100",
|
"tw-bg-secondary-100",
|
||||||
@@ -409,7 +450,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan === this.currentPlan) {
|
if (plan === this.currentPlan && !this.isSubscriptionCanceled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.selectedPlan = plan;
|
this.selectedPlan = plan;
|
||||||
@@ -446,6 +487,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get selectableProducts() {
|
get selectableProducts() {
|
||||||
|
if (this.isSubscriptionCanceled) {
|
||||||
|
// Return only the current plan if the subscription is canceled
|
||||||
|
return [this.currentPlan];
|
||||||
|
}
|
||||||
|
|
||||||
if (this.acceptingSponsorship) {
|
if (this.acceptingSponsorship) {
|
||||||
const familyPlan = this.passwordManagerPlans.find(
|
const familyPlan = this.passwordManagerPlans.find(
|
||||||
(plan) => plan.type === PlanType.FamiliesAnnually,
|
(plan) => plan.type === PlanType.FamiliesAnnually,
|
||||||
@@ -692,11 +738,18 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const doSubmit = async (): Promise<string> => {
|
const doSubmit = async (): Promise<string> => {
|
||||||
let orgId: string = null;
|
let orgId: string = null;
|
||||||
orgId = await this.updateOrganization();
|
if (this.isSubscriptionCanceled) {
|
||||||
|
await this.restartSubscription();
|
||||||
|
orgId = this.organizationId;
|
||||||
|
} else {
|
||||||
|
orgId = await this.updateOrganization();
|
||||||
|
}
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: null,
|
title: null,
|
||||||
message: this.i18nService.t("organizationUpgraded"),
|
message: this.isSubscriptionCanceled
|
||||||
|
? this.i18nService.t("restartOrganizationSubscription")
|
||||||
|
: this.i18nService.t("organizationUpgraded"),
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.apiService.refreshIdentityToken();
|
await this.apiService.refreshIdentityToken();
|
||||||
@@ -726,6 +779,44 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private async restartSubscription() {
|
||||||
|
const org = await this.organizationApiService.get(this.organizationId);
|
||||||
|
const organization: OrganizationInformation = {
|
||||||
|
name: org.name,
|
||||||
|
billingEmail: org.billingEmail,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plan: PlanInformation = {
|
||||||
|
type: this.selectedPlan.type,
|
||||||
|
passwordManagerSeats: org.seats,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (org.useSecretsManager) {
|
||||||
|
plan.subscribeToSecretsManager = true;
|
||||||
|
plan.secretsManagerSeats = org.smSeats;
|
||||||
|
}
|
||||||
|
|
||||||
|
let paymentMethod: [string, PaymentMethodType];
|
||||||
|
|
||||||
|
if (this.deprecateStripeSourcesAPI) {
|
||||||
|
const { type, token } = await this.paymentV2Component.tokenize();
|
||||||
|
paymentMethod = [token, type];
|
||||||
|
} else {
|
||||||
|
paymentMethod = await this.paymentComponent.createPaymentToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
const payment: PaymentInformation = {
|
||||||
|
paymentMethod,
|
||||||
|
billing: this.getBillingInformationFromTaxInfoComponent(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.organizationBillingService.restartSubscription(this.organization.id, {
|
||||||
|
organization,
|
||||||
|
plan,
|
||||||
|
payment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async updateOrganization() {
|
private async updateOrganization() {
|
||||||
const request = new OrganizationUpgradeRequest();
|
const request = new OrganizationUpgradeRequest();
|
||||||
if (this.selectedPlan.productTier !== ProductTierType.Families) {
|
if (this.selectedPlan.productTier !== ProductTierType.Families) {
|
||||||
@@ -802,6 +893,18 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getBillingInformationFromTaxInfoComponent(): BillingInformation {
|
||||||
|
return {
|
||||||
|
country: this.taxInformation.country,
|
||||||
|
postalCode: this.taxInformation.postalCode,
|
||||||
|
taxId: this.taxInformation.taxId,
|
||||||
|
addressLine1: this.taxInformation.line1,
|
||||||
|
addressLine2: this.taxInformation.line2,
|
||||||
|
city: this.taxInformation.city,
|
||||||
|
state: this.taxInformation.state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void {
|
private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void {
|
||||||
request.useSecretsManager = this.organization.useSecretsManager;
|
request.useSecretsManager = this.organization.useSecretsManager;
|
||||||
if (!this.organization.useSecretsManager) {
|
if (!this.organization.useSecretsManager) {
|
||||||
@@ -997,6 +1100,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected canUpdatePaymentInformation(): boolean {
|
protected canUpdatePaymentInformation(): boolean {
|
||||||
return this.upgradeRequiresPaymentMethod || this.showPayment || this.isPaymentSourceEmpty();
|
return (
|
||||||
|
this.upgradeRequiresPaymentMethod ||
|
||||||
|
this.showPayment ||
|
||||||
|
this.isPaymentSourceEmpty() ||
|
||||||
|
this.isSubscriptionCanceled
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { BillingSharedModule } from "../shared";
|
|||||||
import { AdjustSubscription } from "./adjust-subscription.component";
|
import { AdjustSubscription } from "./adjust-subscription.component";
|
||||||
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
||||||
import { BillingSyncKeyComponent } from "./billing-sync-key.component";
|
import { BillingSyncKeyComponent } from "./billing-sync-key.component";
|
||||||
import { ChangePlanDialogComponent } from "./change-plan-dialog.component";
|
|
||||||
import { ChangePlanComponent } from "./change-plan.component";
|
import { ChangePlanComponent } from "./change-plan.component";
|
||||||
import { DownloadLicenceDialogComponent } from "./download-license.component";
|
import { DownloadLicenceDialogComponent } from "./download-license.component";
|
||||||
import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component";
|
import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component";
|
||||||
@@ -44,7 +43,6 @@ import { SubscriptionStatusComponent } from "./subscription-status.component";
|
|||||||
SecretsManagerSubscribeStandaloneComponent,
|
SecretsManagerSubscribeStandaloneComponent,
|
||||||
SubscriptionHiddenComponent,
|
SubscriptionHiddenComponent,
|
||||||
SubscriptionStatusComponent,
|
SubscriptionStatusComponent,
|
||||||
ChangePlanDialogComponent,
|
|
||||||
OrganizationPaymentMethodComponent,
|
OrganizationPaymentMethodComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,25 +2,37 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
import { lastValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||||
import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response";
|
import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response";
|
||||||
import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
|
import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
|
||||||
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
||||||
import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
|
import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { FreeTrial } from "../../core/types/free-trial";
|
import { FreeTrial } from "../../core/types/free-trial";
|
||||||
|
import {
|
||||||
|
ChangePlanDialogResultType,
|
||||||
|
openChangePlanDialog,
|
||||||
|
} from "../organizations/change-plan-dialog.component";
|
||||||
|
|
||||||
@Injectable({ providedIn: "root" })
|
@Injectable({ providedIn: "root" })
|
||||||
export class TrialFlowService {
|
export class TrialFlowService {
|
||||||
|
private resellerManagedOrgAlert: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
protected dialogService: DialogService,
|
protected dialogService: DialogService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
protected billingApiService: BillingApiServiceAbstraction,
|
protected billingApiService: BillingApiServiceAbstraction,
|
||||||
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
checkForOrgsWithUpcomingPaymentIssues(
|
checkForOrgsWithUpcomingPaymentIssues(
|
||||||
organization: Organization,
|
organization: Organization,
|
||||||
@@ -66,16 +78,31 @@ export class TrialFlowService {
|
|||||||
org: Organization,
|
org: Organization,
|
||||||
organizationBillingMetadata: OrganizationBillingMetadataResponse,
|
organizationBillingMetadata: OrganizationBillingMetadataResponse,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (organizationBillingMetadata.isSubscriptionUnpaid) {
|
if (
|
||||||
const confirmed = await this.promptForPaymentNavigation(org);
|
organizationBillingMetadata.isSubscriptionUnpaid ||
|
||||||
|
organizationBillingMetadata.isSubscriptionCanceled
|
||||||
|
) {
|
||||||
|
const confirmed = await this.promptForPaymentNavigation(
|
||||||
|
org,
|
||||||
|
organizationBillingMetadata.isSubscriptionCanceled,
|
||||||
|
organizationBillingMetadata.isSubscriptionUnpaid,
|
||||||
|
);
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
await this.navigateToPaymentMethod(org?.id);
|
await this.navigateToPaymentMethod(org?.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async promptForPaymentNavigation(org: Organization): Promise<boolean> {
|
private async promptForPaymentNavigation(
|
||||||
if (!org?.isOwner) {
|
org: Organization,
|
||||||
|
isCanceled: boolean,
|
||||||
|
isUnpaid: boolean,
|
||||||
|
): Promise<boolean> {
|
||||||
|
this.resellerManagedOrgAlert = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.ResellerManagedOrgAlert,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!org?.isOwner && !org.providerId) {
|
||||||
await this.dialogService.openSimpleDialog({
|
await this.dialogService.openSimpleDialog({
|
||||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
||||||
content: { key: "suspendedUserOrgMessage" },
|
content: { key: "suspendedUserOrgMessage" },
|
||||||
@@ -85,13 +112,31 @@ export class TrialFlowService {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return await this.dialogService.openSimpleDialog({
|
|
||||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
if (org.providerId && this.resellerManagedOrgAlert) {
|
||||||
content: { key: "suspendedOwnerOrgMessage" },
|
await this.dialogService.openSimpleDialog({
|
||||||
type: "danger",
|
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
|
||||||
acceptButtonText: this.i18nService.t("continue"),
|
content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] },
|
||||||
cancelButtonText: this.i18nService.t("close"),
|
type: "danger",
|
||||||
});
|
acceptButtonText: this.i18nService.t("close"),
|
||||||
|
cancelButtonText: null,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (org.isOwner && isUnpaid) {
|
||||||
|
return await this.dialogService.openSimpleDialog({
|
||||||
|
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
|
||||||
|
content: { key: "suspendedOwnerOrgMessage" },
|
||||||
|
type: "danger",
|
||||||
|
acceptButtonText: this.i18nService.t("continue"),
|
||||||
|
cancelButtonText: this.i18nService.t("close"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (org.isOwner && isCanceled && this.resellerManagedOrgAlert) {
|
||||||
|
await this.changePlan(org);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async navigateToPaymentMethod(orgId: string) {
|
private async navigateToPaymentMethod(orgId: string) {
|
||||||
@@ -99,4 +144,20 @@ export class TrialFlowService {
|
|||||||
state: { launchPaymentModalAutomatically: true },
|
state: { launchPaymentModalAutomatically: true },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async changePlan(org: Organization) {
|
||||||
|
const subscription = await this.organizationApiService.getSubscription(org.id);
|
||||||
|
const reference = openChangePlanDialog(this.dialogService, {
|
||||||
|
data: {
|
||||||
|
organizationId: org.id,
|
||||||
|
subscription: subscription,
|
||||||
|
productTierType: org.productTierType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await lastValueFrom(reference.closed);
|
||||||
|
if (result === ChangePlanDialogResultType.Closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { firstValueFrom, Subject } from "rxjs";
|
|||||||
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
@@ -16,6 +15,7 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
|||||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { TrialFlowService } from "../../../../billing/services/trial-flow.service";
|
||||||
import { VaultFilterService } from "../services/abstractions/vault-filter.service";
|
import { VaultFilterService } from "../services/abstractions/vault-filter.service";
|
||||||
import {
|
import {
|
||||||
VaultFilterList,
|
VaultFilterList,
|
||||||
@@ -91,6 +91,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
|||||||
return "searchVault";
|
return "searchVault";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private trialFlowService = inject(TrialFlowService);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected vaultFilterService: VaultFilterService,
|
protected vaultFilterService: VaultFilterService,
|
||||||
protected policyService: PolicyService,
|
protected policyService: PolicyService,
|
||||||
@@ -126,13 +128,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.i18nService.t("disabledOrganizationFilterError"),
|
this.i18nService.t("disabledOrganizationFilterError"),
|
||||||
);
|
);
|
||||||
const metadata = await this.billingApiService.getOrganizationBillingMetadata(orgNode.node.id);
|
const metadata = await this.billingApiService.getOrganizationBillingMetadata(orgNode.node.id);
|
||||||
if (metadata.isSubscriptionUnpaid) {
|
await this.trialFlowService.handleUnpaidSubscriptionDialog(orgNode.node, metadata);
|
||||||
const confirmed = await this.promptForPaymentNavigation(orgNode.node);
|
|
||||||
if (confirmed) {
|
|
||||||
await this.navigateToPaymentMethod(orgNode.node.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const filter = this.activeFilter;
|
const filter = this.activeFilter;
|
||||||
if (orgNode?.node.id === "AllVaults") {
|
if (orgNode?.node.id === "AllVaults") {
|
||||||
@@ -144,32 +140,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
|||||||
await this.vaultFilterService.expandOrgFilter();
|
await this.vaultFilterService.expandOrgFilter();
|
||||||
};
|
};
|
||||||
|
|
||||||
private async promptForPaymentNavigation(org: Organization): Promise<boolean> {
|
|
||||||
if (!org?.isOwner) {
|
|
||||||
await this.dialogService.openSimpleDialog({
|
|
||||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
|
||||||
content: { key: "suspendedUserOrgMessage" },
|
|
||||||
type: "danger",
|
|
||||||
acceptButtonText: this.i18nService.t("close"),
|
|
||||||
cancelButtonText: null,
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return await this.dialogService.openSimpleDialog({
|
|
||||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
|
||||||
content: { key: "suspendedOwnerOrgMessage" },
|
|
||||||
type: "danger",
|
|
||||||
acceptButtonText: this.i18nService.t("continue"),
|
|
||||||
cancelButtonText: this.i18nService.t("close"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async navigateToPaymentMethod(orgId: string) {
|
|
||||||
await this.router.navigate(["organizations", `${orgId}`, "billing", "payment-method"], {
|
|
||||||
state: { launchPaymentModalAutomatically: true },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTypeFilter = async (filterNode: TreeNode<CipherTypeFilter>): Promise<void> => {
|
applyTypeFilter = async (filterNode: TreeNode<CipherTypeFilter>): Promise<void> => {
|
||||||
const filter = this.activeFilter;
|
const filter = this.activeFilter;
|
||||||
filter.resetFilter();
|
filter.resetFilter();
|
||||||
|
|||||||
@@ -1128,6 +1128,12 @@
|
|||||||
"verifyIdentity": {
|
"verifyIdentity": {
|
||||||
"message": "Verify your Identity"
|
"message": "Verify your Identity"
|
||||||
},
|
},
|
||||||
|
"whatIsADevice": {
|
||||||
|
"message": "What is a device?"
|
||||||
|
},
|
||||||
|
"aDeviceIs": {
|
||||||
|
"message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times."
|
||||||
|
},
|
||||||
"logInInitiated": {
|
"logInInitiated": {
|
||||||
"message": "Log in initiated"
|
"message": "Log in initiated"
|
||||||
},
|
},
|
||||||
@@ -1715,6 +1721,12 @@
|
|||||||
"logBackIn": {
|
"logBackIn": {
|
||||||
"message": "Please log back in."
|
"message": "Please log back in."
|
||||||
},
|
},
|
||||||
|
"currentSession": {
|
||||||
|
"message": "Current session"
|
||||||
|
},
|
||||||
|
"requestPending": {
|
||||||
|
"message": "Request pending"
|
||||||
|
},
|
||||||
"logBackInOthersToo": {
|
"logBackInOthersToo": {
|
||||||
"message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well."
|
"message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well."
|
||||||
},
|
},
|
||||||
@@ -3765,6 +3777,15 @@
|
|||||||
"device": {
|
"device": {
|
||||||
"message": "Device"
|
"message": "Device"
|
||||||
},
|
},
|
||||||
|
"loginStatus": {
|
||||||
|
"message": "Login status"
|
||||||
|
},
|
||||||
|
"firstLogin": {
|
||||||
|
"message": "First login"
|
||||||
|
},
|
||||||
|
"trusted": {
|
||||||
|
"message": "Trusted"
|
||||||
|
},
|
||||||
"creatingAccountOn": {
|
"creatingAccountOn": {
|
||||||
"message": "Creating account on"
|
"message": "Creating account on"
|
||||||
},
|
},
|
||||||
@@ -8236,6 +8257,18 @@
|
|||||||
"approveRequest": {
|
"approveRequest": {
|
||||||
"message": "Approve request"
|
"message": "Approve request"
|
||||||
},
|
},
|
||||||
|
"deviceApproved": {
|
||||||
|
"message": "Device approved"
|
||||||
|
},
|
||||||
|
"deviceRemoved": {
|
||||||
|
"message": "Device removed"
|
||||||
|
},
|
||||||
|
"removeDevice": {
|
||||||
|
"message": "Remove device"
|
||||||
|
},
|
||||||
|
"removeDeviceConfirmation": {
|
||||||
|
"message": "Are you sure you want to remove this device?"
|
||||||
|
},
|
||||||
"noDeviceRequests": {
|
"noDeviceRequests": {
|
||||||
"message": "No device requests"
|
"message": "No device requests"
|
||||||
},
|
},
|
||||||
@@ -9939,6 +9972,12 @@
|
|||||||
"removeMembers": {
|
"removeMembers": {
|
||||||
"message": "Remove members"
|
"message": "Remove members"
|
||||||
},
|
},
|
||||||
|
"devices": {
|
||||||
|
"message": "Devices"
|
||||||
|
},
|
||||||
|
"deviceListDescription": {
|
||||||
|
"message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now."
|
||||||
|
},
|
||||||
"claimedDomains": {
|
"claimedDomains": {
|
||||||
"message": "Claimed domains"
|
"message": "Claimed domains"
|
||||||
},
|
},
|
||||||
@@ -10066,5 +10105,20 @@
|
|||||||
"example": "02/14/2024"
|
"example": "02/14/2024"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"restartOrganizationSubscription": {
|
||||||
|
"message": "Organization subscription restarted"
|
||||||
|
},
|
||||||
|
"restartSubscription": {
|
||||||
|
"message": "Restart your subscription"
|
||||||
|
},
|
||||||
|
"suspendedManagedOrgMessage": {
|
||||||
|
"message": "Contact $PROVIDER$ for assistance.",
|
||||||
|
"placeholders": {
|
||||||
|
"provider": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Acme c"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1109,7 +1109,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DevicesServiceAbstraction,
|
provide: DevicesServiceAbstraction,
|
||||||
useClass: DevicesServiceImplementation,
|
useClass: DevicesServiceImplementation,
|
||||||
deps: [DevicesApiServiceAbstraction],
|
deps: [DevicesApiServiceAbstraction, AppIdServiceAbstraction],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DeviceTrustServiceAbstraction,
|
provide: DeviceTrustServiceAbstraction,
|
||||||
|
|||||||
@@ -36,4 +36,10 @@ export abstract class DevicesApiServiceAbstraction {
|
|||||||
* @param deviceIdentifier - current device identifier
|
* @param deviceIdentifier - current device identifier
|
||||||
*/
|
*/
|
||||||
postDeviceTrustLoss: (deviceIdentifier: string) => Promise<void>;
|
postDeviceTrustLoss: (deviceIdentifier: string) => Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivates a device
|
||||||
|
* @param deviceId - The device ID
|
||||||
|
*/
|
||||||
|
deactivateDevice: (deviceId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { DeviceResponse } from "./responses/device.response";
|
||||||
import { DeviceView } from "./views/device.view";
|
import { DeviceView } from "./views/device.view";
|
||||||
|
|
||||||
export abstract class DevicesServiceAbstraction {
|
export abstract class DevicesServiceAbstraction {
|
||||||
getDevices$: () => Observable<Array<DeviceView>>;
|
abstract getDevices$(): Observable<Array<DeviceView>>;
|
||||||
getDeviceByIdentifier$: (deviceIdentifier: string) => Observable<DeviceView>;
|
abstract getDeviceByIdentifier$(deviceIdentifier: string): Observable<DeviceView>;
|
||||||
isDeviceKnownForUser$: (email: string, deviceIdentifier: string) => Observable<boolean>;
|
abstract isDeviceKnownForUser$(email: string, deviceIdentifier: string): Observable<boolean>;
|
||||||
updateTrustedDeviceKeys$: (
|
abstract updateTrustedDeviceKeys$(
|
||||||
deviceIdentifier: string,
|
deviceIdentifier: string,
|
||||||
devicePublicKeyEncryptedUserKey: string,
|
devicePublicKeyEncryptedUserKey: string,
|
||||||
userKeyEncryptedDevicePublicKey: string,
|
userKeyEncryptedDevicePublicKey: string,
|
||||||
deviceKeyEncryptedDevicePrivateKey: string,
|
deviceKeyEncryptedDevicePrivateKey: string,
|
||||||
) => Observable<DeviceView>;
|
): Observable<DeviceView>;
|
||||||
|
abstract deactivateDevice$(deviceId: string): Observable<void>;
|
||||||
|
abstract getCurrentDevice$(): Observable<DeviceResponse>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ export class DeviceResponse extends BaseResponse {
|
|||||||
type: DeviceType;
|
type: DeviceType;
|
||||||
creationDate: string;
|
creationDate: string;
|
||||||
revisionDate: string;
|
revisionDate: string;
|
||||||
|
isTrusted: boolean;
|
||||||
|
devicePendingAuthRequest: { id: string; creationDate: string } | null;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.id = this.getResponseProperty("Id");
|
this.id = this.getResponseProperty("Id");
|
||||||
@@ -18,5 +21,7 @@ export class DeviceResponse extends BaseResponse {
|
|||||||
this.type = this.getResponseProperty("Type");
|
this.type = this.getResponseProperty("Type");
|
||||||
this.creationDate = this.getResponseProperty("CreationDate");
|
this.creationDate = this.getResponseProperty("CreationDate");
|
||||||
this.revisionDate = this.getResponseProperty("RevisionDate");
|
this.revisionDate = this.getResponseProperty("RevisionDate");
|
||||||
|
this.isTrusted = this.getResponseProperty("IsTrusted");
|
||||||
|
this.devicePendingAuthRequest = this.getResponseProperty("DevicePendingAuthRequest");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class DeviceView implements View {
|
|||||||
type: DeviceType;
|
type: DeviceType;
|
||||||
creationDate: string;
|
creationDate: string;
|
||||||
revisionDate: string;
|
revisionDate: string;
|
||||||
|
response: DeviceResponse;
|
||||||
|
|
||||||
constructor(deviceResponse: DeviceResponse) {
|
constructor(deviceResponse: DeviceResponse) {
|
||||||
Object.assign(this, deviceResponse);
|
Object.assign(this, deviceResponse);
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export class UpdateDevicesTrustRequest extends SecretVerificationRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DeviceKeysUpdateRequest {
|
export class DeviceKeysUpdateRequest {
|
||||||
encryptedPublicKey: string;
|
encryptedPublicKey: string | undefined;
|
||||||
encryptedUserKey: string;
|
encryptedUserKey: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OtherDeviceKeysUpdateRequest extends DeviceKeysUpdateRequest {
|
export class OtherDeviceKeysUpdateRequest extends DeviceKeysUpdateRequest {
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { ApiService } from "../../abstractions/api.service";
|
||||||
|
import { DeviceResponse } from "../abstractions/devices/responses/device.response";
|
||||||
|
|
||||||
|
import { DevicesApiServiceImplementation } from "./devices-api.service.implementation";
|
||||||
|
|
||||||
|
describe("DevicesApiServiceImplementation", () => {
|
||||||
|
let devicesApiService: DevicesApiServiceImplementation;
|
||||||
|
let apiService: MockProxy<ApiService>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
apiService = mock<ApiService>();
|
||||||
|
devicesApiService = new DevicesApiServiceImplementation(apiService);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getKnownDevice", () => {
|
||||||
|
it("calls api with correct parameters", async () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const deviceIdentifier = "device123";
|
||||||
|
apiService.send.mockResolvedValue(true);
|
||||||
|
|
||||||
|
const result = await devicesApiService.getKnownDevice(email, deviceIdentifier);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(apiService.send).toHaveBeenCalledWith(
|
||||||
|
"GET",
|
||||||
|
"/devices/knowndevice",
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getDeviceByIdentifier", () => {
|
||||||
|
it("returns device response", async () => {
|
||||||
|
const deviceIdentifier = "device123";
|
||||||
|
const mockResponse = { id: "123", name: "Test Device" };
|
||||||
|
apiService.send.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await devicesApiService.getDeviceByIdentifier(deviceIdentifier);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(DeviceResponse);
|
||||||
|
expect(apiService.send).toHaveBeenCalledWith(
|
||||||
|
"GET",
|
||||||
|
`/devices/identifier/${deviceIdentifier}`,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateTrustedDeviceKeys", () => {
|
||||||
|
it("updates device keys and returns device response", async () => {
|
||||||
|
const deviceIdentifier = "device123";
|
||||||
|
const publicKeyEncrypted = "encryptedPublicKey";
|
||||||
|
const userKeyEncrypted = "encryptedUserKey";
|
||||||
|
const deviceKeyEncrypted = "encryptedDeviceKey";
|
||||||
|
const mockResponse = { id: "123", name: "Test Device" };
|
||||||
|
apiService.send.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await devicesApiService.updateTrustedDeviceKeys(
|
||||||
|
deviceIdentifier,
|
||||||
|
publicKeyEncrypted,
|
||||||
|
userKeyEncrypted,
|
||||||
|
deviceKeyEncrypted,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(DeviceResponse);
|
||||||
|
expect(apiService.send).toHaveBeenCalledWith(
|
||||||
|
"PUT",
|
||||||
|
`/devices/${deviceIdentifier}/keys`,
|
||||||
|
{
|
||||||
|
encryptedPrivateKey: deviceKeyEncrypted,
|
||||||
|
encryptedPublicKey: userKeyEncrypted,
|
||||||
|
encryptedUserKey: publicKeyEncrypted,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("error handling", () => {
|
||||||
|
it("propagates api errors", async () => {
|
||||||
|
const error = new Error("API Error");
|
||||||
|
apiService.send.mockRejectedValue(error);
|
||||||
|
|
||||||
|
await expect(devicesApiService.getDevices()).rejects.toThrow("API Error");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -117,4 +117,8 @@ export class DevicesApiServiceImplementation implements DevicesApiServiceAbstrac
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deactivateDevice(deviceId: string): Promise<void> {
|
||||||
|
await this.apiService.send("POST", `/devices/${deviceId}/deactivate`, null, true, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Observable, defer, map } from "rxjs";
|
import { Observable, defer, map } from "rxjs";
|
||||||
|
|
||||||
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
|
||||||
import { ListResponse } from "../../../models/response/list.response";
|
import { ListResponse } from "../../../models/response/list.response";
|
||||||
import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction";
|
import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction";
|
||||||
import { DeviceResponse } from "../../abstractions/devices/responses/device.response";
|
import { DeviceResponse } from "../../abstractions/devices/responses/device.response";
|
||||||
@@ -15,7 +17,10 @@ import { DevicesApiServiceAbstraction } from "../../abstractions/devices-api.ser
|
|||||||
* (i.e., promsise --> observables are cold until subscribed to)
|
* (i.e., promsise --> observables are cold until subscribed to)
|
||||||
*/
|
*/
|
||||||
export class DevicesServiceImplementation implements DevicesServiceAbstraction {
|
export class DevicesServiceImplementation implements DevicesServiceAbstraction {
|
||||||
constructor(private devicesApiService: DevicesApiServiceAbstraction) {}
|
constructor(
|
||||||
|
private devicesApiService: DevicesApiServiceAbstraction,
|
||||||
|
private appIdService: AppIdService,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Gets the list of all devices.
|
* @description Gets the list of all devices.
|
||||||
@@ -65,4 +70,21 @@ export class DevicesServiceImplementation implements DevicesServiceAbstraction {
|
|||||||
),
|
),
|
||||||
).pipe(map((deviceResponse: DeviceResponse) => new DeviceView(deviceResponse)));
|
).pipe(map((deviceResponse: DeviceResponse) => new DeviceView(deviceResponse)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Deactivates a device
|
||||||
|
*/
|
||||||
|
deactivateDevice$(deviceId: string): Observable<void> {
|
||||||
|
return defer(() => this.devicesApiService.deactivateDevice(deviceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Gets the current device.
|
||||||
|
*/
|
||||||
|
getCurrentDevice$(): Observable<DeviceResponse> {
|
||||||
|
return defer(async () => {
|
||||||
|
const deviceIdentifier = await this.appIdService.getAppId();
|
||||||
|
return this.devicesApiService.getDeviceByIdentifier(deviceIdentifier);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/reque
|
|||||||
import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response";
|
import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response";
|
||||||
import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response";
|
import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response";
|
||||||
|
|
||||||
|
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
|
||||||
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||||
import { PlanResponse } from "../../billing/models/response/plan.response";
|
import { PlanResponse } from "../../billing/models/response/plan.response";
|
||||||
@@ -74,4 +75,9 @@ export abstract class BillingApiServiceAbstraction {
|
|||||||
organizationId: string,
|
organizationId: string,
|
||||||
request: VerifyBankAccountRequest,
|
request: VerifyBankAccountRequest,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
|
restartSubscription: (
|
||||||
|
organizationId: string,
|
||||||
|
request: OrganizationCreateRequest,
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,4 +57,9 @@ export abstract class OrganizationBillingServiceAbstraction {
|
|||||||
) => Promise<OrganizationResponse>;
|
) => Promise<OrganizationResponse>;
|
||||||
|
|
||||||
startFree: (subscription: SubscriptionInformation) => Promise<OrganizationResponse>;
|
startFree: (subscription: SubscriptionInformation) => Promise<OrganizationResponse>;
|
||||||
|
|
||||||
|
restartSubscription: (
|
||||||
|
organizationId: string,
|
||||||
|
subscription: SubscriptionInformation,
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export class OrganizationBillingMetadataResponse extends BaseResponse {
|
|||||||
invoiceDueDate: Date | null;
|
invoiceDueDate: Date | null;
|
||||||
invoiceCreatedDate: Date | null;
|
invoiceCreatedDate: Date | null;
|
||||||
subPeriodEndDate: Date | null;
|
subPeriodEndDate: Date | null;
|
||||||
|
isSubscriptionCanceled: boolean;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
@@ -23,6 +24,7 @@ export class OrganizationBillingMetadataResponse extends BaseResponse {
|
|||||||
this.invoiceDueDate = this.parseDate(this.getResponseProperty("InvoiceDueDate"));
|
this.invoiceDueDate = this.parseDate(this.getResponseProperty("InvoiceDueDate"));
|
||||||
this.invoiceCreatedDate = this.parseDate(this.getResponseProperty("InvoiceCreatedDate"));
|
this.invoiceCreatedDate = this.parseDate(this.getResponseProperty("InvoiceCreatedDate"));
|
||||||
this.subPeriodEndDate = this.parseDate(this.getResponseProperty("SubPeriodEndDate"));
|
this.subPeriodEndDate = this.parseDate(this.getResponseProperty("SubPeriodEndDate"));
|
||||||
|
this.isSubscriptionCanceled = this.getResponseProperty("IsSubscriptionCanceled");
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseDate(dateString: any): Date | null {
|
private parseDate(dateString: any): Date | null {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { ToastService } from "@bitwarden/components";
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { ApiService } from "../../abstractions/api.service";
|
import { ApiService } from "../../abstractions/api.service";
|
||||||
|
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
|
||||||
import { BillingApiServiceAbstraction } from "../../billing/abstractions";
|
import { BillingApiServiceAbstraction } from "../../billing/abstractions";
|
||||||
import { PaymentMethodType } from "../../billing/enums";
|
import { PaymentMethodType } from "../../billing/enums";
|
||||||
import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request";
|
import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request";
|
||||||
@@ -214,6 +215,19 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async restartSubscription(
|
||||||
|
organizationId: string,
|
||||||
|
request: OrganizationCreateRequest,
|
||||||
|
): Promise<void> {
|
||||||
|
return await this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/organizations/" + organizationId + "/billing/restart-subscription",
|
||||||
|
request,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async execute(request: () => Promise<any>): Promise<any> {
|
private async execute(request: () => Promise<any>): Promise<any> {
|
||||||
try {
|
try {
|
||||||
return await request();
|
return await request();
|
||||||
|
|||||||
@@ -223,4 +223,17 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
|
|||||||
request.additionalStorageGb = information.storage;
|
request.additionalStorageGb = information.storage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async restartSubscription(
|
||||||
|
organizationId: string,
|
||||||
|
subscription: SubscriptionInformation,
|
||||||
|
): Promise<void> {
|
||||||
|
const request = new OrganizationCreateRequest();
|
||||||
|
const organizationKeys = await this.makeOrganizationKeys();
|
||||||
|
this.setOrganizationKeys(request, organizationKeys);
|
||||||
|
this.setOrganizationInformation(request, subscription.organization);
|
||||||
|
this.setPlanInformation(request, subscription.plan);
|
||||||
|
this.setPaymentInformation(request, subscription.payment);
|
||||||
|
await this.billingApiService.restartSubscription(organizationId, request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,18 +27,40 @@ export enum DeviceType {
|
|||||||
LinuxCLI = 25,
|
LinuxCLI = 25,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MobileDeviceTypes: Set<DeviceType> = new Set([
|
/**
|
||||||
DeviceType.Android,
|
* Device type metadata
|
||||||
DeviceType.iOS,
|
* Each device type has a category corresponding to the client type and platform (Android, iOS, Chrome, Firefox, etc.)
|
||||||
DeviceType.AndroidAmazon,
|
*/
|
||||||
]);
|
interface DeviceTypeMetadata {
|
||||||
|
category: "mobile" | "extension" | "webVault" | "desktop" | "cli" | "sdk" | "server";
|
||||||
|
platform: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const DesktopDeviceTypes: Set<DeviceType> = new Set([
|
export const DeviceTypeMetadata: Record<DeviceType, DeviceTypeMetadata> = {
|
||||||
DeviceType.WindowsDesktop,
|
[DeviceType.Android]: { category: "mobile", platform: "Android" },
|
||||||
DeviceType.MacOsDesktop,
|
[DeviceType.iOS]: { category: "mobile", platform: "iOS" },
|
||||||
DeviceType.LinuxDesktop,
|
[DeviceType.AndroidAmazon]: { category: "mobile", platform: "Amazon" },
|
||||||
DeviceType.UWP,
|
[DeviceType.ChromeExtension]: { category: "extension", platform: "Chrome" },
|
||||||
DeviceType.WindowsCLI,
|
[DeviceType.FirefoxExtension]: { category: "extension", platform: "Firefox" },
|
||||||
DeviceType.MacOsCLI,
|
[DeviceType.OperaExtension]: { category: "extension", platform: "Opera" },
|
||||||
DeviceType.LinuxCLI,
|
[DeviceType.EdgeExtension]: { category: "extension", platform: "Edge" },
|
||||||
]);
|
[DeviceType.VivaldiExtension]: { category: "extension", platform: "Vivaldi" },
|
||||||
|
[DeviceType.SafariExtension]: { category: "extension", platform: "Safari" },
|
||||||
|
[DeviceType.ChromeBrowser]: { category: "webVault", platform: "Chrome" },
|
||||||
|
[DeviceType.FirefoxBrowser]: { category: "webVault", platform: "Firefox" },
|
||||||
|
[DeviceType.OperaBrowser]: { category: "webVault", platform: "Opera" },
|
||||||
|
[DeviceType.EdgeBrowser]: { category: "webVault", platform: "Edge" },
|
||||||
|
[DeviceType.IEBrowser]: { category: "webVault", platform: "IE" },
|
||||||
|
[DeviceType.SafariBrowser]: { category: "webVault", platform: "Safari" },
|
||||||
|
[DeviceType.VivaldiBrowser]: { category: "webVault", platform: "Vivaldi" },
|
||||||
|
[DeviceType.UnknownBrowser]: { category: "webVault", platform: "Unknown" },
|
||||||
|
[DeviceType.WindowsDesktop]: { category: "desktop", platform: "Windows" },
|
||||||
|
[DeviceType.MacOsDesktop]: { category: "desktop", platform: "macOS" },
|
||||||
|
[DeviceType.LinuxDesktop]: { category: "desktop", platform: "Linux" },
|
||||||
|
[DeviceType.UWP]: { category: "desktop", platform: "Windows UWP" },
|
||||||
|
[DeviceType.WindowsCLI]: { category: "cli", platform: "Windows" },
|
||||||
|
[DeviceType.MacOsCLI]: { category: "cli", platform: "macOS" },
|
||||||
|
[DeviceType.LinuxCLI]: { category: "cli", platform: "Linux" },
|
||||||
|
[DeviceType.SDK]: { category: "sdk", platform: "" },
|
||||||
|
[DeviceType.Server]: { category: "server", platform: "" },
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||||
<p class="tw-text-center" bitTypography="body1">
|
<p class="tw-text-center" bitTypography="body1">
|
||||||
{{ "newDeviceVerificationNoticeContentPage1" | i18n }}
|
{{ "newDeviceVerificationNoticeContentPage1" | i18n }}
|
||||||
<a
|
<a bitLink (click)="navigateToNewDeviceVerificationHelp($event)" href="#">
|
||||||
bitLink
|
|
||||||
href="https://bitwarden.com/help/new-device-verification/"
|
|
||||||
rel="noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{ "learnMore" | i18n }}.
|
{{ "learnMore" | i18n }}.
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -121,4 +121,10 @@ export class NewDeviceVerificationNoticePageOneComponent implements OnInit, Afte
|
|||||||
|
|
||||||
await this.router.navigate(["/vault"]);
|
await this.router.navigate(["/vault"]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
navigateToNewDeviceVerificationHelp(event: Event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.platformUtilsService.launchUri("https://bitwarden.com/help/new-device-verification/");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
265
package-lock.json
generated
265
package-lock.json
generated
@@ -26,6 +26,7 @@
|
|||||||
"@angular/router": "18.2.13",
|
"@angular/router": "18.2.13",
|
||||||
"@bitwarden/sdk-internal": "0.2.0-main.38",
|
"@bitwarden/sdk-internal": "0.2.0-main.38",
|
||||||
"@electron/fuses": "1.8.0",
|
"@electron/fuses": "1.8.0",
|
||||||
|
"@emotion/css": "11.13.5",
|
||||||
"@koa/multer": "3.0.2",
|
"@koa/multer": "3.0.2",
|
||||||
"@koa/router": "13.1.0",
|
"@koa/router": "13.1.0",
|
||||||
"@microsoft/signalr": "8.0.7",
|
"@microsoft/signalr": "8.0.7",
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
"koa": "2.15.3",
|
"koa": "2.15.3",
|
||||||
"koa-bodyparser": "4.4.1",
|
"koa-bodyparser": "4.4.1",
|
||||||
"koa-json": "2.0.2",
|
"koa-json": "2.0.2",
|
||||||
|
"lit": "3.2.1",
|
||||||
"lowdb": "1.0.0",
|
"lowdb": "1.0.0",
|
||||||
"lunr": "2.3.9",
|
"lunr": "2.3.9",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
@@ -4344,7 +4346,6 @@
|
|||||||
"version": "7.25.0",
|
"version": "7.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
||||||
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@@ -5650,6 +5651,109 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin": {
|
||||||
|
"version": "11.13.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||||
|
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-module-imports": "^7.16.7",
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/hash": "^0.9.2",
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"babel-plugin-macros": "^3.1.0",
|
||||||
|
"convert-source-map": "^1.5.0",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"find-root": "^1.1.0",
|
||||||
|
"source-map": "^0.5.7",
|
||||||
|
"stylis": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin/node_modules/source-map": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/cache": {
|
||||||
|
"version": "11.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
||||||
|
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/sheet": "^1.4.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
|
"stylis": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/css": {
|
||||||
|
"version": "11.13.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz",
|
||||||
|
"integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
|
"@emotion/cache": "^11.13.5",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"@emotion/sheet": "^1.4.0",
|
||||||
|
"@emotion/utils": "^1.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/hash": {
|
||||||
|
"version": "0.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||||
|
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/memoize": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/serialize": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/hash": "^0.9.2",
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/unitless": "^0.10.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/sheet": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/unitless": {
|
||||||
|
"version": "0.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
|
||||||
|
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/utils": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/weak-memoize": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.23.0",
|
"version": "0.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz",
|
||||||
@@ -5836,6 +5940,50 @@
|
|||||||
"lit": "^2.1.3"
|
"lit": "^2.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@figspec/components/node_modules/@lit/reactive-element": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
|
||||||
|
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@figspec/components/node_modules/lit": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit/reactive-element": "^1.6.0",
|
||||||
|
"lit-element": "^3.3.0",
|
||||||
|
"lit-html": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@figspec/components/node_modules/lit-element": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.1.0",
|
||||||
|
"@lit/reactive-element": "^1.3.0",
|
||||||
|
"lit-html": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@figspec/components/node_modules/lit-html": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@figspec/react": {
|
"node_modules/@figspec/react": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@figspec/react/-/react-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@figspec/react/-/react-1.0.3.tgz",
|
||||||
@@ -6971,17 +7119,15 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz",
|
||||||
"integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==",
|
"integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/@lit/reactive-element": {
|
"node_modules/@lit/reactive-element": {
|
||||||
"version": "1.6.3",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
|
||||||
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
|
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lit-labs/ssr-dom-shim": "^1.0.0"
|
"@lit-labs/ssr-dom-shim": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lmdb/lmdb-darwin-arm64": {
|
"node_modules/@lmdb/lmdb-darwin-arm64": {
|
||||||
@@ -9702,7 +9848,6 @@
|
|||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||||
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
|
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/plist": {
|
"node_modules/@types/plist": {
|
||||||
@@ -9871,7 +10016,6 @@
|
|||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/unist": {
|
"node_modules/@types/unist": {
|
||||||
@@ -11961,6 +12105,46 @@
|
|||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/babel-plugin-macros": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"cosmiconfig": "^7.0.0",
|
||||||
|
"resolve": "^1.19.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/babel-plugin-macros/node_modules/cosmiconfig": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/parse-json": "^4.0.0",
|
||||||
|
"import-fresh": "^3.2.1",
|
||||||
|
"parse-json": "^5.0.0",
|
||||||
|
"path-type": "^4.0.0",
|
||||||
|
"yaml": "^1.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/babel-plugin-macros/node_modules/yaml": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-plugin-polyfill-corejs2": {
|
"node_modules/babel-plugin-polyfill-corejs2": {
|
||||||
"version": "0.4.11",
|
"version": "0.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
|
||||||
@@ -12909,7 +13093,6 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -13812,7 +13995,6 @@
|
|||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
@@ -14333,7 +14515,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/data-urls": {
|
"node_modules/data-urls": {
|
||||||
@@ -15560,7 +15741,6 @@
|
|||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-arrayish": "^0.2.1"
|
"is-arrayish": "^0.2.1"
|
||||||
@@ -15809,7 +15989,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -17241,6 +17420,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/find-root": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/find-up": {
|
"node_modules/find-up": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||||
@@ -18899,7 +19084,6 @@
|
|||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
|
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parent-module": "^1.0.0",
|
"parent-module": "^1.0.0",
|
||||||
@@ -18916,7 +19100,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
@@ -19216,7 +19399,6 @@
|
|||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/is-bigint": {
|
"node_modules/is-bigint": {
|
||||||
@@ -19292,7 +19474,6 @@
|
|||||||
"version": "2.15.1",
|
"version": "2.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
@@ -22116,7 +22297,6 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lint-staged": {
|
"node_modules/lint-staged": {
|
||||||
@@ -22459,34 +22639,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lit": {
|
"node_modules/lit": {
|
||||||
"version": "2.8.0",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz",
|
||||||
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
|
"integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lit/reactive-element": "^1.6.0",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
"lit-element": "^3.3.0",
|
"lit-element": "^4.1.0",
|
||||||
"lit-html": "^2.8.0"
|
"lit-html": "^3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lit-element": {
|
"node_modules/lit-element": {
|
||||||
"version": "3.3.3",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz",
|
||||||
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
|
"integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lit-labs/ssr-dom-shim": "^1.1.0",
|
"@lit-labs/ssr-dom-shim": "^1.2.0",
|
||||||
"@lit/reactive-element": "^1.3.0",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
"lit-html": "^2.8.0"
|
"lit-html": "^3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lit-html": {
|
"node_modules/lit-html": {
|
||||||
"version": "2.8.0",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz",
|
||||||
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
|
"integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/trusted-types": "^2.0.2"
|
"@types/trusted-types": "^2.0.2"
|
||||||
@@ -26075,7 +26252,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"callsites": "^3.0.0"
|
"callsites": "^3.0.0"
|
||||||
@@ -26088,7 +26264,6 @@
|
|||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.0.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
@@ -26107,7 +26282,6 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/parse-node-version": {
|
"node_modules/parse-node-version": {
|
||||||
@@ -26371,7 +26545,6 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
@@ -26408,7 +26581,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -27759,7 +27931,6 @@
|
|||||||
"version": "0.14.1",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/regenerator-transform": {
|
"node_modules/regenerator-transform": {
|
||||||
@@ -28058,7 +28229,6 @@
|
|||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.13.0",
|
"is-core-module": "^2.13.0",
|
||||||
@@ -29954,6 +30124,12 @@
|
|||||||
"webpack": "^5.27.0"
|
"webpack": "^5.27.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylis": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/sucrase": {
|
"node_modules/sucrase": {
|
||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||||
@@ -30016,7 +30192,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|||||||
@@ -156,6 +156,7 @@
|
|||||||
"@angular/router": "18.2.13",
|
"@angular/router": "18.2.13",
|
||||||
"@bitwarden/sdk-internal": "0.2.0-main.38",
|
"@bitwarden/sdk-internal": "0.2.0-main.38",
|
||||||
"@electron/fuses": "1.8.0",
|
"@electron/fuses": "1.8.0",
|
||||||
|
"@emotion/css": "11.13.5",
|
||||||
"@koa/multer": "3.0.2",
|
"@koa/multer": "3.0.2",
|
||||||
"@koa/router": "13.1.0",
|
"@koa/router": "13.1.0",
|
||||||
"@microsoft/signalr": "8.0.7",
|
"@microsoft/signalr": "8.0.7",
|
||||||
@@ -180,6 +181,7 @@
|
|||||||
"koa": "2.15.3",
|
"koa": "2.15.3",
|
||||||
"koa-bodyparser": "4.4.1",
|
"koa-bodyparser": "4.4.1",
|
||||||
"koa-json": "2.0.2",
|
"koa-json": "2.0.2",
|
||||||
|
"lit": "3.2.1",
|
||||||
"lowdb": "1.0.0",
|
"lowdb": "1.0.0",
|
||||||
"lunr": "2.3.9",
|
"lunr": "2.3.9",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "1.4.5-lts.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user