@eeacms/volto-clms-theme 1.1.158 → 1.1.159

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ 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.159](https://github.com/eea/volto-clms-theme/compare/1.1.158...1.1.159) - 17 June 2024
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - Code cleanup [Tiberiu Ichim - [`d00b1eb`](https://github.com/eea/volto-clms-theme/commit/d00b1eb2a9015947a1e1406803156cc461cbf550)]
12
+ - Add a spinner [Tiberiu Ichim - [`a302963`](https://github.com/eea/volto-clms-theme/commit/a3029632b5e4bc3f201b545a638a85bf340e6a81)]
13
+ - Rewrite Header as a function component [Tiberiu Ichim - [`e43e8b5`](https://github.com/eea/volto-clms-theme/commit/e43e8b5d6cc51ac33724b5b76877db09c5ccb526)]
7
14
  ### [1.1.158](https://github.com/eea/volto-clms-theme/compare/1.1.157...1.1.158) - 13 June 2024
8
15
 
9
16
  ### [1.1.157](https://github.com/eea/volto-clms-theme/compare/1.1.156...1.1.157) - 5 June 2024
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.1.158",
3
+ "version": "1.1.159",
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",
@@ -125,7 +125,7 @@ export const CartIconCounter = () => {
125
125
  <Icon
126
126
  onClick={() => setshowPopup(false)}
127
127
  name={clearSVG}
128
- size={20}
128
+ size="20px"
129
129
  style={{ cursor: 'pointer' }}
130
130
  />
131
131
  </Segment>
@@ -15,7 +15,7 @@ import { toBase64 } from '../CclUtils';
15
15
  *
16
16
  */
17
17
  function CclLoginModal(props) {
18
- let {
18
+ const {
19
19
  classname = 'header-login-link',
20
20
  triggerComponent = () => (
21
21
  <span className={classname}>
@@ -0,0 +1,289 @@
1
+ import React from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { Link, Redirect } from 'react-router-dom';
5
+ import jwtDecode from 'jwt-decode';
6
+
7
+ import Cookies from 'universal-cookie';
8
+
9
+ import { getUser, loginRenew } from '@plone/volto/actions';
10
+ import { Logo, Navigation, SearchWidget } from '@plone/volto/components';
11
+ import { BodyClass, getCookieOptions } from '@plone/volto/helpers';
12
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
13
+ import { UniversalLink } from '@plone/volto/components';
14
+
15
+ import { getCartItems } from '@eeacms/volto-clms-utils/actions';
16
+
17
+ import CclLoginModal from '@eeacms/volto-clms-theme/components/CclLoginModal/CclLoginModal';
18
+ import CclTopMainMenu from '@eeacms/volto-clms-theme/components/CclTopMainMenu/CclTopMainMenu';
19
+ import CartIconCounter from '@eeacms/volto-clms-theme/components/CartIconCounter/CartIconCounter';
20
+
21
+ import usePrevious from '@eeacms/volto-clms-theme/helpers/usePrevious';
22
+
23
+ import { Loader } from 'semantic-ui-react';
24
+
25
+ // IMPORT isnt nedded until translations are created
26
+ // import CclLanguageSelector from '@eeacms/volto-clms-theme/components/CclLanguageSelector/CclLanguageSelector';
27
+
28
+ const HeaderDropdown = ({ user }) => {
29
+ const intl = useSelector((state) => state.intl);
30
+ return (
31
+ <>
32
+ <span>
33
+ <FontAwesomeIcon
34
+ icon={['fas', 'user']}
35
+ style={{ marginRight: '0.5rem' }}
36
+ />
37
+ {user?.fullname || user?.id || ''}
38
+ <span className="ccl-icon-chevron-thin-down"></span>
39
+ </span>
40
+ <ul>
41
+ <li>
42
+ <Link to={`/${intl.locale}/profile`} className="header-login-link">
43
+ My settings
44
+ </Link>
45
+ </li>
46
+ <li>
47
+ <Link
48
+ to={`/${intl.locale}/cart-downloads`}
49
+ className="header-login-link"
50
+ >
51
+ Downloads
52
+ </Link>
53
+ </li>
54
+ <li>
55
+ <Link
56
+ to={`/${intl.locale}/all-downloads`}
57
+ className="header-login-link"
58
+ >
59
+ Historic downloads
60
+ </Link>
61
+ </li>
62
+ <li>
63
+ <Link to="/logout" className="header-login-link">
64
+ <FormattedMessage id="logout" defaultMessage="Logout" />
65
+ </Link>
66
+ </li>
67
+ </ul>
68
+ </>
69
+ );
70
+ };
71
+
72
+ export default function Header({ pathname }) {
73
+ const dispatch = useDispatch();
74
+ const user = useSelector((state) => state.users.user);
75
+ const userRequest = useSelector((state) => state.users.get);
76
+
77
+ const token = useSelector((state) => {
78
+ const jwtToken = state.userSession.token;
79
+ return jwtToken ? jwtDecode(jwtToken).sub : '';
80
+ });
81
+ const isLoadingUser = userRequest?.loading === true;
82
+
83
+ const prevToken = usePrevious(token);
84
+
85
+ const userId = user?.id;
86
+ const prevUserId = usePrevious(userId);
87
+
88
+ React.useEffect(() => {
89
+ if (token && prevToken !== token) {
90
+ dispatch(getUser(token));
91
+ }
92
+ if (prevUserId !== userId) {
93
+ dispatch(getCartItems(userId));
94
+ }
95
+ }, [dispatch, token, prevToken, prevUserId, userId]);
96
+
97
+ const apiStatusCode = useSelector(
98
+ (state) => state.apierror?.statusCode?.statusCode,
99
+ );
100
+ const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
101
+ const [mobileSearchBoxOpen, setMobileSearchBoxOpen] = React.useState(false);
102
+
103
+ React.useEffect(() => {
104
+ const cookies = new Cookies();
105
+ const query = new URLSearchParams(window.location.search);
106
+ const token = query.get('access_token');
107
+ const auth_token = token ? jwtDecode(token) : null;
108
+
109
+ if (auth_token?.sub) {
110
+ cookies.set(
111
+ 'auth_token',
112
+ token,
113
+ getCookieOptions({
114
+ expires: new Date(jwtDecode(token).exp * 1000),
115
+ }),
116
+ );
117
+ dispatch(getUser(auth_token.sub));
118
+ query.delete('access_token');
119
+ window.history.replaceState(
120
+ {},
121
+ '',
122
+ query.size > 0
123
+ ? `${window.location.pathname}?${query}${
124
+ window.location.hash && `${window.location.hash}`
125
+ }`
126
+ : `${window.location.pathname}${
127
+ window.location.hash && `${window.location.hash}`
128
+ }`,
129
+ );
130
+ dispatch(loginRenew());
131
+ } else {
132
+ if (token) {
133
+ dispatch(getUser(token));
134
+ }
135
+ }
136
+ }, [dispatch]);
137
+
138
+ return (
139
+ <>
140
+ {(user?.affiliation === null ||
141
+ user?.country === null ||
142
+ user?.sector_of_activity === null ||
143
+ user?.thematic_activity === null) &&
144
+ !user.roles.includes('Manager') && (
145
+ <Redirect
146
+ to={{
147
+ pathname: '/en/profile',
148
+ }}
149
+ />
150
+ )}
151
+ <div>
152
+ <header className="ccl-header">
153
+ {/* Body class depending on sections */}
154
+ <BodyClass className="ccl-style ccl-color_land" />
155
+
156
+ <div className="ccl-header-tools">
157
+ <div className="ccl-container">
158
+ <div
159
+ className="ccl-main-menu-collapse-button"
160
+ aria-label="Toggle main menu"
161
+ onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
162
+ onKeyDown={() => setMobileMenuOpen(!mobileMenuOpen)}
163
+ tabIndex="0"
164
+ role="button"
165
+ >
166
+ <span
167
+ className={
168
+ mobileMenuOpen ? 'ccl-icon-close' : 'ccl-icon-menu'
169
+ }
170
+ ></span>
171
+ </div>
172
+ <div
173
+ className="ccl-search-collapse-button"
174
+ aria-label="Toggle search menu"
175
+ onClick={() => setMobileSearchBoxOpen(!mobileSearchBoxOpen)}
176
+ onKeyDown={() => setMobileSearchBoxOpen(!mobileSearchBoxOpen)}
177
+ tabIndex="0"
178
+ role="button"
179
+ >
180
+ <span className="ccl-icon-zoom"></span>
181
+ </div>
182
+
183
+ <div className="ccl-header-tools-container">
184
+ <ul className="ccl-header-menu-tools">
185
+ <CclTopMainMenu></CclTopMainMenu>
186
+ <li className="header-vertical-line">
187
+ <div>|</div>
188
+ </li>
189
+ {(token && user?.id && (
190
+ <>
191
+ <li className="header-dropdown">
192
+ <HeaderDropdown user={user} />
193
+ </li>
194
+ <li>
195
+ <CartIconCounter />
196
+ </li>
197
+ </>
198
+ )) || (
199
+ <li>
200
+ {apiStatusCode === 401 ? (
201
+ <UniversalLink href="/en/login">
202
+ Register/Login
203
+ </UniversalLink>
204
+ ) : isLoadingUser ? (
205
+ <Loader active inline size="mini" />
206
+ ) : (
207
+ <CclLoginModal />
208
+ )}
209
+ </li>
210
+ )}
211
+ <li className="header-vertical-line">
212
+ <div>|</div>
213
+ </li>
214
+ </ul>
215
+ <div
216
+ className={
217
+ mobileSearchBoxOpen
218
+ ? 'ccl-header-search-show'
219
+ : 'ccl-header-search-hidden'
220
+ }
221
+ >
222
+ <SearchWidget
223
+ pathname={pathname}
224
+ setHeaderState={({ mobileSearchBoxOpen }) =>
225
+ setMobileSearchBoxOpen(mobileSearchBoxOpen)
226
+ }
227
+ />
228
+ </div>
229
+ {/* Language selector wont be shown until translations are completed */}
230
+ {/* <CclLanguageSelector /> */}
231
+ </div>
232
+ </div>
233
+ </div>
234
+ <div className="ccl-header-nav ">
235
+ <div className="ccl-container">
236
+ <Logo />
237
+ <nav
238
+ className={
239
+ mobileMenuOpen
240
+ ? 'ccl-main-menu ccl-collapsible-open'
241
+ : 'ccl-main-menu'
242
+ }
243
+ >
244
+ <Navigation
245
+ pathname={pathname}
246
+ setHeaderState={({ mobileMenuOpen }) =>
247
+ setMobileMenuOpen(mobileMenuOpen)
248
+ }
249
+ />
250
+ <ul className="ccl-header-menu-tools ccl-collapsible-toolmenu">
251
+ <CclTopMainMenu></CclTopMainMenu>
252
+ <li className="header-vertical-line">
253
+ <div>|</div>
254
+ </li>
255
+ {(user.id && mobileMenuOpen && (
256
+ <>
257
+ <li className="header-dropdown">
258
+ <HeaderDropdown user={user} />
259
+ </li>
260
+ <li>
261
+ <CartIconCounter />
262
+ </li>
263
+ </>
264
+ )) || (
265
+ <li>
266
+ {apiStatusCode === 401 ? (
267
+ <UniversalLink href="/en/login">
268
+ Register/Login
269
+ </UniversalLink>
270
+ ) : isLoadingUser ? (
271
+ <Loader active inline size="mini" />
272
+ ) : (
273
+ <CclLoginModal />
274
+ )}
275
+ </li>
276
+ )}
277
+ <li className="header-vertical-line">
278
+ <div>|</div>
279
+ </li>
280
+ </ul>
281
+ </nav>
282
+ </div>
283
+ </div>
284
+ <hr />
285
+ </header>
286
+ </div>
287
+ </>
288
+ );
289
+ }
@@ -1,346 +1,2 @@
1
- import React, { Component } from 'react';
2
- /* eslint-disable react-hooks/rules-of-hooks */
3
- /* eslint-disable no-undef */
4
- /**
5
- * Header component.
6
- * @module components/theme/Header/Header
7
- */
8
- import { FormattedMessage, injectIntl } from 'react-intl';
9
- import { connect, useSelector } from 'react-redux';
10
- import { Link } from 'react-router-dom';
11
- import { Redirect } from 'react-router-dom';
12
- import { compose } from 'redux';
13
-
14
- import { getUser, loginRenew } from '@plone/volto/actions';
15
- import { Logo, Navigation, SearchWidget } from '@plone/volto/components';
16
- import { BodyClass, getCookieOptions } from '@plone/volto/helpers';
17
- import CartIconCounter from '@eeacms/volto-clms-theme/components/CartIconCounter/CartIconCounter';
18
- // IMPORT isnt nedded until translations are created
19
- // import CclLanguageSelector from '@eeacms/volto-clms-theme/components/CclLanguageSelector/CclLanguageSelector';
20
- import CclLoginModal from '@eeacms/volto-clms-theme/components/CclLoginModal/CclLoginModal';
21
- import CclTopMainMenu from '@eeacms/volto-clms-theme/components/CclTopMainMenu/CclTopMainMenu';
22
- import { getCartItems } from '@eeacms/volto-clms-utils/actions';
23
- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
24
- import { UniversalLink } from '@plone/volto/components';
25
-
26
- import jwtDecode from 'jwt-decode';
27
- import PropTypes from 'prop-types';
28
- import Cookies from 'universal-cookie';
29
-
30
- const HeaderDropdown = ({ user }) => {
31
- const intl = useSelector((state) => state.intl);
32
- return (
33
- <>
34
- <span>
35
- <FontAwesomeIcon
36
- icon={['fas', 'user']}
37
- style={{ marginRight: '0.5rem' }}
38
- />
39
- {user?.fullname || user?.id || ''}
40
- <span className="ccl-icon-chevron-thin-down"></span>
41
- </span>
42
- <ul>
43
- <li>
44
- <Link to={`/${intl.locale}/profile`} className="header-login-link">
45
- My settings
46
- </Link>
47
- </li>
48
- <li>
49
- <Link
50
- to={`/${intl.locale}/cart-downloads`}
51
- className="header-login-link"
52
- >
53
- Downloads
54
- </Link>
55
- </li>
56
- <li>
57
- <Link
58
- to={`/${intl.locale}/all-downloads`}
59
- className="header-login-link"
60
- >
61
- Historic downloads
62
- </Link>
63
- </li>
64
- <li>
65
- <Link to="/logout" className="header-login-link">
66
- <FormattedMessage id="logout" defaultMessage="Logout" />
67
- </Link>
68
- </li>
69
- </ul>
70
- </>
71
- );
72
- };
73
-
74
- /**
75
- * Header component class.
76
- * @class Header
77
- * @extends Component
78
- */
79
-
80
- class Header extends Component {
81
- /**
82
- * Property types.
83
- * @property {Object} propTypes Property types.
84
- * @static
85
- */
86
- static propTypes = {
87
- token: PropTypes.string,
88
- pathname: PropTypes.string.isRequired,
89
- user: PropTypes.shape({
90
- fullname: PropTypes.string,
91
- email: PropTypes.string,
92
- home_page: PropTypes.string,
93
- location: PropTypes.string,
94
- roles: PropTypes.array,
95
- }).isRequired,
96
- getUser: PropTypes.func.isRequired,
97
- };
98
-
99
- componentDidMount() {
100
- const cookies = new Cookies();
101
- const query = new URLSearchParams(window.location.search);
102
- const token = query.get('access_token');
103
- const auth_token = token ? jwtDecode(token) : null;
104
- if (auth_token?.sub) {
105
- cookies.set(
106
- 'auth_token',
107
- token,
108
- getCookieOptions({
109
- expires: new Date(jwtDecode(token).exp * 1000),
110
- }),
111
- );
112
- this.props.getUser(auth_token.sub);
113
- query.delete('access_token');
114
- window.history.replaceState(
115
- {},
116
- '',
117
- query.size > 0
118
- ? `${window.location.pathname}?${query}${
119
- window.location.hash && `${window.location.hash}`
120
- }`
121
- : `${window.location.pathname}${
122
- window.location.hash && `${window.location.hash}`
123
- }`,
124
- );
125
- this.props.loginRenew();
126
- } else {
127
- this.props.getUser(this.props.token);
128
- }
129
- }
130
- UNSAFE_componentWillReceiveProps(nextProps) {
131
- if (nextProps.token !== this.props.token) {
132
- this.props.getUser(nextProps.token);
133
- }
134
- // if (nextProps.user.id !== this.props.user.id) {
135
- // this.props.getCartItems(this.props?.user?.id);
136
- // }
137
- }
138
-
139
- /**
140
- * Default properties.
141
- * @property {Object} defaultProps Default properties.
142
- * @static
143
- */
144
- static defaultProps = {
145
- token: null,
146
- };
147
-
148
- constructor(props) {
149
- super(props);
150
-
151
- this.state = {
152
- mobileMenuOpen: false,
153
- mobileSearchBoxOpen: false,
154
- };
155
- }
156
-
157
- /**
158
- * Render method.
159
- * @method render
160
- * @returns {string} Markup for the component.
161
- */
162
- render() {
163
- return (
164
- <>
165
- {(this.props.user?.affiliation === null ||
166
- this.props.user?.country === null ||
167
- this.props.user?.sector_of_activity === null ||
168
- this.props.user?.thematic_activity === null) &&
169
- !this.props.user.roles.includes('Manager') && (
170
- <Redirect
171
- to={{
172
- pathname: '/en/profile',
173
- }}
174
- />
175
- )}
176
- <div>
177
- <header className="ccl-header">
178
- {/* Body class depending on sections */}
179
- <BodyClass className="ccl-style ccl-color_land" />
180
-
181
- <div className="ccl-header-tools">
182
- <div className="ccl-container">
183
- <div
184
- className="ccl-main-menu-collapse-button"
185
- aria-label="Toggle main menu"
186
- onClick={() =>
187
- this.setState({
188
- mobileMenuOpen: !this.state.mobileMenuOpen,
189
- })
190
- }
191
- onKeyDown={() =>
192
- this.setState({
193
- mobileMenuOpen: !this.state.mobileMenuOpen,
194
- })
195
- }
196
- tabIndex="0"
197
- role="button"
198
- >
199
- <span
200
- className={
201
- this.state.mobileMenuOpen
202
- ? 'ccl-icon-close'
203
- : 'ccl-icon-menu'
204
- }
205
- ></span>
206
- </div>
207
- <div
208
- className="ccl-search-collapse-button"
209
- aria-label="Toggle search menu"
210
- onClick={() =>
211
- this.setState({
212
- mobileSearchBoxOpen: !this.state.mobileSearchBoxOpen,
213
- })
214
- }
215
- onKeyDown={() =>
216
- this.setState({
217
- mobileSearchBoxOpen: !this.state.mobileSearchBoxOpen,
218
- })
219
- }
220
- tabIndex="0"
221
- role="button"
222
- >
223
- <span className="ccl-icon-zoom"></span>
224
- </div>
225
-
226
- <div className="ccl-header-tools-container">
227
- <ul className="ccl-header-menu-tools">
228
- <CclTopMainMenu></CclTopMainMenu>
229
- <li className="header-vertical-line">
230
- <div>|</div>
231
- </li>
232
- {(this.props.token && this.props.user?.id && (
233
- <>
234
- <li className="header-dropdown">
235
- <HeaderDropdown user={this.props.user} />
236
- </li>
237
- <li>
238
- <CartIconCounter />
239
- </li>
240
- </>
241
- )) || (
242
- <li>
243
- {this.props.apiStatusCode === 401 ? (
244
- <UniversalLink href="/en/login">
245
- Register/Login
246
- </UniversalLink>
247
- ) : (
248
- <CclLoginModal />
249
- )}
250
- </li>
251
- )}
252
- <li className="header-vertical-line">
253
- <div>|</div>
254
- </li>
255
- </ul>
256
- <div
257
- className={
258
- this.state.mobileSearchBoxOpen
259
- ? 'ccl-header-search-show'
260
- : 'ccl-header-search-hidden'
261
- }
262
- >
263
- <SearchWidget
264
- pathname={this.props.pathname}
265
- setHeaderState={(p) => {
266
- this.setState(p);
267
- }}
268
- />
269
- </div>
270
- {/* Language selector wont be shown until translations are completed */}
271
- {/* <CclLanguageSelector /> */}
272
- </div>
273
- </div>
274
- </div>
275
- <div className="ccl-header-nav ">
276
- <div className="ccl-container">
277
- <Logo />
278
- <nav
279
- className={
280
- this.state.mobileMenuOpen
281
- ? 'ccl-main-menu ccl-collapsible-open'
282
- : 'ccl-main-menu'
283
- }
284
- >
285
- <Navigation
286
- pathname={this.props.pathname}
287
- setHeaderState={(p) => {
288
- this.setState(p);
289
- }}
290
- />
291
- <ul className="ccl-header-menu-tools ccl-collapsible-toolmenu">
292
- <CclTopMainMenu></CclTopMainMenu>
293
- <li className="header-vertical-line">
294
- <div>|</div>
295
- </li>
296
- {(this.props.user.id && this.state.mobileMenuOpen && (
297
- <>
298
- <li className="header-dropdown">
299
- <HeaderDropdown user={this.props.user} />
300
- </li>
301
- <li>
302
- <CartIconCounter />
303
- </li>
304
- </>
305
- )) || (
306
- <li>
307
- {this.props.apiStatusCode === 401 ? (
308
- <UniversalLink href="/en/login">
309
- Register/Login
310
- </UniversalLink>
311
- ) : (
312
- <CclLoginModal />
313
- )}
314
- </li>
315
- )}
316
- <li className="header-vertical-line">
317
- <div>|</div>
318
- </li>
319
- </ul>
320
- </nav>
321
- </div>
322
- </div>
323
- <hr />
324
- </header>
325
- </div>
326
- </>
327
- );
328
- }
329
- }
330
-
331
- export default compose(
332
- injectIntl,
333
- connect(
334
- (state) => ({
335
- locale: state.intl.locale,
336
- cart: state.cart_items.items,
337
- user: state.users.user,
338
- token: state.userSession.token
339
- ? jwtDecode(state.userSession.token).sub
340
- : '',
341
- rawtoken: state.userSession.token,
342
- apiStatusCode: state.apierror?.statusCode?.statusCode,
343
- }),
344
- { getUser, getCartItems, loginRenew },
345
- ),
346
- )(Header);
1
+ import Header from '@eeacms/volto-clms-theme/components/Header/Header';
2
+ export default Header;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+
3
+ export default function usePrevious(value) {
4
+ const ref = React.useRef();
5
+ React.useEffect(() => {
6
+ ref.current = value;
7
+ });
8
+ return ref.current;
9
+ }