diff --git a/.changeset/kind-years-laugh.md b/.changeset/kind-years-laugh.md new file mode 100644 index 0000000000..8f2edda465 --- /dev/null +++ b/.changeset/kind-years-laugh.md @@ -0,0 +1,11 @@ +--- +'@commercetools-frontend/application-shell-connectors': patch +'@commercetools-frontend/application-components': patch +'@commercetools-frontend/application-shell': patch +'@commercetools-frontend/permissions': patch +'@commercetools-local/visual-testing-app': patch +'@commercetools-frontend/i18n': patch +'@commercetools-local/playground': patch +--- + +New UI of the App Bar selectors. diff --git a/packages/application-components/package.json b/packages/application-components/package.json index d21e4a56f7..ce71ad1221 100644 --- a/packages/application-components/package.json +++ b/packages/application-components/package.json @@ -39,6 +39,7 @@ "@commercetools-frontend/sentry": "22.17.2", "@commercetools-uikit/accessible-button": "^18.1.0", "@commercetools-uikit/card": "^18.1.0", + "@commercetools-uikit/stamp": "^18.1.0", "@commercetools-uikit/constraints": "^18.1.0", "@commercetools-uikit/design-system": "^18.1.0", "@commercetools-uikit/flat-button": "^18.1.0", diff --git a/packages/application-components/src/components/project-stamp/messages.ts b/packages/application-components/src/components/project-stamp/messages.ts new file mode 100644 index 0000000000..6962ca705c --- /dev/null +++ b/packages/application-components/src/components/project-stamp/messages.ts @@ -0,0 +1,21 @@ +import { defineMessages } from 'react-intl'; + +export default defineMessages({ + ProjectProduction: { + id: 'ProjectStamp.production', + defaultMessage: 'Production', + }, + ProjectSuspended: { + id: 'ProjectStamp.suspended', + defaultMessage: 'Suspended', + }, + ProjectExpired: { + id: 'ProjectStamp.expired', + defaultMessage: 'Trial expired', + }, + ProjectWillExpire: { + id: 'ProjectStamp.willExpire', + defaultMessage: + '{daysLeft, select, 0 {Trial ends today} 1 {Trial ends in 1 day} other {Trial ends in {daysLeft} days}}', + }, +}); diff --git a/packages/application-components/src/components/project-stamp/project-stamp.spec.tsx b/packages/application-components/src/components/project-stamp/project-stamp.spec.tsx new file mode 100644 index 0000000000..b885e2ce68 --- /dev/null +++ b/packages/application-components/src/components/project-stamp/project-stamp.spec.tsx @@ -0,0 +1,30 @@ +import { screen, renderComponent } from '../../test-utils'; +import ProjectStamp from './project-stamp'; + +describe('rendering', () => { + it('should render ProjectStamp - IsProduction', () => { + renderComponent(); + + expect(screen.getByText('Production')).toBeInTheDocument(); + }); + it('should render ProjectStamp - IsExpired', () => { + renderComponent(); + + expect(screen.getByText('Trial expired')).toBeInTheDocument(); + }); + it('should render ProjectStamp - WillExpire with 0 days left', () => { + renderComponent(); + + expect(screen.getByText('Trial ends today')).toBeInTheDocument(); + }); + it('should render ProjectStamp - WillExpire with 1 days left', () => { + renderComponent(); + + expect(screen.getByText('Trial ends in 1 day')).toBeInTheDocument(); + }); + it('should render ProjectStamp - WillExpire with 4 days left', () => { + renderComponent(); + + expect(screen.getByText('Trial ends in 4 days')).toBeInTheDocument(); + }); +}); diff --git a/packages/application-components/src/components/project-stamp/project-stamp.tsx b/packages/application-components/src/components/project-stamp/project-stamp.tsx new file mode 100644 index 0000000000..1f5607fa0f --- /dev/null +++ b/packages/application-components/src/components/project-stamp/project-stamp.tsx @@ -0,0 +1,71 @@ +import { ReactElement } from 'react'; +import { css } from '@emotion/react'; +import { MessageDescriptor, useIntl } from 'react-intl'; +import { DotIcon } from '@commercetools-uikit/icons'; +import Stamp, { TTone } from '@commercetools-uikit/stamp'; +import messages from './messages'; + +type TCustomStampProps = { + tone: TTone; + label: MessageDescriptor & { values?: Record }; + icon?: ReactElement; +}; +function CustomStamp(props: TCustomStampProps) { + const intl = useIntl(); + const { values, ...message } = props.label; + return ( + + ); +} + +const IsProduction = () => ( + + + + } + /> +); + +const IsSuspended = () => ( + +); + +const IsExpired = () => ( + +); + +const WillExpire = (props: { daysLeft: number }) => ( + +); + +const ProjectStamp = { + IsProduction, + IsSuspended, + IsExpired, + WillExpire, +}; + +export default ProjectStamp; diff --git a/packages/application-components/src/index.ts b/packages/application-components/src/index.ts index b03aac2a5e..a02a9b0f94 100644 --- a/packages/application-components/src/index.ts +++ b/packages/application-components/src/index.ts @@ -48,6 +48,9 @@ export { default as Drawer } from './components/drawer'; export { default as CustomViewLoader } from './components/custom-views/custom-view-loader'; export { default as CustomViewsSelector } from './components/custom-views/custom-views-selector'; +// Stamps for the project states +export { default as ProjectStamp } from './components/project-stamp/project-stamp'; + // Utilities export { default as PortalsContainer } from './components/portals-container'; export { default as useModalState } from './hooks/use-modal-state'; diff --git a/packages/application-shell-connectors/src/types/generated/mc.ts b/packages/application-shell-connectors/src/types/generated/mc.ts index 1dd175d231..3c6a76a513 100644 --- a/packages/application-shell-connectors/src/types/generated/mc.ts +++ b/packages/application-shell-connectors/src/types/generated/mc.ts @@ -425,6 +425,7 @@ export type TQuery = { release?: Maybe; releases?: Maybe; storeOAuthScopes: Array; + systemStatus: TSystemStatus; }; @@ -591,6 +592,17 @@ export type TSupportedStoreScope = { name: Scalars['String']; }; +export enum TSystemOperabilityStatus { + Degraded = 'DEGRADED', + Operational = 'OPERATIONAL', + Outage = 'OUTAGE' +} + +export type TSystemStatus = { + __typename?: 'SystemStatus'; + status: TSystemOperabilityStatus; +}; + export type TUser = TMetaData & { __typename?: 'User'; businessRole?: Maybe; @@ -659,7 +671,7 @@ export type TFetchProjectQuery = { __typename?: 'Query', project?: { __typename? export type TFetchLoggedInUserQueryVariables = Exact<{ [key: string]: never; }>; -export type TFetchLoggedInUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email: string, createdAt: string, gravatarHash: string, firstName: string, lastName: string, language: string, numberFormat: string, timeZone?: string | null, launchdarklyTrackingId: string, launchdarklyTrackingGroup: string, launchdarklyTrackingSubgroup?: string | null, launchdarklyTrackingTeam?: Array | null, launchdarklyTrackingTenant: string, launchdarklyTrackingCloudEnvironment: string, defaultProjectKey?: string | null, businessRole?: string | null, projects: { __typename?: 'ProjectQueryResult', total: number, results: Array<{ __typename?: 'Project', name: string, key: string, suspension: { __typename?: 'ProjectSuspension', isActive: boolean }, expiry: { __typename?: 'ProjectExpiry', isActive: boolean } }> }, idTokenUserInfo?: { __typename?: 'IdTokenUserInfo', iss: string, sub: string, aud: string, exp: number, iat?: number | null, email?: string | null, name?: string | null, additionalClaims?: string | null } | null } | null }; +export type TFetchLoggedInUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email: string, createdAt: string, gravatarHash: string, firstName: string, lastName: string, language: string, numberFormat: string, timeZone?: string | null, launchdarklyTrackingId: string, launchdarklyTrackingGroup: string, launchdarklyTrackingSubgroup?: string | null, launchdarklyTrackingTeam?: Array | null, launchdarklyTrackingTenant: string, launchdarklyTrackingCloudEnvironment: string, defaultProjectKey?: string | null, businessRole?: string | null, projects: { __typename?: 'ProjectQueryResult', total: number, results: Array<{ __typename?: 'Project', name: string, key: string, isProductionProject: boolean, suspension: { __typename?: 'ProjectSuspension', isActive: boolean }, expiry: { __typename?: 'ProjectExpiry', isActive: boolean } }> }, idTokenUserInfo?: { __typename?: 'IdTokenUserInfo', iss: string, sub: string, aud: string, exp: number, iat?: number | null, email?: string | null, name?: string | null, additionalClaims?: string | null } | null } | null }; export type TFetchUserProjectsQueryVariables = Exact<{ [key: string]: never; }>; diff --git a/packages/application-shell/package.json b/packages/application-shell/package.json index c7c9efec2f..12e574993b 100644 --- a/packages/application-shell/package.json +++ b/packages/application-shell/package.json @@ -52,6 +52,7 @@ "@commercetools-uikit/design-system": "^18.1.0", "@commercetools-uikit/flat-button": "^18.1.0", "@commercetools-uikit/icons": "^18.1.0", + "@commercetools-uikit/icon-button": "^18.1.0", "@commercetools-uikit/loading-spinner": "^18.1.0", "@commercetools-uikit/notifications": "^18.1.0", "@commercetools-uikit/primary-button": "^18.1.0", diff --git a/packages/application-shell/src/components/app-bar/app-bar.tsx b/packages/application-shell/src/components/app-bar/app-bar.tsx index f7b033dac8..6c61cd07c9 100644 --- a/packages/application-shell/src/components/app-bar/app-bar.tsx +++ b/packages/application-shell/src/components/app-bar/app-bar.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; +import { ProjectStamp } from '@commercetools-frontend/application-components'; import { designTokens as uikitDesignTokens } from '@commercetools-uikit/design-system'; import Spacings from '@commercetools-uikit/spacings'; import { CONTAINERS, DIMENSIONS } from '../../constants'; @@ -47,30 +48,57 @@ const AppBar = (props: Props) => { `} > - +
{(() => { if (!props.user) { return ; } // The `` should be rendered only if the // user is fetched and the user has projects while the app runs in an project context. - if (props.user.projects.total > 0 && props.projectKeyFromUrl) + if (props.user.projects.total > 0 && props.projectKeyFromUrl) { + const selectedProject = props.user.projects.results.find( + (project) => project.key === props.projectKeyFromUrl + ); return ( - +
+ {selectedProject?.isProductionProject && ( +
+ +
+ )} + +
); + } if (!props.user.defaultProjectKey) return null; return ; })()} {/* This node is used by a react portal */}
- +
{ describe('when switching project', () => { it('should render app for new project', async () => { renderApp(); - const input = await screen.findByLabelText('Projects menu'); + const input = await screen.findByLabelText('Projects'); fireEvent.focus(input); fireEvent.keyDown(input, { key: 'ArrowDown' }); diff --git a/packages/application-shell/src/components/fetch-user/fetch-user.mc.graphql b/packages/application-shell/src/components/fetch-user/fetch-user.mc.graphql index f120de1260..b41a50ee0c 100644 --- a/packages/application-shell/src/components/fetch-user/fetch-user.mc.graphql +++ b/packages/application-shell/src/components/fetch-user/fetch-user.mc.graphql @@ -28,6 +28,7 @@ query FetchLoggedInUser { expiry { isActive } + isProductionProject } } idTokenUserInfo { diff --git a/packages/application-shell/src/components/locale-switcher/locale-switcher.spec.tsx b/packages/application-shell/src/components/locale-switcher/locale-switcher.spec.tsx new file mode 100644 index 0000000000..74dd874542 --- /dev/null +++ b/packages/application-shell/src/components/locale-switcher/locale-switcher.spec.tsx @@ -0,0 +1,50 @@ +import { render, fireEvent, waitFor, screen } from '@testing-library/react'; +import { IntlProvider } from 'react-intl'; +import LocaleSwitcher from './locale-switcher'; + +const setProjectDataLocale = jest.fn(); +const availableLocales = ['en', 'de', 'fr']; + +const renderLocaleSwitcher = () => { + return render( + + + + ); +}; + +describe('LocaleSwitcher', () => { + it('should render and handle locale selection', async () => { + renderLocaleSwitcher(); + const input = await screen.findByLabelText('Locales'); + fireEvent.focus(input); + fireEvent.keyDown(input, { key: 'ArrowDown' }); + fireEvent.click(screen.getByText('fr')); + + await waitFor(() => { + expect(setProjectDataLocale).toHaveBeenCalledWith('fr'); + }); + }); + it('should open and close the locale dialog', async () => { + renderLocaleSwitcher(); + const input = await screen.findByLabelText('Locales'); + fireEvent.focus(input); + fireEvent.keyDown(input, { key: 'ArrowDown' }); + const iconButton = await screen.findByRole('button', { + name: 'Locales info', + }); + fireEvent.click(iconButton); + + // expect to see the dialog opens after clicking the icon button + const dialogText = await screen.findByText('Selecting a data locale'); + expect(dialogText).toBeInTheDocument(); + + // close the dialog + fireEvent.click(screen.getByRole('button', { name: 'Close dialog' })); + expect(dialogText).not.toBeInTheDocument(); + }); +}); diff --git a/packages/application-shell/src/components/locale-switcher/locale-switcher.tsx b/packages/application-shell/src/components/locale-switcher/locale-switcher.tsx index 280fde50ef..f5c269d3d2 100644 --- a/packages/application-shell/src/components/locale-switcher/locale-switcher.tsx +++ b/packages/application-shell/src/components/locale-switcher/locale-switcher.tsx @@ -1,16 +1,23 @@ import { useCallback } from 'react'; -import { css } from '@emotion/react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import type { SingleValueProps, ValueContainerProps, MenuListProps, + GroupHeadingProps, } from 'react-select'; import { components } from 'react-select'; +import { + InfoDialog, + useModalState, +} from '@commercetools-frontend/application-components'; import AccessibleHidden from '@commercetools-uikit/accessible-hidden'; import { designTokens } from '@commercetools-uikit/design-system'; -import { WorldIcon } from '@commercetools-uikit/icons'; +import IconButton from '@commercetools-uikit/icon-button'; +import { WorldIcon, InformationIcon } from '@commercetools-uikit/icons'; import SelectInput from '@commercetools-uikit/select-input'; +import Spacings from '@commercetools-uikit/spacings'; +import Text from '@commercetools-uikit/text'; import messages from './messages'; type Props = { @@ -23,24 +30,10 @@ const LOCALE_SWITCHER_LABEL_ID = 'locale-switcher-label'; export const SingleValue = (props: SingleValueProps) => { return ( -
+ - - {props.children} - -
+ {props.children} + ); }; SingleValue.displayName = 'SingleValue'; @@ -59,14 +52,51 @@ const CustomMenuList = (props: MenuListProps) => { return {props.children}; }; +const CustomGroupHeading = ( + props: GroupHeadingProps & { setIsOpen: (value: boolean) => void } +) => { + const { setIsOpen, ...groupProps } = props; + return ( + <> + + + {groupProps.children} + } + label="Locales info" + size="small" + onClick={() => setIsOpen(true)} + /> + + + + ); +}; +CustomGroupHeading.displayName = 'CustomGroupHeading'; + const LocaleSwitcher = (props: Props) => { + const { isModalOpen, openModal, closeModal } = useModalState(); const { setProjectDataLocale } = props; + const getNewLine = () =>
; + const intl = useIntl(); + const handleSelection = useCallback( (event) => { setProjectDataLocale(event.target.value); }, [setProjectDataLocale] ); + + const localeOptions = [ + { + label: , + options: props.availableLocales.map((locale) => ({ + label: locale, + value: locale, + })), + }, + ]; + return (
@@ -79,14 +109,14 @@ const LocaleSwitcher = (props: Props) => { name="locale-switcher" aria-labelledby={LOCALE_SWITCHER_LABEL_ID} onChange={handleSelection} - options={props.availableLocales.map((locale) => ({ - label: locale, - value: locale, - }))} + options={localeOptions} components={{ SingleValue, ValueContainer: PatchedValueContainer, MenuList: CustomMenuList, + GroupHeading: (groupProps) => ( + + ), }} isClearable={false} backspaceRemovesValue={false} @@ -94,7 +124,23 @@ const LocaleSwitcher = (props: Props) => { horizontalConstraint={'auto'} appearance="quiet" maxMenuHeight={360} + minMenuWidth={3} /> + {/* Dialog that explains the locales */} + + +
); }; diff --git a/packages/application-shell/src/components/locale-switcher/messages.ts b/packages/application-shell/src/components/locale-switcher/messages.ts index a494b8da50..45a0fe6d54 100644 --- a/packages/application-shell/src/components/locale-switcher/messages.ts +++ b/packages/application-shell/src/components/locale-switcher/messages.ts @@ -4,6 +4,17 @@ export default defineMessages({ localesLabel: { id: 'LocaleSwitcher.localesLabel', description: 'The label for project dropdown switcher', - defaultMessage: 'Locales menu', + defaultMessage: 'Locales', + }, + dialogLocaleTitle: { + id: 'LocaleSwitcher.dialogLocaleTitle', + description: 'The title for the data locale dialog', + defaultMessage: 'Selecting a data locale', + }, + dialogLocaleDescription: { + id: 'LocaleSwitcher.dialogLocaleDescription', + description: 'The description for the data locale dialog', + defaultMessage: + "The selected data locale will serve as the default setting for all localized fields within the Merchant Center, including names, descriptions, and other localized attributes. It's important to note that this selection does not affect the interface language of the Merchant Center or any data formatting options. To modify these settings, navigate to your user profile.", }, }); diff --git a/packages/application-shell/src/components/project-switcher/messages.ts b/packages/application-shell/src/components/project-switcher/messages.ts index 4a884e3219..7a2d696b85 100644 --- a/packages/application-shell/src/components/project-switcher/messages.ts +++ b/packages/application-shell/src/components/project-switcher/messages.ts @@ -4,7 +4,7 @@ export default defineMessages({ projectsLabel: { id: 'ProjectSwitcher.projectsLabel', description: 'The label for project dropdown switcher', - defaultMessage: 'Projects menu', + defaultMessage: 'Projects', }, searchPlaceholder: { id: 'ProjectSwitcher.searchPlaceholder', diff --git a/packages/application-shell/src/components/project-switcher/project-switcher-test-utils.ts b/packages/application-shell/src/components/project-switcher/project-switcher-test-utils.ts index 102f0f41b2..9cc1ff36db 100644 --- a/packages/application-shell/src/components/project-switcher/project-switcher-test-utils.ts +++ b/packages/application-shell/src/components/project-switcher/project-switcher-test-utils.ts @@ -5,6 +5,7 @@ type CreateGraphqlResponseForProjectsQueryOptions = { numberOfProjects?: number; getIsSuspended?: (key: string) => boolean; getIsExpired?: (key: string) => boolean; + getIsProduction?: (key: string) => boolean; }; const falsy = () => false; @@ -13,6 +14,7 @@ export const createGraphqlResponseForProjectsQuery = ({ numberOfProjects = 4, getIsSuspended = falsy, getIsExpired = falsy, + getIsProduction = falsy, }: CreateGraphqlResponseForProjectsQueryOptions = {}) => ({ request: { query: ProjectsQuery, @@ -42,7 +44,7 @@ export const createGraphqlResponseForProjectsQuery = ({ __typename: 'ProjectExpiry', isActive: getIsExpired(key), }, - isProductionProject: false, + isProductionProject: getIsProduction(key), }; }), }, diff --git a/packages/application-shell/src/components/project-switcher/project-switcher.spec.tsx b/packages/application-shell/src/components/project-switcher/project-switcher.spec.tsx index 21ac844ae8..7a41ece234 100644 --- a/packages/application-shell/src/components/project-switcher/project-switcher.spec.tsx +++ b/packages/application-shell/src/components/project-switcher/project-switcher.spec.tsx @@ -1,5 +1,11 @@ import { mocked } from 'jest-mock'; -import { screen, renderApp, fireEvent, waitFor } from '../../test-utils'; +import { + screen, + renderApp, + fireEvent, + waitFor, + within, +} from '../../test-utils'; import { location } from '../../utils/location'; import ProjectSwitcher from './project-switcher'; import { createGraphqlResponseForProjectsQuery } from './project-switcher-test-utils'; @@ -9,7 +15,8 @@ jest.mock('../../utils/location'); const render = () => { const mockedRequest = [ createGraphqlResponseForProjectsQuery({ - getIsSuspended: (key) => key === 'key-2', + getIsProduction: (key) => key === 'key-1' || key === 'key-3', + getIsSuspended: (key) => key === 'key-2' || key === 'key-3', getIsExpired: (key) => key === 'key-3', }), ]; @@ -19,6 +26,19 @@ const render = () => { }); }; +function verifyProjectOptionStamps( + projectOption: HTMLElement, + expectedText: string[] | null[] +) { + const stamps = within(projectOption).queryAllByText( + /Production|Suspended|Trial expired/i + ); + + stamps.forEach((stamp, index) => { + expect(stamp.textContent).toEqual(expectedText[index]); + }); +} + describe('rendering', () => { beforeEach(() => { mocked(location.replace).mockClear(); @@ -26,7 +46,7 @@ describe('rendering', () => { it('should search and select a project', async () => { render(); - const input = await screen.findByLabelText('Projects menu'); + const input = await screen.findByLabelText('Projects'); fireEvent.focus(input); fireEvent.change(input, { target: { value: 'key-1' } }); fireEvent.keyDown(input, { key: 'Enter', keyCode: 13, which: 13 }); @@ -37,7 +57,7 @@ describe('rendering', () => { }); it('should see no results message when search does not match any project', async () => { render(); - const input = await screen.findByLabelText('Projects menu'); + const input = await screen.findByLabelText('Projects'); fireEvent.focus(input); fireEvent.change(input, { target: { value: 'not existing' } }); @@ -47,10 +67,10 @@ describe('rendering', () => { }); it('should prevent clicking on a suspended project', async () => { render(); - const input = await screen.findByLabelText('Projects menu'); + const input = await screen.findByLabelText('Projects'); fireEvent.focus(input); fireEvent.keyDown(input, { key: 'ArrowDown' }); - fireEvent.click(screen.getByText(/Suspended/i)); + fireEvent.click((await screen.findAllByText(/Suspended/i))[0]); await waitFor(() => expect(mocked(location.replace)).not.toHaveBeenCalled() @@ -58,7 +78,7 @@ describe('rendering', () => { }); it('should prevent clicking on an expired project', async () => { render(); - const input = await screen.findByLabelText('Projects menu'); + const input = await screen.findByLabelText('Projects'); fireEvent.focus(input); fireEvent.keyDown(input, { key: 'ArrowDown' }); fireEvent.click(screen.getByText(/Expired/i)); @@ -67,4 +87,25 @@ describe('rendering', () => { expect(mocked(location.replace)).not.toHaveBeenCalled() ); }); + it('should render the expected stamps for each project', async () => { + render(); + const input = await screen.findByLabelText('Projects'); + fireEvent.focus(input); + fireEvent.keyDown(input, { key: 'ArrowDown' }); + + const swticherProjectsOptions = await screen.findAllByRole('option'); + + // We define which stamps are expected for each switcher option + // The stamps are ordered so we can also verified they're rendered in the correct order + const expectedOrderedStamps = [ + [null], // First option has no stamps + ['Production'], // Second option has only one "Production" stamp + ['Suspended'], // Third option has only one "Suspended" stamp + ['Production', 'Suspended', 'Trial expired'], // Fourth option has all three stamps + ]; + + swticherProjectsOptions.forEach((projectOption, index) => { + verifyProjectOptionStamps(projectOption, expectedOrderedStamps[index]); + }); + }); }); diff --git a/packages/application-shell/src/components/project-switcher/project-switcher.tsx b/packages/application-shell/src/components/project-switcher/project-switcher.tsx index 046b228ff0..592022f69a 100644 --- a/packages/application-shell/src/components/project-switcher/project-switcher.tsx +++ b/packages/application-shell/src/components/project-switcher/project-switcher.tsx @@ -8,6 +8,7 @@ import type { ControlProps, } from 'react-select'; import { components } from 'react-select'; +import { ProjectStamp } from '@commercetools-frontend/application-components'; import { useMcQuery, oidcStorage, @@ -17,8 +18,9 @@ import { GRAPHQL_TARGETS } from '@commercetools-frontend/constants'; import { reportErrorToSentry } from '@commercetools-frontend/sentry'; import AccessibleHidden from '@commercetools-uikit/accessible-hidden'; import { designTokens } from '@commercetools-uikit/design-system'; -import { ErrorIcon } from '@commercetools-uikit/icons'; import SelectInput from '@commercetools-uikit/select-input'; +import Spacings from '@commercetools-uikit/spacings'; +import Text from '@commercetools-uikit/text'; import type { TProject, TFetchUserProjectsQuery, @@ -42,110 +44,89 @@ type OptionType = Pick< const PROJECT_SWITCHER_LABEL_ID = 'project-switcher-label'; -export const ValueContainer = ({ ...restProps }: ValueContainerProps) => { +export const ValueContainer = ({ + children, + ...restProps +}: ValueContainerProps) => { return ( -
-
- - {restProps.children} - -
-
+ + + {children} + + ); }; +type TProjectStampsListProps = Pick< + TProject, + 'isProductionProject' | 'suspension' | 'expiry' +>; +const ProjectStampsList = (props: TProjectStampsListProps) => ( + + {props.isProductionProject && } + {props.suspension && props.suspension.isActive && ( + + )} + {props.expiry && props.expiry.isActive && } + {props.expiry && Boolean(props.expiry.daysLeft) && ( + + )} + +); + export const ProjectSwitcherOption = (props: OptionProps) => { const project = props.data as OptionType; + return ( -
+
- {project.name} - {props.isDisabled && ( - - - - )} -
-
- {project.key} -
- {project.suspension && project.suspension.isActive && ( -
- -
- )} - {project.expiry && project.expiry.isActive && ( -
- -
- )} -
+ {project.name} + + + {project.key} + +
+ + +
); }; -const mapProjectsToOptions = memoize((projects) => - projects.map((project: TProject) => ({ - key: project.key, - name: project.name, - label: project.name, - value: project.key, - suspension: project.suspension, - expiry: project.expiry, - isProductionProject: project.isProductionProject, - })) -); +const mapProjectsToOptions = memoize((projects) => { + return [ + { + label: , + options: projects.map((project: TProject) => ({ + key: project.key, + name: project.name, + label: project.name, + value: project.key, + suspension: project.suspension, + expiry: project.expiry, + isProductionProject: project.isProductionProject, + })), + }, + ]; +}); const CustomMenuList = (props: MenuListProps) => { return ( -
+
{props.children}
); @@ -203,7 +184,10 @@ const ProjectSwitcher = (props: Props) => { } }} options={ - data && data.user && mapProjectsToOptions(data.user.projects.results) + (data && + data.user && + mapProjectsToOptions(data.user.projects.results)) || + [] } isOptionDisabled={(option) => { const project = option as OptionType; @@ -221,7 +205,9 @@ const ProjectSwitcher = (props: Props) => { noOptionsMessage={() => intl.formatMessage(messages.noResults)} horizontalConstraint={'auto'} appearance="quiet" - maxMenuHeight={360} + maxMenuHeight={380} + maxMenuWidth={8} + minMenuWidth={8} />
); diff --git a/packages/application-shell/src/types/generated/mc.ts b/packages/application-shell/src/types/generated/mc.ts index 1dd175d231..3c6a76a513 100644 --- a/packages/application-shell/src/types/generated/mc.ts +++ b/packages/application-shell/src/types/generated/mc.ts @@ -425,6 +425,7 @@ export type TQuery = { release?: Maybe; releases?: Maybe; storeOAuthScopes: Array; + systemStatus: TSystemStatus; }; @@ -591,6 +592,17 @@ export type TSupportedStoreScope = { name: Scalars['String']; }; +export enum TSystemOperabilityStatus { + Degraded = 'DEGRADED', + Operational = 'OPERATIONAL', + Outage = 'OUTAGE' +} + +export type TSystemStatus = { + __typename?: 'SystemStatus'; + status: TSystemOperabilityStatus; +}; + export type TUser = TMetaData & { __typename?: 'User'; businessRole?: Maybe; @@ -659,7 +671,7 @@ export type TFetchProjectQuery = { __typename?: 'Query', project?: { __typename? export type TFetchLoggedInUserQueryVariables = Exact<{ [key: string]: never; }>; -export type TFetchLoggedInUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email: string, createdAt: string, gravatarHash: string, firstName: string, lastName: string, language: string, numberFormat: string, timeZone?: string | null, launchdarklyTrackingId: string, launchdarklyTrackingGroup: string, launchdarklyTrackingSubgroup?: string | null, launchdarklyTrackingTeam?: Array | null, launchdarklyTrackingTenant: string, launchdarklyTrackingCloudEnvironment: string, defaultProjectKey?: string | null, businessRole?: string | null, projects: { __typename?: 'ProjectQueryResult', total: number, results: Array<{ __typename?: 'Project', name: string, key: string, suspension: { __typename?: 'ProjectSuspension', isActive: boolean }, expiry: { __typename?: 'ProjectExpiry', isActive: boolean } }> }, idTokenUserInfo?: { __typename?: 'IdTokenUserInfo', iss: string, sub: string, aud: string, exp: number, iat?: number | null, email?: string | null, name?: string | null, additionalClaims?: string | null } | null } | null }; +export type TFetchLoggedInUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email: string, createdAt: string, gravatarHash: string, firstName: string, lastName: string, language: string, numberFormat: string, timeZone?: string | null, launchdarklyTrackingId: string, launchdarklyTrackingGroup: string, launchdarklyTrackingSubgroup?: string | null, launchdarklyTrackingTeam?: Array | null, launchdarklyTrackingTenant: string, launchdarklyTrackingCloudEnvironment: string, defaultProjectKey?: string | null, businessRole?: string | null, projects: { __typename?: 'ProjectQueryResult', total: number, results: Array<{ __typename?: 'Project', name: string, key: string, isProductionProject: boolean, suspension: { __typename?: 'ProjectSuspension', isActive: boolean }, expiry: { __typename?: 'ProjectExpiry', isActive: boolean } }> }, idTokenUserInfo?: { __typename?: 'IdTokenUserInfo', iss: string, sub: string, aud: string, exp: number, iat?: number | null, email?: string | null, name?: string | null, additionalClaims?: string | null } | null } | null }; export type TFetchUserProjectsQueryVariables = Exact<{ [key: string]: never; }>; diff --git a/packages/i18n/README.md b/packages/i18n/README.md index 667e2bc5a3..1d97cba266 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -146,7 +146,7 @@ const Application = (props) => { After you have defined the `intl` messages in your React components, you should extract those messages into the source file `core.json`. This file contains a key-value map of the message `id` and the message value. -To extract the messages simply run `mc-scripts extract-intl [options]`. +To extract the messages simply run `pnpm extract-intl`. ## Syncing translations with Transifex diff --git a/packages/i18n/data/core.json b/packages/i18n/data/core.json index 0254d9cfde..1591ef6820 100644 --- a/packages/i18n/data/core.json +++ b/packages/i18n/data/core.json @@ -53,7 +53,9 @@ "ErrorApologizer.title": "Sorry! An unexpected error occured.", "FailedAuthentication.paragraph1": "Please try again and contact your system administrator if you have any further questions.", "FailedAuthentication.title": "We are unable to authorize you to use the Merchant Center.", - "LocaleSwitcher.localesLabel": "Locales menu", + "LocaleSwitcher.dialogLocaleDescription": "The selected data locale will serve as the default setting for all localized fields within the Merchant Center, including names, descriptions, and other localized attributes. It's important to note that this selection does not affect the interface language of the Merchant Center or any data formatting options. To modify these settings, navigate to your user profile.", + "LocaleSwitcher.dialogLocaleTitle": "Selecting a data locale", + "LocaleSwitcher.localesLabel": "Locales", "NavBar.MCSupport.title": "Support", "Notification.hideNotification": "Hide notification", "PageNotFound.paragraph1": "The item you are looking for may have been deleted, does not exist, or the URL was entered incorrectly. Check the URL and try again. Please contact your system administrator or the commercetools Help Desk if you have any further questions.", @@ -69,12 +71,16 @@ "ProjectNotFound.title": "We could not find this Project", "ProjectNotInitialized.paragraph1": "Initialization should not take longer than a few minutes. Please contact us at {mailto} in case the project does not get initialized.", "ProjectNotInitialized.title": "Your project has not yet been initialized", + "ProjectStamp.expired": "Trial expired", + "ProjectStamp.production": "Production", + "ProjectStamp.suspended": "Suspended", + "ProjectStamp.willExpire": "{daysLeft, select, 0 {Trial ends today} 1 {Trial ends in 1 day} other {Trial ends in {daysLeft} days}}", "ProjectSuspended.defaultSuspensionMessage": "Your Project has been suspended", "ProjectSuspended.paragraph1": "Please contact your system administrator if you have any further questions or select another Project to view.", "ProjectSuspended.temporaryMaintenanceSuspensionMessage": "Your Project is temporarily suspended due to maintenance.", "ProjectSwitcher.expired": "Expired", "ProjectSwitcher.noResults": "Sorry, but there are no projects that match your search.", - "ProjectSwitcher.projectsLabel": "Projects menu", + "ProjectSwitcher.projectsLabel": "Projects", "ProjectSwitcher.searchPlaceholder": "Search for a project", "ProjectSwitcher.suspended": "Suspended", "QuickAccess.inputPlaceholder": "Go to...", diff --git a/packages/i18n/data/en.json b/packages/i18n/data/en.json index f4e196e84e..160a76aa59 100644 --- a/packages/i18n/data/en.json +++ b/packages/i18n/data/en.json @@ -53,7 +53,7 @@ "ErrorApologizer.title": "Sorry! An unexpected error occured.", "FailedAuthentication.paragraph1": "Please try again or contact your system administrator if you have any further questions.", "FailedAuthentication.title": "We are unable to authorize you to use the Merchant Center.", - "LocaleSwitcher.localesLabel": "Locales menu", + "LocaleSwitcher.localesLabel": "Locales", "NavBar.MCSupport.title": "Support", "Notification.hideNotification": "Hide notification", "PageNotFound.paragraph1": "The item you are looking for may have been deleted, does not exist, or the URL was entered incorrectly. Check the URL and try again. Please contact your system administrator or the commercetools Help Desk if you have any further questions.", @@ -74,7 +74,7 @@ "ProjectSuspended.temporaryMaintenanceSuspensionMessage": "Your Project is temporarily suspended due to maintenance.", "ProjectSwitcher.expired": "Expired", "ProjectSwitcher.noResults": "Sorry, but there are no projects that match your search.", - "ProjectSwitcher.projectsLabel": "Projects menu", + "ProjectSwitcher.projectsLabel": "Projects", "ProjectSwitcher.searchPlaceholder": "Search for a project", "ProjectSwitcher.suspended": "Suspended", "QuickAccess.inputPlaceholder": "Go to...", diff --git a/packages/permissions/src/components/branch-on-permissions/branch-on-permissions.spec.tsx b/packages/permissions/src/components/branch-on-permissions/branch-on-permissions.spec.tsx index 0f73317d2c..33b28d574f 100644 --- a/packages/permissions/src/components/branch-on-permissions/branch-on-permissions.spec.tsx +++ b/packages/permissions/src/components/branch-on-permissions/branch-on-permissions.spec.tsx @@ -31,6 +31,7 @@ const renderWithPermissions = (demandedPermissions: string[]) => { name: 'P1 ', expiry: { isActive: false }, suspension: { isActive: false }, + isProductionProject: false, }, ], }, diff --git a/packages/permissions/src/types/generated/mc.ts b/packages/permissions/src/types/generated/mc.ts index 1dd175d231..3c6a76a513 100644 --- a/packages/permissions/src/types/generated/mc.ts +++ b/packages/permissions/src/types/generated/mc.ts @@ -425,6 +425,7 @@ export type TQuery = { release?: Maybe; releases?: Maybe; storeOAuthScopes: Array; + systemStatus: TSystemStatus; }; @@ -591,6 +592,17 @@ export type TSupportedStoreScope = { name: Scalars['String']; }; +export enum TSystemOperabilityStatus { + Degraded = 'DEGRADED', + Operational = 'OPERATIONAL', + Outage = 'OUTAGE' +} + +export type TSystemStatus = { + __typename?: 'SystemStatus'; + status: TSystemOperabilityStatus; +}; + export type TUser = TMetaData & { __typename?: 'User'; businessRole?: Maybe; @@ -659,7 +671,7 @@ export type TFetchProjectQuery = { __typename?: 'Query', project?: { __typename? export type TFetchLoggedInUserQueryVariables = Exact<{ [key: string]: never; }>; -export type TFetchLoggedInUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email: string, createdAt: string, gravatarHash: string, firstName: string, lastName: string, language: string, numberFormat: string, timeZone?: string | null, launchdarklyTrackingId: string, launchdarklyTrackingGroup: string, launchdarklyTrackingSubgroup?: string | null, launchdarklyTrackingTeam?: Array | null, launchdarklyTrackingTenant: string, launchdarklyTrackingCloudEnvironment: string, defaultProjectKey?: string | null, businessRole?: string | null, projects: { __typename?: 'ProjectQueryResult', total: number, results: Array<{ __typename?: 'Project', name: string, key: string, suspension: { __typename?: 'ProjectSuspension', isActive: boolean }, expiry: { __typename?: 'ProjectExpiry', isActive: boolean } }> }, idTokenUserInfo?: { __typename?: 'IdTokenUserInfo', iss: string, sub: string, aud: string, exp: number, iat?: number | null, email?: string | null, name?: string | null, additionalClaims?: string | null } | null } | null }; +export type TFetchLoggedInUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email: string, createdAt: string, gravatarHash: string, firstName: string, lastName: string, language: string, numberFormat: string, timeZone?: string | null, launchdarklyTrackingId: string, launchdarklyTrackingGroup: string, launchdarklyTrackingSubgroup?: string | null, launchdarklyTrackingTeam?: Array | null, launchdarklyTrackingTenant: string, launchdarklyTrackingCloudEnvironment: string, defaultProjectKey?: string | null, businessRole?: string | null, projects: { __typename?: 'ProjectQueryResult', total: number, results: Array<{ __typename?: 'Project', name: string, key: string, isProductionProject: boolean, suspension: { __typename?: 'ProjectSuspension', isActive: boolean }, expiry: { __typename?: 'ProjectExpiry', isActive: boolean } }> }, idTokenUserInfo?: { __typename?: 'IdTokenUserInfo', iss: string, sub: string, aud: string, exp: number, iat?: number | null, email?: string | null, name?: string | null, additionalClaims?: string | null } | null } | null }; export type TFetchUserProjectsQueryVariables = Exact<{ [key: string]: never; }>; diff --git a/playground/src/i18n/data/core.json b/playground/src/i18n/data/core.json index c6af2fe3ce..f2f69522fe 100644 --- a/playground/src/i18n/data/core.json +++ b/playground/src/i18n/data/core.json @@ -1,24 +1,21 @@ { "Menu.StateMachines": "State Machines", "Menu.EchoServer": "Echo Server", - "LocaleSwitcher.localesLabel": "Locales menu", - "ProjectSwitcher.projectsLabel": "Projects menu", - "UserSettingsMenu.menuLabel": "User settings menu", "StateMachines.EchoServer.description": "This page demonstrates how to connect a Custom Application to an external API, using the \"/proxy/forward-to\" endpoint. For demo purposes, the external API used by this page is a simple echo server, which just returns some information about the request sent.", "StateMachines.EchoServer.labelIncludeForwardHeaderInRequest": "Inlude Forward Header in request", "StateMachines.EchoServer.labelIncludeParamsInRequest": "Inlude Parameter in request", "StateMachines.EchoServer.labelSendRequest": "Send request", "StateMachines.EchoServer.labelSending": "Sending...", "StateMachines.EchoServer.title": "Echo Server", + "StateMachines.FormattersDemo.dateSelectorLabel": "Date selector:", + "StateMachines.FormattersDemo.fullDateLabel": "Full date:", + "StateMachines.FormattersDemo.localeLabel": "Current locale:", + "StateMachines.FormattersDemo.moneyLabel": "Money:", + "StateMachines.FormattersDemo.subtitle": "You can see here formatted dates and numbers with different locales.", + "StateMachines.FormattersDemo.title": "Formatters Demo", "StateMachines.ListView.column.stateMachineKey": "Key", "StateMachines.ListView.column.stateMachineName": "Name", "StateMachines.ListView.noResults": "There are no results matching your search criteria.", "StateMachines.ListView.objectsInCache": "There are {count} objects in the cache.", - "StateMachines.ListView.title": "State Machines", - "StateMachines.FormattersDemo.title": "Formatters Demo", - "StateMachines.FormattersDemo.subtitle": "You can see here formatted dates and numbers with different locales.", - "StateMachines.FormattersDemo.localeLabel": "Current locale:", - "StateMachines.FormattersDemo.fullDateLabel": "Full date:", - "StateMachines.FormattersDemo.dateSelectorLabel": "Date selector:", - "StateMachines.FormattersDemo.moneyLabel": "Money:" + "StateMachines.ListView.title": "State Machines" } diff --git a/playground/src/i18n/data/de.json b/playground/src/i18n/data/de.json index c6af2fe3ce..0aac582cca 100644 --- a/playground/src/i18n/data/de.json +++ b/playground/src/i18n/data/de.json @@ -1,8 +1,6 @@ { "Menu.StateMachines": "State Machines", "Menu.EchoServer": "Echo Server", - "LocaleSwitcher.localesLabel": "Locales menu", - "ProjectSwitcher.projectsLabel": "Projects menu", "UserSettingsMenu.menuLabel": "User settings menu", "StateMachines.EchoServer.description": "This page demonstrates how to connect a Custom Application to an external API, using the \"/proxy/forward-to\" endpoint. For demo purposes, the external API used by this page is a simple echo server, which just returns some information about the request sent.", "StateMachines.EchoServer.labelIncludeForwardHeaderInRequest": "Inlude Forward Header in request", diff --git a/playground/src/i18n/data/en.json b/playground/src/i18n/data/en.json index c6af2fe3ce..f15aae6429 100644 --- a/playground/src/i18n/data/en.json +++ b/playground/src/i18n/data/en.json @@ -1,8 +1,8 @@ { "Menu.StateMachines": "State Machines", "Menu.EchoServer": "Echo Server", - "LocaleSwitcher.localesLabel": "Locales menu", - "ProjectSwitcher.projectsLabel": "Projects menu", + "LocaleSwitcher.localesLabel": "Locales", + "ProjectSwitcher.projectsLabel": "Projects", "UserSettingsMenu.menuLabel": "User settings menu", "StateMachines.EchoServer.description": "This page demonstrates how to connect a Custom Application to an external API, using the \"/proxy/forward-to\" endpoint. For demo purposes, the external API used by this page is a simple echo server, which just returns some information about the request sent.", "StateMachines.EchoServer.labelIncludeForwardHeaderInRequest": "Inlude Forward Header in request", diff --git a/playground/src/i18n/data/es.json b/playground/src/i18n/data/es.json index c6af2fe3ce..f15aae6429 100644 --- a/playground/src/i18n/data/es.json +++ b/playground/src/i18n/data/es.json @@ -1,8 +1,8 @@ { "Menu.StateMachines": "State Machines", "Menu.EchoServer": "Echo Server", - "LocaleSwitcher.localesLabel": "Locales menu", - "ProjectSwitcher.projectsLabel": "Projects menu", + "LocaleSwitcher.localesLabel": "Locales", + "ProjectSwitcher.projectsLabel": "Projects", "UserSettingsMenu.menuLabel": "User settings menu", "StateMachines.EchoServer.description": "This page demonstrates how to connect a Custom Application to an external API, using the \"/proxy/forward-to\" endpoint. For demo purposes, the external API used by this page is a simple echo server, which just returns some information about the request sent.", "StateMachines.EchoServer.labelIncludeForwardHeaderInRequest": "Inlude Forward Header in request", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 887475e5e6..747c6c8f57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1426,6 +1426,9 @@ importers: '@commercetools-uikit/spacings': specifier: ^18.1.0 version: 18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react@17.0.2) + '@commercetools-uikit/stamp': + specifier: ^18.1.0 + version: 18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react-intl@6.4.5)(react@17.0.2) '@commercetools-uikit/text': specifier: ^18.1.0 version: 18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react-intl@6.4.5)(react@17.0.2) @@ -1659,6 +1662,9 @@ importers: '@commercetools-uikit/flat-button': specifier: ^18.1.0 version: 18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react@17.0.2)(typescript@5.2.2) + '@commercetools-uikit/icon-button': + specifier: ^18.1.0 + version: 18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react@17.0.2)(typescript@5.2.2) '@commercetools-uikit/icons': specifier: ^18.1.0 version: 18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react@17.0.2) @@ -6970,16 +6976,16 @@ packages: shelljs: 0.8.5 dev: false - /@commercetools-frontend/application-config@22.17.1: - resolution: {integrity: sha512-XmXYrmRXarz6jnyrEz+12uXQtCChZtA5T8c9EiprtTjT7oXHFquZwf1y2BSz5L8mbQ8Qrc5rjoN//yQTq8sCAw==} + /@commercetools-frontend/application-config@22.17.2: + resolution: {integrity: sha512-SKzW1V668UX/JtkFtT2ueysfM74lqzTt7t+BZBMn5HnX3rq9sNSae+yHh3z1OrVBgHso8sHRpzMfl58rSSv/ow==} engines: {node: 16.x || >=18.0.0} dependencies: '@babel/core': 7.23.0 '@babel/register': 7.22.15(@babel/core@7.23.0) '@babel/runtime': 7.23.2 '@babel/runtime-corejs3': 7.22.15 - '@commercetools-frontend/babel-preset-mc-app': 22.17.1 - '@commercetools-frontend/constants': 22.17.1 + '@commercetools-frontend/babel-preset-mc-app': 22.17.2 + '@commercetools-frontend/constants': 22.17.2 '@types/dompurify': 2.4.0 '@types/lodash': 4.14.198 '@types/react': 17.0.56 @@ -6997,8 +7003,8 @@ packages: - utf-8-validate dev: true - /@commercetools-frontend/babel-preset-mc-app@22.17.1: - resolution: {integrity: sha512-kzGDO+xbR7GuCuDlG9z5oa1jw9nBTJj57wjqSWTGDu8FxdDSBkw3RvVhVx/n4peaL9FBRl66dirEG7b3r3pE/A==} + /@commercetools-frontend/babel-preset-mc-app@22.17.2: + resolution: {integrity: sha512-UXKBsFvtkX1X4r4fmpr+elimroj+ZxSD9cPkMGwb4KT0ehklkkiqEkBe1PBjtBKr/tAwdUqRGi8FnPVaRRlaxg==} engines: {node: 16.x || >=18.0.0} dependencies: '@babel/core': 7.23.0 @@ -7030,8 +7036,8 @@ packages: - supports-color dev: true - /@commercetools-frontend/constants@22.17.1: - resolution: {integrity: sha512-1ly/uW6G0dOYrSEZEdWJ9zSqiWVLhzX39JvDURGWLZ2XigH+OUlobJFexDPpkCmkimnBvLy4hVcydF5GoItsqA==} + /@commercetools-frontend/constants@22.17.2: + resolution: {integrity: sha512-UDp+KjwCNfXbfyVaga8I6pq7zGykXtuOkKj8fdHLBCJvUU/YZ+vzzyPedn4PBERRHQuqc1uJgPwiqVfiV2p4ng==} dependencies: '@babel/runtime': 7.23.2 '@babel/runtime-corejs3': 7.22.15 @@ -7104,8 +7110,8 @@ packages: dependencies: '@babel/runtime': 7.23.2 '@babel/runtime-corejs3': 7.22.15 - '@commercetools-frontend/application-config': 22.17.1 - '@commercetools-frontend/constants': 22.17.1 + '@commercetools-frontend/application-config': 22.17.2 + '@commercetools-frontend/constants': 22.17.2 '@commercetools-test-data/commons': 6.4.1 '@commercetools-test-data/core': 6.4.1 '@commercetools-test-data/utils': 6.4.1 @@ -9707,6 +9713,26 @@ packages: - react-intl dev: false + /@commercetools-uikit/stamp@18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react-intl@6.4.5)(react@17.0.2): + resolution: {integrity: sha512-MPLVL+asW5pKogH8gr/4PofcahxrjzfjCEdmt6wVLyFbunoe+XK9EqqiEEFtlO19hcHS63OcOsr3XJmB//MZ6Q==} + peerDependencies: + react: 17.x + dependencies: + '@babel/runtime': 7.23.2 + '@babel/runtime-corejs3': 7.22.15 + '@commercetools-uikit/design-system': 18.1.0(@types/react@17.0.56)(react-dom@17.0.2) + '@commercetools-uikit/spacings-inline': 18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react@17.0.2) + '@commercetools-uikit/text': 18.1.0(@types/react@17.0.56)(react-dom@17.0.2)(react-intl@6.4.5)(react@17.0.2) + '@commercetools-uikit/utils': 18.1.0(react@17.0.2) + '@emotion/react': 11.11.1(@types/react@17.0.56)(react@17.0.2) + prop-types: 15.8.1 + react: 17.0.2 + transitivePeerDependencies: + - '@types/react' + - react-dom + - react-intl + dev: false + /@commercetools-uikit/text-field@16.12.1(@types/react@17.0.56)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2): resolution: {integrity: sha512-vg5H0YKKaSfr4Af/yzKCIFLhN7hiBt+0amo/L3o/dQhwSha7L2z7S3kcrN2OVnxGVS/dbk1UiLu/a/vF8EnvMw==} peerDependencies: @@ -19387,7 +19413,7 @@ packages: peerDependencies: react: '>=16.12.0' dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.2 compute-scroll-into-view: 1.0.20 prop-types: 15.8.1 react: 18.2.0 @@ -34413,7 +34439,3 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false diff --git a/schemas/mc.json b/schemas/mc.json index 184e898533..25ca4898eb 100644 --- a/schemas/mc.json +++ b/schemas/mc.json @@ -3909,6 +3909,22 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "systemStatus", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "SystemStatus", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -4876,6 +4892,62 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "ENUM", + "name": "SystemOperabilityStatus", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "DEGRADED", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OPERATIONAL", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OUTAGE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SystemStatus", + "description": null, + "fields": [ + { + "name": "status", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "SystemOperabilityStatus", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "User", diff --git a/test-data/types/generated/mc.ts b/test-data/types/generated/mc.ts index 1dd175d231..3c6a76a513 100644 --- a/test-data/types/generated/mc.ts +++ b/test-data/types/generated/mc.ts @@ -425,6 +425,7 @@ export type TQuery = { release?: Maybe; releases?: Maybe; storeOAuthScopes: Array; + systemStatus: TSystemStatus; }; @@ -591,6 +592,17 @@ export type TSupportedStoreScope = { name: Scalars['String']; }; +export enum TSystemOperabilityStatus { + Degraded = 'DEGRADED', + Operational = 'OPERATIONAL', + Outage = 'OUTAGE' +} + +export type TSystemStatus = { + __typename?: 'SystemStatus'; + status: TSystemOperabilityStatus; +}; + export type TUser = TMetaData & { __typename?: 'User'; businessRole?: Maybe; @@ -659,7 +671,7 @@ export type TFetchProjectQuery = { __typename?: 'Query', project?: { __typename? export type TFetchLoggedInUserQueryVariables = Exact<{ [key: string]: never; }>; -export type TFetchLoggedInUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email: string, createdAt: string, gravatarHash: string, firstName: string, lastName: string, language: string, numberFormat: string, timeZone?: string | null, launchdarklyTrackingId: string, launchdarklyTrackingGroup: string, launchdarklyTrackingSubgroup?: string | null, launchdarklyTrackingTeam?: Array | null, launchdarklyTrackingTenant: string, launchdarklyTrackingCloudEnvironment: string, defaultProjectKey?: string | null, businessRole?: string | null, projects: { __typename?: 'ProjectQueryResult', total: number, results: Array<{ __typename?: 'Project', name: string, key: string, suspension: { __typename?: 'ProjectSuspension', isActive: boolean }, expiry: { __typename?: 'ProjectExpiry', isActive: boolean } }> }, idTokenUserInfo?: { __typename?: 'IdTokenUserInfo', iss: string, sub: string, aud: string, exp: number, iat?: number | null, email?: string | null, name?: string | null, additionalClaims?: string | null } | null } | null }; +export type TFetchLoggedInUserQuery = { __typename?: 'Query', user?: { __typename?: 'User', id: string, email: string, createdAt: string, gravatarHash: string, firstName: string, lastName: string, language: string, numberFormat: string, timeZone?: string | null, launchdarklyTrackingId: string, launchdarklyTrackingGroup: string, launchdarklyTrackingSubgroup?: string | null, launchdarklyTrackingTeam?: Array | null, launchdarklyTrackingTenant: string, launchdarklyTrackingCloudEnvironment: string, defaultProjectKey?: string | null, businessRole?: string | null, projects: { __typename?: 'ProjectQueryResult', total: number, results: Array<{ __typename?: 'Project', name: string, key: string, isProductionProject: boolean, suspension: { __typename?: 'ProjectSuspension', isActive: boolean }, expiry: { __typename?: 'ProjectExpiry', isActive: boolean } }> }, idTokenUserInfo?: { __typename?: 'IdTokenUserInfo', iss: string, sub: string, aud: string, exp: number, iat?: number | null, email?: string | null, name?: string | null, additionalClaims?: string | null } | null } | null }; export type TFetchUserProjectsQueryVariables = Exact<{ [key: string]: never; }>; diff --git a/visual-testing-app/src/components/project-stamp/project-stamp.visualroute.tsx b/visual-testing-app/src/components/project-stamp/project-stamp.visualroute.tsx new file mode 100644 index 0000000000..e17356c4c3 --- /dev/null +++ b/visual-testing-app/src/components/project-stamp/project-stamp.visualroute.tsx @@ -0,0 +1,21 @@ +import { ProjectStamp } from '@commercetools-frontend/application-components'; +import { Suite, Spec } from '../../test-utils'; + +export const routePath = '/project-stamp'; + +export const Component = () => ( + + + + + + + + + + + + + + +); diff --git a/visual-testing-app/src/components/project-stamp/project-stamp.visualspec.ts b/visual-testing-app/src/components/project-stamp/project-stamp.visualspec.ts new file mode 100644 index 0000000000..f784bd775d --- /dev/null +++ b/visual-testing-app/src/components/project-stamp/project-stamp.visualspec.ts @@ -0,0 +1,13 @@ +import percySnapshot from '@percy/puppeteer'; +import { HOST } from '../../constants'; + +describe('ProjectStamp', () => { + beforeAll(async () => { + await page.goto(`${HOST}/project-stamp`); + }); + + it('Default', async () => { + await page.waitForSelector('text/Production project stamp'); + await percySnapshot(page, 'ProjectStamp'); + }); +});