@eeacms/volto-cca-policy 1.0.36 → 1.0.38

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 (43) hide show
  1. package/CHANGELOG.md +40 -14
  2. package/locales/bg/LC_MESSAGES/volto.po +30 -9
  3. package/locales/cs/LC_MESSAGES/volto.po +30 -9
  4. package/locales/da/LC_MESSAGES/volto.po +30 -9
  5. package/locales/de/LC_MESSAGES/volto.po +30 -9
  6. package/locales/el/LC_MESSAGES/volto.po +30 -9
  7. package/locales/en/LC_MESSAGES/volto.po +30 -9
  8. package/locales/es/LC_MESSAGES/volto.po +30 -9
  9. package/locales/et/LC_MESSAGES/volto.po +30 -9
  10. package/locales/fi/LC_MESSAGES/volto.po +30 -9
  11. package/locales/fr/LC_MESSAGES/volto.po +30 -9
  12. package/locales/ga/LC_MESSAGES/volto.po +30 -9
  13. package/locales/hr/LC_MESSAGES/volto.po +30 -9
  14. package/locales/hu/LC_MESSAGES/volto.po +30 -9
  15. package/locales/is/LC_MESSAGES/volto.po +30 -9
  16. package/locales/it/LC_MESSAGES/volto.po +30 -9
  17. package/locales/lt/LC_MESSAGES/volto.po +30 -9
  18. package/locales/lv/LC_MESSAGES/volto.po +30 -9
  19. package/locales/mt/LC_MESSAGES/volto.po +30 -9
  20. package/locales/nl/LC_MESSAGES/volto.po +30 -9
  21. package/locales/nn/LC_MESSAGES/volto.po +30 -9
  22. package/locales/pl/LC_MESSAGES/volto.po +30 -9
  23. package/locales/pt/LC_MESSAGES/volto.po +30 -9
  24. package/locales/ro/LC_MESSAGES/volto.po +30 -9
  25. package/locales/sk/LC_MESSAGES/volto.po +30 -9
  26. package/locales/sl/LC_MESSAGES/volto.po +30 -9
  27. package/locales/sv/LC_MESSAGES/volto.po +30 -9
  28. package/locales/tr/LC_MESSAGES/volto.po +30 -9
  29. package/locales/volto.pot +22 -1
  30. package/package.json +1 -1
  31. package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueMapState.jsx +72 -0
  32. package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueMapState.test.jsx +46 -0
  33. package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueMapView.jsx +20 -1
  34. package/src/components/Search/NavigatorCatalogue/NavigatorCatalogueMapView.test.jsx +88 -0
  35. package/src/components/Search/NavigatorCatalogue/NavigatorGeographicCoverageFacet.jsx +47 -39
  36. package/src/components/Search/NavigatorCatalogue/NavigatorGeographicCoverageFacet.test.jsx +65 -1
  37. package/src/components/Search/NavigatorCatalogue/utils.js +24 -0
  38. package/src/components/Search/NavigatorGuide/NavigatorGuideContentView.test.jsx +67 -0
  39. package/src/components/theme/CompareTools/CompareToolsView.test.jsx +59 -0
  40. package/src/components/theme/Footer/Footer.jsx +137 -0
  41. package/src/customizations/volto/components/theme/App/App.jsx +1 -1
  42. package/src/messages.js +20 -0
  43. package/theme/globals/navigator.less +79 -1
@@ -3,7 +3,9 @@ import {
3
3
  getColorForCount,
4
4
  normalizeCountryName,
5
5
  buildCountryCounts,
6
+ clearMapIncompatibleGeographicFilters,
6
7
  filterCountryCounts,
8
+ hasMapIncompatibleGeographicSelection,
7
9
  mapLegendItems,
8
10
  setCountryFilter,
9
11
  } from './utils';
@@ -185,6 +187,92 @@ describe('filterCountryCounts', () => {
185
187
  });
186
188
  });
187
189
 
190
+ describe('hasMapIncompatibleGeographicSelection', () => {
191
+ it('allows no geographic selection and country selections', () => {
192
+ expect(hasMapIncompatibleGeographicSelection()).toBe(false);
193
+ expect(hasMapIncompatibleGeographicSelection([])).toBe(false);
194
+ expect(
195
+ hasMapIncompatibleGeographicSelection([
196
+ {
197
+ field: 'cca_geographic_countries.keyword',
198
+ values: [],
199
+ type: 'any',
200
+ },
201
+ ]),
202
+ ).toBe(false);
203
+ expect(
204
+ hasMapIncompatibleGeographicSelection([
205
+ {
206
+ field: 'cca_geographic_countries.keyword',
207
+ values: ['Germany'],
208
+ type: 'any',
209
+ },
210
+ ]),
211
+ ).toBe(false);
212
+ });
213
+
214
+ it('rejects Global, Europe, and transnational selections', () => {
215
+ expect(
216
+ hasMapIncompatibleGeographicSelection([
217
+ {
218
+ field: 'cca_navigator_geographic_scope',
219
+ values: ['Global', 'Europe'],
220
+ type: 'any',
221
+ },
222
+ {
223
+ field: 'cca_geographic_transnational_region.keyword',
224
+ values: ['Alpine Space'],
225
+ type: 'any',
226
+ },
227
+ ]),
228
+ ).toBe(true);
229
+ });
230
+ });
231
+
232
+ describe('clearMapIncompatibleGeographicFilters', () => {
233
+ it('clears only map-incompatible geographic filters', () => {
234
+ const searchContext = {
235
+ filters: [
236
+ {
237
+ field: 'cca_navigator_geographic_scope',
238
+ values: ['Europe'],
239
+ type: 'any',
240
+ },
241
+ {
242
+ field: 'cca_geographic_transnational_region.keyword',
243
+ values: ['Alpine Space'],
244
+ type: 'any',
245
+ },
246
+ {
247
+ field: 'cca_geographic_countries.keyword',
248
+ values: ['Germany'],
249
+ type: 'any',
250
+ },
251
+ {
252
+ field: 'cca_climate_impacts.keyword',
253
+ values: ['Flooding'],
254
+ type: 'any',
255
+ },
256
+ ],
257
+ removeFilter: jest.fn(),
258
+ };
259
+
260
+ clearMapIncompatibleGeographicFilters(searchContext);
261
+
262
+ expect(searchContext.removeFilter).toHaveBeenCalledTimes(2);
263
+ expect(searchContext.removeFilter).toHaveBeenCalledWith(
264
+ 'cca_navigator_geographic_scope',
265
+ null,
266
+ 'any',
267
+ );
268
+ expect(searchContext.removeFilter).toHaveBeenCalledWith(
269
+ 'cca_geographic_transnational_region.keyword',
270
+ null,
271
+ 'any',
272
+ );
273
+ });
274
+ });
275
+
188
276
  describe('mapLegendItems', () => {
189
277
  it('has 5 legend items', () => {
190
278
  expect(mapLegendItems).toHaveLength(5);
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { Button, Card, Icon } from 'semantic-ui-react';
3
3
  import { defineMessages, useIntl } from 'react-intl';
4
4
  import { Term } from '@eeacms/search/components';
5
- import { useSearchContext } from '@eeacms/search/lib/hocs';
5
+ import { useSearchContext, useViews } from '@eeacms/search/lib/hocs';
6
6
  import { markSelectedFacetValuesFromFilters } from '@eeacms/search/lib/search/helpers';
7
7
  import {
8
8
  CHARACTERISATION_FIELD,
@@ -70,6 +70,7 @@ const FacetOption = ({ field, option, onChange }) => {
70
70
  };
71
71
 
72
72
  const ExpandableFacetGroup = ({
73
+ defaultOpen = false,
73
74
  field,
74
75
  id,
75
76
  label,
@@ -78,11 +79,11 @@ const ExpandableFacetGroup = ({
78
79
  onChange,
79
80
  }) => {
80
81
  const hasSelection = options.some(({ selected }) => selected);
81
- const [isOpen, setIsOpen] = React.useState(hasSelection);
82
+ const [isOpen, setIsOpen] = React.useState(hasSelection || defaultOpen);
82
83
 
83
84
  React.useEffect(() => {
84
- if (hasSelection) setIsOpen(true);
85
- }, [hasSelection]);
85
+ if (hasSelection || defaultOpen) setIsOpen(true);
86
+ }, [defaultOpen, hasSelection]);
86
87
 
87
88
  return (
88
89
  <div className="navigator-geographic-facet-group">
@@ -119,16 +120,10 @@ const ExpandableFacetGroup = ({
119
120
  );
120
121
  };
121
122
 
122
- const NavigatorActiveFilters = ({ filters, onRemove }) => {
123
+ const NavigatorActiveFilters = ({ fields, filters, onRemove }) => {
123
124
  const intl = useIntl();
124
- const geographicFields = [
125
- CHARACTERISATION_FIELD,
126
- TRANSNATIONAL_REGION_FIELD,
127
- COUNTRIES_FIELD,
128
- ];
129
125
  const activeFilters = filters.filter(
130
- ({ field, values }) =>
131
- geographicFields.includes(field) && values?.length > 0,
126
+ ({ field, values }) => fields.includes(field) && values?.length > 0,
132
127
  );
133
128
 
134
129
  if (!activeFilters.length) return null;
@@ -190,6 +185,8 @@ const NavigatorGeographicCoverageFacet = ({
190
185
  }) => {
191
186
  const intl = useIntl();
192
187
  const { addFilter, facets, filters = [], removeFilter } = useSearchContext();
188
+ const { activeViewId } = useViews();
189
+ const isMapView = activeViewId === 'map';
193
190
  const transnationalOptions = getFacetOptions(
194
191
  facets,
195
192
  filters,
@@ -219,6 +216,7 @@ const NavigatorGeographicCoverageFacet = ({
219
216
  }
220
217
 
221
218
  clearFilter(CHARACTERISATION_FIELD);
219
+ if (isMapView) clearFilter(TRANSNATIONAL_REGION_FIELD);
222
220
  addFilter(field, option.value, 'any');
223
221
  };
224
222
 
@@ -226,25 +224,28 @@ const NavigatorGeographicCoverageFacet = ({
226
224
  <fieldset
227
225
  className={`sui-facet searchlib-multiterm-facet navigator-geographic-facet ${className}`}
228
226
  >
229
- <div className="sui-multi-checkbox-facet navigator-geographic-facet-direct-options">
230
- {options.map((option) => {
231
- const directOption = {
232
- ...option,
233
- selected:
234
- option.selected || selectedDirectValues.includes(option.value),
235
- };
227
+ {!isMapView && (
228
+ <div className="sui-multi-checkbox-facet navigator-geographic-facet-direct-options">
229
+ {options.map((option) => {
230
+ const directOption = {
231
+ ...option,
232
+ selected:
233
+ option.selected || selectedDirectValues.includes(option.value),
234
+ };
236
235
 
237
- return (
238
- <FacetOption
239
- key={valueAsText(option.value)}
240
- field={CHARACTERISATION_FIELD}
241
- option={directOption}
242
- onChange={updateDirectFilter}
243
- />
244
- );
245
- })}
246
- </div>
236
+ return (
237
+ <FacetOption
238
+ key={valueAsText(option.value)}
239
+ field={CHARACTERISATION_FIELD}
240
+ option={directOption}
241
+ onChange={updateDirectFilter}
242
+ />
243
+ );
244
+ })}
245
+ </div>
246
+ )}
247
247
  <ExpandableFacetGroup
248
+ defaultOpen={isMapView}
248
249
  field={COUNTRIES_FIELD}
249
250
  id="navigator-geographic-countries"
250
251
  label={intl.formatMessage(countriesLabel)}
@@ -252,17 +253,24 @@ const NavigatorGeographicCoverageFacet = ({
252
253
  options={countryOptions}
253
254
  onChange={(option) => updateChildFilter(COUNTRIES_FIELD, option)}
254
255
  />
255
- <ExpandableFacetGroup
256
- field={TRANSNATIONAL_REGION_FIELD}
257
- id="navigator-geographic-transnational-regions"
258
- label={intl.formatMessage(transnationalRegionsLabel)}
259
- emptyMessage={intl.formatMessage(messages.noOptionsAvailable)}
260
- options={transnationalOptions}
261
- onChange={(option) =>
262
- updateChildFilter(TRANSNATIONAL_REGION_FIELD, option)
263
- }
264
- />
256
+ {!isMapView && (
257
+ <ExpandableFacetGroup
258
+ field={TRANSNATIONAL_REGION_FIELD}
259
+ id="navigator-geographic-transnational-regions"
260
+ label={intl.formatMessage(transnationalRegionsLabel)}
261
+ emptyMessage={intl.formatMessage(messages.noOptionsAvailable)}
262
+ options={transnationalOptions}
263
+ onChange={(option) =>
264
+ updateChildFilter(TRANSNATIONAL_REGION_FIELD, option)
265
+ }
266
+ />
267
+ )}
265
268
  <NavigatorActiveFilters
269
+ fields={[
270
+ CHARACTERISATION_FIELD,
271
+ TRANSNATIONAL_REGION_FIELD,
272
+ COUNTRIES_FIELD,
273
+ ]}
266
274
  filters={filters}
267
275
  onRemove={(field, value, type) => removeFilter(field, value, type)}
268
276
  />
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import '@testing-library/jest-dom';
3
3
  import { fireEvent, render, screen } from '@testing-library/react';
4
4
  import { IntlProvider } from 'react-intl';
5
- import { useSearchContext } from '@eeacms/search/lib/hocs';
5
+ import { useSearchContext, useViews } from '@eeacms/search/lib/hocs';
6
6
  import NavigatorGeographicCoverageFacet from './NavigatorGeographicCoverageFacet';
7
7
  import {
8
8
  CHARACTERISATION_FIELD,
@@ -15,9 +15,11 @@ jest.mock('@eeacms/search/components', () => ({
15
15
  }));
16
16
  jest.mock('@eeacms/search/lib/hocs', () => ({
17
17
  useSearchContext: jest.fn(),
18
+ useViews: jest.fn(),
18
19
  }));
19
20
 
20
21
  const renderFacet = ({
22
+ activeViewId = 'listing',
21
23
  detailedOptions = true,
22
24
  filters = [],
23
25
  onRemove = jest.fn(),
@@ -26,6 +28,8 @@ const renderFacet = ({
26
28
  const addFilter = jest.fn();
27
29
  const removeFilter = jest.fn();
28
30
 
31
+ useViews.mockReturnValue({ activeViewId });
32
+
29
33
  useSearchContext.mockReturnValue({
30
34
  addFilter,
31
35
  removeFilter,
@@ -190,4 +194,64 @@ describe('NavigatorGeographicCoverageFacet', () => {
190
194
  screen.getByRole('button', { name: 'Transnational regions' }),
191
195
  ).toBeEnabled();
192
196
  });
197
+
198
+ it('shows only countries in the map view', () => {
199
+ renderFacet({ activeViewId: 'map' });
200
+
201
+ expect(screen.queryByLabelText('Europe')).not.toBeInTheDocument();
202
+ expect(screen.queryByLabelText('Global')).not.toBeInTheDocument();
203
+ expect(screen.getByRole('button', { name: 'Countries' })).toHaveAttribute(
204
+ 'aria-expanded',
205
+ 'true',
206
+ );
207
+ expect(screen.getByLabelText('Germany')).toBeVisible();
208
+ expect(
209
+ screen.queryByRole('button', { name: 'Transnational regions' }),
210
+ ).not.toBeInTheDocument();
211
+ });
212
+
213
+ it('clears hidden geographic filters when selecting a country on the map', () => {
214
+ const { addFilter, removeFilter } = renderFacet({
215
+ activeViewId: 'map',
216
+ filters: [
217
+ {
218
+ field: TRANSNATIONAL_REGION_FIELD,
219
+ values: ['Alpine Space'],
220
+ type: 'any',
221
+ },
222
+ ],
223
+ });
224
+
225
+ fireEvent.click(screen.getByLabelText('Germany'));
226
+
227
+ expect(removeFilter).toHaveBeenCalledWith(
228
+ CHARACTERISATION_FIELD,
229
+ null,
230
+ 'any',
231
+ );
232
+ expect(removeFilter).toHaveBeenCalledWith(
233
+ TRANSNATIONAL_REGION_FIELD,
234
+ null,
235
+ 'any',
236
+ );
237
+ expect(addFilter).toHaveBeenCalledWith(COUNTRIES_FIELD, 'Germany', 'any');
238
+ });
239
+
240
+ it('shows hidden active geographic filters in the map view', () => {
241
+ renderFacet({
242
+ activeViewId: 'map',
243
+ detailedOptions: false,
244
+ filters: [
245
+ {
246
+ field: CHARACTERISATION_FIELD,
247
+ values: ['Europe'],
248
+ type: 'any',
249
+ },
250
+ ],
251
+ });
252
+
253
+ expect(screen.queryByLabelText('Europe')).not.toBeInTheDocument();
254
+ expect(screen.getByText('Active filters:')).toBeInTheDocument();
255
+ expect(screen.getByText('Europe')).toBeInTheDocument();
256
+ });
193
257
  });
@@ -61,6 +61,30 @@ export const filterCountryCounts = (countryCounts, filters) => {
61
61
  );
62
62
  };
63
63
 
64
+ const mapIncompatibleGeographicFields = new Set([
65
+ 'cca_navigator_geographic_scope',
66
+ 'cca_geographic_transnational_region.keyword',
67
+ ]);
68
+
69
+ export const hasMapIncompatibleGeographicSelection = (filters) =>
70
+ Boolean(
71
+ filters?.find(
72
+ ({ field, values }) =>
73
+ mapIncompatibleGeographicFields.has(field) && values?.length > 0,
74
+ ),
75
+ );
76
+
77
+ export const clearMapIncompatibleGeographicFilters = (searchContext) => {
78
+ searchContext?.filters
79
+ ?.filter(
80
+ ({ field, values }) =>
81
+ mapIncompatibleGeographicFields.has(field) && values?.length > 0,
82
+ )
83
+ .forEach(({ field, type = 'any' }) =>
84
+ searchContext.removeFilter(field, null, type),
85
+ );
86
+ };
87
+
64
88
  export const setCountryFilter = (searchContext, countryName) => {
65
89
  searchContext?.setFilter(
66
90
  'cca_geographic_countries.keyword',
@@ -268,3 +268,70 @@ describe('NavigatorGuideContentView climate hazards', () => {
268
268
  ).toHaveTextContent('Flooding');
269
269
  });
270
270
  });
271
+
272
+ describe('NavigatorGuideContentView actions', () => {
273
+ beforeEach(() => {
274
+ jest.clearAllMocks();
275
+ useSelector.mockReturnValue('en');
276
+ useSearchDriver.mockReturnValue(undefined);
277
+ useGuideFacetOptions.mockReturnValue({
278
+ 'cca_adaptation_sectors.keyword': ['Water'],
279
+ });
280
+ });
281
+
282
+ it('removes a selected option from the current step', () => {
283
+ const removeFilter = jest.fn();
284
+ useAtom.mockReturnValue([0, jest.fn()]);
285
+ useHistory.mockReturnValue({ push: jest.fn() });
286
+
287
+ renderGuide(
288
+ { title: 'Guide tool', href: '/tools/guide-tool' },
289
+ {
290
+ removeFilter,
291
+ facets: {
292
+ 'cca_adaptation_sectors.keyword': [
293
+ { data: [{ value: 'Water', count: 4 }] },
294
+ ],
295
+ },
296
+ },
297
+ );
298
+
299
+ fireEvent.click(screen.getByText('Water'));
300
+
301
+ expect(removeFilter).toHaveBeenCalledWith(
302
+ 'cca_adaptation_sectors.keyword',
303
+ 'Water',
304
+ 'all',
305
+ );
306
+ });
307
+
308
+ it('opens catalogue results with only supported Guide filters', () => {
309
+ const history = { push: jest.fn() };
310
+ useAtom.mockReturnValue([0, jest.fn()]);
311
+ useHistory.mockReturnValue(history);
312
+
313
+ renderGuide(
314
+ { title: 'Guide tool', href: '/tools/guide-tool' },
315
+ {
316
+ filters: [
317
+ {
318
+ field: 'cca_adaptation_sectors.keyword',
319
+ values: ['Water'],
320
+ type: 'all',
321
+ },
322
+ { field: 'unrelated.keyword', values: ['Ignored'], type: 'any' },
323
+ ],
324
+ },
325
+ );
326
+
327
+ fireEvent.click(screen.getByRole('button', { name: 'Skip to results' }));
328
+
329
+ expect(history.push).toHaveBeenCalledWith(
330
+ expect.objectContaining({
331
+ pathname: '/en/navigator/tool-catalogue',
332
+ search: expect.any(String),
333
+ }),
334
+ );
335
+ expect(history.push.mock.calls[0][0].search).not.toContain('unrelated');
336
+ });
337
+ });
@@ -12,6 +12,7 @@ import { useAtom } from 'jotai';
12
12
  import { useDispatch, useSelector } from 'react-redux';
13
13
  import { useHistory, useLocation } from 'react-router-dom';
14
14
  import CompareToolsView from './CompareToolsView';
15
+ import { exportComparisonTable } from '../../Search/NavigatorCatalogue/utils';
15
16
  import { fetchResultsByUid } from './utils';
16
17
 
17
18
  jest.mock('jotai', () => ({
@@ -62,6 +63,11 @@ jest.mock('./utils', () => ({
62
63
  getPathname: (url) => url?.split('?')[0] || '',
63
64
  }));
64
65
 
66
+ jest.mock('../../Search/NavigatorCatalogue/utils', () => ({
67
+ ...jest.requireActual('../../Search/NavigatorCatalogue/utils'),
68
+ exportComparisonTable: jest.fn(),
69
+ }));
70
+
65
71
  describe('CompareToolsView', () => {
66
72
  beforeEach(() => {
67
73
  jest.clearAllMocks();
@@ -246,4 +252,57 @@ describe('CompareToolsView', () => {
246
252
  expect(thumbnail.querySelector('img')).not.toBeInTheDocument();
247
253
  expect(thumbnail.querySelector('.ri-file-line')).toBeInTheDocument();
248
254
  });
255
+
256
+ it('removes a tool from the selection and URL', async () => {
257
+ const setSelectedTools = jest.fn();
258
+ const history = { push: jest.fn() };
259
+ useAtom.mockReturnValue([[], setSelectedTools]);
260
+ useHistory.mockReturnValue(history);
261
+
262
+ render(
263
+ <IntlProvider locale="en">
264
+ <CompareToolsView />
265
+ </IntlProvider>,
266
+ );
267
+ await screen.findByRole('table', { name: 'Compare tools' });
268
+
269
+ fireEvent.click(
270
+ screen.getByRole('button', {
271
+ name: 'Remove Tool one from comparison',
272
+ }),
273
+ );
274
+
275
+ const updateSelection = setSelectedTools.mock.calls[0][0];
276
+ expect(updateSelection([{ uid: 'one' }, { uid: 'two' }])).toEqual([
277
+ { uid: 'two' },
278
+ ]);
279
+ expect(history.push).toHaveBeenCalledWith({
280
+ pathname: '/en/navigator/compare',
281
+ search: '?uid=two',
282
+ hash: '',
283
+ state: {},
284
+ });
285
+ });
286
+
287
+ it('exports comparison data with only applicable table options', async () => {
288
+ render(
289
+ <IntlProvider locale="en">
290
+ <CompareToolsView />
291
+ </IntlProvider>,
292
+ );
293
+ await screen.findByRole('table', { name: 'Compare tools' });
294
+
295
+ fireEvent.click(screen.getByRole('button', { name: 'Export table' }));
296
+
297
+ expect(exportComparisonTable).toHaveBeenCalledTimes(1);
298
+ const [exportedTools] = exportComparisonTable.mock.calls[0];
299
+ expect(exportedTools[0].comparisonOptions).toEqual(
300
+ expect.objectContaining({
301
+ cca_type_of_outputs: ['Maps and graphs'],
302
+ cca_adaptation_support_cycle_step: [
303
+ 'Step 2: Assessing Climate Change Risks and Vulnerabilities',
304
+ ],
305
+ }),
306
+ );
307
+ });
249
308
  });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Policy Footer component.
3
+ * @module components/theme/Footer/Footer
4
+ */
5
+
6
+ import React from 'react';
7
+ import { useIntl } from 'react-intl';
8
+ import { useSelector, shallowEqual } from 'react-redux';
9
+ import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
10
+ import EEAFooter from '@eeacms/volto-eea-design-system/ui/Footer/Footer';
11
+ import config from '@plone/volto/registry';
12
+ import isArray from 'lodash/isArray';
13
+ import messages from '@eeacms/volto-cca-policy/messages';
14
+
15
+ const footerMessages = [
16
+ messages.sitemap,
17
+ messages.managedBy,
18
+ messages.exploreEnvironmentalInformationSystems,
19
+ messages.about,
20
+ messages.aboutUs,
21
+ messages.contact,
22
+ messages.contactUs,
23
+ messages.help,
24
+ messages.privacy,
25
+ messages.privacyStatement,
26
+ messages.accessibility,
27
+ messages.legalNotice,
28
+ messages.dashboard,
29
+ messages.login,
30
+ messages.cmsLogin,
31
+ ].reduce((result, message) => ({ ...result, [message.id]: message }), {});
32
+
33
+ const Footer = () => {
34
+ const intl = useIntl();
35
+ const { eea } = config.settings;
36
+ const {
37
+ footerActions,
38
+ copyrightActions,
39
+ socialActions,
40
+ contactActions,
41
+ contactExtraActions,
42
+ } = useSelector(
43
+ (state) => ({
44
+ footerActions: state.actions?.actions?.footer_actions,
45
+ copyrightActions: state.actions?.actions?.copyright_actions,
46
+ socialActions: state.actions?.actions?.social_actions,
47
+ contactActions: state.actions?.actions?.contact_actions,
48
+ contactExtraActions: state.actions?.actions?.contact_extra_actions,
49
+ }),
50
+ shallowEqual,
51
+ );
52
+
53
+ const translate = (text) =>
54
+ footerMessages[text] ? intl.formatMessage(footerMessages[text]) : text;
55
+
56
+ // ZMI > portal_actions > footer_actions
57
+ const actions = isArray(footerActions)
58
+ ? footerActions.map((action) => ({
59
+ title: translate(action.title),
60
+ url: flattenToAppURL(action.url),
61
+ }))
62
+ : eea.footerOpts.actions?.map((action) => ({
63
+ ...action,
64
+ title: translate(action.title),
65
+ }));
66
+
67
+ // ZMI > portal_actions > copyright_actions
68
+ const copyright = isArray(copyrightActions)
69
+ ? copyrightActions.map((action) => ({
70
+ title: translate(action.title),
71
+ site: translate(action.title),
72
+ url: flattenToAppURL(action.url),
73
+ }))
74
+ : eea.footerOpts.copyright?.map((action) => ({
75
+ ...action,
76
+ title: translate(action.title),
77
+ site: translate(action.site),
78
+ }));
79
+
80
+ // ZMI > portal_actions > social_actions
81
+ const social = isArray(socialActions)
82
+ ? socialActions.map((action) => ({
83
+ name: action.id,
84
+ icon: action.icon,
85
+ url: action.url,
86
+ }))
87
+ : eea.footerOpts.social;
88
+
89
+ // ZMI > portal_actions > contact_actions
90
+ const contacts = isArray(contactActions)
91
+ ? contactActions.map((action, idx) => ({
92
+ text: translate(action.title),
93
+ icon: action.icon,
94
+ url: flattenToAppURL(action.url),
95
+ children:
96
+ idx === 0
97
+ ? (contactExtraActions || []).map((child) => ({
98
+ text: translate(child.title),
99
+ icon: child.icon,
100
+ url: flattenToAppURL(child.url),
101
+ }))
102
+ : [],
103
+ }))
104
+ : eea.footerOpts.contacts?.map((contact) => ({
105
+ ...contact,
106
+ text: translate(contact.text),
107
+ children: contact.children?.map((child) => ({
108
+ ...child,
109
+ text: translate(child.text),
110
+ })),
111
+ }));
112
+
113
+ // Update options with actions from backend
114
+ const options = {
115
+ ...eea.footerOpts,
116
+ social,
117
+ contacts,
118
+ };
119
+
120
+ return (
121
+ <EEAFooter>
122
+ <EEAFooter.Header>
123
+ {translate(eea.footerOpts.logosHeader)}
124
+ </EEAFooter.Header>
125
+ <EEAFooter.SubFooter {...options} />
126
+ <EEAFooter.Header>{translate(eea.footerOpts.header)}</EEAFooter.Header>
127
+ <EEAFooter.SitesButton
128
+ buttonName={translate(eea.footerOpts.buttonName)}
129
+ hrefButton={eea.footerOpts.hrefButton}
130
+ />
131
+ <EEAFooter.Social {...options} />
132
+ <EEAFooter.Actions actions={actions} copyright={copyright} />
133
+ </EEAFooter>
134
+ );
135
+ };
136
+
137
+ export default Footer;
@@ -25,7 +25,7 @@ import { injectIntl } from 'react-intl';
25
25
  import Error from '@plone/volto/error';
26
26
 
27
27
  import Breadcrumbs from '@plone/volto/components/theme/Breadcrumbs/Breadcrumbs';
28
- import Footer from '@plone/volto/components/theme/Footer/Footer';
28
+ import Footer from '@eeacms/volto-cca-policy/components/theme/Footer/Footer';
29
29
  import Header from '@plone/volto/components/theme/Header/Header';
30
30
  import Icon from '@plone/volto/components/theme/Icon/Icon';
31
31
  import OutdatedBrowser from '@plone/volto/components/theme/OutdatedBrowser/OutdatedBrowser';