@pagopa/selfcare-common-frontend 1.37.0 → 2.0.0

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,8 +4,18 @@ 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
+ #### [v2.0.0](https://github.com/pagopa/selfcare-common-frontend/compare/v1.38.0...v2.0.0)
8
+
9
+ #### [v1.38.0](https://github.com/pagopa/selfcare-common-frontend/compare/v1.37.0...v1.38.0)
10
+
11
+ > 24 July 2026
12
+
13
+ - feat: implement onSuccess redirect logic for login/logout flows [`#349`](https://github.com/pagopa/selfcare-common-frontend/pull/349)
14
+
7
15
  #### [v1.37.0](https://github.com/pagopa/selfcare-common-frontend/compare/v1.36.0...v1.37.0)
8
16
 
17
+ > 9 June 2026
18
+
9
19
  - chore: release v1.36.0 [`#346`](https://github.com/pagopa/selfcare-common-frontend/pull/346)
10
20
  - [SELC-8631] Feat: integration of UTM for PNPG [`#8631`](https://pagopa.atlassian.net/browse/8631)
11
21
 
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
5
5
  import { useLocation } from 'react-router-dom';
6
6
  import { CONFIG } from '../../config/env';
7
7
  import { buildAssistanceURI } from '../../services/assistanceService';
8
+ import { withOnSuccess } from '../../utils/login-utils';
8
9
  import { isPagoPaUser } from '../../utils/storage';
9
10
  const SELFCARE_PRODUCT = {
10
11
  id: 'prod-selfcare',
@@ -35,7 +36,7 @@ const Header = ({ withSecondHeader, productsList = [], selectedPartyId, selected
35
36
  const renderedPartyId = fixedParty ? fixedParty.id : selectedPartyId;
36
37
  // Memoize the product list
37
38
  const fullProductList = useMemo(() => (addSelfcareProduct ? [productToRender, ...productsList] : productsList), [addSelfcareProduct, productToRender, productsList]);
38
- return (_jsxs(Fragment, { children: [_jsx("header", { children: _jsx(HeaderAccount, { rootLink: ROOT_LINK, loggedUser: loggedUser, onAssistanceClick: () => onExit(() => window.location.assign(buildAssistanceURI(assistanceEmail))), onLogin: () => onExit(() => window.location.assign(CONFIG.URL_FE.LOGIN)), onLogout: onLogoutClick ?? (() => onExit(() => window.location.assign(CONFIG.URL_FE.LOGOUT))), enableLogin: enableLogin, userActions: userActions, enableDropdown: enableDropdown, enableAssistanceButton: enableAssistanceButton, onDocumentationClick: onDocumentationClick }) }), withSecondHeader === true ? (_jsx("nav", { children: _jsx(HeaderProduct, { productId: selectedProductId || productToRender.id, productsList: fullProductList, partyId: renderedPartyId, partyList: renderedPartyList, onSelectedProduct: onSelectedProduct, onSelectedParty: onSelectedParty, maxCharactersNumberMultiLineButton: maxCharactersNumberMultiLineButton, maxCharactersNumberMultiLineItem: maxCharactersNumberMultiLineItem, ...(isPagoPaUser() &&
39
+ return (_jsxs(Fragment, { children: [_jsx("header", { children: _jsx(HeaderAccount, { rootLink: ROOT_LINK, loggedUser: loggedUser, onAssistanceClick: () => onExit(() => window.location.assign(buildAssistanceURI(assistanceEmail))), onLogin: () => onExit(() => window.location.assign(withOnSuccess(CONFIG.URL_FE.LOGIN))), onLogout: onLogoutClick ?? (() => onExit(() => window.location.assign(CONFIG.URL_FE.LOGOUT))), enableLogin: enableLogin, userActions: userActions, enableDropdown: enableDropdown, enableAssistanceButton: enableAssistanceButton, onDocumentationClick: onDocumentationClick }) }), withSecondHeader === true ? (_jsx("nav", { children: _jsx(HeaderProduct, { productId: selectedProductId || productToRender.id, productsList: fullProductList, partyId: renderedPartyId, partyList: renderedPartyList, onSelectedProduct: onSelectedProduct, onSelectedParty: onSelectedParty, maxCharactersNumberMultiLineButton: maxCharactersNumberMultiLineButton, maxCharactersNumberMultiLineItem: maxCharactersNumberMultiLineItem, ...(isPagoPaUser() &&
39
40
  !isAdminPage && {
40
41
  chipLabel: t('common.header.chipLabel'),
41
42
  chipColor: 'primary',
@@ -2,6 +2,7 @@ import isEmpty from 'lodash/isEmpty';
2
2
  import { useDispatch } from 'react-redux';
3
3
  import { CONFIG } from '../config/env';
4
4
  import { userActions } from '../redux/slices/userSlice';
5
+ import { withOnSuccess } from '../utils/login-utils';
5
6
  import { isPagoPaUser, storageTokenOps, storageUserOps } from '../utils/storage';
6
7
  const testToken = CONFIG.TEST.JWT;
7
8
  const mockedUser = {
@@ -29,7 +30,7 @@ export const useLogin = () => {
29
30
  // Remove any partial data that might have remained, just for safety
30
31
  storageUserOps.delete();
31
32
  // Go to the login view
32
- globalThis.location.assign(LOGIN_URL);
33
+ globalThis.location.assign(withOnSuccess(LOGIN_URL));
33
34
  // This return is necessary
34
35
  return;
35
36
  }
@@ -3,6 +3,7 @@ import { AbortableFetch, setFetchTimeout, toFetch } from '@pagopa/ts-commons/lib
3
3
  import { isRight, toError } from 'fp-ts/lib/Either';
4
4
  import { CONFIG } from '../config/env';
5
5
  import { appStateActions } from '../redux/slices/appStateSlice';
6
+ import { withOnSuccess } from './login-utils';
6
7
  import { isPagoPaUser } from './storage';
7
8
  /** To show an error popup to inform of the not valid session */
8
9
  export const onRedirectToLogin = (store) => store.dispatch(appStateActions.addError({
@@ -22,7 +23,10 @@ export const buildFetchApi = (timeoutMs = 300000) => {
22
23
  // tslint:disable-next-line: no-any
23
24
  return fetchWithTimeout;
24
25
  };
25
- const LOGIN_URL = isPagoPaUser() ? CONFIG.URL_FE.LOGOUT_GOOGLE : CONFIG.URL_FE.LOGOUT;
26
+ /** Resolved when the session turns out to be invalid, and not at import time: the applications
27
+ override CONFIG.URL_FE and write the logged user in the storage inside their own entrypoint, which
28
+ ESM evaluates after this module. */
29
+ const buildInvalidSessionUrl = () => withOnSuccess(isPagoPaUser() ? CONFIG.URL_FE.LOGOUT_GOOGLE : CONFIG.URL_FE.LOGOUT);
26
30
  /** Extract the response of a @pagopa/openapi-codegen-ts generated client rest invocation having status code successHttpStatus.
27
31
  If notValidTokenHttpStatus is not null and the returned status is equal to notValidTokenHttpStatus, it will call the onRedirectToLogin function and will schedule the redirect towards logout path.
28
32
  If notAuthorizedTokenHttpStatus is not null and the returned status is equal to notAuthorizedTokenHttpStatus, it will throw an Error with message "Operation not allowed".
@@ -36,7 +40,8 @@ export const extractResponse = async (response, successHttpStatus, onRedirectToL
36
40
  }
37
41
  else if (notValidTokenHttpStatus && response.right.status === notValidTokenHttpStatus) {
38
42
  onRedirectToLogin();
39
- window.setTimeout(() => window.location.assign(LOGIN_URL), 2000);
43
+ const invalidSessionUrl = buildInvalidSessionUrl();
44
+ window.setTimeout(() => window.location.assign(invalidSessionUrl), 2000);
40
45
  return new Promise(() => null);
41
46
  }
42
47
  else if (notAuthorizedTokenHttpStatus &&
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Adds to a login/logout URL the `onSuccess` param that the login FE reads to send the user back to
3
+ * the page they came from. It is resolved when the redirect happens, so it also covers a session
4
+ * expiring in the middle of the navigation, and not only the entry point of the application.
5
+ * A destination already present in the URL wins: applications building it on their own keep working
6
+ * and the param is never duplicated.
7
+ */
8
+ export declare const withOnSuccess: (url: string) => string;
@@ -0,0 +1,19 @@
1
+ const ON_SUCCESS_PARAM = 'onSuccess';
2
+ /** Where the user is right now, in the form the login FE expects in order to bring them back */
3
+ const currentDestination = () => globalThis.location.pathname + globalThis.location.search;
4
+ /**
5
+ * Adds to a login/logout URL the `onSuccess` param that the login FE reads to send the user back to
6
+ * the page they came from. It is resolved when the redirect happens, so it also covers a session
7
+ * expiring in the middle of the navigation, and not only the entry point of the application.
8
+ * A destination already present in the URL wins: applications building it on their own keep working
9
+ * and the param is never duplicated.
10
+ */
11
+ export const withOnSuccess = (url) => {
12
+ const queryStart = url.indexOf('?');
13
+ const query = queryStart === -1 ? '' : url.slice(queryStart + 1);
14
+ const destination = currentDestination();
15
+ if (!destination || new URLSearchParams(query).has(ON_SUCCESS_PARAM)) {
16
+ return url;
17
+ }
18
+ return `${url}${queryStart === -1 ? '?' : '&'}${ON_SUCCESS_PARAM}=${encodeURIComponent(destination)}`;
19
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pagopa/selfcare-common-frontend",
3
3
  "description": "Shared React components for the pagopa selfcare project",
4
- "version": "1.37.0",
4
+ "version": "2.0.0",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/pagopa/selfcare-common-frontend",
7
7
  "author": "https://www.pagopa.gov.it",