# EPP Workshop: How to create a new component ## Architecture When navigating to a content page... 1. Frontend asks backend what the content of that subpage is 2. Backend responds with list of components 3. Frontend fetches data for all of the components (one by one) 4. Backend responds with component data 5. Frontend initializes components with the relevant data ![](https://i.imgur.com/m2jDlpW.jpg) For an example request roundtrip, click [here](https://hackmd.io/moKlM1oeQjaQZpFGrw9PdQ) ## 0. Create a branch ``` git checkout develop git pull git checkout -b EPPNC-1234-some-slug-describing-your-ticket ``` ## 1. Create component files ``/dgl-gui/src/shop/client/components/molecules/cms/test-component/`` ```javascript // CMSTestComponent.tsx import React, { memo, FunctionComponent } from 'react'; import { convertComponent } from 'shop/client/utils/helpers/contentHelper'; import { ICMSComponent } from 'shop/client/interfaces/base/cms/CMSComponent'; import 'shop/client/components/molecules/cms/multi-teaser/CMSMultiTeaserComponent.scss'; export interface IMultiTeaserComponent { headline: string } type Props = ICMSComponent<IMultiTeaserComponent>; const MultiTeaserComponent: FunctionComponent<Props> = (props) => { const { headline } = convertComponent<Props>(props); return ( <div className="test-component"> <h1 className="test-component__headline">{headline}</h1> </div> ); }; export default memo(MultiTeaserComponent); ``` ```sass // CmsTestComponent.scss .test-component { &__headline { color: #ff0000; } } ``` ``` javascript // CMSTestComponent.spec.tsx // Empty ``` Example commit: https://bitbucket.netconomy.net/projects/DGL/repos/dgl-gui/commits/730b138ed182eb93458074fee4746d9c4905d7ba ## 2. Add the component to patternlab ### 2.1 Add mock data `dgl-gui/src/shop/client/constants/CMSConstants.ts` ```diff EMPTY_CART = 'CMSEmptyCartComponent', EMPTY_WISH_LIST = 'CMSEmptyWishListComponent', MULTI_TEASER = 'CMSMultiTeaserSplitComponent', + TEST_COMPONENT = 'CMSTestComponent', ORDER_CONFIRMATION = 'OrderConfirmationComponent', PRODUCT_CAROUSEL = 'ProductCarouselComponent', SITE_LOGO = 'SiteLogoComponent', ``` `dgl-gui/src/shop/client/utils/__mocks__/TestComponentMockData.ts` ```javascript import { CMSComponent } from 'shop/client/constants/CMSConstants'; export const TestComponentMockResponse = { uid: 'test-multi-teaser-split', uuid: 'eyJpdGVtSWQiOiJ0ZXN0LW11bHRpLXRlYXNlci1zcGxpdCIsImNhdGFsb2dJZCI6ImRnbC1nbG9iYWwtY29udGVudCIsImNhdGFsb2dWZXJzaW9uIjoiT25saW5lIn0=', typeCode: CMSComponent.TEST_COMPONENT, modifiedtime: '2019-07-12T14:35:30.000+0000', name: 'Test Component', otherProperties: { headline: "Thats a nice headline!" } }; ``` ### 2.2 Create patternlab template (container) files `/dgl-gui/patternlab/source/_patterns/Components/test-component/` test-component.mustache ```html <div class="test-component-patternlab-container"></div> ``` test-component-again.mustache ```html <div class="test-component-patternlab-container-again"></div> ``` ### 2.3 Add react component connection to patternlab `dgl-gui/patternlab/source/js/components/testComponent.js` ```javascript import CMSTestComponent from 'shop/client/components/molecules/cms/test-component/CMSTestComponent'; import { TestComponentMockResponse } from 'shop/client/utils/__mocks__/TestComponentMockData'; export default [ { Component: CMSTestComponent, entrypointClass: 'test-component-patternlab-container', props: TestComponentMockResponse }, { Component: CMSTestComponent, entrypointClass: 'test-component-patternlab-container-again', props: TestComponentMockResponse }, ] ``` `patternlab/source/js/index.js` ```diff import squareImage from './components/squareImage' import specialBrandImage from './components/specialBrandImage' import teaserSplit from './components/teaserSplit'; + import testComponent from './components/testComponent'; import platinSplitTeaser from './components/platinSplitTeaser'; import platinOverlayTeaser from './components/platinOverlayTeaser'; import cmseyecatcher from './components/cmseyecatcher'; ``` ```diff ...squareImage, ...specialBrandImage, ...teaserSplit, + ...testComponent, ...platinSplitTeaser, ...platinOverlayTeaser, ...cmseyecatcher, ``` Run it: `npm run patternlab:serve` https://bitbucket.netconomy.net/projects/DGL/repos/dgl-gui/commits/42849c06893dfcccbbc0acd18a1e18af1f0c0f6e ## 3. Add unit test ``` javascript // CMSTestComponent.spec.tsx import React from 'react'; import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash' import CMSTestComponent from 'shop/client/components/molecules/cms/test-component/CMSTestComponent'; import { TestComponentMockResponse } from 'shop/client/utils/__mocks__/TestComponentMockData'; describe('<CMSTestComponent /> component:', () => { describe('Rendering MultiTeaser', () => { let shallowComponent: any = null; beforeAll(() => { shallowComponent = shallow( <CMSTestComponent {...TestComponentMockResponse} /> ); }); it('should exist ', () => { expect(shallowComponent.instance()).toBeDefined(); expect(shallowComponent).toHaveLength(1); }); it('should render a title ', () => { expect(shallowComponent.find(".test-component__headline")).toHaveText(TestComponentMockResponse.otherProperties.headline); }); it('should not render a title if its not given in props', () => { const newProps = cloneDeep(TestComponentMockResponse); delete newProps.otherProperties.headline; shallowComponent.setProps(newProps); expect(shallowComponent.find(".test-component__headline")).toHaveText(""); }); }); }); ``` `npm run test -t "TestComponent"` Example commit: https://bitbucket.netconomy.net/projects/DGL/repos/dgl-gui/commits/20a8c30255ae9d143e4faa097c24000f26f35a30 ### 4. Connect it to SmartEdit `dgl-gui/src/shop/client/components/molecules/cms/base/registry/CMSRegistry.ts` ```diff loading: Loading }); + const CMSTestComponent = Loadable({ + loader: () => import('shop/client/components/molecules/cms/test-component/CMSTestComponent'), + loading: Loading + }); const CMSMultiTeaserComponent = Loadable({ loader: () => import('shop/client/components/molecules/cms/multi-teaser/CMSMultiTeaserComponent'), loading: Loading ``` ```diff registerCMSComponent(CMSConstants.CMSComponent.SPECIAL_BRAND_IMAGE, CMSSpecialBrandImageComponent); registerCMSComponent(CMSConstants.CMSComponent.EYE_CATCHER, CMSEyeCatcherComponent); registerCMSComponent(CMSConstants.CMSComponent.TEASER_SPLIT, CMSTeaserSplitComponent); + registerCMSComponent(CMSConstants.CMSComponent.TEST_COMPONENT, CMSTestComponent); registerCMSComponent(CMSConstants.CMSComponent.MULTI_TEASER, CMSMultiTeaserComponent); registerCMSComponent(CMSConstants.CMSComponent.BUTTON_LINK, CMSButtonLinkComponent); registerCMSComponent(CMSConstants.CMSComponent.SQUARE_IMAGE, CMSSquareImageComponent); ``` Example commit: https://bitbucket.netconomy.net/projects/DGL/repos/dgl-gui/commits/40013b8ce8db7986f02b43aa68b87311ca7cc33e