@eeacms/volto-clms-theme 1.1.292 → 1.1.294

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,11 +4,25 @@ 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.292](https://github.com/eea/volto-clms-theme/compare/1.1.291...1.1.292) - 2 June 2026
7
+ ### [1.1.294](https://github.com/eea/volto-clms-theme/compare/1.1.293...1.1.294) - 5 June 2026
8
8
 
9
9
  #### :hammer_and_wrench: Others
10
10
 
11
- - Refs #289838 - Implement CSP support in Volto's webpack implementation. [GhitaB - [`6a342dd`](https://github.com/eea/volto-clms-theme/commit/6a342dde51548f960e87a19fb6c44b0fccc64a1a)]
11
+ - Fix redirect [Dobricean Ioan Dorian - [`18dc53d`](https://github.com/eea/volto-clms-theme/commit/18dc53d5b4fde69cf2ce95013ce7624b8dcb156d)]
12
+ ### [1.1.293](https://github.com/eea/volto-clms-theme/compare/1.1.292...1.1.293) - 5 June 2026
13
+
14
+ #### :house: Internal changes
15
+
16
+ - style: Automated code fix [eea-jenkins - [`8c05ecd`](https://github.com/eea/volto-clms-theme/commit/8c05ecd107a922be765a39df0c161e5d407ae053)]
17
+
18
+ #### :hammer_and_wrench: Others
19
+
20
+ - add deps [Dobricean Ioan Dorian - [`1000ea9`](https://github.com/eea/volto-clms-theme/commit/1000ea9261702391c255f833e961b27c6fd9a2d5)]
21
+ - add deps [Dobricean Ioan Dorian - [`199a21f`](https://github.com/eea/volto-clms-theme/commit/199a21f7022ff81337cd8e10c609bbbdf0421351)]
22
+ - Add volto-authomatic dependency to package.json [dobri1408 - [`a9151d6`](https://github.com/eea/volto-clms-theme/commit/a9151d62c369a4225a6e0750b28f25f492b37138)]
23
+ - add entra id [Dobricean Ioan Dorian - [`a167b6a`](https://github.com/eea/volto-clms-theme/commit/a167b6ac92480f9e99c1478d322cfc829c99d238)]
24
+ ### [1.1.292](https://github.com/eea/volto-clms-theme/compare/1.1.291...1.1.292) - 2 June 2026
25
+
12
26
  ### [1.1.291](https://github.com/eea/volto-clms-theme/compare/1.1.290...1.1.291) - 22 May 2026
13
27
 
14
28
  ### [1.1.290](https://github.com/eea/volto-clms-theme/compare/1.1.289...1.1.290) - 21 May 2026
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.1.292",
3
+ "version": "1.1.294",
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",
@@ -32,7 +32,8 @@
32
32
  "volto-cookie-banner",
33
33
  "@eeacms/volto-tableau",
34
34
  "@eeacms/volto-embed",
35
- "@eeacms/volto-globalsearch"
35
+ "@eeacms/volto-globalsearch",
36
+ "@plone-collective/volto-authomatic"
36
37
  ],
37
38
  "resolutions": {
38
39
  "@elastic/search-ui": "1.21.2",
@@ -59,6 +60,7 @@
59
60
  "@fortawesome/react-fontawesome": "0.1.14",
60
61
  "@ginkgo-bioworks/react-json-schema-form-builder": "2.10.1",
61
62
  "@kitconcept/volto-blocks-grid": "7.0.2",
63
+ "@plone-collective/volto-authomatic": "2.0.1",
62
64
  "connected-react-router": "6.8.0",
63
65
  "d3-array": "^2.12.1",
64
66
  "husky": "7.0.4",
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Combined Login container - supports both external providers and Plone login.
3
+ * @module components/Login/Login
4
+ */
5
+ import React, { useEffect, useState } from 'react';
6
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
7
+ import { Link, useHistory, useLocation } from 'react-router-dom';
8
+ import {
9
+ Container,
10
+ Button,
11
+ Form,
12
+ Input,
13
+ Segment,
14
+ Grid,
15
+ } from 'semantic-ui-react';
16
+ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
17
+ import { useCookies } from 'react-cookie';
18
+
19
+ import { Helmet } from '@plone/volto/helpers';
20
+ import config from '@plone/volto/registry';
21
+ import { Icon } from '@plone/volto/components';
22
+ import { login, resetLoginRequest } from '@plone/volto/actions';
23
+ import { toast } from 'react-toastify';
24
+ import { Toast } from '@plone/volto/components';
25
+ import aheadSVG from '@plone/volto/icons/ahead.svg';
26
+ import clearSVG from '@plone/volto/icons/clear.svg';
27
+ import './Login.less';
28
+
29
+ // Import authomatic components and actions
30
+ import {
31
+ authomaticRedirect,
32
+ listAuthOptions,
33
+ oidcRedirect,
34
+ } from '@plone-collective/volto-authomatic/actions';
35
+ import AuthProviders from '@plone-collective/volto-authomatic/components/AuthProviders/AuthProviders';
36
+ import { getReturnUrl } from './loginUrl';
37
+
38
+ const messages = defineMessages({
39
+ login: {
40
+ id: 'Log in',
41
+ defaultMessage: 'Log in',
42
+ },
43
+ loginName: {
44
+ id: 'Login Name',
45
+ defaultMessage: 'Login Name',
46
+ },
47
+ Login: {
48
+ id: 'Login',
49
+ defaultMessage: 'Login',
50
+ },
51
+ password: {
52
+ id: 'Password',
53
+ defaultMessage: 'Password',
54
+ },
55
+ cancel: {
56
+ id: 'Cancel',
57
+ defaultMessage: 'Cancel',
58
+ },
59
+ error: {
60
+ id: 'Error',
61
+ defaultMessage: 'Error',
62
+ },
63
+ loginFailed: {
64
+ id: 'Login Failed',
65
+ defaultMessage: 'Login Failed',
66
+ },
67
+ loginFailedContent: {
68
+ id:
69
+ 'Both email address and password are case sensitive, check that caps lock is not enabled.',
70
+ defaultMessage:
71
+ 'Both email address and password are case sensitive, check that caps lock is not enabled.',
72
+ },
73
+ register: {
74
+ id: 'Register',
75
+ defaultMessage: 'Register',
76
+ },
77
+ forgotPassword: {
78
+ id: 'box_forgot_password_option',
79
+ defaultMessage: 'Forgot your password?',
80
+ },
81
+ signInWith: {
82
+ id: 'Sign in with EEA Microsoft Entra ID',
83
+ defaultMessage: 'Sign in with EEA Microsoft Entra ID',
84
+ },
85
+ orSignIn: {
86
+ id: 'Or sign in with EEA Entra ID:',
87
+ defaultMessage: 'Or sign in with EEA Entra ID:',
88
+ },
89
+ loading: {
90
+ id: 'Loading',
91
+ defaultMessage: 'Loading',
92
+ },
93
+ });
94
+
95
+ /**
96
+ * Combined Login function.
97
+ * @function Login
98
+ * @returns {JSX.Element} Markup of the Login page.
99
+ */
100
+ function Login({ intl }) {
101
+ const dispatch = useDispatch();
102
+ const history = useHistory();
103
+ const location = useLocation();
104
+
105
+ // Authomatic state
106
+ const [startedOAuth, setStartedOAuth] = useState(false);
107
+ const [startedOIDC, setStartedOIDC] = useState(false);
108
+ const loading = useSelector((state) => state.authOptions.loading);
109
+ const options = useSelector((state) => state.authOptions.options);
110
+ const loginOAuthValues = useSelector((state) => state.authomaticRedirect);
111
+ const loginOIDCValues = useSelector((state) => state.oidcRedirect);
112
+ const [, setCookie] = useCookies();
113
+
114
+ // Plone login state
115
+ const token = useSelector((state) => state.userSession.token, shallowEqual);
116
+ const error = useSelector((state) => state.userSession.login.error);
117
+ const ploneLoading = useSelector((state) => state.userSession.login.loading);
118
+
119
+ const returnUrl = getReturnUrl(location);
120
+
121
+ useEffect(() => {
122
+ dispatch(listAuthOptions());
123
+ }, [dispatch]);
124
+
125
+ // Handle successful Plone login
126
+ useEffect(() => {
127
+ if (token) {
128
+ history.push(returnUrl || '/');
129
+ if (toast.isActive('loggedOut')) {
130
+ toast.dismiss('loggedOut');
131
+ }
132
+ if (toast.isActive('loginFailed')) {
133
+ toast.dismiss('loginFailed');
134
+ }
135
+ }
136
+ if (error) {
137
+ if (toast.isActive('loggedOut')) {
138
+ toast.dismiss('loggedOut');
139
+ }
140
+ if (!toast.isActive('loginFailed')) {
141
+ toast.error(
142
+ <Toast
143
+ error
144
+ title={intl.formatMessage(messages.loginFailed)}
145
+ content={intl.formatMessage(messages.loginFailedContent)}
146
+ />,
147
+ { autoClose: false, toastId: 'loginFailed' },
148
+ );
149
+ }
150
+ }
151
+ return () => {
152
+ if (toast.isActive('loginFailed')) {
153
+ toast.dismiss('loginFailed');
154
+ dispatch(resetLoginRequest());
155
+ }
156
+ };
157
+ }, [dispatch, token, error, intl, history, returnUrl]);
158
+
159
+ // Handle OAuth redirects
160
+ useEffect(() => {
161
+ const next_url = loginOAuthValues.next_url;
162
+ const session = loginOAuthValues.session;
163
+ if (next_url && session && startedOAuth) {
164
+ setStartedOAuth(false);
165
+ // Give time to save state to localstorage
166
+ setTimeout(function () {
167
+ window.location.href = next_url;
168
+ }, 500);
169
+ }
170
+ }, [startedOAuth, loginOAuthValues]);
171
+
172
+ useEffect(() => {
173
+ const next_url = loginOIDCValues.next_url;
174
+ if (next_url && startedOIDC) {
175
+ setStartedOIDC(false);
176
+ // Give time to save state to localstorage
177
+ setTimeout(function () {
178
+ window.location.href = next_url;
179
+ }, 500);
180
+ }
181
+ }, [startedOIDC, loginOIDCValues]);
182
+
183
+ useEffect(() => {
184
+ if (
185
+ options !== undefined &&
186
+ options.length === 1 &&
187
+ options[0].id === 'oidc'
188
+ ) {
189
+ setStartedOIDC(true);
190
+ dispatch(oidcRedirect('oidc'));
191
+ }
192
+ }, [options, dispatch]);
193
+
194
+ // Handle provider selection
195
+ const onSelectProvider = (provider) => {
196
+ setStartedOAuth(true);
197
+ setCookie('return_url', getReturnUrl(location), { path: '/' });
198
+ dispatch(authomaticRedirect(provider.id));
199
+ };
200
+
201
+ // Handle Plone login form submission
202
+ const onLogin = (event) => {
203
+ dispatch(
204
+ login(
205
+ document.getElementsByName('login')[0].value,
206
+ document.getElementsByName('password')[0].value,
207
+ ),
208
+ );
209
+ event.preventDefault();
210
+ };
211
+
212
+ // Prepare providers for external login
213
+ const validProviders = options
214
+ ? options.filter((provider) => provider.id !== 'oidc')
215
+ : [];
216
+
217
+ return (
218
+ <div id="page-login">
219
+ <Helmet title={intl.formatMessage(messages.Login)} />
220
+ <Container text>
221
+ <Segment.Group raised>
222
+ <Segment className="primary">
223
+ <FormattedMessage id="Log In" defaultMessage="Login" />
224
+ </Segment>
225
+ <Segment secondary>
226
+ <FormattedMessage
227
+ id="Sign in to start session"
228
+ defaultMessage="Sign in to start session"
229
+ />
230
+ </Segment>
231
+
232
+ {/* Plone Login Form */}
233
+ <Segment className="form">
234
+ <Form method="post" onSubmit={onLogin}>
235
+ <Form.Field inline className="help">
236
+ <Grid>
237
+ <Grid.Row stretched>
238
+ <Grid.Column width="4">
239
+ <div className="wrapper">
240
+ <label htmlFor="login">
241
+ <FormattedMessage
242
+ id="Login Name"
243
+ defaultMessage="Login Name"
244
+ />
245
+ </label>
246
+ </div>
247
+ </Grid.Column>
248
+ <Grid.Column width="8">
249
+ <Input
250
+ id="login"
251
+ name="login"
252
+ placeholder={intl.formatMessage(messages.loginName)}
253
+ />
254
+ </Grid.Column>
255
+ </Grid.Row>
256
+ </Grid>
257
+ </Form.Field>
258
+ <Form.Field inline className="help">
259
+ <Grid>
260
+ <Grid.Row stretched>
261
+ <Grid.Column stretched width="4">
262
+ <div className="wrapper">
263
+ <label htmlFor="password">
264
+ <FormattedMessage
265
+ id="Password"
266
+ defaultMessage="Password"
267
+ />
268
+ </label>
269
+ </div>
270
+ </Grid.Column>
271
+ <Grid.Column stretched width="8">
272
+ <Input
273
+ type="password"
274
+ id="password"
275
+ autoComplete="current-password"
276
+ name="password"
277
+ placeholder={intl.formatMessage(messages.password)}
278
+ tabIndex={0}
279
+ />
280
+ </Grid.Column>
281
+ </Grid.Row>
282
+ </Grid>
283
+ </Form.Field>
284
+ <Form.Field inline className="help">
285
+ <Grid>
286
+ <Grid.Row stretched>
287
+ {config.settings.showSelfRegistration && (
288
+ <Grid.Column stretched width="12">
289
+ <p className="help">
290
+ <Link to="/register">
291
+ {intl.formatMessage(messages.register)}
292
+ </Link>
293
+ </p>
294
+ </Grid.Column>
295
+ )}
296
+ <Grid.Column stretched width="12">
297
+ <p className="help">
298
+ <Link to="/passwordreset">
299
+ {intl.formatMessage(messages.forgotPassword)}
300
+ </Link>
301
+ </p>
302
+ </Grid.Column>
303
+ </Grid.Row>
304
+ </Grid>
305
+ </Form.Field>
306
+ </Form>
307
+ </Segment>
308
+
309
+ <Segment className="actions" clearing>
310
+ <Button
311
+ basic
312
+ primary
313
+ icon
314
+ floated="right"
315
+ type="submit"
316
+ form="login-form"
317
+ id="login-form-submit"
318
+ aria-label={intl.formatMessage(messages.login)}
319
+ title={intl.formatMessage(messages.login)}
320
+ loading={ploneLoading}
321
+ onClick={onLogin}
322
+ >
323
+ <Icon className="circled" name={aheadSVG} size="30px" />
324
+ </Button>
325
+
326
+ <Button
327
+ basic
328
+ secondary
329
+ icon
330
+ floated="right"
331
+ id="login-form-cancel"
332
+ as={Link}
333
+ to="/"
334
+ aria-label={intl.formatMessage(messages.cancel)}
335
+ title={intl.formatMessage(messages.cancel)}
336
+ >
337
+ <Icon className="circled" name={clearSVG} size="30px" />
338
+ </Button>
339
+ </Segment>
340
+ </Segment.Group>
341
+
342
+ {/* External Login Providers - Outside the main form */}
343
+ {validProviders && validProviders.length > 0 && (
344
+ <div style={{ marginTop: '2rem', width: '100%' }}>
345
+ <div style={{ textAlign: 'center', marginBottom: '1rem' }}>
346
+ <FormattedMessage
347
+ id="Or sign in with EEA Entra ID:"
348
+ defaultMessage="Or sign in with EEA Entra ID:"
349
+ />
350
+ </div>
351
+ <div style={{ width: '100%' }}>
352
+ {!loading && validProviders && (
353
+ <AuthProviders
354
+ providers={validProviders}
355
+ action="login"
356
+ onSelectProvider={onSelectProvider}
357
+ />
358
+ )}
359
+ {(loading || validProviders.length === 0) && (
360
+ <div style={{ textAlign: 'center', padding: '1rem' }}>
361
+ {intl.formatMessage(messages.loading)}
362
+ </div>
363
+ )}
364
+ </div>
365
+ </div>
366
+ )}
367
+ </Container>
368
+ </div>
369
+ );
370
+ }
371
+
372
+ export default injectIntl(Login);
@@ -0,0 +1,8 @@
1
+ .ui.button.authenticationProvider {
2
+ display: inline-flex;
3
+ width: 100% !important;
4
+ align-items: center;
5
+ margin: 0.5rem auto;
6
+ border-radius: 3px;
7
+ box-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px;
8
+ }
@@ -0,0 +1,9 @@
1
+ import qs from 'query-string';
2
+
3
+ export function getReturnUrl(location) {
4
+ return (
5
+ qs.parse(location.search || '').return_url ||
6
+ (location.pathname || '/').replace(/\/(?:login|login-plone)\/?$/, '') ||
7
+ '/'
8
+ );
9
+ }
package/src/index.js CHANGED
@@ -4,7 +4,7 @@ import customBlocks, {
4
4
  } from '@eeacms/volto-clms-theme/components/Blocks/customBlocks';
5
5
 
6
6
  // ROUTE VIEWS
7
- import { ContactForm, Search, Sitemap, Login } from '@plone/volto/components';
7
+ import { ContactForm, Search, Sitemap } from '@plone/volto/components';
8
8
 
9
9
  // VIEWS
10
10
  import CLMSDatasetDetailView from '@eeacms/volto-clms-theme/components/CLMSDatasetDetailView/CLMSDatasetDetailView';
@@ -52,7 +52,7 @@ import { CheckboxHtmlWidget } from './components/Blocks/CustomTemplates/VoltoFor
52
52
  import reducers from './reducers';
53
53
  import CookieBanner from 'volto-cookie-banner/CookieBannerContainer';
54
54
  import CLMSLoginView from './components/CLMSLoginView/CLMSLogin';
55
-
55
+ import AuthomaticLoginPlone from './components/CLMSLoginView/AuthomaticLoginPlone';
56
56
  //SLATE CONFIGURATION
57
57
  import installLinkEditor from '@plone/volto-slate/editor/plugins/AdvancedLink';
58
58
 
@@ -274,11 +274,11 @@ const applyConfig = (config) => {
274
274
  ...config.addonRoutes,
275
275
  {
276
276
  path: '/login-plone',
277
- component: Login,
277
+ component: AuthomaticLoginPlone,
278
278
  },
279
279
  {
280
280
  path: '/**/login-plone',
281
- component: Login,
281
+ component: AuthomaticLoginPlone,
282
282
  },
283
283
  {
284
284
  path: '/login',