diff --git a/CHANGELOG.md b/CHANGELOG.md index 2360d2a73..68b00bbdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## [1.36.1](https://github.com/mParticle/aquarium/compare/v1.36.0...v1.36.1) (2024-12-06) + +### Bug Fixes + +- rename notification icons ([#508](https://github.com/mParticle/aquarium/issues/508)) ([98ea06f](https://github.com/mParticle/aquarium/commit/98ea06f1c0f66d7ca469f77158b1d4cf75cb8f6f)) + +# [1.36.0](https://github.com/mParticle/aquarium/compare/v1.35.0...v1.36.0) (2024-11-28) + +### Bug Fixes + +- typography component updates ([#497](https://github.com/mParticle/aquarium/issues/497)) ([d32d390](https://github.com/mParticle/aquarium/commit/d32d3900c3d89406a0f88bccd967ed356e8edfe5)) + +### Features + +- add abSplit icon ([#499](https://github.com/mParticle/aquarium/issues/499)) ([8776f1c](https://github.com/mParticle/aquarium/commit/8776f1c2fced9cc75af92fb74960510b4e56833a)) +- add notificationSubscriptions icon ([#505](https://github.com/mParticle/aquarium/issues/505)) ([b4976bc](https://github.com/mParticle/aquarium/commit/b4976bca3878813fadb453ff2426154b7fa1ef04)) +- utilize generics in UserPreferencesService ([#504](https://github.com/mParticle/aquarium/issues/504)) ([4838a86](https://github.com/mParticle/aquarium/commit/4838a86929bd743f606688ceefeaf13ebf5b1f9d)) + # [1.35.0](https://github.com/mParticle/aquarium/compare/v1.34.0...v1.35.0) (2024-11-15) ### Bug Fixes diff --git a/package-lock.json b/package-lock.json index bf9a97599..05b33f1d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mparticle/aquarium", - "version": "1.35.0", + "version": "1.36.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@mparticle/aquarium", - "version": "1.35.0", + "version": "1.36.1", "license": "Apache-2.0", "dependencies": { "lodash.clonedeep": "4.5.0" @@ -33,6 +33,7 @@ "@types/multer": "1.4.12", "@typescript-eslint/eslint-plugin": "6.19.0", "@vitejs/plugin-react": "4.2.1", + "@vitest/coverage-v8": "1.3.1", "concurrently": "8.2.2", "cz-conventional-changelog": "3.3.0", "eslint": "8.56.0", @@ -414,18 +415,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -540,10 +541,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.26.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -805,14 +809,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -7146,6 +7149,33 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.3.1.tgz", + "integrity": "sha512-UuBnkSJUNE9rdHjDCPyJ4fYuMkoMtnghes1XohYa4At0MS3OQSAo97FrbwSLRshYsXThMZy1+ybD/byK5llyIg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "1.3.1" + } + }, "node_modules/@vitest/expect": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", @@ -17486,6 +17516,17 @@ "node": ">=12" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -31820,15 +31861,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index aa06a8809..75bb14915 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mparticle/aquarium", - "version": "1.35.0", + "version": "1.36.1", "description": "mParticle Component Library", "license": "Apache-2.0", "keywords": [ @@ -52,6 +52,7 @@ "@types/multer": "1.4.12", "@typescript-eslint/eslint-plugin": "6.19.0", "@vitejs/plugin-react": "4.2.1", + "@vitest/coverage-v8": "1.3.1", "concurrently": "8.2.2", "cz-conventional-changelog": "3.3.0", "eslint": "8.56.0", diff --git a/src/assets/svg/crosshair.svg b/src/assets/svg/crosshair.svg new file mode 100644 index 000000000..dd26ccdb1 --- /dev/null +++ b/src/assets/svg/crosshair.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/svg/mp_pm_lt_notification_subscribe.svg b/src/assets/svg/mp_pm_lt_notification_subscribe.svg new file mode 100644 index 000000000..94aacc50b --- /dev/null +++ b/src/assets/svg/mp_pm_lt_notification_subscribe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/mp_pm_lt_notification_subscribed.svg b/src/assets/svg/mp_pm_lt_notification_subscribed.svg new file mode 100644 index 000000000..8ae0f1e2c --- /dev/null +++ b/src/assets/svg/mp_pm_lt_notification_subscribed.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/general/Typography/Link.stories.tsx b/src/components/general/Typography/Link.stories.tsx index 74e9e65bf..8786cd167 100644 --- a/src/components/general/Typography/Link.stories.tsx +++ b/src/components/general/Typography/Link.stories.tsx @@ -1,35 +1,74 @@ -import { type Meta } from '@storybook/react' -import { type StoryObj } from '@storybook/react' -import { Space } from 'src/components' +import type { Meta } from '@storybook/react' +import type { StoryObj } from '@storybook/react' +import { Flex, Icon, Space, Tooltip } from 'src/components' import { Typography } from 'src/components/general/Typography/Typography' import { ExampleStory } from 'src/utils/ExampleStory' +import { TypographyColors } from './colors' const meta: Meta = { - title: 'Components/General/Link', + title: 'Components/General/Typography/Link', component: props => Example Link, args: { + children: 'Example Text', + type: undefined, + color: undefined, + size: 'base', code: false, copyable: false, delete: false, disabled: false, editable: false, ellipsis: false, - keyboard: false, mark: false, strong: false, italic: false, - type: undefined, underline: false, - onClick: (event: React.MouseEvent) => { - console.log('Link Clicked') - }, }, argTypes: { + children: { + control: 'text', + name: 'text', + }, type: { control: 'select', options: ['secondary', 'success', 'warning', 'danger'], }, + size: { + control: 'select', + options: ['base', 'sm', 'lg', 'xl'], + }, + color: { + control: 'select', + options: TypographyColors, + }, + copyable: { + control: 'boolean', + }, + delete: { + control: 'boolean', + }, + disabled: { + control: 'boolean', + }, + editable: { + control: 'boolean', + }, + ellipsis: { + control: 'boolean', + }, + mark: { + control: 'boolean', + }, + strong: { + control: 'boolean', + }, + italic: { + control: 'boolean', + }, + code: { + control: 'boolean', + }, }, } export default meta @@ -43,95 +82,28 @@ type Story = StoryObj export const Primary: Story = {} -export const Code: Story = { - args: { - code: true, - }, -} - -export const Copyable: Story = { - args: { - copyable: true, - }, -} - -export const Deleted: Story = { - args: { - delete: true, - }, -} - -export const Disabled: Story = { - args: { - disabled: true, - }, -} - -export const Editable: Story = { - args: { - editable: true, - }, -} - -export const Keyboard: Story = { - args: { - keyboard: true, - }, -} - -export const Marked: Story = { - args: { - mark: true, - }, -} - -export const Strong: Story = { - args: { - strong: true, - }, -} - -export const Italic: Story = { - args: { - italic: true, - }, -} - -export const Success: Story = { - args: { - type: 'success', - }, -} - -export const Secondary: Story = { - args: { - type: 'secondary', - }, -} - -export const Warning: Story = { - args: { - type: 'warning', - }, -} - -export const Danger: Story = { - args: { - type: 'danger', - }, -} - -export const Underline: Story = { - args: { - underline: true, - }, -} - -export const CustomOnClick: Story = { - args: { - onClick: event => { - alert('Custom Click Handler') - }, +export const InsideTooltip: Story = { + render: () => { + return ( + + + Typography inside of a tooltip, hover icon to see + + + Help lorem ipsum{' '} + + Learn More + + + + }> + + + + + ) }, } diff --git a/src/components/general/Typography/Paragraph.stories.tsx b/src/components/general/Typography/Paragraph.stories.tsx index e77bb86c7..1719a6473 100644 --- a/src/components/general/Typography/Paragraph.stories.tsx +++ b/src/components/general/Typography/Paragraph.stories.tsx @@ -1,5 +1,5 @@ -import { type Meta } from '@storybook/react' -import { type StoryObj } from '@storybook/react' +import type { Meta } from '@storybook/react' +import type { StoryObj } from '@storybook/react' import { Typography } from 'src/components/general/Typography/Typography' import { ExampleStory } from 'src/utils/ExampleStory' import { useMemo } from 'react' @@ -8,12 +8,16 @@ import { Radio } from 'src/components' import { Switch } from 'src/components' import { Slider } from 'src/components' import { Icon } from 'src/components' +import { TypographyColors } from './colors' const meta: Meta = { - title: 'Components/General/Typography.Paragraph', + title: 'Components/General/Typography/Paragraph', component: props => Paragraph text goes here, - args: { + children: 'Example Text', + type: undefined, + color: undefined, + size: 'base', code: false, copyable: false, delete: false, @@ -23,17 +27,52 @@ const meta: Meta = { mark: false, strong: false, italic: false, - type: undefined, underline: false, - onClick: event => { - console.log('Paragraph Clicked') - }, }, argTypes: { + children: { + control: 'text', + name: 'text', + }, type: { control: 'select', options: ['secondary', 'success', 'warning', 'danger'], }, + size: { + control: 'select', + options: ['base', 'sm', 'lg', 'xl'], + }, + color: { + control: 'select', + options: TypographyColors, + }, + copyable: { + control: 'boolean', + }, + delete: { + control: 'boolean', + }, + disabled: { + control: 'boolean', + }, + editable: { + control: 'boolean', + }, + ellipsis: { + control: 'boolean', + }, + mark: { + control: 'boolean', + }, + strong: { + control: 'boolean', + }, + italic: { + control: 'boolean', + }, + code: { + control: 'boolean', + }, }, } export default meta @@ -47,98 +86,6 @@ type Story = StoryObj export const Primary: Story = {} -export const Code: Story = { - args: { - code: true, - }, -} - -export const Copyable: Story = { - args: { - copyable: true, - }, -} - -export const DeletedLine: Story = { - args: { - delete: true, - }, -} - -export const Disabled: Story = { - args: { - disabled: true, - }, -} - -export const Editable: Story = { - args: { - editable: true, - }, -} - -export const Ellipsis: Story = { - args: { - ellipsis: true, - }, -} - -export const Marked: Story = { - args: { - mark: true, - }, -} - -export const Strong: Story = { - args: { - strong: true, - }, -} - -export const Italic: Story = { - args: { - italic: true, - }, -} - -export const Success: Story = { - args: { - type: 'success', - }, -} - -export const Secondary: Story = { - args: { - type: 'secondary', - }, -} - -export const Warning: Story = { - args: { - type: 'warning', - }, -} - -export const Danger: Story = { - args: { - type: 'danger', - }, -} - -export const Underline: Story = { - args: { - underline: true, - }, -} - -export const CustomOnClick: Story = { - args: { - onClick: event => { - alert('Custom Click Handler') - }, - }, -} - export const ExampleEditable: Story = { render: () => { const [editableStr, setEditableStr] = useState('This is an editable text.') diff --git a/src/components/general/Typography/Text.stories.tsx b/src/components/general/Typography/Text.stories.tsx index 35a867493..3545ff394 100644 --- a/src/components/general/Typography/Text.stories.tsx +++ b/src/components/general/Typography/Text.stories.tsx @@ -1,35 +1,37 @@ -import { type Meta } from '@storybook/react' -import { type StoryObj } from '@storybook/react' +import type { Meta } from '@storybook/react' +import type { StoryObj } from '@storybook/react' import { Space } from 'src/components' import { Switch } from 'src/components' import { Typography } from 'src/components/general/Typography/Typography' import { ExampleStory } from 'src/utils/ExampleStory' import { useState } from 'react' import { expect } from '@storybook/test' +import { TypographyColors } from './colors' const meta: Meta = { - title: 'Components/General/Typography.Text', - component: props => Example Text, - + title: 'Components/General/Typography/Text', + component: props => {props.children}, args: { - code: false, + children: 'Example Text', + type: undefined, + color: undefined, size: 'base', + code: false, copyable: false, delete: false, disabled: false, editable: false, ellipsis: false, - keyboard: false, mark: false, strong: false, italic: false, - type: undefined, underline: false, - onClick: (event: React.MouseEvent) => { - console.log('Text Clicked') - }, }, argTypes: { + children: { + control: 'text', + name: 'text', + }, type: { control: 'select', options: ['secondary', 'success', 'warning', 'danger'], @@ -38,17 +40,43 @@ const meta: Meta = { control: 'select', options: ['base', 'sm', 'lg', 'xl'], }, + color: { + control: 'select', + options: TypographyColors, + }, + copyable: { + control: 'boolean', + }, + delete: { + control: 'boolean', + }, + disabled: { + control: 'boolean', + }, + editable: { + control: 'boolean', + }, + ellipsis: { + control: 'boolean', + }, + mark: { + control: 'boolean', + }, + strong: { + control: 'boolean', + }, + italic: { + control: 'boolean', + }, + code: { + control: 'boolean', + }, }, } export default meta type Story = StoryObj -/* - Initial story templates generated by AI. - Customize the stories based on specific requirements. -*/ - export const Primary: Story = { play: async context => { const text = context.canvasElement.querySelector('span') @@ -56,116 +84,24 @@ export const Primary: Story = { }, } -export const Code: Story = { - args: { - code: true, - }, -} - -export const Copyable: Story = { - args: { - copyable: true, - }, -} - -export const Deleted: Story = { - args: { - delete: true, - }, -} - -export const Disabled: Story = { - args: { - disabled: true, - }, -} - -export const Editable: Story = { - args: { - editable: true, - }, -} - -export const Keyboard: Story = { - args: { - keyboard: true, - }, -} - -export const Marked: Story = { - args: { - mark: true, - }, -} - -export const Strong: Story = { - args: { - strong: true, - }, -} - -export const Italic: Story = { - args: { - italic: true, - }, -} - -export const Success: Story = { - args: { - type: 'success', - }, -} - -export const Secondary: Story = { - args: { - type: 'secondary', - }, -} - -export const Warning: Story = { - args: { - type: 'warning', - }, -} - -export const Danger: Story = { - args: { - type: 'danger', - }, -} - -export const Underline: Story = { - args: { - underline: true, - }, -} - -export const CustomOnClick: Story = { - args: { - onClick: event => { - alert('Custom Click Handler') - }, - }, -} - export const ExampleTexts: Story = { render: () => { return ( }> - Ant Design (default) - Ant Design (secondary) - Ant Design (success) - Ant Design (warning) - Ant Design (danger) - Ant Design (disabled) - Ant Design (mark) - Ant Design (code) - Ant Design (keyboard) - Ant Design (underline) - Ant Design (delete) - Ant Design (strong) - Ant Design (italic) + Aquarium (default) + Aquarium (secondary) + Aquarium (success) + Aquarium (warning) + Aquarium (danger) + Aquarium (disabled) + Aquarium (mark) + Aquarium (code) + Aquarium (keyboard) + Aquarium (underline) + Aquarium (delete) + Aquarium (strong) + Aquarium (italic) ) diff --git a/src/components/general/Typography/Title.stories.tsx b/src/components/general/Typography/Title.stories.tsx index 3d60f9b03..61617f1f4 100644 --- a/src/components/general/Typography/Title.stories.tsx +++ b/src/components/general/Typography/Title.stories.tsx @@ -1,38 +1,35 @@ -import { type Meta } from '@storybook/react' -import { type StoryObj } from '@storybook/react' +import type { Meta } from '@storybook/react' +import type { StoryObj } from '@storybook/react' import { Typography } from 'src/components/general/Typography/Typography' import { ExampleStory } from 'src/utils/ExampleStory' const meta: Meta = { - title: 'Components/General/Title', + title: 'Components/General/Typography/Title', component: Typography.Title, - - args: {}, } + export default meta type Story = StoryObj -export const Primary = {} - -export const ExampleHeadings: Story = { +export const Primary: Story = { render: () => { return ( - h1. Ant Design + h1. Aquarium Component Library - h2. Ant Design + h2. Aquarium Component Library - h3. Ant Design + h3. Aquarium Component Library - h4. Ant Design + h4. Aquarium Component Library - h5. Ant Design + h5. Aquarium Component Library ) diff --git a/src/components/general/Typography/Typography.tsx b/src/components/general/Typography/Typography.tsx index 76a911c92..2538ced80 100644 --- a/src/components/general/Typography/Typography.tsx +++ b/src/components/general/Typography/Typography.tsx @@ -1,29 +1,14 @@ -import { - Typography as AntTypography, - type TypographyProps as AntTypographyProps, - ConfigProvider as AntConfigProvider, -} from 'antd' +import { Typography as AntTypography } from 'antd' import { ConfigProvider } from 'src/components' -import { type ReactNode } from 'react' -import { type TextProps as AntTextProps } from 'antd/es/typography/Text' -import { type TitleProps as AntTitleProps } from 'antd/es/typography/Title' -import { type LinkProps as AntLinkProps } from 'antd/es/typography/Link' -import { type ParagraphProps as AntParagraphProps } from 'antd/es/typography/Paragraph' +import type { ReactNode } from 'react' +import type { TextProps as AntTextProps } from 'antd/es/typography/Text' +import type { TitleProps as AntTitleProps } from 'antd/es/typography/Title' +import type { LinkProps as AntLinkProps } from 'antd/es/typography/Link' +import type { ParagraphProps as AntParagraphProps } from 'antd/es/typography/Paragraph' +import { getColorFromStyles, type TypographyColor } from './colors' +import { ColorTextLightSolid } from 'src/styles/style' -export interface ITypographyProps extends AntTypographyProps { - children: ReactNode -} - -export const Typography = (props: ITypographyProps) => ( - - {props.children} - -) - -type TypographySize = 'base' | 'sm' | 'lg' | 'xl' -export interface ITextProps extends AntTextProps { - size?: TypographySize -} +type TypographySize = 'base' | 'sm' | 'lg' | 'xl' | number // TODO: Replace hardcoded values in getFontSize and getLineHeight with tokens when design is ready // These values are currently coming from https://www.figma.com/design/LffDbOUjeYqDMZ3djs9Cga/mParticle-Foundation-v1.0.1?node-id=3745-8164&m=dev @@ -31,7 +16,8 @@ const getFontSize = (size: TypographySize): number => { if (size === 'base') return 14 if (size === 'sm') return 12 if (size === 'lg') return 16 - return 20 + if (size === 'xl') return 20 + return size } const getLineHeight = (size: TypographySize): number => { @@ -41,50 +27,106 @@ const getLineHeight = (size: TypographySize): number => { return 1.4 } -const Text = ({ size = 'base', ...props }: ITextProps) => { - const fontSize = getFontSize(size) - const lineHeight = getLineHeight(size) +interface InternalTypographyProps { + size?: TypographySize + color?: TypographyColor + children: ReactNode +} + +export const Typography = () => { + return <>DO NOT USE +} + +type InternalTextProps = InternalTypographyProps & AntTextProps +type InternalTitleProps = InternalTypographyProps & AntTitleProps +type InternalLinkProps = InternalTypographyProps & AntLinkProps +type InternalParagraphProps = InternalTypographyProps & AntParagraphProps + +export interface ITextProps extends InternalTextProps { + tooltip?: boolean +} + +export interface ITitleProps extends InternalTitleProps {} + +export interface ILinkProps extends InternalLinkProps { + tooltip?: boolean +} + +export interface IParagraphProps extends InternalParagraphProps {} + +// Tried generalizing into a higher order component but couldn't do it type-safely, so just repeated the code +const Text = (props: ITextProps) => { + const { size, color, type, tooltip, children, ...rest } = props + + const fontSize = size ? getFontSize(size) : undefined + const lineHeight = size ? getLineHeight(size) : undefined + const fontColor = !type && color ? getColorFromStyles(color) : tooltip ? ColorTextLightSolid : undefined return ( - - {props.children} - + + {children} + ) } Typography.Text = Text -interface ITitleProps extends AntTitleProps { - children: ReactNode +const Title = (props: ITitleProps) => { + const { size, color, type, children, ...rest } = props + + const fontSize = size ? getFontSize(size) : undefined + const lineHeight = size ? getLineHeight(size) : undefined + const fontColor = !type && color ? getColorFromStyles(color) : undefined + + return ( + + + {children} + + + ) } -const Title = (props: ITitleProps) => ( - - {props.children} - -) Typography.Title = Title -export interface ILinkProps extends AntLinkProps { - children: ReactNode +const Link = (props: ILinkProps) => { + const { size, color, type, tooltip, underline, children, ...rest } = props + + const fontSize = size ? getFontSize(size) : undefined + const lineHeight = size ? getLineHeight(size) : undefined + const fontColor = !type && color ? getColorFromStyles(color) : tooltip ? ColorTextLightSolid : undefined + + return ( + + + {children} + + + ) } -const Link = (props: ILinkProps) => ( - - {props.children} - -) Typography.Link = Link -export interface IParagraphProps extends AntParagraphProps { - children: ReactNode +const Paragraph = (props: IParagraphProps) => { + const { size, color, type, children, ...rest } = props + + const fontSize = size ? getFontSize(size) : undefined + const lineHeight = size ? getLineHeight(size) : undefined + const fontColor = !type && color ? getColorFromStyles(color) : undefined + + return ( + + + {children} + + + ) } -const Paragraph = (props: IParagraphProps) => ( - - {props.children} - -) Typography.Paragraph = Paragraph diff --git a/src/components/general/Typography/colors.ts b/src/components/general/Typography/colors.ts new file mode 100644 index 000000000..af807bcd0 --- /dev/null +++ b/src/components/general/Typography/colors.ts @@ -0,0 +1,41 @@ +import * as styles from '../../../styles/style' + +export const TypographyColors = [ + 'ColorText', + 'ColorTextSecondary', + 'ColorTextTertiary', + 'ColorTextQuaternary', + 'ColorInfoText', + 'ColorInfoTextActive', + 'ColorPrimaryTextHover', + 'ColorPrimaryText', + 'ColorPrimaryTextActive', + 'ColorSuccessTextHover', + 'ColorSuccessText', + 'ColorSuccessTextActive', + 'ColorErrorTextHover', + 'ColorErrorText', + 'ColorErrorTextActive', + 'ColorWarningTextHover', + 'ColorWarningText', + 'ColorWarningTextActive', + 'ColorLink', + 'ColorLinkHover', + 'ColorLinkActive', + 'ColorTextPlaceholder', + 'ColorTextDisabled', + 'ColorTextHeading', + 'ColorTextLabel', + 'ColorTextDescription', + 'ColorTextLightSolid', +] as const + +export type TypographyColor = (typeof TypographyColors)[number] + +export function getColorFromStyles(color: TypographyColor | string): string { + if (styles[color as TypographyColor]) { + return (styles as unknown as Record)[color as TypographyColor] + } + + return color +} diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts index 289ee159f..9fddb3642 100644 --- a/src/components/icons/index.ts +++ b/src/components/icons/index.ts @@ -8,6 +8,7 @@ import ChartLineIcon from 'src/assets/svg/chart-line.svg?react' import CheckIcon from 'src/assets/svg/check.svg?react' import CircleNodesIcon from 'src/assets/svg/circle-nodes.svg?react' import CloudIcon from 'src/assets/svg/cloud.svg?react' +import CrosshairIcon from 'src/assets/svg/crosshair.svg?react' import ConnectionsIcon from 'src/assets/svg/connections.svg?react' import DataPlatformIconDt from 'src/assets/svg/mp_pm_dt_data-platform.svg?react' import DatabaseIcon from 'src/assets/svg/database.svg?react' @@ -60,6 +61,8 @@ import DirectoryIcon from 'src/assets/svg/mp_pm_lt_directory.svg?react' import LockIcon from 'src/assets/svg/mp_act_lt_lock.svg?react' import UnlockIcon from 'src/assets/svg/mp_act_lt_unlock.svg?react' import NotificationIcon from 'src/assets/svg/mp_pm_lt_notification.svg?react' +import NotificationSubscribedIcon from 'src/assets/svg/mp_pm_lt_notification_subscribed.svg?react' +import NotificationSubscribeIcon from 'src/assets/svg/mp_pm_lt_notification_subscribe.svg?react' import PremiumIconDt from 'src/assets/svg/mp_info_dt_premium.svg?react' import OverviewIconDt from 'src/assets/svg/mp_pm_dt_overview.svg?react' import FilterIcon from 'src/assets/svg/mp_act_lt_filter.svg?react' @@ -104,6 +107,7 @@ export { CheckIcon, CircleNodesIcon, CloudIcon, + CrosshairIcon, Cohort, ConnectionsIcon, ConversionIcon, @@ -146,6 +150,8 @@ export { MpLogoIcon, NextIcon, NotificationIcon, + NotificationSubscribedIcon, + NotificationSubscribeIcon, ObservabilityIcon, OpenTabIcon, Organization, diff --git a/src/components/index.ts b/src/components/index.ts index d33abfaa8..60b1a6f4d 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -121,19 +121,17 @@ export { SuiteLogo } from './navigation/GlobalNavigation/SuiteLogo' export { Typography } from './general/Typography/Typography' // UPS -export { UserPreferencesService } from '../services/user-preferences/user-preferences' +export { UserPreferencesService } from '../services/user-preferences' export { CompositeUserPreferencesService } from '../services/user-preferences/composite-user-preferences-service' -export { type CompositeUserPreferences } from '../services/user-preferences/models/user-preferences/composite-user-preferences' export { UserPreferenceScopeType, type UserPreferenceDefinition, type UserPreferenceDefinitions, + type UserPreferencesPerScope, } from '../services/user-preferences/models/definitions' export { - type UserPreferences, USER_PREFERENCE_SCOPE_SEPARATOR, UserPreferenceGlobalScope, - type UserPreference, type UserPreferenceScope, } from '../services/user-preferences/models/storage-models' export { diff --git a/src/constants/Icons.ts b/src/constants/Icons.ts index e392cdff2..dae4f0609 100644 --- a/src/constants/Icons.ts +++ b/src/constants/Icons.ts @@ -11,6 +11,7 @@ import { CheckIcon, CircleNodesIcon, CloudIcon, + CrosshairIcon, ConnectionsIcon, DataPlatformIconDt, DatabaseIcon, @@ -63,6 +64,8 @@ import { LockIcon, UnlockIcon, NotificationIcon, + NotificationSubscribedIcon, + NotificationSubscribeIcon, PremiumIconDt, OverviewIconDt, FilterIcon, @@ -184,6 +187,10 @@ export const Icons: Record = { light: Copy, default: 'light', }, + crosshair: { + light: CrosshairIcon, + default: 'light', + }, dashboard: { light: Dashboard, default: 'light', @@ -362,6 +369,14 @@ export const Icons: Record = { light: NotificationIcon, default: 'light', }, + notificationSubscribed: { + light: NotificationSubscribedIcon, + default: 'light', + }, + notificationSubscribe: { + light: NotificationSubscribeIcon, + default: 'light', + }, observability: { light: ObservabilityIcon, default: 'light', diff --git a/src/services/user-preferences/composite-user-preferences-service/composite-user-preferences-service.spec.ts b/src/services/user-preferences/composite-user-preferences-service/composite-user-preferences-service.spec.ts index 0aece0f1e..84603249e 100644 --- a/src/services/user-preferences/composite-user-preferences-service/composite-user-preferences-service.spec.ts +++ b/src/services/user-preferences/composite-user-preferences-service/composite-user-preferences-service.spec.ts @@ -1,31 +1,31 @@ import { describe, it, expect, beforeEach } from 'vitest' import { CompositeUserPreferencesService } from 'src/services/user-preferences/composite-user-preferences-service' -import { type UserPreferences } from 'src/services/user-preferences/models/storage-models/user-preferences' import { type UserPreferenceScope } from 'src/services/user-preferences/models/storage-models/user-preference-scope' -import { type UserPreferenceDefinitions } from 'src/services/user-preferences/models/definitions/user-preference-definitions' import { UserPreferenceScopeType } from 'src/services/user-preferences/models/definitions/user-preference-scope-type' import { type UserPreferenceDefinition } from 'src/services/user-preferences/models/definitions/user-preference-definition' import { makeBuilderFromDefinition, + type TestType, TestUserPreferenceDefinitionsFakeFactory, - TestUserPreferenceId, type TestUserPreferencesFakeBuilder, TestUserPreferencesFakeFactory, } from 'src/services/user-preferences/user-preferences-service.spec' +import { type UserPreferenceDefinitions } from 'src/components' +import { type UserPreferencesPerScope } from '../models/definitions/user-preference-per-scope' describe('When testing CompositeUserPreferencesService', () => { - let compositeUserPreferencesService: CompositeUserPreferencesService - let userPreferences: UserPreferences + let compositeUserPreferencesService: CompositeUserPreferencesService + let userPreferences: UserPreferencesPerScope let expectedScope: UserPreferenceScope - let definitions: UserPreferenceDefinitions + let definitions: UserPreferenceDefinitions beforeEach(() => { - definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions + definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions const prefsBuilder = makeBuilderFromDefinition(definitions) - userPreferences = TestUserPreferencesFakeFactory([prefsBuilder]) as UserPreferences + userPreferences = TestUserPreferencesFakeFactory([prefsBuilder]) as UserPreferencesPerScope expectedScope = Object.keys(userPreferences)[0] as UserPreferenceScope - compositeUserPreferencesService = new CompositeUserPreferencesService() + compositeUserPreferencesService = new CompositeUserPreferencesService() }) describe('and getting scoped user preferences', () => { @@ -42,8 +42,8 @@ describe('When testing CompositeUserPreferencesService', () => { // assert Object.entries(actualScopedUserPreferences).forEach(([preferenceId, actualPreference]) => { - const definition = definitions[preferenceId as TestUserPreferenceId] - const expectedScopedUserPreferences = { optedIn: definition?.isOptedInByDefault } + const definition = definitions[preferenceId as keyof TestType] + const expectedScopedUserPreferences = definition.defaultValue expect(actualPreference).toEqual(expectedScopedUserPreferences) }) }) @@ -77,21 +77,26 @@ describe('When testing CompositeUserPreferencesService', () => { allowedScope: UserPreferenceScopeType, ) => { // arrange + const { preferenceId: builderPreferenceId } = getFirstDefinition(definitions) + const updatingId = builderPreferenceId + const userPreferencesBuilder: TestUserPreferencesFakeBuilder[] = [ - { scope: expectedScope, userPreferenceIds: [TestUserPreferenceId.Default], optedIns: [true] }, + { + scope: expectedScope, + keys: [updatingId], + defaultValues: [{ isOptedIn: true }], + }, { wantsRandom: true }, { wantsRandom: true }, ] - userPreferences = TestUserPreferencesFakeFactory( - userPreferencesBuilder, - ) as UserPreferences + userPreferences = TestUserPreferencesFakeFactory(userPreferencesBuilder) as UserPreferencesPerScope definitions = TestUserPreferenceDefinitionsFakeFactory([ { - id: TestUserPreferenceId.Default, + id: 'Default', isOptedInByDefault: true, allowedScope, }, - ]) as UserPreferenceDefinitions + ]) as UserPreferenceDefinitions // act const actualScopedUserPreferences = compositeUserPreferencesService.getScopedUserPreferences( @@ -124,24 +129,26 @@ describe('When testing CompositeUserPreferencesService', () => { allowedScope: UserPreferenceScopeType, ) => { // arrange - const testPreferenceValue = true + const testPreferenceValues = { isOptedIn: true } const { preferenceId: builderPreferenceId } = getFirstDefinition(definitions) const updatingId = builderPreferenceId const userPreferencesBuilder: TestUserPreferencesFakeBuilder[] = [ - { scope: expectedScope, userPreferenceIds: [updatingId], optedIns: [testPreferenceValue] }, + { + scope: expectedScope, + keys: [updatingId], + defaultValues: [testPreferenceValues], + }, { wantsRandom: true }, { wantsRandom: true }, ] - userPreferences = TestUserPreferencesFakeFactory( - userPreferencesBuilder, - ) as UserPreferences + userPreferences = TestUserPreferencesFakeFactory(userPreferencesBuilder) as UserPreferencesPerScope // act - const expectedPreferenceValue = !testPreferenceValue + const expectedValues = { isOptedIn: false } const actualUserPreferences = compositeUserPreferencesService.getUpdatedUserPreferenceStorageObject( updatingId, - expectedPreferenceValue, + expectedValues, currentScope, userPreferences, allowedScope, @@ -149,7 +156,7 @@ describe('When testing CompositeUserPreferencesService', () => { // assert const actualUserPreference = actualUserPreferences?.[expectedScope]?.[updatingId] - expect(actualUserPreference?.optedIn).toEqual(expectedPreferenceValue) + expect(actualUserPreference?.isOptedIn).toEqual(expectedValues.isOptedIn) expect(actualUserPreference).not.toBe(userPreferences) }, ) @@ -169,15 +176,16 @@ describe('When testing CompositeUserPreferencesService', () => { allowedScope: UserPreferenceScopeType, ) => { // arrange - const expectedPreferenceValue = true - const updatingId = TestUserPreferenceId.Default + const expectedValues = { isOptedIn: true } + const { preferenceId: builderPreferenceId } = getFirstDefinition(definitions) + const updatingId = builderPreferenceId - userPreferences = {} satisfies UserPreferences + userPreferences = {} satisfies UserPreferencesPerScope // act const actualUserPreferences = compositeUserPreferencesService.getUpdatedUserPreferenceStorageObject( updatingId, - expectedPreferenceValue, + expectedValues, expectedScope, userPreferences, allowedScope, @@ -185,19 +193,19 @@ describe('When testing CompositeUserPreferencesService', () => { // assert const actualUserPreference = actualUserPreferences?.[expectedScope]?.[updatingId] - expect(actualUserPreference?.optedIn).toEqual(expectedPreferenceValue) + expect(actualUserPreference?.isOptedIn).toEqual(expectedValues.isOptedIn) expect(actualUserPreference).not.toBe(userPreferences) }, ) }) }) -function getFirstDefinition(definitions: UserPreferenceDefinitions): { - definition?: UserPreferenceDefinition - preferenceId: TestUserPreferenceId +function getFirstDefinition(definitions: UserPreferenceDefinitions): { + definition: UserPreferenceDefinition + preferenceId: keyof TestType } { - const preferenceId = Object.keys(definitions)[0] as TestUserPreferenceId - const definition = definitions[preferenceId] + const preferenceId = Object.keys(definitions)[0] as keyof TestType + const definition = definitions[preferenceId] as unknown as UserPreferenceDefinition return { definition, preferenceId } } diff --git a/src/services/user-preferences/composite-user-preferences-service/composite-user-preferences-service.ts b/src/services/user-preferences/composite-user-preferences-service/composite-user-preferences-service.ts index f0a26eb03..edcdff3bd 100644 --- a/src/services/user-preferences/composite-user-preferences-service/composite-user-preferences-service.ts +++ b/src/services/user-preferences/composite-user-preferences-service/composite-user-preferences-service.ts @@ -1,78 +1,75 @@ -import { type UserPreferences } from 'src/services/user-preferences/models/storage-models/user-preferences' +import { + type UserPreferenceDefinition, + UserPreferenceScopeType, + type UserPreferenceDefinitions, +} from 'src/services/user-preferences/models/definitions' import { USER_PREFERENCE_SCOPE_SEPARATOR, UserPreferenceGlobalScope, type UserPreferenceScope, -} from 'src/services/user-preferences/models/storage-models/user-preference-scope' -import { type UserPreferenceDefinitions } from 'src/services/user-preferences/models/definitions/user-preference-definitions' -import { type CompositeUserPreferences } from 'src/services/user-preferences/models/user-preferences/composite-user-preferences' -import { type UserPreference } from 'src/services/user-preferences/models/storage-models/user-preference' -import { UserPreferenceScopeType } from 'src/services/user-preferences/models/definitions/user-preference-scope-type' +} from 'src/services/user-preferences/models/storage-models' import cloneDeep from 'lodash/cloneDeep' -import { type UserPreferenceDefinition } from 'src/services/user-preferences/models/definitions/user-preference-definition' +import { type UserPreferencesPerScope } from '../models/definitions/user-preference-per-scope' -export class CompositeUserPreferencesService { +export class CompositeUserPreferencesService> { public getScopedUserPreferences( - storedPreferences: UserPreferences, + userPreferencesPerScope: UserPreferencesPerScope, currentScope: UserPreferenceScope, - definitions: UserPreferenceDefinitions, - ): CompositeUserPreferences { - const entriesByIdAndUserPreference = Object.entries(definitions).map<[TPreferenceIds, UserPreference]>( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - this.createUserPreferenceEntryFromDefinition.bind(this, storedPreferences, currentScope), - ) - - return this.createCompositePreferencesFromEntries(entriesByIdAndUserPreference) + definitions: UserPreferenceDefinitions, + ): T { + const entriesByIdAndUserPreference = (Object.keys(definitions) as Array).map(key => { + const definition = definitions[key] + return this.createUserPreferenceEntryFromDefinition(userPreferencesPerScope, currentScope, key, definition) + }) + + const composite: T = {} as unknown as T + + for (const [key, value] of entriesByIdAndUserPreference) { + composite[key] = value + } + + return composite } - public getUpdatedUserPreferenceStorageObject( - preferenceId: TPreferenceIds, - isOptedIn: boolean, + public getUpdatedUserPreferenceStorageObject( + userPreferenceKey: TKey, + value: T[TKey], currentScope: UserPreferenceScope, - currentPreferences: UserPreferences, + currentPreferences: UserPreferencesPerScope, allowedScope: UserPreferenceScopeType, - ): UserPreferences { - const userPreferencesToUpdate = currentPreferences ? cloneDeep(currentPreferences) : {} + ): UserPreferencesPerScope { + const userPreferencesToUpdate: UserPreferencesPerScope = currentPreferences ? cloneDeep(currentPreferences) : {} const effectiveScope = this.getEffectiveScope(currentScope, allowedScope) - const scopedUserPreferences = userPreferencesToUpdate[effectiveScope] ?? {} + const scopedUserPreferences: T = userPreferencesToUpdate[effectiveScope] ?? ({} as unknown as T) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - scopedUserPreferences[preferenceId] = { optedIn: isOptedIn } + scopedUserPreferences[userPreferenceKey] = value - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error userPreferencesToUpdate[effectiveScope] = scopedUserPreferences return userPreferencesToUpdate } - private createUserPreferenceEntryFromDefinition( - storedPreferences: UserPreferences, + private createUserPreferenceEntryFromDefinition( + userPreferencesPerScope: UserPreferencesPerScope, currentScope: UserPreferenceScope, - [definedUserPreferenceId, definition]: [TPreferenceIds, UserPreferenceDefinition], - ): [TPreferenceIds, UserPreference] { - if (!storedPreferences) { - const userPreferenceDefault = { optedIn: definition.isOptedInByDefault } - return this.createPreferenceEntry(definedUserPreferenceId, userPreferenceDefault) + userPreferenceKey: TKey, + definition: UserPreferenceDefinition, + ): [TKey, TValue] { + if (!userPreferencesPerScope) { + return [userPreferenceKey, definition.defaultValue] } - const { allowedScope } = definition + const effectiveScope = this.getEffectiveScope(currentScope, definition.allowedScope) - const effectiveScope = this.getEffectiveScope(currentScope, allowedScope) + const scopedUserPreferences = userPreferencesPerScope[effectiveScope] - const scopedUserPreferences = storedPreferences[effectiveScope] + const valueForCurrentScope = scopedUserPreferences?.[userPreferenceKey] - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const userPreferenceForCurrentDefinition: UserPreference = scopedUserPreferences?.[definedUserPreferenceId] + const value = valueForCurrentScope ?? definition.defaultValue - const optedIn = userPreferenceForCurrentDefinition?.optedIn ?? definition.isOptedInByDefault - const userPreference = { optedIn } - return this.createPreferenceEntry(definedUserPreferenceId, userPreference) + return [userPreferenceKey, value as TValue] } private getEffectiveScope( @@ -102,32 +99,6 @@ export class CompositeUserPreferencesService currenScopeParts.length = scopeLength // Truncating scope parts that are too granular for the allowed scope - const effectiveScope = currenScopeParts.join(USER_PREFERENCE_SCOPE_SEPARATOR) as UserPreferenceScope - - return effectiveScope - } - - private createPreferenceEntry( - userPreferenceId: TPreferenceIds, - userPreference: UserPreference, - ): [TPreferenceIds, UserPreference] { - return [userPreferenceId, userPreference] - } - - // TODO: Should be replaced with Object.fromEntries when the transpiler is updated - private createCompositePreferencesFromEntries( - entries: Array<[TPreferenceIds, UserPreference]>, - ): CompositeUserPreferences { - return entries.reduce( - ( - composite: CompositeUserPreferences, - [userPreferenceId, preference]: [TPreferenceIds, UserPreference], - ) => { - composite[userPreferenceId] = preference - return composite - }, - // eslint-disable-next-line - {} as CompositeUserPreferences, - ) + return currenScopeParts.join(USER_PREFERENCE_SCOPE_SEPARATOR) as UserPreferenceScope } } diff --git a/src/services/user-preferences/index.ts b/src/services/user-preferences/index.ts new file mode 100644 index 000000000..b7931a207 --- /dev/null +++ b/src/services/user-preferences/index.ts @@ -0,0 +1 @@ +export * from './user-preferences-service' diff --git a/src/services/user-preferences/models/definitions/index.ts b/src/services/user-preferences/models/definitions/index.ts index 1037109ca..2dac49be5 100644 --- a/src/services/user-preferences/models/definitions/index.ts +++ b/src/services/user-preferences/models/definitions/index.ts @@ -1,3 +1,4 @@ export * from './user-preference-definition' export * from './user-preference-definitions' export * from './user-preference-scope-type' +export * from './user-preference-per-scope' diff --git a/src/services/user-preferences/models/definitions/user-preference-definition.ts b/src/services/user-preferences/models/definitions/user-preference-definition.ts index 22a5d34f8..d7df18c5a 100644 --- a/src/services/user-preferences/models/definitions/user-preference-definition.ts +++ b/src/services/user-preferences/models/definitions/user-preference-definition.ts @@ -1,6 +1,6 @@ import { type UserPreferenceScopeType } from './user-preference-scope-type' -export interface UserPreferenceDefinition { - isOptedInByDefault: boolean +export type UserPreferenceDefinition = { allowedScope: UserPreferenceScopeType + defaultValue: T } diff --git a/src/services/user-preferences/models/definitions/user-preference-definitions.ts b/src/services/user-preferences/models/definitions/user-preference-definitions.ts index d9c5e752c..bf0cedee3 100644 --- a/src/services/user-preferences/models/definitions/user-preference-definitions.ts +++ b/src/services/user-preferences/models/definitions/user-preference-definitions.ts @@ -1,5 +1,5 @@ import { type UserPreferenceDefinition } from './user-preference-definition' -export type UserPreferenceDefinitions = { - [Id in UserPreferenceId]?: UserPreferenceDefinition +export type UserPreferenceDefinitions = { + [P in keyof T]: UserPreferenceDefinition } diff --git a/src/services/user-preferences/models/definitions/user-preference-per-scope.ts b/src/services/user-preferences/models/definitions/user-preference-per-scope.ts new file mode 100644 index 000000000..a6ab0c5b3 --- /dev/null +++ b/src/services/user-preferences/models/definitions/user-preference-per-scope.ts @@ -0,0 +1,5 @@ +import { type UserPreferenceScope } from '../storage-models' + +export type UserPreferencesPerScope = { + [K in UserPreferenceScope]?: T +} diff --git a/src/services/user-preferences/models/storage-models/index.ts b/src/services/user-preferences/models/storage-models/index.ts index 5153d8d6d..51c39a096 100644 --- a/src/services/user-preferences/models/storage-models/index.ts +++ b/src/services/user-preferences/models/storage-models/index.ts @@ -1,3 +1 @@ -export * from './user-preference' export * from './user-preference-scope' -export * from './user-preferences' diff --git a/src/services/user-preferences/models/storage-models/user-preference.ts b/src/services/user-preferences/models/storage-models/user-preference.ts deleted file mode 100644 index 56f487db5..000000000 --- a/src/services/user-preferences/models/storage-models/user-preference.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface UserPreference { - optedIn: boolean -} diff --git a/src/services/user-preferences/models/storage-models/user-preferences.ts b/src/services/user-preferences/models/storage-models/user-preferences.ts deleted file mode 100644 index 95b255d78..000000000 --- a/src/services/user-preferences/models/storage-models/user-preferences.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type UserPreferenceScope } from './user-preference-scope' -import { type UserPreference } from './user-preference' - -export type UserPreferences = { - [K in UserPreferenceScope]?: { [Id in UserPreferenceId]: UserPreference } -} diff --git a/src/services/user-preferences/models/user-preferences/composite-user-preferences.ts b/src/services/user-preferences/models/user-preferences/composite-user-preferences.ts deleted file mode 100644 index 30ebb2113..000000000 --- a/src/services/user-preferences/models/user-preferences/composite-user-preferences.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type UserPreference } from '../storage-models/user-preference' - -export type CompositeUserPreferences = { - [Id in UserPreferenceId]: UserPreference -} diff --git a/src/services/user-preferences/user-preferences-service.spec.ts b/src/services/user-preferences/user-preferences-service.spec.ts index 212303cd9..1eb8e0998 100644 --- a/src/services/user-preferences/user-preferences-service.spec.ts +++ b/src/services/user-preferences/user-preferences-service.spec.ts @@ -2,32 +2,70 @@ import * as Cookies from '../../utils/Cookies' import { describe, afterEach, it, expect } from 'vitest' -import { UserPreferencesService } from 'src/services/user-preferences/user-preferences' +import { UserPreferencesService } from 'src/services/user-preferences/user-preferences-service' import { type UserPreferenceScope } from 'src/services/user-preferences/models/storage-models/user-preference-scope' -import { type UserPreferences } from 'src/services/user-preferences/models/storage-models/user-preferences' -import { type UserPreferenceDefinitions } from 'src/services/user-preferences/models/definitions/user-preference-definitions' -import { CompositeUserPreferencesService } from 'src/services/user-preferences/composite-user-preferences-service' import { UserPreferenceScopeType } from 'src/services/user-preferences/models/definitions/user-preference-scope-type' import { type Sync } from 'factory.ts' import { faker } from '@faker-js/faker' +import { type UserPreferenceDefinitions } from 'src/components' +import { type UserPreferencesPerScope } from './models/definitions/user-preference-per-scope' + +export type TestType = { + Default: { isOptedIn: boolean } + PreferenceOne: { isOptedIn: boolean } +} + +describe('When testing the User Preferences Service', () => { + it('', async () => { + const definitions: UserPreferenceDefinitions = { + Default: { allowedScope: UserPreferenceScopeType.Global, defaultValue: { isOptedIn: faker.datatype.boolean() } }, + PreferenceOne: { + allowedScope: UserPreferenceScopeType.Global, + defaultValue: { isOptedIn: faker.datatype.boolean() }, + }, + } + + const userPreferencesService = new UserPreferencesService(definitions, '1-1-1', { key: 'mp_u_p' }) + + await userPreferencesService.init() + + expect((await userPreferencesService.getPreference('Default')).isOptedIn).toBe( + definitions.Default.defaultValue.isOptedIn, + ) + expect((await userPreferencesService.getPreference('PreferenceOne')).isOptedIn).toBe( + definitions.PreferenceOne.defaultValue.isOptedIn, + ) + + await userPreferencesService.setPreference('Default', { isOptedIn: !definitions.Default.defaultValue.isOptedIn }) + await userPreferencesService.setPreference('PreferenceOne', { + isOptedIn: !definitions.PreferenceOne.defaultValue.isOptedIn, + }) + + expect((await userPreferencesService.getPreference('Default')).isOptedIn).toBe( + !definitions.Default.defaultValue.isOptedIn, + ) + expect((await userPreferencesService.getPreference('PreferenceOne')).isOptedIn).toBe( + !definitions.PreferenceOne.defaultValue.isOptedIn, + ) + }) +}) describe('When testing the User Preferences Service', () => { - let userPreferencesService: UserPreferencesService + let userPreferencesService: UserPreferencesService const cookieKey = 'mp_u_p' const lowLevelScope: UserPreferenceScope = '1-1-1' - let userPreferences: UserPreferences + let userPreferencesPerScope: UserPreferencesPerScope - let definitions: UserPreferenceDefinitions - const compositeUserPreferencesService = new CompositeUserPreferencesService() + let definitions: UserPreferenceDefinitions function setupPreferencesWithScope( - definition: UserPreferenceDefinitions, + definition: UserPreferenceDefinitions, scope: UserPreferenceScope | undefined, ): void { const scopedPreference = makeBuilderFromDefinition(definition, scope) - userPreferences = TestUserPreferencesFakeFactory([scopedPreference]) as UserPreferences + userPreferencesPerScope = TestUserPreferencesFakeFactory([scopedPreference]) as UserPreferencesPerScope - Cookies.putObject(cookieKey, userPreferences) + Cookies.putObject(cookieKey, userPreferencesPerScope) } afterEach(() => { @@ -42,79 +80,63 @@ describe('When testing the User Preferences Service', () => { ])('it should read preferences when there are scoped prefs', async (allowedScope: UserPreferenceScopeType) => { // arrange definitions = TestUserPreferenceDefinitionsFakeFactory([ - { id: TestUserPreferenceId.Default, allowedScope }, - { id: TestUserPreferenceId.PreferenceOne, allowedScope }, - ]) as UserPreferenceDefinitions + { id: 'Default', allowedScope }, + { id: 'PreferenceOne', allowedScope }, + ]) as UserPreferenceDefinitions setupPreferencesWithScope(definitions, lowLevelScope) - userPreferencesService = new UserPreferencesService( - definitions, - compositeUserPreferencesService, - lowLevelScope, - { key: cookieKey }, - ) + userPreferencesService = new UserPreferencesService(definitions, lowLevelScope, { key: cookieKey }) await userPreferencesService.init() - const scopedPreferences = userPreferences[lowLevelScope] + const scopedPreferences = userPreferencesPerScope[lowLevelScope] // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - for (const [id, preference] of Object.entries(scopedPreferences)) { + for (const [id, expectedIsOptedIn] of Object.entries(scopedPreferences)) { // act - const isOptedIn = await userPreferencesService.isOptedIn(id as TestUserPreferenceId) + const isOptedIn = await userPreferencesService.getPreference(id as keyof TestType) // assert - const expectedIsOptedIn = preference.optedIn - expect(isOptedIn).not.toBeNull() - expect(isOptedIn).toBe(expectedIsOptedIn) + expect(isOptedIn?.isOptedIn).not.toBeNull() + expect(isOptedIn.isOptedIn).toBe(expectedIsOptedIn.isOptedIn) } }) it('it should read preferences when there are no scoped prefs', async () => { // arrange - definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions + definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions const someScope = '1' setupPreferencesWithScope(definitions, someScope) const currentScope = '2' - userPreferencesService = new UserPreferencesService( - definitions, - compositeUserPreferencesService, - currentScope, - { key: cookieKey }, - ) + userPreferencesService = new UserPreferencesService(definitions, currentScope, { key: cookieKey }) await userPreferencesService.init() for (const [id, definition] of Object.entries(definitions)) { // act - const isOptedIn = await userPreferencesService.isOptedIn(id as TestUserPreferenceId) + const isOptedIn = await userPreferencesService.getPreference(id as keyof TestType) // assert - const expectedIsOptedIn = definition.isOptedInByDefault - expect(isOptedIn).not.toBeNull() - expect(isOptedIn).toBe(expectedIsOptedIn) + const expectedIsOptedIn = definition.defaultValue + expect(isOptedIn?.isOptedIn).not.toBeNull() + expect(isOptedIn.isOptedIn).toBe(expectedIsOptedIn.isOptedIn) } }) it("it should throw when the preference can't be found", async () => { // arrange - definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions + definitions = TestUserPreferenceDefinitionsFakeFactory() as UserPreferenceDefinitions const someScope = '1' setupPreferencesWithScope(definitions, someScope) - userPreferencesService = new UserPreferencesService( - definitions, - compositeUserPreferencesService, - someScope, - { key: cookieKey }, - ) + userPreferencesService = new UserPreferencesService(definitions, someScope, { key: cookieKey }) await userPreferencesService.init() // act const unknownId = 'unknown' - const isOptedInDelegate = userPreferencesService.isOptedIn.bind( + const isOptedInDelegate = userPreferencesService.getPreference.bind( userPreferencesService, // @ts-expect-error - we are testing an incorrect usage unknownId, @@ -134,108 +156,93 @@ describe('When testing the User Preferences Service', () => { 'it should be able to update a preferences when the preference exists', async (expectedScope, allowedScope: UserPreferenceScopeType) => { // arrange - const userPreferenceId = TestUserPreferenceId.Default + const userPreferenceId = 'Default' const testOptedInState = true definitions = TestUserPreferenceDefinitionsFakeFactory([ { id: userPreferenceId, allowedScope, isOptedInByDefault: testOptedInState }, - ]) as UserPreferenceDefinitions + ]) as UserPreferenceDefinitions setupPreferencesWithScope(definitions, expectedScope as UserPreferenceScope) - userPreferencesService = new UserPreferencesService( - definitions, - compositeUserPreferencesService, - lowLevelScope, - { key: cookieKey }, - ) + userPreferencesService = new UserPreferencesService(definitions, lowLevelScope, { key: cookieKey }) await userPreferencesService.init() // pre-assert - const testState = await userPreferencesService.isOptedIn(userPreferenceId) + const testState = await userPreferencesService.getPreference(userPreferenceId) - expect(testState).toBe(testOptedInState) + expect(testState.isOptedIn).toBe(testOptedInState) // act const expectedOptedInState = !testOptedInState - await userPreferencesService.setPreference(userPreferenceId, expectedOptedInState) + await userPreferencesService.setPreference(userPreferenceId, { isOptedIn: expectedOptedInState }) // assert - const actualState = await userPreferencesService.isOptedIn(userPreferenceId) + const actualState = await userPreferencesService.getPreference(userPreferenceId) - expect(actualState).toBe(expectedOptedInState) + expect(actualState.isOptedIn).toBe(expectedOptedInState) }, ) it('it should be able to update a preferences when the preference does not exist', async () => { // arrange - const userPreferenceId = TestUserPreferenceId.Default + const userPreferenceId = 'Default' const allowedScope = UserPreferenceScopeType.LevelThreeScope const testOptedInState = true definitions = TestUserPreferenceDefinitionsFakeFactory([ { id: userPreferenceId, allowedScope, isOptedInByDefault: testOptedInState }, - ]) as UserPreferenceDefinitions + ]) as UserPreferenceDefinitions - userPreferencesService = new UserPreferencesService( - definitions, - compositeUserPreferencesService, - lowLevelScope, - { key: cookieKey }, - ) + userPreferencesService = new UserPreferencesService(definitions, lowLevelScope, { key: cookieKey }) await userPreferencesService.init() // pre-assert - const testState = await userPreferencesService.isOptedIn(userPreferenceId) + const testState = await userPreferencesService.getPreference(userPreferenceId) - expect(testState).toBe(testOptedInState) + expect(testState.isOptedIn).toBe(testOptedInState) // act const expectedOptedInState = !testOptedInState - await userPreferencesService.setPreference(userPreferenceId, expectedOptedInState) + await userPreferencesService.setPreference(userPreferenceId, { isOptedIn: expectedOptedInState }) // assert - const actualState = await userPreferencesService.isOptedIn(userPreferenceId) + const actualState = await userPreferencesService.getPreference(userPreferenceId) - expect(actualState).toBe(expectedOptedInState) + expect(actualState.isOptedIn).toBe(expectedOptedInState) }) }) }) -export enum TestUserPreferenceId { - Default = 'default-id', - PreferenceOne = 'preference-one', -} - export function TestUserPreferenceDefinitionsFakeFactory( config?: Array<{ - id: TestUserPreferenceId + id: string isOptedInByDefault?: boolean allowedScope?: UserPreferenceScopeType }>, -): Sync.Builder< - UserPreferenceDefinitions, - keyof UserPreferenceDefinitions -> { +): Sync.Builder, keyof UserPreferenceDefinitions> { if (!config) { - config = Object.values(TestUserPreferenceId).map(id => ({ id })) + config = [{ id: 'Default' }, { id: 'PreferenceOne' }] } return config.reduce((definitions, { id, isOptedInByDefault, allowedScope }) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - definitions[id] = createDefinition({ isOptedInByDefault, allowedScope }) + definitions[id] = createDefinition({ + defaultValue: isOptedInByDefault !== undefined ? { isOptedIn: isOptedInByDefault } : undefined, + allowedScope, + }) return definitions - }, {}) + }, {}) as UserPreferenceDefinitions } type Definition = { - isOptedInByDefault?: boolean + defaultValue?: { isOptedIn: boolean } allowedScope?: UserPreferenceScopeType } -function createDefinition({ isOptedInByDefault, allowedScope }: Definition = {}): Definition { +function createDefinition({ defaultValue, allowedScope }: Definition = {}): Definition { return { - isOptedInByDefault: isOptedInByDefault ?? faker.datatype.boolean(), + defaultValue: defaultValue ?? { isOptedIn: faker.datatype.boolean() }, allowedScope: allowedScope ?? faker.helpers.enumValue(UserPreferenceScopeType), } } @@ -243,49 +250,50 @@ function createDefinition({ isOptedInByDefault, allowedScope }: Definition = {}) export interface TestUserPreferencesFakeBuilder { wantsRandom?: boolean scope?: UserPreferenceScope - userPreferenceIds?: TestUserPreferenceId[] - optedIns?: boolean[] + keys?: string[] + defaultValues?: any[] } export function makeBuilderFromDefinition( - definitions: UserPreferenceDefinitions, + definitions: UserPreferenceDefinitions, scope?: UserPreferenceScope, ): TestUserPreferencesFakeBuilder { return { scope: scope ?? getRandomScope({ excludeGlobal: true }), - userPreferenceIds: Object.keys(definitions) as TestUserPreferenceId[], - optedIns: Object.values(definitions).map(({ isOptedInByDefault }) => isOptedInByDefault), + keys: Object.keys(definitions), + defaultValues: Object.values(definitions).map(definition => definition.defaultValue), } } export function TestUserPreferencesFakeFactory( scopes: TestUserPreferencesFakeBuilder[] = [], -): Sync.Builder, keyof UserPreferences> { - return scopes.reduce((scopedPreferences, { wantsRandom = false, scope, userPreferenceIds, optedIns }) => { +): Sync.Builder, keyof UserPreferencesPerScope> { + return scopes.reduce((scopedPreferences, { wantsRandom = false, scope, keys, defaultValues }) => { const effectiveScope = scope ?? getRandomScope({ excludeGlobal: true }) + if (wantsRandom) { - const numberOfValues = faker.number.int({ max: Object.keys(TestUserPreferenceId).length, min: 1 }) - userPreferenceIds = Array.from({ length: numberOfValues }, () => faker.helpers.enumValue(TestUserPreferenceId)) - optedIns = Array.from({ length: numberOfValues }, () => faker.datatype.boolean()) + const numberOfValues = faker.number.int({ max: 2, min: 1 }) + keys = Array.from({ length: numberOfValues }, () => faker.helpers.arrayElement(['Default', 'PreferenceOne'])) + defaultValues = Array.from({ length: numberOfValues }, () => faker.datatype.boolean()) } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - scopedPreferences[effectiveScope] = userPreferenceIds.reduce((preferences, userPreferenceId, index) => { - const effectiveId = userPreferenceId ?? TestUserPreferenceId.Default + scopedPreferences[effectiveScope] = keys.reduce((preferences, userPreferenceId, index) => { + const effectiveId = userPreferenceId ?? 'Default' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - const effectiveOptedInState = optedIns[index] ?? faker.datatype.boolean() + const effectiveOptedInState = defaultValues[index] ?? { isOptedIn: faker.datatype.boolean() } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - preferences[effectiveId] = { optedIn: effectiveOptedInState } + preferences[effectiveId] = effectiveOptedInState return preferences }, {}) return scopedPreferences - }, {}) as UserPreferences + }, {}) as UserPreferencesPerScope } function getRandomScope({ maxScope = 3, excludeGlobal = false }): UserPreferenceScope { diff --git a/src/services/user-preferences/user-preferences-service.ts b/src/services/user-preferences/user-preferences-service.ts new file mode 100644 index 000000000..c052d5cec --- /dev/null +++ b/src/services/user-preferences/user-preferences-service.ts @@ -0,0 +1,70 @@ +import { type UserPreferenceScope } from 'src/services/user-preferences/models/storage-models' +import * as Cookies from 'src/utils/Cookies' +import { type CookieOptions } from 'src/utils/Cookies' +import { CompositeUserPreferencesService } from './composite-user-preferences-service' +import { type UserPreferenceDefinitions } from 'src/components' +import { type UserPreferencesPerScope } from './models/definitions/user-preference-per-scope' + +export class UserPreferencesService> { + private preferences!: T + private readonly compositeUserPreferencesService: CompositeUserPreferencesService + constructor( + private readonly definitions: UserPreferenceDefinitions, + private readonly currentScope: UserPreferenceScope, + private readonly cookieOptions: CookieOptions & { key: string }, + private readonly onUpdate?: (resolvedPreferences: T) => void, + ) { + this.compositeUserPreferencesService = new CompositeUserPreferencesService() + } + + public async getPreference(key: TKey): Promise { + const userPreference = this.preferences[key] + + if (!userPreference) await Promise.reject(new Error(`Invalid Operation. A user preference could not be found.`)) + + return userPreference as TValue + } + + public async setPreference(key: TKey, value: TValue): Promise { + const currentStoredPreferences = await this.getStoredPreferences() + + const storedPreferences = this.compositeUserPreferencesService.getUpdatedUserPreferenceStorageObject( + key, + value, + this.currentScope, + currentStoredPreferences, + this.definitions[key].allowedScope, + ) + + await this.setStoredPreferences(storedPreferences) + + this.preferences = this.compositeUserPreferencesService.getScopedUserPreferences( + storedPreferences, + this.currentScope, + this.definitions, + ) + + this.onUpdate?.(this.preferences) + } + + public async init(): Promise { + const storedPreferences = await this.getStoredPreferences() + this.preferences = this.compositeUserPreferencesService.getScopedUserPreferences( + storedPreferences, + this.currentScope, + this.definitions, + ) + + this.onUpdate?.(this.preferences) + } + + private async getStoredPreferences(): Promise> { + return await Promise.resolve(Cookies.getObject(this.cookieOptions.key) ?? {}) + } + + private async setStoredPreferences(storedPreferences: UserPreferencesPerScope): Promise { + Cookies.putObject(this.cookieOptions.key, storedPreferences, this.cookieOptions) + + await Promise.resolve() + } +} diff --git a/src/services/user-preferences/user-preferences.ts b/src/services/user-preferences/user-preferences.ts deleted file mode 100644 index 608bb9a1e..000000000 --- a/src/services/user-preferences/user-preferences.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* eslint-disable @typescript-eslint/no-extraneous-class,no-unused-vars,@typescript-eslint/no-unused-vars */ -import { type UserPreferences } from 'src/services/user-preferences/models/storage-models/user-preferences' -import { type CompositeUserPreferences } from 'src/services/user-preferences/models/user-preferences/composite-user-preferences' -import { type UserPreferenceScope } from 'src/services/user-preferences/models/storage-models/user-preference-scope' -import { type UserPreferenceDefinitions } from 'src/services/user-preferences/models/definitions/user-preference-definitions' -import { type CompositeUserPreferencesService } from 'src/services/user-preferences/composite-user-preferences-service' -import * as Cookies from 'src/utils/Cookies' -import { type CookieOptions } from 'src/utils/Cookies' - -export class UserPreferencesService { - public preferences!: CompositeUserPreferences - - constructor( - private readonly definitions: UserPreferenceDefinitions, - private readonly compositeUserPreferencesService: CompositeUserPreferencesService, - private readonly currentScope: UserPreferenceScope, - private readonly cookieOptions: CookieOptions & { key: string }, - private readonly onUpdate?: (resolvedPreferences: CompositeUserPreferences) => void, - ) {} - - public async init(): Promise { - const storedPreferences = await this.getStoredPreferences() - - this.preferences = this.compositeUserPreferencesService.getScopedUserPreferences( - storedPreferences, - this.currentScope, - this.definitions, - ) - - this.onUpdate?.(this.preferences) - } - - public async isOptedIn(userPreferenceId: TUserPreferenceId): Promise { - const userPreference = this.preferences[userPreferenceId] - - if (!userPreference) await Promise.reject(new Error(`Invalid Operation. A user preference could not be found.`)) - - return userPreference.optedIn - } - - public async setPreference(userPreferenceId: TUserPreferenceId, isOptedIn: boolean): Promise { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const { allowedScope } = this.definitions[userPreferenceId] - - const currentStoredPreferences = Cookies.getObject(this.cookieOptions.key) - - const storedPreferences = this.compositeUserPreferencesService.getUpdatedUserPreferenceStorageObject( - userPreferenceId, - isOptedIn, - this.currentScope, - currentStoredPreferences as unknown as UserPreferences, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - allowedScope, - ) - - await this.setStoredPreferences(storedPreferences) - - this.preferences = this.compositeUserPreferencesService.getScopedUserPreferences( - storedPreferences, - this.currentScope, - this.definitions, - ) - - this.onUpdate?.(this.preferences) - - // eslint-disable-next-line @typescript-eslint/return-await - return Promise.resolve() - } - - private async getStoredPreferences(): Promise> { - return await Promise.resolve(Cookies.getObject(this.cookieOptions.key) ?? {}) - } - - private async setStoredPreferences(storedPreferences: UserPreferences): Promise { - Cookies.putObject(this.cookieOptions.key, storedPreferences, this.cookieOptions) - - await Promise.resolve() - } -} diff --git a/src/types/icons.ts b/src/types/icons.ts index c7dfd502e..03a2426ff 100644 --- a/src/types/icons.ts +++ b/src/types/icons.ts @@ -36,6 +36,7 @@ export type IconNames = | 'connections' | 'conversion' | 'copy' + | 'crosshair' | 'dashboard' | 'database' | 'dataPlatform' @@ -79,6 +80,8 @@ export type IconNames = | 'myHub' | 'next' | 'notification' + | 'notificationSubscribed' + | 'notificationSubscribe' | 'observability' | 'openTab' | 'organization'