@eeacms/volto-marine-policy 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/jest-addon.config.js +4 -4
  3. package/package.json +9 -8
  4. package/src/components/Blocks/DemoSitesExplorer/DemoSitesExplorerEdit.js +5 -0
  5. package/src/components/Blocks/DemoSitesExplorer/DemoSitesExplorerView.js +115 -0
  6. package/src/components/Blocks/DemoSitesExplorer/DemoSitesFilters.jsx +406 -0
  7. package/src/components/Blocks/DemoSitesExplorer/DemoSitesFilters.test.jsxZ +91 -0
  8. package/src/components/Blocks/DemoSitesExplorer/DemoSitesListing.jsx +383 -0
  9. package/src/components/Blocks/DemoSitesExplorer/DemoSitesMap.jsx +230 -0
  10. package/src/components/Blocks/DemoSitesExplorer/FeatureDisplay.jsx +97 -0
  11. package/src/components/Blocks/DemoSitesExplorer/FeatureDisplay.test.jsxZ +48 -0
  12. package/src/components/Blocks/DemoSitesExplorer/FeatureInteraction.jsx +95 -0
  13. package/src/components/Blocks/DemoSitesExplorer/InfoOverlay.jsx +79 -0
  14. package/src/components/Blocks/DemoSitesExplorer/hooks.js +20 -0
  15. package/src/components/Blocks/DemoSitesExplorer/images/icon-depth.png +0 -0
  16. package/src/components/Blocks/DemoSitesExplorer/images/icon-light.png +0 -0
  17. package/src/components/Blocks/DemoSitesExplorer/images/search.svg +3 -0
  18. package/src/components/Blocks/DemoSitesExplorer/index.js +16 -0
  19. package/src/components/Blocks/DemoSitesExplorer/mockJsdom.js +8 -0
  20. package/src/components/Blocks/DemoSitesExplorer/styles.less +376 -0
  21. package/src/components/Blocks/DemoSitesExplorer/utils.js +193 -0
  22. package/src/components/Blocks/DemoSitesExplorer/utils.test.jsZ +63 -0
  23. package/src/components/index.js +1 -0
  24. package/src/components/theme/DatabaseItemView/DatabaseItemView.jsx +0 -1
  25. package/src/express-middleware.js +37 -0
  26. package/src/index.js +13 -12
  27. package/theme/extras/print.less +64 -0
  28. package/theme/globals/site.overrides +11 -6
  29. package/theme/globals/site.variables +1 -0
  30. package/src/components/Blocks/ContextNavigation/Accordion.jsx +0 -85
  31. package/src/components/Blocks/ContextNavigation/Accordion.test.jsx +0 -106
  32. package/src/components/Blocks/ContextNavigation/AccordionContent.jsx +0 -66
  33. package/src/components/Blocks/ContextNavigation/ContextNavigation.jsx +0 -41
  34. package/src/components/Blocks/ContextNavigation/Edit.jsx +0 -41
  35. package/src/components/Blocks/ContextNavigation/Edit.test.jsx +0 -71
  36. package/src/components/Blocks/ContextNavigation/View.jsx +0 -42
  37. package/src/components/Blocks/ContextNavigation/View.test.jsx +0 -71
  38. package/src/components/Blocks/ContextNavigation/index.js +0 -25
  39. package/src/components/Blocks/ContextNavigation/schema.js +0 -43
  40. package/src/components/Blocks/ContextNavigation/styles.less +0 -65
package/src/index.js CHANGED
@@ -14,7 +14,6 @@ import installMsfdDataExplorerBlock from './components/Blocks/MsfdDataExplorerBl
14
14
  import { breadcrumb, localnavigation } from './reducers';
15
15
  import customBlockTemplates from '@eeacms/volto-marine-policy/components/Blocks/CustomBlockTemplates/customBlockTemplates';
16
16
  import TextAlignWidget from './components/Widgets/TextAlign';
17
- import installContextNavigation from './components/Blocks/ContextNavigation';
18
17
  import './slate-styles.less';
19
18
 
20
19
  import installSearchEngine from './search';
@@ -29,7 +28,7 @@ import { linkDeserializer } from '@plone/volto-slate/editor/plugins/AdvancedLink
29
28
  import LinkEditSchema from '@plone/volto-slate/editor/plugins/AdvancedLink/schema';
30
29
  import { getBlocks } from '@plone/volto/helpers';
31
30
  import { defineMessages } from 'react-intl'; // , defineMessages
32
-
31
+ import installDemoSitesExplorer from './components/Blocks/DemoSitesExplorer';
33
32
  import marineLogo from '@eeacms/volto-marine-policy/../theme/assets/images/Header/wise-marine-logo.svg';
34
33
  import marineLogoWhite from '@eeacms/volto-marine-policy/../theme/assets/images/Header/wise-marine-logo-white.svg';
35
34
  import eeaWhiteLogo from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/logo/eea-logo-white.svg';
@@ -110,6 +109,11 @@ const applyConfig = (config) => {
110
109
  localnavigation,
111
110
  };
112
111
 
112
+ if (__SERVER__) {
113
+ const installExpressMiddleware = require('./express-middleware').default;
114
+ config = installExpressMiddleware(config);
115
+ }
116
+
113
117
  config.widgets.widget.text_align = TextAlignWidget;
114
118
  // check if it breaks the 'theme' field in volto-tabs-block in the 'horizontal carousel' layout
115
119
  // We have a 'theme' field in the wise catalogue metadata (CatalogueMetadata)
@@ -127,12 +131,12 @@ const applyConfig = (config) => {
127
131
  };
128
132
 
129
133
  // on home contextNavigation should return false
130
- config.blocks.blocksConfig.contextNavigation = {
131
- ...config.blocks.blocksConfig.contextNavigation,
132
- blockHasValue: (data) => {
133
- return data.pathname !== '/';
134
- },
135
- };
134
+ // config.blocks.blocksConfig.contextNavigation = {
135
+ // ...config.blocks.blocksConfig.contextNavigation,
136
+ // blockHasValue: (data) => {
137
+ // return data.pathname !== '/';
138
+ // },
139
+ // };
136
140
  config.blocks.blocksConfig.listing = {
137
141
  ...config.blocks.blocksConfig.listing,
138
142
  variations: [
@@ -269,9 +273,6 @@ const applyConfig = (config) => {
269
273
 
270
274
  config.settings.openExternalLinkInNewTab = true;
271
275
 
272
- if (config.blocks.blocksConfig.contextNavigation)
273
- config.blocks.blocksConfig.contextNavigation.restricted = false;
274
-
275
276
  config.settings.pluggableStyles = [
276
277
  ...(config.settings.pluggableStyles || []),
277
278
  {
@@ -519,7 +520,7 @@ const applyConfig = (config) => {
519
520
  const final = [
520
521
  installMsfdDataExplorerBlock,
521
522
  installSearchEngine,
522
- installContextNavigation,
523
+ installDemoSitesExplorer,
523
524
  ].reduce((acc, apply) => apply(acc), config);
524
525
 
525
526
  return final;
@@ -0,0 +1,64 @@
1
+ @media print {
2
+ a {
3
+ word-break: break-word;
4
+ }
5
+
6
+ .logo {
7
+ margin-left: 0 !important;
8
+ }
9
+
10
+ .block.call-to-action,
11
+ .block.context-navigation {
12
+ display: none;
13
+ }
14
+
15
+ .content-box-inner {
16
+ padding: 25px !important;
17
+
18
+ .column-blocks-wrapper {
19
+ margin-bottom: 0;
20
+ }
21
+ }
22
+
23
+ [class~='container'] > [class*='styled-'] {
24
+ margin-top: 0.5rem;
25
+ margin-bottom: 0.5rem;
26
+ }
27
+
28
+ .ui.section.divider {
29
+ margin-top: 0.5rem;
30
+ margin-bottom: 0.5rem;
31
+ }
32
+
33
+ .ui.grid > .row > [class*='twelve wide'].column,
34
+ .ui.grid > .column.row > [class*='twelve wide'].column,
35
+ .ui.grid > [class*='twelve wide'].column,
36
+ .ui.column.grid > [class*='twelve wide'].column {
37
+ padding: 0 25px !important;
38
+ }
39
+
40
+ .visualization,
41
+ .content-box-inner,
42
+ .embed-map,
43
+ .embed-tableau,
44
+ .embed-map-visualization,
45
+ .embed-visualization {
46
+ break-inside: avoid;
47
+ }
48
+ }
49
+
50
+ @media print and (min-width: 600px) {
51
+ .columns-view {
52
+ .ui.grid > .row > [class*='four wide computer'].column,
53
+ .ui.grid > .column.row > [class*='four wide computer'].column,
54
+ .ui.grid > [class*='four wide computer'].column,
55
+ .ui.column.grid > [class*='four wide computer'].column {
56
+ width: @fourWide !important;
57
+ }
58
+ }
59
+ }
60
+
61
+ @page {
62
+ margin-top: 15mm;
63
+ margin-bottom: 15mm;
64
+ }
@@ -2,15 +2,16 @@
2
2
  Global Overrides
3
3
  *******************************/
4
4
 
5
+ @import '../extras/print';
6
+
5
7
  .documentFirstHeading {
6
8
  border-bottom: none !important;
7
-
9
+
8
10
  &::before {
9
11
  border-bottom: none !important;
10
12
  }
11
13
  }
12
14
 
13
-
14
15
  #page-document > blockquote {
15
16
  margin-right: revert !important;
16
17
  margin-left: revert !important;
@@ -200,7 +201,9 @@ body.view-viewview .full {
200
201
  }
201
202
  }
202
203
 
203
- body.view-viewview.has-toolbar-collapsed:not(.has-sidebar):not(.has-sidebar-collapsed) {
204
+ body.view-viewview.has-toolbar-collapsed:not(.has-sidebar):not(
205
+ .has-sidebar-collapsed
206
+ ) {
204
207
  .full > div {
205
208
  width: calc(100% - @collapsedToolbarWidth) !important;
206
209
  }
@@ -233,7 +236,9 @@ body.view-viewview .full {
233
236
 
234
237
  @media screen and (max-width: @largestMobileScreen) {
235
238
  body.view-viewview.has-toolbar:not(.has-sidebar):not(.has-sidebar-collapsed),
236
- body.view-viewview.has-toolbar-collapsed:not(.has-sidebar):not(.has-sidebar-collapsed) {
239
+ body.view-viewview.has-toolbar-collapsed:not(.has-sidebar):not(
240
+ .has-sidebar-collapsed
241
+ ) {
237
242
  .full > div {
238
243
  width: 100% !important;
239
244
  }
@@ -373,8 +378,8 @@ body.custom-page-header .breadcrumbs {
373
378
  }
374
379
 
375
380
  @media only screen and (max-height: @tabletBreakpoint) {
376
- #toolbar .toolbar-content.show {
381
+ #toolbar .toolbar-content.show {
377
382
  max-height: 700px !important;
378
383
  z-index: 5;
384
+ }
379
385
  }
380
- }
@@ -1,6 +1,7 @@
1
1
  /*******************************
2
2
  Site Settings
3
3
  *******************************/
4
+
4
5
  @import '../extras/mixins';
5
6
 
6
7
  /*-------------------
@@ -1,85 +0,0 @@
1
- import React from 'react';
2
- import { Accordion as SemanticAccordion, Icon } from 'semantic-ui-react';
3
- import AccordionContent from './AccordionContent';
4
- import { useHistory } from 'react-router-dom';
5
-
6
- const Accordion = (props) => {
7
- const { items = {}, curent_location, activeMenu, data = {} } = props;
8
- const [currentIndex, setIndex] = React.useState(activeMenu ?? 0);
9
- const [showChildren, setShowChildren] = React.useState(false);
10
- const history = useHistory();
11
-
12
- const handleClick = (e, item) => {
13
- let itemUrl = '/' + item['@id'].split('/').slice(3).join('/');
14
- history.push(itemUrl);
15
- };
16
-
17
- const handleIconClick = (e, index) => {
18
- e.stopPropagation();
19
- const newIndex = currentIndex === index ? -1 : index;
20
- setIndex(newIndex);
21
- };
22
-
23
- return (
24
- <>
25
- <div className="context-navigation-header">{data?.title}</div>
26
- {items.map((item, index) => {
27
- const { id } = item;
28
- const active = currentIndex === index;
29
-
30
- return (
31
- <SemanticAccordion id={id} key={index} className="secondary">
32
- <SemanticAccordion.Title
33
- role="button"
34
- tabIndex={0}
35
- active={activeMenu === index}
36
- aria-expanded={activeMenu === index}
37
- index={index}
38
- onClick={(e) => {
39
- handleClick(e, item);
40
- }}
41
- onKeyDown={(e) => {
42
- if (e.keyCode === 13 || e.keyCode === 32) {
43
- e.preventDefault();
44
- handleClick(e, item);
45
- }
46
- }}
47
- >
48
- <span className="item-title">{item.title}</span>
49
- {active && showChildren ? (
50
- <Icon
51
- className="ri-arrow-up-s-line"
52
- onClick={(e) => {
53
- handleIconClick(e, index);
54
- }}
55
- />
56
- ) : showChildren ? (
57
- <Icon
58
- className="ri-arrow-down-s-line"
59
- onClick={(e) => {
60
- handleIconClick(e, index);
61
- }}
62
- />
63
- ) : null}
64
- </SemanticAccordion.Title>
65
- <SemanticAccordion.Content active={active && showChildren}>
66
- <AccordionContent
67
- curent_location={curent_location}
68
- key={index}
69
- main={{
70
- title: item.title,
71
- href: item['@id'],
72
- url: item.url,
73
- }}
74
- data={data}
75
- setShowChildren={setShowChildren}
76
- />
77
- </SemanticAccordion.Content>
78
- </SemanticAccordion>
79
- );
80
- })}
81
- </>
82
- );
83
- };
84
-
85
- export default Accordion;
@@ -1,106 +0,0 @@
1
- import React from 'react';
2
- import { MemoryRouter } from 'react-router-dom';
3
- import configureStore from 'redux-mock-store';
4
- import { render, fireEvent, waitFor, screen } from '@testing-library/react';
5
- import '@testing-library/jest-dom/extend-expect';
6
- import { Provider } from 'react-intl-redux';
7
- import Accordion from './Accordion';
8
-
9
- const mockStore = configureStore();
10
-
11
- describe('RASTAccordion', () => {
12
- let store;
13
- let data;
14
-
15
- beforeEach(() => {
16
- store = mockStore({
17
- userSession: { token: '1234' },
18
- intl: {
19
- locale: 'en',
20
- messages: {},
21
- },
22
- content: {
23
- subrequests: {},
24
- },
25
- });
26
-
27
- data = {
28
- items: [
29
- {
30
- id: 'item1',
31
- title: 'Item 1',
32
- '@id': '/item1',
33
- '@type': 'Folder',
34
- href: '/item1-href',
35
- },
36
- {
37
- id: 'item2',
38
- title: 'Item 2',
39
- '@id': '/item2',
40
- '@type': 'Folder',
41
- href: '/item2-href',
42
- },
43
- ],
44
- activeMenu: 1,
45
- curent_location: '/item1-href',
46
- data: { title: "Accordion's Title" },
47
- };
48
- });
49
-
50
- it('renders the component with initial data', () => {
51
- const { getByText } = render(
52
- <Provider store={store}>
53
- <MemoryRouter>
54
- <Accordion {...data} />
55
- </MemoryRouter>
56
- </Provider>,
57
- );
58
-
59
- expect(getByText('Item 1')).toBeInTheDocument();
60
- expect(getByText('Item 2')).toBeInTheDocument();
61
- });
62
-
63
- it('navigates to correct path on item click', () => {
64
- const { getByText, history } = render(
65
- <Provider store={store}>
66
- <MemoryRouter>
67
- <Accordion {...data} />
68
- </MemoryRouter>
69
- </Provider>,
70
- );
71
-
72
- fireEvent.click(getByText('Item 1'));
73
- waitFor(() => expect(history.location.pathname).toBe('/item1'));
74
- });
75
-
76
- it('clicks on accordion items', () => {
77
- const { container } = render(
78
- <Provider store={store}>
79
- <MemoryRouter>
80
- <Accordion {...data} />
81
- </MemoryRouter>
82
- </Provider>,
83
- );
84
-
85
- screen.debug();
86
-
87
- // check that item1 is expanded by default
88
- expect(container.querySelector('#item2 .active.title')).toBeInTheDocument();
89
-
90
- const item1 = container.querySelector('#item1');
91
- fireEvent.click(item1);
92
- });
93
-
94
- it('responds to keyboard events', () => {
95
- const { getByText } = render(
96
- <Provider store={store}>
97
- <MemoryRouter>
98
- <Accordion {...data} />
99
- </MemoryRouter>
100
- </Provider>,
101
- );
102
-
103
- fireEvent.keyDown(getByText('Item 1'), { keyCode: 13 }); // Enter key
104
- waitFor(() => expect(getByText('Content of Item 1')).toBeVisible());
105
- });
106
- });
@@ -1,66 +0,0 @@
1
- import React from 'react';
2
- import { List } from 'semantic-ui-react';
3
- import { Link } from 'react-router-dom';
4
- import { compose } from 'redux';
5
- import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers';
6
- import { useChildren } from './View';
7
-
8
- const AccordionContent = (props) => {
9
- const {
10
- main,
11
- curent_location,
12
- data: { types = [] },
13
- setShowChildren,
14
- } = props;
15
- const location = main.url;
16
-
17
- // React.useEffect(() => {
18
- // const action = getContent(location, null, location);
19
- // dispatch(action);
20
- // }, [location, dispatch]);
21
-
22
- // items = useSelector(
23
- // (state) => state.content?.subrequests?.[location]?.data?.items || [],
24
- // );
25
- const items = useChildren(location);
26
-
27
- React.useEffect(() => {
28
- const filteredItems = items.filter((item) =>
29
- types.length ? types.includes(item['@type']) : item,
30
- );
31
- if (filteredItems.length) setShowChildren(true);
32
- }, [items, setShowChildren, types]);
33
-
34
- return (
35
- <div className="dataset-content">
36
- <div>
37
- {items.length
38
- ? items
39
- .filter((item) =>
40
- types.length ? types.includes(item['@type']) : item,
41
- )
42
- .map((item) => (
43
- <List.Item
44
- key={item.id}
45
- className={`${
46
- item['@id'].endsWith(curent_location.pathname)
47
- ? 'active'
48
- : ''
49
- }`}
50
- >
51
- <List.Content>
52
- <div className="dataset-item">
53
- <Link to={flattenToAppURL(getBaseUrl(item['@id']))}>
54
- {item.title}
55
- </Link>
56
- </div>
57
- </List.Content>
58
- </List.Item>
59
- ))
60
- : null}
61
- </div>
62
- </div>
63
- );
64
- };
65
-
66
- export default compose()(AccordionContent);
@@ -1,41 +0,0 @@
1
- import React from 'react';
2
- import { compose } from 'redux';
3
-
4
- import Accordion from './Accordion';
5
- import { useLocation } from 'react-router-dom';
6
-
7
- /**
8
- * A navigation slot implementation, similar to the classic Plone navigation
9
- * portlet. It uses the same API, so the options are similar to
10
- * INavigationPortlet
11
- */
12
- export function ContextNavigationComponent(props) {
13
- const { items, data } = props;
14
- let activeMenu = null;
15
-
16
- const curent_location = useLocation();
17
- for (let i = 0; i < items.length; i++) {
18
- let itemUrl = '/' + items[i]['@id'].split('/').slice(3).join('/');
19
- items[i].is_active = false;
20
- if (curent_location.pathname.includes(itemUrl)) {
21
- activeMenu = i;
22
- items[i].is_active = true;
23
- }
24
- }
25
-
26
- return (
27
- <>
28
- {items.length ? (
29
- <Accordion
30
- items={items}
31
- curent_location={curent_location}
32
- activeMenu={activeMenu}
33
- data={data}
34
- />
35
- ) : null}
36
- </>
37
- );
38
- }
39
-
40
- // withContentNavigation
41
- export default compose()(ContextNavigationComponent);
@@ -1,41 +0,0 @@
1
- import React from 'react';
2
- import { useSelector } from 'react-redux';
3
- import { SidebarPortal } from '@plone/volto/components';
4
- import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
5
-
6
- import View from './View';
7
- import schema from './schema';
8
-
9
- export default function Edit(props) {
10
- const {
11
- block,
12
- data = {},
13
- onChangeBlock,
14
- selected,
15
- id,
16
- formData = {},
17
- } = props;
18
- const contentTypes = useSelector((state) => state?.types.types);
19
- const blockSchema = schema({ formData, data, contentTypes });
20
-
21
- return (
22
- <div>
23
- <View data={data} id={id} mode="edit" />
24
- <SidebarPortal selected={selected}>
25
- <BlockDataForm
26
- block={block}
27
- title={blockSchema.title}
28
- schema={blockSchema}
29
- onChangeField={(id, value) => {
30
- onChangeBlock(block, {
31
- ...data,
32
- [id]: value,
33
- });
34
- }}
35
- onChangeBlock={onChangeBlock}
36
- formData={data}
37
- />
38
- </SidebarPortal>
39
- </div>
40
- );
41
- }
@@ -1,71 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import { Provider } from 'react-intl-redux';
4
- import configureStore from 'redux-mock-store';
5
- import ContextNavigationEdit from './Edit';
6
- import { Router } from 'react-router-dom';
7
- import { createMemoryHistory } from 'history';
8
- import '@testing-library/jest-dom/extend-expect';
9
-
10
- const mockStore = configureStore();
11
-
12
- jest.mock('@plone/volto/components', () => ({
13
- SidebarPortal: ({ children }) => (
14
- <div>
15
- <div>SidebarPortal</div>
16
- {children}
17
- </div>
18
- ),
19
- }));
20
-
21
- jest.mock('@plone/volto/components/manage/Form/BlockDataForm', () => {
22
- return {
23
- __esModule: true,
24
- default: ({ params }) => {
25
- return <div>BlockDataForm {params}</div>;
26
- },
27
- };
28
- });
29
-
30
- const store = mockStore({
31
- userSession: { token: '1234' },
32
- intl: {
33
- locale: 'en',
34
- messages: {},
35
- },
36
- content: {
37
- subrequests: {},
38
- },
39
- types: {
40
- types: [],
41
- },
42
- });
43
-
44
- describe('ContextNavigationEdit', () => {
45
- it('renders corectly', () => {
46
- const history = createMemoryHistory();
47
- const { getByText } = render(
48
- <Provider store={store}>
49
- <Router history={history}>
50
- <ContextNavigationEdit />
51
- </Router>
52
- </Provider>,
53
- );
54
-
55
- expect(getByText('SidebarPortal')).toBeInTheDocument();
56
- });
57
-
58
- it('renders corectly', () => {
59
- const history = createMemoryHistory();
60
- const { getByText } = render(
61
- <Provider store={store}>
62
- <Router history={history}>
63
- <ContextNavigationEdit selected={true} onChangeBlock={() => {}} />
64
- </Router>
65
- </Provider>,
66
- );
67
-
68
- // expect(getByText('InlineForm')).toBeInTheDocument();
69
- expect(getByText('SidebarPortal')).toBeInTheDocument();
70
- });
71
- });
@@ -1,42 +0,0 @@
1
- import React from 'react';
2
- import './styles.less';
3
- import ContextNavigation from './ContextNavigation';
4
- import { useDispatch, useSelector } from 'react-redux';
5
- import { getContent } from '@plone/volto/actions';
6
-
7
- export function useChildren(location) {
8
- const dispatch = useDispatch();
9
- React.useEffect(() => {
10
- const action = getContent(location, null, location);
11
- dispatch(action);
12
- }, [location, dispatch]);
13
-
14
- const items = useSelector(
15
- (state) => state.content.subrequests?.[location]?.data?.items || [],
16
- );
17
- return items;
18
- }
19
-
20
- export default function View(props) {
21
- const { data } = props;
22
- let root_path = data?.root_path;
23
- if (typeof root_path === 'undefined') {
24
- root_path = '/';
25
- }
26
- let items = useChildren(root_path);
27
- if (root_path === '/') {
28
- items = [];
29
- }
30
-
31
- return (
32
- <div className="block rast-block">
33
- <ContextNavigation
34
- items={items}
35
- location={{
36
- pathname: root_path,
37
- }}
38
- data={data}
39
- />
40
- </div>
41
- );
42
- }