Skip to content

Commit

Permalink
AppBar changes (phase 2) (#3403)
Browse files Browse the repository at this point in the history
* chore: creating project-stamp component

* chore: removing old ui and improving project-stamp

* chore: styling project switcher

* chore: update all ui-kit dependencies

* chore: setting up the width and height of selectors

* chore: adding style for smaller DotIcon

* chore: styling the stamps inside the project selector

* chore: adjusting the fonts for project selector

* chore: adjusting the style of stamps

* chore: adjusting the layout of the appbar components

* chore: adding the labels to project and locale switchers

* chore: replace old designtokens

* chore: small UI changes

* chore: replace the GroupHeading props

* chore: changeset

* chore: adding stamp dependency

* chore: added the dialog for locale explanation

* chore: changeset

* fix: the react-select import

* fix: typescript error in CustomGroupHeading component

* chore: change the react-select import

* chore: removing the unneccessary files

* chore: replacing one Stamp component with individual ones for each state

* refactor(application-components): refactor app bar selectors styling

* fix(application-shell): fix typing issues

* chore: improving project stamps

* chore: removing the 'export' typo

* chore: test update

* chore: code improvements

* chore: small code improvement

* chore: adding the VTRs and updating the ProjectStamp label

* chore: adding the translation to locale switcher label

* chore: updating the README.md

* chore: adding the translation to project switcher label

* chore: adding the project-stamp test

* chore: adding the locale switcher test

* chore: updated the copy of the locales dialog

* chore: adding the test for stamps in project-switcher

* chore: copy improvements

* chore: added the needed dependencies in packgage.json files

* chore: adding the types

* chore: stamp improvements

* chore: include `useModalState` hook

* chore: updating the translations

* chore: removing the translations

* chore: updated changeset

* chore: updating messages.ts

* chore: add a prop type

* chore: code improvements

* chore: updating the project-switcher test

* chore: prop type

* chore: updating the prop type

* chore: revert the prop types changes

* chore: updating the title of InfoDialog

* chore: updating dialogLocaleDescription message

* refactor(application-shell): extract helper function out of the test implementation

* chore: updated translation

* chore: updated test

* chore: improved locale description copy

---------

Co-authored-by: Carlos Cortizas <carlos.martines@commercetools.com>
  • Loading branch information
chloe0592 and CarlosCortizasCT authored Feb 23, 2024
1 parent 6750705 commit d575e84
Show file tree
Hide file tree
Showing 33 changed files with 654 additions and 173 deletions.
11 changes: 11 additions & 0 deletions .changeset/kind-years-laugh.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions packages/application-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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}}',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { screen, renderComponent } from '../../test-utils';
import ProjectStamp from './project-stamp';

describe('rendering', () => {
it('should render ProjectStamp - IsProduction', () => {
renderComponent(<ProjectStamp.IsProduction />);

expect(screen.getByText('Production')).toBeInTheDocument();
});
it('should render ProjectStamp - IsExpired', () => {
renderComponent(<ProjectStamp.IsExpired />);

expect(screen.getByText('Trial expired')).toBeInTheDocument();
});
it('should render ProjectStamp - WillExpire with 0 days left', () => {
renderComponent(<ProjectStamp.WillExpire daysLeft={0} />);

expect(screen.getByText('Trial ends today')).toBeInTheDocument();
});
it('should render ProjectStamp - WillExpire with 1 days left', () => {
renderComponent(<ProjectStamp.WillExpire daysLeft={1} />);

expect(screen.getByText('Trial ends in 1 day')).toBeInTheDocument();
});
it('should render ProjectStamp - WillExpire with 4 days left', () => {
renderComponent(<ProjectStamp.WillExpire daysLeft={4} />);

expect(screen.getByText('Trial ends in 4 days')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -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<string, string | number> };
icon?: ReactElement;
};
function CustomStamp(props: TCustomStampProps) {
const intl = useIntl();
const { values, ...message } = props.label;
return (
<Stamp
tone={props.tone}
isCondensed={true}
label={intl.formatMessage(message, values || {})}
icon={props.icon}
/>
);
}

const IsProduction = () => (
<CustomStamp
tone="positive"
label={messages.ProjectProduction}
icon={
<div
css={css`
height: 18px;
svg {
height: 18px;
width: 12px;
}
`}
>
<DotIcon color="primary" />
</div>
}
/>
);

const IsSuspended = () => (
<CustomStamp tone="critical" label={messages.ProjectSuspended} />
);

const IsExpired = () => (
<CustomStamp tone="critical" label={messages.ProjectExpired} />
);

const WillExpire = (props: { daysLeft: number }) => (
<CustomStamp
tone="information"
label={{
...messages.ProjectWillExpire,
values: { daysLeft: props.daysLeft },
}}
/>
);

const ProjectStamp = {
IsProduction,
IsSuspended,
IsExpired,
WillExpire,
};

export default ProjectStamp;
3 changes: 3 additions & 0 deletions packages/application-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
14 changes: 13 additions & 1 deletion packages/application-shell-connectors/src/types/generated/mc.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/application-shell/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
50 changes: 39 additions & 11 deletions packages/application-shell/src/components/app-bar/app-bar.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -47,30 +48,57 @@ const AppBar = (props: Props) => {
`}
>
<Spacings.Inline scale="m" alignItems="center">
<Spacings.Inline scale="m" alignItems="center">
<div
css={css`
display: flex;
gap: ${uikitDesignTokens.spacing30};
align-items: center;
`}
>
{(() => {
if (!props.user) {
return <LoadingPlaceholder shape="rect" size="s" />;
}
// The `<ProjectSwitcher>` 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 (
<ProjectSwitcher
// In this case it's not necessary to check if the `projectKey` param
// is included in the list of projects. In such case
// the dropdown will still be rendered but no project will be selected.
// This is fine becase the user has still the possibility to "switch"
// to a project.
projectKey={props.projectKeyFromUrl || previousProjectKey}
/>
<div
css={css`
display: flex;
gap: ${uikitDesignTokens.spacing20};
align-items: center;
`}
>
{selectedProject?.isProductionProject && (
<div
css={css`
height: 22px;
`}
>
<ProjectStamp.IsProduction />
</div>
)}
<ProjectSwitcher
// In this case it's not necessary to check if the `projectKey` param
// is included in the list of projects. In such case
// the dropdown will still be rendered but no project will be selected.
// This is fine becase the user has still the possibility to "switch"
// to a project.
projectKey={props.projectKeyFromUrl || previousProjectKey}
/>
</div>
);
}
if (!props.user.defaultProjectKey) return null;
return <BackToProject projectKey={previousProjectKey} />;
})()}
{/* This node is used by a react portal */}
<div id={CONTAINERS.LOCALE_SWITCHER} />
</Spacings.Inline>
</div>
<Spacings.Inline>
<div
id={REQUESTS_IN_FLIGHT_LOADER_DOM_ID}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ describe('when user does not have permissions to access the project', () => {
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' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ query FetchLoggedInUser {
expiry {
isActive
}
isProductionProject
}
}
idTokenUserInfo {
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<IntlProvider locale="en" messages={{}}>
<LocaleSwitcher
projectDataLocale="en"
setProjectDataLocale={setProjectDataLocale}
availableLocales={availableLocales}
/>
</IntlProvider>
);
};

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();
});
});
Loading

1 comment on commit d575e84

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for application-kit-custom-views ready!

✅ Preview
https://application-kit-custom-views-3tkfxw0qw-commercetools.vercel.app

Built with commit d575e84.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.