Skip to content

Commit

Permalink
New CustomViewsSelector component (#3242)
Browse files Browse the repository at this point in the history
* refactor(application-components): first iteration on new custom views selector component

* refactor: implement custom views data fetching

* refactor: implement custom views data fetching

* fix(application-shell): fix types

* refactor: include custom views selector in all page components

* Extract logic from `application-shell` to `application-shell-connector` package (#3245)

* refactor: extract apollo related utilities from app-shell to app-shell-connector package

* fix: lintin fixes

* refactor: remove unused imports

* refactor: move custom view components from app-shell to app-shell-connectors package

* refactor: move constants to constants package

* fix: keep export types for backwards compatibility

* refactor: remove unused packages from app-shell

* fix: remove unwanted depdency and fix some tests

---------

Co-authored-by: Nicola Molinari <nicola.molinari@commercetools.com>

* refactor: early return if locator code is not provided

* refactor: error handling

* refactor: remove mocks

* test(playground): add mocks and test for custom panel demo

* docs: changesets

* refactor: define message

---------

Co-authored-by: Nicola Molinari <nicola.molinari@commercetools.com>
  • Loading branch information
CarlosCortizasCT and emmenko authored Oct 10, 2023
1 parent 3ef39b7 commit 6023ff2
Show file tree
Hide file tree
Showing 110 changed files with 12,403 additions and 4,892 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-queens-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@commercetools-frontend/constants': patch
---

Expose new constants `SUPPORTED_HEADERS` and `STORAGE_KEYS`
20 changes: 20 additions & 0 deletions .changeset/moody-ravens-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@commercetools-frontend/application-shell-connectors': patch
'@commercetools-frontend/application-shell': patch
---

Expose HTTP utilities from `application-shell-connectors` package.

- `buildApiUrl`
- `createApolloClient`
- `createApolloContextForProxyForwardTo`
- `createHttpClientOptions`
- `executeHttpClientRequest`
- `getMcApiUrl`
- `selectUserId`
- `selectProjectKeyFromUrl`
- `useMcQuery`
- `useMcLazyQuery`
- `useMcMutation`

For backwards compatibility these are also exported from the `application-shell` package.
8 changes: 8 additions & 0 deletions @types-extensions/graphql-settings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ declare module '*/fetch-project-extension-image-regex.settings.graphql' {
export default defaultDocument;
}

declare module '*/fetch-custom-views-by-locator.settings.graphql' {
import { DocumentNode } from 'graphql';
const defaultDocument: DocumentNode;
export const FetchCustomViewsByLocator: DocumentNode;

export default defaultDocument;
}

declare module '*/fetch-project-extensions-navbar.settings.graphql' {
import { DocumentNode } from 'graphql';
const defaultDocument: DocumentNode;
Expand Down
3 changes: 3 additions & 0 deletions packages/application-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"@babel/runtime": "^7.22.15",
"@babel/runtime-corejs3": "^7.22.15",
"@commercetools-frontend/actions-global": "22.8.4",
"@commercetools-frontend/application-config": "22.8.4",
"@commercetools-frontend/application-shell-connectors": "22.8.4",
"@commercetools-frontend/assets": "22.8.4",
"@commercetools-frontend/constants": "22.8.4",
"@commercetools-frontend/i18n": "22.8.4",
Expand All @@ -42,6 +44,7 @@
"@commercetools-uikit/hooks": "^16.7.3",
"@commercetools-uikit/icon-button": "^16.7.3",
"@commercetools-uikit/icons": "^16.7.3",
"@commercetools-uikit/label": "^16.7.3",
"@commercetools-uikit/messages": "^16.7.3",
"@commercetools-uikit/primary-button": "^16.7.3",
"@commercetools-uikit/secondary-button": "^16.7.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { CustomViewData } from '@commercetools-frontend/constants';
import { reportErrorToSentry } from '@commercetools-frontend/sentry';
import { screen, renderApp, waitFor, fireEvent } from '../../../test-utils';
import {
screen,
renderComponent,
fireEvent,
waitFor,
} from '../../../test-utils';
import {
TCustomViewSize,
TCustomViewType,
} from '../../types/generated/settings';
} from '../../../types/generated/settings';
import CustomViewLoader from './custom-view-loader';

const mockShowNotification = jest.fn();
Expand Down Expand Up @@ -59,8 +64,10 @@ describe('CustomViewLoader', () => {
});

it('should render a custom view', async () => {
renderApp(
<CustomViewLoader customView={TEST_CUSTOM_VIEW} onClose={jest.fn()} />
const projectKey = 'test-with-big-data';
renderComponent(
<CustomViewLoader customView={TEST_CUSTOM_VIEW} onClose={jest.fn()} />,
{ projectKey }
);

const iFrame = screen.getByTitle(
Expand All @@ -70,7 +77,7 @@ describe('CustomViewLoader', () => {
`custom-view-${TEST_CUSTOM_VIEW.id}`
);
expect(iFrame.getAttribute('src')).toContain(
`/custom-views/${TEST_CUSTOM_VIEW.id}/projects/test-with-big-data`
`/custom-views/${TEST_CUSTOM_VIEW.id}/projects/${projectKey}`
);
});

Expand All @@ -80,7 +87,9 @@ describe('CustomViewLoader', () => {
url: 'https://example.com/',
};

renderApp(<CustomViewLoader customView={customView} onClose={jest.fn()} />);
renderComponent(
<CustomViewLoader customView={customView} onClose={jest.fn()} />
);

fireEvent.load(
screen.getByTitle(`Custom View: ${TEST_CUSTOM_VIEW.defaultLabel}`)
Expand All @@ -99,7 +108,7 @@ describe('CustomViewLoader', () => {
type: 'InvalidType',
};

renderApp(
renderComponent(
<CustomViewLoader
// @ts-expect-error: we are testing an unknown type
customView={customView}
Expand All @@ -120,7 +129,7 @@ describe('CustomViewLoader', () => {
it('should call onClose when the custom view is closed', async () => {
const onCloseMock = jest.fn();

renderApp(
renderComponent(
<CustomViewLoader customView={TEST_CUSTOM_VIEW} onClose={onCloseMock} />
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useCallback, useEffect, useRef } from 'react';
import styled from '@emotion/styled';
import { useIntl } from 'react-intl';
import { useShowNotification } from '@commercetools-frontend/actions-global';
import { CustomPanel } from '@commercetools-frontend/application-components';
import { useApplicationContext } from '@commercetools-frontend/application-shell-connectors';
import {
CUSTOM_VIEWS_EVENTS_NAMES,
Expand All @@ -12,6 +11,7 @@ import {
CustomViewData,
} from '@commercetools-frontend/constants';
import { reportErrorToSentry } from '@commercetools-frontend/sentry';
import CustomPanel from '../custom-panel';
import messages from './messages';

type TCustomViewIframeMessage = {
Expand Down Expand Up @@ -104,8 +104,9 @@ function CustomViewLoader(props: TCustomViewLoaderProps) {

useEffect(() => {
// Close the channel when the component unmounts
const communicationChannel = iFrameCommunicationChannel.current;
return () => {
iFrameCommunicationChannel.current?.port1.close();
communicationChannel?.port1.close();
};
}, []);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { CustomViewData } from '@commercetools-frontend/constants';
import { reportErrorToSentry } from '@commercetools-frontend/sentry';
import Constraints from '@commercetools-uikit/constraints';
import { designTokens } from '@commercetools-uikit/design-system';
import { SidebarCollapseIcon } from '@commercetools-uikit/icons';
import SecondaryButton from '@commercetools-uikit/secondary-button';
import Spacings from '@commercetools-uikit/spacings';
import Text from '@commercetools-uikit/text';
import CustomViewLoader from '../custom-view-loader';
import messages from './messages';
import { useCustomViewsConnector } from './use-custom-views-connector';

const COMPONENT_HEIGHT = '56px';

type TCustomViewSelectorBaseProps = {
onCustomViewsResolved?: (customViews: CustomViewData[]) => void;
};
type TCustomViewSelectorProps = TCustomViewSelectorBaseProps & {
customViewLocatorCode?: string;
};
type TCustomViewSelectorWithRequiredProps = TCustomViewSelectorBaseProps & {
customViewLocatorCode: string;
};

type TWrapperProps = {
shouldRender: boolean;
};
const Wrapper = styled.div<TWrapperProps>`
height: ${(props) => (props.shouldRender ? COMPONENT_HEIGHT : '0')};
overflow: hidden;
transition: height 0.3s ease-in-out;
`;

const Container = styled.div`
background-color: ${designTokens.colorAccent98};
padding: ${designTokens.spacing25} ${designTokens.spacing60};
`;

function CustomViewSelector(props: TCustomViewSelectorWithRequiredProps) {
const [selectedCustomView, setSelectedCustomView] =
useState<CustomViewData | null>(null);
const { customViews, error, loading } = useCustomViewsConnector({
customViewLocatorCode: props.customViewLocatorCode,
});

const { onCustomViewsResolved } = props;
useEffect(() => {
if (!loading && !error && onCustomViewsResolved) {
onCustomViewsResolved(customViews);
}
}, [customViews, error, loading, onCustomViewsResolved]);
if (error) {
reportErrorToSentry(error, {
extra: {
customViewLocatorCode: props.customViewLocatorCode,
},
});
return null;
}

return (
<Wrapper shouldRender={customViews.length > 0}>
<Container>
<Constraints.Horizontal max="scale">
<Spacings.Inline
scale="m"
justifyContent="flex-end"
alignItems="center"
>
<Spacings.Inline scale="xs" alignItems="center">
<SidebarCollapseIcon size="medium" color="neutral60" />
<Text.Detail tone="secondary" intlMessage={messages.title} />
</Spacings.Inline>
{customViews.map((customView) => (
<SecondaryButton
key={customView.id}
label={customView.defaultLabel}
size="medium"
onClick={() => {
setSelectedCustomView(customView);
}}
/>
))}
</Spacings.Inline>

{selectedCustomView && (
<CustomViewLoader
customView={selectedCustomView}
onClose={() => {
setSelectedCustomView(null);
}}
/>
)}
</Constraints.Horizontal>
</Container>
</Wrapper>
);
}

const CustomViewSelectorOrNothing = (props: TCustomViewSelectorProps) => {
if (!props.customViewLocatorCode) {
return null;
}
return (
<CustomViewSelector
{...props}
customViewLocatorCode={props.customViewLocatorCode}
/>
);
};

export default CustomViewSelectorOrNothing;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
query FetchCustomViewsByLocator($customViewLocatorCode: String!) {
allCustomViewsInstallationsByLocator(locator: $customViewLocatorCode) {
id
customView {
id
defaultLabel
labelAllLocales {
locale
value
}
url
type
typeSettings {
size
}
locators
permissions {
name
oAuthScopes
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './custom-views-selector';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineMessages } from 'react-intl';

export default defineMessages({
title: {
id: 'CustomViewsSelector.title',
defaultMessage: 'Custom Views:',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useMcQuery } from '@commercetools-frontend/application-shell-connectors';
import {
CustomViewData,
GRAPHQL_TARGETS,
} from '@commercetools-frontend/constants';
import {
TFetchCustomViewsByLocatorQuery,
TFetchCustomViewsByLocatorQueryVariables,
} from '../../../types/generated/settings';
import FetchCustomViewsQuery from './fetch-custom-views-by-locator.settings.graphql';

type TUseCustomViewsFetcherParams = {
customViewLocatorCode: string;
};
type TUseCustomViewsFetcher = (props: TUseCustomViewsFetcherParams) => {
customViews: CustomViewData[];
error?: Error;
loading: boolean;
};

export const useCustomViewsConnector: TUseCustomViewsFetcher = ({
customViewLocatorCode,
}) => {
const { data, error, loading } = useMcQuery<
TFetchCustomViewsByLocatorQuery,
TFetchCustomViewsByLocatorQueryVariables
>(FetchCustomViewsQuery, {
variables: {
customViewLocatorCode,
},
context: {
target: GRAPHQL_TARGETS.SETTINGS_SERVICE,
},
});

return {
customViews:
data?.allCustomViewsInstallationsByLocator?.map(
(installation) => installation.customView as CustomViewData
) || [],
error,
loading,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { designTokens as uiKitDesignTokens } from '@commercetools-uikit/design-s
import Spacings from '@commercetools-uikit/spacings';
import { warning } from '@commercetools-uikit/utils';
import { designTokens as appKitDesignTokens } from '../../../theming';
import CustomViewsSelector from '../../custom-views/custom-views-selector';
import {
FormPrimaryButton,
FormSecondaryButton,
Expand Down Expand Up @@ -65,6 +66,10 @@ type CustomFormDetailPageProps = {
* A return route path label.
*/
previousPathLabel?: string | MessageDescriptor;
/**
* This code is used to configure which Custom Views are available for this page.
*/
customViewLocatorCode?: string;
/**
* Function called when back button is pressed.
*/
Expand All @@ -85,6 +90,9 @@ const CustomFormDetailPage = (props: CustomFormDetailPageProps) => {

return (
<PageWrapper>
<CustomViewsSelector
customViewLocatorCode={props.customViewLocatorCode}
/>
<DetailPageContainer>
<PageTopBar
color="neutral"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ type FormDetailPageProps = {
* Replaces the title/subtitle row with a custom one (for special use cases).
*/
customTitleRow?: ReactNode;
/**
* This code is used to configure which Custom Views are available for this page.
*/
customViewLocatorCode?: string;
/**
* Any React node displayed as the content within the page.
*/
Expand Down Expand Up @@ -99,6 +103,7 @@ const FormDetailPage = (props: FormDetailPageProps) => (
title={props.title}
subtitle={props.subtitle}
customTitleRow={props.customTitleRow}
customViewLocatorCode={props.customViewLocatorCode}
previousPathLabel={props.previousPathLabel}
onPreviousPathClick={props.onPreviousPathClick}
hideControls={props.hideControls}
Expand Down
Loading

1 comment on commit 6023ff2

@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 merchant-center-application-kit ready!

✅ Preview
https://merchant-center-application-65y3wey21-commercetools.vercel.app

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

Please sign in to comment.