@eeacms/volto-clms-theme 1.1.38 → 1.1.40

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 (22) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/package.json +2 -1
  3. package/src/components/Blocks/CustomTemplates/VoltoSearchBlock/AccordionFacet.jsx +3 -3
  4. package/src/components/Blocks/CustomTemplates/VoltoSearchBlock/CheckboxTreeFacet.jsx +3 -3
  5. package/src/components/Blocks/CustomTemplates/VoltoSearchBlock/CheckboxTreeParentFacet.jsx +3 -3
  6. package/src/components/Blocks/CustomTemplates/VoltoSearchBlock/DoubleRangeFacet.jsx +3 -20
  7. package/src/components/Blocks/CustomTemplates/VoltoSearchBlock/DoubleRangeSpatialFacet.jsx +3 -21
  8. package/src/components/Blocks/CustomTemplates/VoltoSearchBlock/utils.js +36 -0
  9. package/src/components/CLMSDownloadCartView/CLMSDownloadCartView.jsx +0 -2
  10. package/src/components/CLMSDownloadCartView/conversion.js +1 -1
  11. package/src/components/CLMSProfileView/CLMSApiTokensView.jsx +1 -1
  12. package/src/components/CLMSProfileView/CLMSDeleteProfileView.jsx +1 -1
  13. package/src/components/CLMSProfileView/CLMSNewsletterSubscriberView.jsx +3 -3
  14. package/src/components/CLMSProfileView/CLMSProfileView.jsx +1 -1
  15. package/src/components/CLMSSubscriptionView/SubscriptionView.jsx +8 -4
  16. package/src/components/CLMSSubscriptionView/subscription_utils.js +1 -1
  17. package/src/components/CartIconCounter/CartIconCounter.jsx +190 -0
  18. package/src/components/CartIconCounter/CartIconCounter.test.jsx +141 -0
  19. package/src/customizations/volto/components/theme/Footer/Footer.jsx +35 -17
  20. package/src/customizations/volto/components/theme/Header/Header.jsx +4 -91
  21. package/theme/site/extras/custom.variables +3 -0
  22. package/theme/site/extras/styles.less +58 -41
package/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [1.1.40](https://github.com/eea/volto-clms-theme/compare/1.1.39...1.1.40) - 14 September 2023
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: cart addition modal text modifications [Ion Lizarazu - [`bf93cf5`](https://github.com/eea/volto-clms-theme/commit/bf93cf5f336cc1e905dd444b17799bd2841b1c38)]
12
+
13
+ #### :bug: Bug Fixes
14
+
15
+ - fix: useCase card style [Ion Lizarazu - [`6ac7e36`](https://github.com/eea/volto-clms-theme/commit/6ac7e360066b9f55614b59ea4a102a150c7f00ba)]
16
+
17
+ #### :hammer_and_wrench: Others
18
+
19
+ - s/CLMS portal/CLMS website [Mikel Larreategi - [`76a17d7`](https://github.com/eea/volto-clms-theme/commit/76a17d7f2ffca88c8841a85315bbac8d8324af0c)]
20
+ - remove warnings [Ion Lizarazu - [`531db38`](https://github.com/eea/volto-clms-theme/commit/531db382e69cef045486d0067788307abf10a8e4)]
21
+ - jest tests [Ion Lizarazu - [`b5e7409`](https://github.com/eea/volto-clms-theme/commit/b5e7409a24e80cebcfed79b467af437cde70f023)]
22
+ - add test [Ion Lizarazu - [`ab76914`](https://github.com/eea/volto-clms-theme/commit/ab76914e901ce9e31faa5f32762f3e13cd157dae)]
23
+ - mv CartIconCounter [Ion Lizarazu - [`8fa987f`](https://github.com/eea/volto-clms-theme/commit/8fa987fdf652912233eab4e78ec17a30a662ab73)]
24
+ ### [1.1.39](https://github.com/eea/volto-clms-theme/compare/1.1.38...1.1.39) - 12 September 2023
25
+
26
+ #### :hammer_and_wrench: Others
27
+
28
+ - remove duplications [Ion Lizarazu - [`80b0a43`](https://github.com/eea/volto-clms-theme/commit/80b0a435926157d2a5d9f0ea2416431356f3d7a4)]
29
+ - Add expanded by default option to the search block facets [Ion Lizarazu - [`f0a30f8`](https://github.com/eea/volto-clms-theme/commit/f0a30f868eca3ab6860e8465647178bdb33c9eb8)]
30
+ - replace newsletter with CLMS updates and modify some footer stuff [Ion Lizarazu - [`1f7255a`](https://github.com/eea/volto-clms-theme/commit/1f7255a81001de686e23c29b36e7f4d855fe2285)]
7
31
  ### [1.1.38](https://github.com/eea/volto-clms-theme/compare/1.1.37...1.1.38) - 7 September 2023
8
32
 
9
33
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.1.38",
3
+ "version": "1.1.40",
4
4
  "description": "volto-clms-theme: Volto theme for CLMS site",
5
5
  "main": "src/index.js",
6
6
  "author": "CodeSyntax for the European Environment Agency",
@@ -47,6 +47,7 @@
47
47
  "@eeacms/volto-tabs-block": "3.0.1",
48
48
  "@eeacms/volto-taxonomy": "3.0.1",
49
49
  "@fortawesome/fontawesome-svg-core": "1.2.35",
50
+ "@fortawesome/free-brands-svg-icons": "6.4.2",
50
51
  "@fortawesome/free-regular-svg-icons": "5.15.3",
51
52
  "@fortawesome/free-solid-svg-icons": "5.15.3",
52
53
  "@fortawesome/react-fontawesome": "0.1.14",
@@ -1,10 +1,10 @@
1
1
  import { Checkbox, List } from 'semantic-ui-react';
2
2
  import React from 'react';
3
3
  import {
4
- selectFacetSchemaEnhancer,
5
4
  selectFacetStateToValue,
6
5
  selectFacetValueToQuery,
7
6
  } from '@plone/volto/components/manage/Blocks/Search/components/base';
7
+ import { enhanceExpandedByDefault } from './utils.js';
8
8
 
9
9
  const AccordionFacet = (props) => {
10
10
  const {
@@ -17,7 +17,7 @@ const AccordionFacet = (props) => {
17
17
  typeName,
18
18
  } = props;
19
19
  const facetValue = value;
20
- var [open, setOpen] = React.useState(false);
20
+ var [open, setOpen] = React.useState(facet.expandedByDefault ?? false);
21
21
  function isChoiceValue(isChecked, choiceValue) {
22
22
  return isChecked ? choiceValue : null;
23
23
  }
@@ -92,7 +92,7 @@ const AccordionFacet = (props) => {
92
92
  );
93
93
  };
94
94
 
95
- AccordionFacet.schemaEnhancer = selectFacetSchemaEnhancer;
95
+ AccordionFacet.schemaEnhancer = enhanceExpandedByDefault;
96
96
  AccordionFacet.stateToValue = selectFacetStateToValue;
97
97
  AccordionFacet.valueToQuery = selectFacetValueToQuery;
98
98
  export default AccordionFacet;
@@ -5,11 +5,11 @@ import { Checkbox, List } from 'semantic-ui-react';
5
5
  import { structure_taxonomy_terms } from '@eeacms/volto-clms-theme/components';
6
6
 
7
7
  import {
8
- selectFacetSchemaEnhancer,
9
8
  selectFacetStateToValue,
10
9
  selectFacetValueToQuery,
11
10
  } from '@plone/volto/components/manage/Blocks/Search/components/base';
12
11
  import { checkAllChildren } from './utils';
12
+ import { enhanceExpandedByDefault } from './utils.js';
13
13
 
14
14
  const hasAllChildrensSelected = (value, childrens) => {
15
15
  var result = true;
@@ -40,7 +40,7 @@ const CheckboxTreeFacet = (props) => {
40
40
  const { facet, choices, onChange, value, typeName } = props;
41
41
 
42
42
  const facetValue = value;
43
- var [open, setOpen] = useState(false);
43
+ var [open, setOpen] = useState(facet.expandedByDefault ?? false);
44
44
  let options = [];
45
45
  if (choices?.length > 0) {
46
46
  options = structure_taxonomy_terms(choices);
@@ -161,7 +161,7 @@ const CheckboxListParent = ({ option, key, onChange, value, id }) => {
161
161
  );
162
162
  };
163
163
 
164
- CheckboxTreeFacet.schemaEnhancer = selectFacetSchemaEnhancer;
164
+ CheckboxTreeFacet.schemaEnhancer = enhanceExpandedByDefault;
165
165
  CheckboxTreeFacet.stateToValue = selectFacetStateToValue;
166
166
  CheckboxTreeFacet.valueToQuery = selectFacetValueToQuery;
167
167
  export default CheckboxTreeFacet;
@@ -5,11 +5,11 @@ import { Checkbox, List } from 'semantic-ui-react';
5
5
  import { structure_taxonomy_terms } from '@eeacms/volto-clms-theme/components';
6
6
 
7
7
  import {
8
- selectFacetSchemaEnhancer,
9
8
  selectFacetStateToValue,
10
9
  selectFacetValueToQuery,
11
10
  } from '@plone/volto/components/manage/Blocks/Search/components/base';
12
11
  import { checkAllChildren, uncheckOptionAndChildren } from './utils';
12
+ import { enhanceExpandedByDefault } from './utils.js';
13
13
 
14
14
  const hasAllChildrensSelected = (value, childrens) => {
15
15
  var result = true;
@@ -39,7 +39,7 @@ const Wrapper = ({ typeName, children }) => {
39
39
  const CheckboxTreeParentFacet = (props) => {
40
40
  const { facet, choices, onChange, value, typeName } = props;
41
41
  const facetValue = value;
42
- var [open, setOpen] = useState(false);
42
+ var [open, setOpen] = useState(facet.expandedByDefault ?? false);
43
43
  let options = [];
44
44
  if (choices?.length > 0) {
45
45
  options = structure_taxonomy_terms(choices);
@@ -153,7 +153,7 @@ const CheckboxListParent = ({ option, key, onChange, value, id }) => {
153
153
  );
154
154
  };
155
155
 
156
- CheckboxTreeParentFacet.schemaEnhancer = selectFacetSchemaEnhancer;
156
+ CheckboxTreeParentFacet.schemaEnhancer = enhanceExpandedByDefault;
157
157
  CheckboxTreeParentFacet.stateToValue = selectFacetStateToValue;
158
158
  CheckboxTreeParentFacet.valueToQuery = selectFacetValueToQuery;
159
159
  export default CheckboxTreeParentFacet;
@@ -8,30 +8,12 @@ import {
8
8
  selectFacetStateToValue,
9
9
  selectFacetValueToQuery,
10
10
  } from '@plone/volto/components/manage/Blocks/Search/components/base';
11
-
12
- const doubleRangeFacetSchemaEnhancer = ({ schema, formData }) => {
13
- // adds (enables) the 'multiple' field after the 'type' dropdown
14
- let { fields } = schema.fieldsets[0];
15
- const pos = fields.indexOf('type') + 1;
16
- fields = [
17
- ...fields.slice(0, pos),
18
- 'step',
19
- 'multiple',
20
- ...fields.slice(pos, fields.length),
21
- ];
22
-
23
- schema.properties = {
24
- ...schema.properties,
25
- step: { title: 'Step', type: 'number', default: 1 },
26
- };
27
- schema.fieldsets[0].fields = fields;
28
- return schema;
29
- };
11
+ import { doubleRangeFacetSchemaEnhancer } from './utils.js';
30
12
 
31
13
  const DoubleRangeFacet = (props) => {
32
14
  const { facet, choices, onChange, value } = props;
33
15
  const facetValue = value;
34
- var [open, setOpen] = React.useState(false);
16
+ var [open, setOpen] = React.useState(facet.expandedByDefault ?? false);
35
17
 
36
18
  const convertToRange = (values) => {
37
19
  return {
@@ -111,6 +93,7 @@ const DoubleRangeFacet = (props) => {
111
93
  };
112
94
 
113
95
  DoubleRangeFacet.schemaEnhancer = doubleRangeFacetSchemaEnhancer;
96
+
114
97
  DoubleRangeFacet.stateToValue = selectFacetStateToValue;
115
98
  DoubleRangeFacet.valueToQuery = selectFacetValueToQuery;
116
99
  export default DoubleRangeFacet;
@@ -8,25 +8,7 @@ import {
8
8
  selectFacetStateToValue,
9
9
  selectFacetValueToQuery,
10
10
  } from '@plone/volto/components/manage/Blocks/Search/components/base';
11
-
12
- const doubleRangeSpatialFacetSchemaEnhancer = ({ schema, formData }) => {
13
- // adds (enables) the 'multiple' field after the 'type' dropdown
14
- let { fields } = schema.fieldsets[0];
15
- const pos = fields.indexOf('type') + 1;
16
- fields = [
17
- ...fields.slice(0, pos),
18
- 'step',
19
- 'multiple',
20
- ...fields.slice(pos, fields.length),
21
- ];
22
-
23
- schema.properties = {
24
- ...schema.properties,
25
- step: { title: 'Step', type: 'number', default: 1 },
26
- };
27
- schema.fieldsets[0].fields = fields;
28
- return schema;
29
- };
11
+ import { doubleRangeFacetSchemaEnhancer } from './utils.js';
30
12
 
31
13
  const mToKm = (m) => {
32
14
  return m >= 1000 ? Math.round((m / 1000) * 10) / 10 + 'km' : m + 'm';
@@ -82,7 +64,7 @@ const convertToRange = (values, step) => {
82
64
  const DoubleRangeSpatialFacet = (props) => {
83
65
  const { facet, choices, onChange, value } = props;
84
66
  const facetValue = value;
85
- var [open, setOpen] = React.useState(false);
67
+ var [open, setOpen] = React.useState(facet.expandedByDefault ?? false);
86
68
 
87
69
  const startingValues = convertToRange(choices, facet?.step);
88
70
 
@@ -156,7 +138,7 @@ const DoubleRangeSpatialFacet = (props) => {
156
138
  );
157
139
  };
158
140
 
159
- DoubleRangeSpatialFacet.schemaEnhancer = doubleRangeSpatialFacetSchemaEnhancer;
141
+ DoubleRangeSpatialFacet.schemaEnhancer = doubleRangeFacetSchemaEnhancer;
160
142
  DoubleRangeSpatialFacet.stateToValue = selectFacetStateToValue;
161
143
  DoubleRangeSpatialFacet.valueToQuery = selectFacetValueToQuery;
162
144
  export default DoubleRangeSpatialFacet;
@@ -21,3 +21,39 @@ export const uncheckOptionAndChildren = (value, option) => {
21
21
  }
22
22
  });
23
23
  };
24
+
25
+ export const enhanceExpandedByDefault = ({ schema, formData }) => {
26
+ return expandedByDefault(schema);
27
+ };
28
+
29
+ export const doubleRangeFacetSchemaEnhancer = ({ schema, formData }) => {
30
+ // adds (enables) the 'multiple' field after the 'type' dropdown
31
+ let { fields } = schema.fieldsets[0];
32
+ const pos = fields.indexOf('type') + 1;
33
+ fields = [
34
+ ...fields.slice(0, pos),
35
+ 'step',
36
+ 'multiple',
37
+ ...fields.slice(pos, fields.length),
38
+ ];
39
+
40
+ schema.properties = {
41
+ ...schema.properties,
42
+ step: { title: 'Step', type: 'number', default: 1 },
43
+ };
44
+ schema.fieldsets[0].fields = fields;
45
+ return expandedByDefault(schema);
46
+ };
47
+
48
+ export const expandedByDefault = (schema) => {
49
+ schema.properties = {
50
+ ...schema.properties,
51
+ expandedByDefault: {
52
+ title: 'Expanded by default',
53
+ type: 'boolean',
54
+ default: false,
55
+ },
56
+ };
57
+ schema.fieldsets[0].fields.push('expandedByDefault');
58
+ return schema;
59
+ };
@@ -13,8 +13,6 @@ import {
13
13
  getDatasetsByUid,
14
14
  getExtraBreadcrumbItems,
15
15
  getNutsNames,
16
- } from '../../actions';
17
- import {
18
16
  getFormatConversionTable,
19
17
  getProjections,
20
18
  getDatasetTimeseries,
@@ -1,5 +1,5 @@
1
1
  export const getAvailableConversion = (conversionTable, defaultValue) => {
2
- if (conversionTable) {
2
+ if (conversionTable && Object.keys(conversionTable).length > 0) {
3
3
  if (
4
4
  conversionTable[defaultValue] === undefined ||
5
5
  conversionTable[defaultValue] === null
@@ -46,7 +46,7 @@ const messages = defineMessages({
46
46
  description: {
47
47
  id: 'descripton',
48
48
  defaultMessage:
49
- 'API tokens are used for machine-to-machine communication with the CLMS portal API. You need to create a new API token for each application in wich you need to use the CLMS portal API. In this screen you can create a new API token, check the list of your tokens and also delete those tokens.',
49
+ 'API tokens are used for machine-to-machine communication with the CLMS website API. You need to create a new API token for each application in wich you need to use the CLMS website API. In this screen you can create a new API token, check the list of your tokens and also delete those tokens.',
50
50
  },
51
51
  createTitle: {
52
52
  id: 'create title',
@@ -89,7 +89,7 @@ export const CLMSDeleteProfileView = (props) => {
89
89
  />
90
90
  </h1>
91
91
  This action will delete your profile and your subscription
92
- settings from the CLMS portal.
92
+ settings from the CLMS website.
93
93
  <br />
94
94
  <br />
95
95
  </div>
@@ -24,7 +24,7 @@ class CLMSNewsletterSubscriberView extends Component {
24
24
  this.handleClick = this.handleClick.bind(this);
25
25
  this.state = {
26
26
  download: false,
27
- headers: [{ label: 'Newsletter subscribers', key: 'email' }],
27
+ headers: [{ label: 'CLMS updates subscribers', key: 'email' }],
28
28
  };
29
29
  }
30
30
  componentDidMount() {
@@ -45,10 +45,10 @@ class CLMSNewsletterSubscriberView extends Component {
45
45
  <Container>
46
46
  <div>
47
47
  <h1 className="page-title">
48
- Download a list of newsletter subscribers
48
+ Download a list of CLMS updates subscribers
49
49
  </h1>
50
50
  <p>
51
- Click in the button below to download a list of all the newsletter
51
+ Click in the button below to download a list of all the CLMS updates
52
52
  subscribers.
53
53
  </p>
54
54
  <CclButton mode={'filled'} onClick={this.handleClick}>
@@ -69,7 +69,7 @@ class CLMSProfileView extends Component {
69
69
  </div>
70
70
  {(this.props.roles?.includes('Manager') ||
71
71
  this.props.roles?.includes('Site Administrator')) && (
72
- <div tabTitle="Newsletter subscribers">
72
+ <div tabTitle="CLMS updates subscribers">
73
73
  <CLMSNewsletterSubscriberView />
74
74
  </div>
75
75
  )}
@@ -275,7 +275,7 @@ class SubscriptionView extends Component {
275
275
  this.props.type === 'events'
276
276
  ? 'event'
277
277
  : this.props.type === 'newsletter'
278
- ? 'the newsletter'
278
+ ? 'the CLMS updates'
279
279
  : this.props.type,
280
280
  })}
281
281
  {this.props.isUnsubscribe === false &&
@@ -284,7 +284,7 @@ class SubscriptionView extends Component {
284
284
  this.props.type === 'events'
285
285
  ? 'event'
286
286
  : this.props.type === 'newsletter'
287
- ? 'the newsletter'
287
+ ? 'the CLMS updates'
288
288
  : this.props.type,
289
289
  })}{' '}
290
290
  {this.props.type !== 'newsletter' &&
@@ -320,7 +320,7 @@ class SubscriptionView extends Component {
320
320
  {
321
321
  type:
322
322
  this.props.type === 'newsletter'
323
- ? 'the newsletter'
323
+ ? 'the CLMS updates'
324
324
  : this.props.type === 'events'
325
325
  ? 'event'
326
326
  : this.props.type,
@@ -420,7 +420,11 @@ class SubscriptionView extends Component {
420
420
  }}
421
421
  >
422
422
  {'Click here if you would like to unsubscribe from our'}{' '}
423
- {this.props.type === 'events' ? 'event' : this.props.type}
423
+ {this.props.type === 'events'
424
+ ? 'event'
425
+ : this.props.type === 'newsletter'
426
+ ? 'CLMS updates'
427
+ : this.props.type}
424
428
  {this.props.type === 'newsletter' ? '' : ' notifications'}
425
429
  </Link>
426
430
  </>
@@ -1,6 +1,6 @@
1
1
  export const AVAILABLE_SUBSCRIPTIONS = [
2
2
  {
3
- title: 'Subscribe to the newsletter',
3
+ title: 'Subscribe to the CLMS updates',
4
4
  type: 'newsletter',
5
5
  back_url: 'newsletter',
6
6
  },
@@ -0,0 +1,190 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { Icon } from '@plone/volto/components';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { Popup, Segment, Divider, Message } from 'semantic-ui-react';
5
+ import CclButton from '@eeacms/volto-clms-theme/components/CclButton/CclButton';
6
+ import clearSVG from '@plone/volto/icons/clear.svg';
7
+ import {
8
+ getDatasetTimeseries,
9
+ getNutsNames,
10
+ getDatasetsByUid,
11
+ } from '@eeacms/volto-clms-theme/actions';
12
+ import { getCartItems } from '@eeacms/volto-clms-utils/actions';
13
+ import { Link } from 'react-router-dom';
14
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
15
+
16
+ export const onlyInLeft = (left, right, compareFunction) =>
17
+ left.filter(
18
+ (leftValue) =>
19
+ !right.some((rightValue) => compareFunction(leftValue, rightValue)),
20
+ );
21
+
22
+ export const CartIconCounter = () => {
23
+ const datasetTimeseries = useSelector((state) => state.datasetTimeseries);
24
+ const nutsnames = useSelector((state) => state.nutsnames);
25
+ const datasetsByUid = useSelector((state) => state.datasetsByUid);
26
+ const cartState = useSelector((state) => state.cart_items);
27
+ const cartState_ref = useRef(cartState);
28
+ const cart_icon_ref = React.useRef();
29
+ const intl = useSelector((state) => state.intl);
30
+ const user_id = useSelector((state) => state.users.user.id);
31
+ const [showPopup, setshowPopup] = useState(false);
32
+ const [cartDiff, setCartDiff] = useState(0);
33
+ const [cartDiffItems, setCartDiffItems] = useState([]);
34
+ const [hasTimeseries, setHasTimeseries] = useState(false);
35
+ const dispatch = useDispatch();
36
+ useEffect(() => {
37
+ dispatch(getCartItems(user_id));
38
+ // eslint-disable-next-line react-hooks/exhaustive-deps
39
+ }, [user_id]);
40
+
41
+ useEffect(() => {
42
+ cartDiffItems.forEach((newItem) => {
43
+ if (
44
+ !datasetTimeseries.loading &&
45
+ datasetTimeseries?.datasets[newItem.UID] === undefined
46
+ ) {
47
+ dispatch(getDatasetTimeseries(newItem.UID));
48
+ }
49
+ if (newItem.area?.type) {
50
+ dispatch(getNutsNames(newItem.area?.value));
51
+ dispatch(getDatasetsByUid(newItem.UID));
52
+ }
53
+ });
54
+ return () => {};
55
+ // eslint-disable-next-line react-hooks/exhaustive-deps
56
+ }, [cartDiffItems]);
57
+
58
+ useEffect(() => {
59
+ let hasTS = false;
60
+ if (datasetTimeseries.datasets) {
61
+ cartDiffItems.forEach((diffItem) => {
62
+ if (
63
+ datasetTimeseries.datasets[diffItem.UID] &&
64
+ datasetTimeseries.datasets[diffItem.UID].start
65
+ ) {
66
+ hasTS = true;
67
+ }
68
+ });
69
+ }
70
+ setHasTimeseries(hasTS);
71
+
72
+ return () => {};
73
+ // eslint-disable-next-line react-hooks/exhaustive-deps
74
+ }, [datasetTimeseries.datasets, datasetTimeseries.loaded, cartDiffItems]);
75
+
76
+ useEffect(() => {
77
+ if (
78
+ cartState_ref.current.set.loading &&
79
+ cartState.set.loaded &&
80
+ cartState.items.length >= cartState_ref.current.items.length
81
+ ) {
82
+ setCartDiff(cartState.items.length - cartState_ref.current.items.length);
83
+ setCartDiffItems(
84
+ onlyInLeft(
85
+ cartState.items,
86
+ cartState_ref.current.items,
87
+ (l, r) => l.unique_id === r.unique_id,
88
+ ),
89
+ );
90
+
91
+ window.scrollTo({
92
+ top: 0,
93
+ left: 0,
94
+ behavior: 'smooth',
95
+ });
96
+ !showPopup && setTimeout(() => setshowPopup(true), 900);
97
+ setTimeout(() => {
98
+ setshowPopup(false);
99
+ }, 11000);
100
+ }
101
+ cartState_ref.current = cartState;
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
+ }, [cartState]);
104
+ return (
105
+ cartState.items && (
106
+ <>
107
+ <Popup
108
+ context={cart_icon_ref}
109
+ open={showPopup}
110
+ position="bottom center"
111
+ flowing
112
+ >
113
+ <Segment
114
+ attached="top"
115
+ style={{ padding: 0, display: 'flex', justifyContent: 'flex-end' }}
116
+ >
117
+ <Icon
118
+ onClick={() => setshowPopup(false)}
119
+ name={clearSVG}
120
+ size={20}
121
+ style={{ cursor: 'pointer' }}
122
+ />
123
+ </Segment>
124
+ <Divider horizontal style={{ margin: 0 }}>
125
+ My cart
126
+ </Divider>
127
+ {cartDiff > 0 ? (
128
+ <Message positive>
129
+ {cartDiffItems.some((cdi) => cdi.area === '') && (
130
+ <p>
131
+ You added{' '}
132
+ <strong>
133
+ {cartDiff} new prepackaged item{cartDiff > 1 ? 's' : ''}
134
+ </strong>{' '}
135
+ to the cart.
136
+ </p>
137
+ )}
138
+ {datasetsByUid.loaded &&
139
+ nutsnames.loaded &&
140
+ cartDiffItems.map((cdi, key) => {
141
+ const ddata = datasetsByUid.loaded
142
+ ? datasetsByUid?.datasets?.items.find(
143
+ (d) => d.UID === cdi.UID,
144
+ )
145
+ : {};
146
+ return (
147
+ <p key={key}>
148
+ <strong>Name:</strong> {ddata?.title} <br />
149
+ <strong>Area:</strong>{' '}
150
+ {nutsnames?.nutsnames?.[cdi?.area?.value]}
151
+ </p>
152
+ );
153
+ })}
154
+ {hasTimeseries && (
155
+ <>
156
+ <br />
157
+ Click on Go to cart to select time interval.
158
+ </>
159
+ )}
160
+ </Message>
161
+ ) : (
162
+ <Message warning>
163
+ The items you tried to add were already added
164
+ </Message>
165
+ )}
166
+ <CclButton
167
+ mode="filled"
168
+ to={`/${intl.locale}/cart`}
169
+ style={{ width: '100%' }}
170
+ >
171
+ Go to cart
172
+ </CclButton>
173
+ </Popup>
174
+ <Link
175
+ to={`/${intl.locale}/cart`}
176
+ className="header-login-link"
177
+ ref={cart_icon_ref}
178
+ >
179
+ <FontAwesomeIcon
180
+ icon={['fas', 'shopping-cart']}
181
+ style={{ marginRight: '0.25rem', maxWidth: '1.5rem' }}
182
+ />
183
+ <strong>{cartState?.items?.length}</strong>
184
+ </Link>
185
+ </>
186
+ )
187
+ );
188
+ };
189
+
190
+ export default CartIconCounter;
@@ -0,0 +1,141 @@
1
+ import { onlyInLeft, CartIconCounter } from './CartIconCounter';
2
+ import Enzyme from 'enzyme';
3
+ import Adapter from 'enzyme-adapter-react-16';
4
+ import renderer from 'react-test-renderer';
5
+ import { MemoryRouter } from 'react-router-dom';
6
+ import { Provider } from 'react-intl-redux';
7
+ import configureStore from 'redux-mock-store';
8
+ Enzyme.configure({ adapter: new Adapter() });
9
+
10
+ describe('onlyInLeft', () => {
11
+ // Returns an array with items that are only in the left array, based on a compare function
12
+ it('should return an array with items that are only in the left array', () => {
13
+ const left = [
14
+ { id: 1, name: 'Item 1' },
15
+ { id: 2, name: 'Item 2' },
16
+ { id: 3, name: 'Item 3' },
17
+ ];
18
+ const right = [
19
+ { id: 2, name: 'Item 2' },
20
+ { id: 4, name: 'Item 4' },
21
+ ];
22
+ const compareFunction = (leftValue, rightValue) =>
23
+ leftValue.id === rightValue.id;
24
+
25
+ const result = onlyInLeft(left, right, compareFunction);
26
+
27
+ expect(result).toEqual([
28
+ { id: 1, name: 'Item 1' },
29
+ { id: 3, name: 'Item 3' },
30
+ ]);
31
+ });
32
+ // Should return an array with items that are only in the left array, based on a compare function
33
+ it('should return an array with items that are only in the left array', () => {
34
+ const left = [
35
+ { id: 1, name: 'Item 1' },
36
+ { id: 2, name: 'Item 2' },
37
+ { id: 3, name: 'Item 3' },
38
+ ];
39
+ const right = [
40
+ { id: 2, name: 'Item 2' },
41
+ { id: 4, name: 'Item 4' },
42
+ ];
43
+ const compareFunction = (leftValue, rightValue) =>
44
+ leftValue.id === rightValue.id;
45
+
46
+ const result = onlyInLeft(left, right, compareFunction);
47
+
48
+ expect(result).toEqual([
49
+ { id: 1, name: 'Item 1' },
50
+ { id: 3, name: 'Item 3' },
51
+ ]);
52
+ });
53
+ // should return an empty array if left array is empty
54
+ it('should return an empty array when left array is empty', () => {
55
+ const left = [];
56
+ const right = [
57
+ { id: 2, name: 'Item 2' },
58
+ { id: 4, name: 'Item 4' },
59
+ ];
60
+ const compareFunction = (leftValue, rightValue) =>
61
+ leftValue.id === rightValue.id;
62
+
63
+ const result = onlyInLeft(left, right, compareFunction);
64
+
65
+ expect(result).toEqual([]);
66
+ });
67
+
68
+ // should return the left array if right array is empty
69
+ it('should return the left array when the right array is empty', () => {
70
+ const left = [
71
+ { id: 1, name: 'Item 1' },
72
+ { id: 2, name: 'Item 2' },
73
+ { id: 3, name: 'Item 3' },
74
+ ];
75
+ const right = [];
76
+ const compareFunction = (leftValue, rightValue) =>
77
+ leftValue.id === rightValue.id;
78
+
79
+ const result = onlyInLeft(left, right, compareFunction);
80
+
81
+ expect(result).toEqual(left);
82
+ });
83
+ // should return an array with items that are only in the left array
84
+ it('should return an array with items that are only in the left array', () => {
85
+ const left = [
86
+ { id: 1, name: 'Item 1' },
87
+ { id: 2, name: 'Item 2' },
88
+ { id: 3, name: 'Item 3' },
89
+ ];
90
+ const right = [
91
+ { id: 2, name: 'Item 2' },
92
+ { id: 4, name: 'Item 4' },
93
+ ];
94
+ const compareFunction = (leftValue, rightValue) =>
95
+ leftValue.id === rightValue.id;
96
+
97
+ const result = onlyInLeft(left, right, compareFunction);
98
+
99
+ expect(result).toEqual([
100
+ { id: 1, name: 'Item 1' },
101
+ { id: 3, name: 'Item 3' },
102
+ ]);
103
+ });
104
+ });
105
+
106
+ const mockStore = configureStore();
107
+ describe('CartIconCounter', () => {
108
+ // Cart icon displays number of items in cart
109
+ it('should display the correct number of items in the cart', () => {
110
+ const store = mockStore({
111
+ cart_items: {
112
+ items: ['one', 'two', 'three'],
113
+ },
114
+ users: {
115
+ user: {
116
+ id: 'g678gfd678',
117
+ },
118
+ },
119
+ datasetTimeseries: {
120
+ datasets: [],
121
+ },
122
+ intl: {
123
+ locale: 'en',
124
+ messages: {},
125
+ },
126
+ });
127
+ // Render the component
128
+ const component = renderer.create(
129
+ <Provider store={store}>
130
+ <MemoryRouter>
131
+ <CartIconCounter />
132
+ </MemoryRouter>
133
+ </Provider>,
134
+ );
135
+ // const component = mount(<CartIconCounter />);
136
+
137
+ // Expect the component toBeDefined
138
+ expect(component).toBeDefined();
139
+ expect(component.toJSON().children[0].children[0]).toEqual('3');
140
+ });
141
+ });
@@ -2,28 +2,29 @@
2
2
  * Footer component.
3
3
  * @module components/theme/Footer/Footer
4
4
  */
5
-
6
5
  import React, { Component } from 'react';
7
6
  import { defineMessages, injectIntl } from 'react-intl';
7
+ import { connect } from 'react-redux';
8
+ import { Link } from 'react-router-dom';
9
+ import { ReactSVG } from 'react-svg';
10
+ import { toast } from 'react-toastify';
11
+ import { compose } from 'redux';
8
12
 
13
+ import { Toast } from '@plone/volto/components';
9
14
  import AtmosphereImage from '@eeacms/volto-clms-theme/../theme/clms/img/ccl-icon-atmosphere.svg';
10
- import CclFooterColumn from '@eeacms/volto-clms-theme/components/CclFooterColumn/CclFooterColumn';
11
- import CclFooterMenu from '@eeacms/volto-clms-theme/components/CclTopMainMenu/CclFooterMenu';
12
15
  import ClimateImage from '@eeacms/volto-clms-theme/../theme/clms/img/ccl-icon-climate.svg';
13
- import CopernicusImage from '@eeacms/volto-clms-theme/../theme/clms/img/copernicus_eu_logo_white.svg';
14
- import ECImage from '@eeacms/volto-clms-theme/../theme/clms/img/eea-logo.svg';
15
- import EEAImage from '@eeacms/volto-clms-theme/../theme/clms/img/ec-logo-white.svg';
16
16
  import EmergencyImage from '@eeacms/volto-clms-theme/../theme/clms/img/ccl-icon-emergency.svg';
17
17
  import LandImage from '@eeacms/volto-clms-theme/../theme/clms/img/ccl-icon-land.svg';
18
- import { Link } from 'react-router-dom';
19
18
  import MarineImage from '@eeacms/volto-clms-theme/../theme/clms/img/ccl-icon-marine.svg';
20
- import { ReactSVG } from 'react-svg';
21
19
  import SecurityImage from '@eeacms/volto-clms-theme/../theme/clms/img/ccl-icon-security.svg';
22
- import { Toast } from '@plone/volto/components';
23
- import { compose } from 'redux';
24
- import { connect } from 'react-redux';
20
+ import CopernicusImage from '@eeacms/volto-clms-theme/../theme/clms/img/copernicus_eu_logo_white.svg';
21
+ import EEAImage from '@eeacms/volto-clms-theme/../theme/clms/img/ec-logo-white.svg';
22
+ import ECImage from '@eeacms/volto-clms-theme/../theme/clms/img/eea-logo.svg';
23
+ import CclFooterColumn from '@eeacms/volto-clms-theme/components/CclFooterColumn/CclFooterColumn';
24
+ import CclFooterMenu from '@eeacms/volto-clms-theme/components/CclTopMainMenu/CclFooterMenu';
25
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
25
26
  import { subscribeTo } from '../../../../../actions';
26
- import { toast } from 'react-toastify';
27
+ import { faLinkedin } from '@fortawesome/free-brands-svg-icons';
27
28
  import validator from 'validator';
28
29
 
29
30
  const messages = defineMessages({
@@ -370,7 +371,7 @@ class Footer extends Component {
370
371
  </CclFooterColumn>
371
372
  <div className="ccl-footer-col">
372
373
  <div className="ccl-footer-form-title">
373
- Sign up to our newsletter
374
+ Sign up to CLMS updates
374
375
  </div>
375
376
  <form action="" className="ccl-footer-form">
376
377
  <div className="ccl-footer-newsletter">
@@ -435,7 +436,7 @@ class Footer extends Component {
435
436
  </div>
436
437
  <div className="ccl-footer-social">
437
438
  <a
438
- href="https://www.youtube.com/channel/UCpuwnbuwGG20enAdE50g6TA"
439
+ href="https://www.youtube.com/playlist?list=PL1_QSyumTz7CZQEZ-1foOTeTOelKDQmxu"
439
440
  target="_blank"
440
441
  rel="noreferrer"
441
442
  aria-label="Youtube"
@@ -458,8 +459,17 @@ class Footer extends Component {
458
459
  >
459
460
  <span className="ccl-icon-facebook"></span>
460
461
  </a>
462
+ <a
463
+ href="https://www.linkedin.com/showcase/copernicus-eea"
464
+ target="_blank"
465
+ rel="noreferrer"
466
+ aria-label="Linkedin"
467
+ >
468
+ <FontAwesomeIcon icon={faLinkedin} />
469
+ </a>
461
470
  </div>
462
471
  </div>
472
+
463
473
  <CclFooterColumn title={'Implemented by'}>
464
474
  <div className="footer-implemented footer-implemented-eea">
465
475
  <a
@@ -497,9 +507,17 @@ class Footer extends Component {
497
507
  wrapper="div"
498
508
  />
499
509
  </a>
500
- <span>
501
- {this.props.intl.formatMessage(messages.jointResearchCenter)}
502
- </span>
510
+ <a
511
+ href="https://joint-research-centre.ec.europa.eu/index_en"
512
+ target="_blank"
513
+ rel="noreferrer"
514
+ >
515
+ <span>
516
+ {this.props.intl.formatMessage(
517
+ messages.jointResearchCenter,
518
+ )}
519
+ </span>
520
+ </a>
503
521
  </div>
504
522
  {/* <div className="ccl-footer-col-title">
505
523
  {intl.formatMessage(messages.expertSupportProvidedBy)}
@@ -6,12 +6,9 @@
6
6
  */
7
7
 
8
8
  import { FormattedMessage, injectIntl } from 'react-intl';
9
- import { Logo, Navigation, SearchWidget, Icon } from '@plone/volto/components';
10
- import React, { Component, useEffect, useRef, useState } from 'react';
11
- import { connect, useDispatch, useSelector } from 'react-redux';
12
- import { Popup, Segment, Divider, Message } from 'semantic-ui-react';
13
- import CclButton from '@eeacms/volto-clms-theme/components/CclButton/CclButton';
14
- import clearSVG from '@plone/volto/icons/clear.svg';
9
+ import { Logo, Navigation, SearchWidget } from '@plone/volto/components';
10
+ import React, { Component } from 'react';
11
+ import { connect, useSelector } from 'react-redux';
15
12
 
16
13
  import { BodyClass } from '@plone/volto/helpers';
17
14
  // IMPORT isnt nedded until translations are created
@@ -26,91 +23,7 @@ import { getCartItems } from '@eeacms/volto-clms-utils/actions';
26
23
  import { getUser } from '@plone/volto/actions';
27
24
  import jwtDecode from 'jwt-decode';
28
25
 
29
- const CartIconCounter = (props) => {
30
- const cartState = useSelector((state) => state.cart_items);
31
- const cartState_ref = useRef(cartState);
32
- const cart_icon_ref = React.useRef();
33
- const intl = useSelector((state) => state.intl);
34
- const user_id = useSelector((state) => state.users.user.id);
35
- const [showPopup, setshowPopup] = useState(false);
36
- const [cartDiff, setCartDiff] = useState(0);
37
- const dispatch = useDispatch();
38
- useEffect(() => {
39
- dispatch(getCartItems(user_id));
40
- // eslint-disable-next-line react-hooks/exhaustive-deps
41
- }, [user_id]);
42
- useEffect(() => {
43
- if (
44
- cartState_ref.current.set.loading &&
45
- cartState.set.loaded &&
46
- cartState.items.length >= cartState_ref.current.items.length
47
- ) {
48
- setCartDiff(cartState.items.length - cartState_ref.current.items.length);
49
- window.scrollTo({
50
- top: 0,
51
- left: 0,
52
- behavior: 'smooth',
53
- });
54
- !showPopup && setTimeout(() => setshowPopup(true), 900);
55
- setTimeout(() => setshowPopup(false), 11000);
56
- }
57
- cartState_ref.current = cartState;
58
- // eslint-disable-next-line react-hooks/exhaustive-deps
59
- }, [cartState]);
60
- return (
61
- cartState.items && (
62
- <>
63
- <Popup
64
- context={cart_icon_ref}
65
- open={showPopup}
66
- position="bottom center"
67
- >
68
- <Segment
69
- attached="top"
70
- style={{ padding: 0, display: 'flex', justifyContent: 'flex-end' }}
71
- >
72
- <Icon
73
- onClick={() => setshowPopup(false)}
74
- name={clearSVG}
75
- size={20}
76
- style={{ cursor: 'pointer' }}
77
- />
78
- </Segment>
79
- <Divider horizontal style={{ margin: 0 }}>
80
- My cart
81
- </Divider>
82
- {cartDiff > 0 ? (
83
- <Message positive>
84
- You added <strong>{cartDiff} new items</strong> to the cart
85
- </Message>
86
- ) : (
87
- <Message warning>
88
- The items you tried to add were already added
89
- </Message>
90
- )}
91
- <CclButton
92
- mode="filled"
93
- to={`/${intl.locale}/cart`}
94
- style={{ width: '100%' }}
95
- >
96
- Go to cart
97
- </CclButton>
98
- </Popup>
99
- <Link
100
- to={`/${intl.locale}/cart`}
101
- className="header-login-link"
102
- ref={cart_icon_ref}
103
- >
104
- <FontAwesomeIcon
105
- icon={['fas', 'shopping-cart']}
106
- style={{ marginRight: '0.25rem', maxWidth: '1.5rem' }}
107
- />
108
- <strong>{cartState?.items?.length}</strong>
109
- </Link>
110
- </>
111
- )
112
- );
113
- };
26
+ import CartIconCounter from '@eeacms/volto-clms-theme/components/CartIconCounter/CartIconCounter';
114
27
 
115
28
  const HeaderDropdown = ({ user }) => {
116
29
  const intl = useSelector((state) => state.intl);
@@ -0,0 +1,3 @@
1
+ @listingCardPadding: 0;
2
+ @imageMinWidth: 400px;
3
+ @imageMaxHeight: 335px;
@@ -27,18 +27,18 @@ hr {
27
27
  }
28
28
 
29
29
  hr
30
- + body:not(.contenttype-lrf:not(.section-cart):not(.section-profile):not(.section-newsletter-notification-subscription):not(.section-newsletter-notification-unsubscription):not(.section-logout))
31
- main {
30
+ + body:not(.contenttype-lrf:not(.section-cart):not(.section-profile):not(.section-newsletter-notification-subscription):not(.section-newsletter-notification-unsubscription):not(.section-logout))
31
+ main {
32
32
  margin-bottom: 2rem;
33
33
  }
34
34
 
35
35
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile):not(.section-newsletter-notification-subscription):not(.section-newsletter-notification-unsubscription):not(.section-logout))
36
- main:not(.map-main) {
36
+ main:not(.map-main) {
37
37
  min-height: 70vh;
38
38
  }
39
39
 
40
40
  .map-body:not(.contenttype-lrf:not(.section-cart):not(.section-profile):not(.section-newsletter-notification-subscription):not(.section-newsletter-notification-unsubscription):not(.section-logout))
41
- main {
41
+ main {
42
42
  margin-bottom: 0;
43
43
  }
44
44
 
@@ -169,77 +169,77 @@ body.contenttype-lrf:not(.section-cart):not(.profilecart) main .ccl-container {
169
169
  }
170
170
 
171
171
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
172
- main
173
- .ccl-container {
172
+ main
173
+ .ccl-container {
174
174
  max-width: 100%;
175
175
  padding: 0;
176
176
  }
177
177
 
178
178
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
179
- main
180
- .ccl-container
181
- .cont-w-25 {
179
+ main
180
+ .ccl-container
181
+ .cont-w-25 {
182
182
  max-width: 100%;
183
183
  flex: 0 0 100%;
184
184
  }
185
185
 
186
186
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
187
- main
188
- .ccl-container
189
- .cont-w-50 {
187
+ main
188
+ .ccl-container
189
+ .cont-w-50 {
190
190
  max-width: 100%;
191
191
  flex: 0 0 100%;
192
192
  }
193
193
 
194
194
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
195
- main
196
- .ccl-container
197
- .cont-w-75 {
195
+ main
196
+ .ccl-container
197
+ .cont-w-75 {
198
198
  max-width: 100%;
199
199
  flex: 0 0 100%;
200
200
  }
201
201
  @media (min-width: 576px) {
202
202
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
203
- main
204
- .ccl-container {
203
+ main
204
+ .ccl-container {
205
205
  max-width: 540px;
206
206
  }
207
207
  }
208
208
  @media (min-width: 768px) {
209
209
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
210
- main
211
- .ccl-container {
210
+ main
211
+ .ccl-container {
212
212
  max-width: 720px;
213
213
  }
214
214
 
215
215
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
216
- main
217
- .ccl-container
218
- .cont-w-25 {
216
+ main
217
+ .ccl-container
218
+ .cont-w-25 {
219
219
  max-width: 30%;
220
220
  flex: 0 0 30%;
221
221
  }
222
222
 
223
223
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
224
- main
225
- .ccl-container
226
- .cont-w-50 {
224
+ main
225
+ .ccl-container
226
+ .cont-w-50 {
227
227
  max-width: 50%;
228
228
  flex: 0 0 50%;
229
229
  }
230
230
 
231
231
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
232
- main
233
- .ccl-container
234
- .cont-w-75 {
232
+ main
233
+ .ccl-container
234
+ .cont-w-75 {
235
235
  max-width: 70%;
236
236
  flex: 0 0 70%;
237
237
  }
238
238
 
239
239
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
240
- main
241
- .ccl-container
242
- .cont-o-1 {
240
+ main
241
+ .ccl-container
242
+ .cont-o-1 {
243
243
  order: 1;
244
244
  }
245
245
 
@@ -253,30 +253,30 @@ body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
253
253
  }
254
254
  @media (min-width: 992px) {
255
255
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
256
- main
257
- .ccl-container {
256
+ main
257
+ .ccl-container {
258
258
  max-width: 960px;
259
259
  }
260
260
  }
261
261
  @media (min-width: 1200px) {
262
262
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
263
- main
264
- .ccl-container {
263
+ main
264
+ .ccl-container {
265
265
  max-width: 1127px;
266
266
  }
267
267
 
268
268
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
269
- main
270
- .ccl-container
271
- .cont-w-25 {
269
+ main
270
+ .ccl-container
271
+ .cont-w-25 {
272
272
  max-width: 25%;
273
273
  flex: 0 0 25%;
274
274
  }
275
275
 
276
276
  body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
277
- main
278
- .ccl-container
279
- .cont-w-75 {
277
+ main
278
+ .ccl-container
279
+ .cont-w-75 {
280
280
  max-width: 75%;
281
281
  flex: 0 0 75%;
282
282
  }
@@ -705,6 +705,23 @@ body:not(.contenttype-lrf:not(.section-cart):not(.section-profile))
705
705
  margin: 0 auto !important;
706
706
  }
707
707
 
708
+ .ui.fluid.card.u-card .extra.content a.ui.button {
709
+ display: inline-block;
710
+ padding: 0.3rem 1rem;
711
+ margin: 0;
712
+ appearance: none;
713
+ background: none;
714
+ color: @clmsGreen;
715
+ color: #a0b128;
716
+ cursor: pointer;
717
+ font-size: 0.85rem;
718
+ line-height: 1.5;
719
+ text-align: center;
720
+ text-decoration: none;
721
+ transition: all 0.3s ease-out;
722
+ vertical-align: middle;
723
+ }
724
+
708
725
  /* Search */
709
726
  .search-results {
710
727
  margin-bottom: 2rem;