From e5648f0630e5a3bc465911ae24471ed34f631a52 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Wed, 15 Oct 2025 17:30:58 +0200 Subject: [PATCH 01/12] microfix ios --- ios/AppReviewModule.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/AppReviewModule.swift b/ios/AppReviewModule.swift index c132fc700c2..c064b183f77 100644 --- a/ios/AppReviewModule.swift +++ b/ios/AppReviewModule.swift @@ -8,7 +8,7 @@ import Foundation import UIKit import StoreKit -import SwiftUICore +import SwiftUI /** `@objc` attribute exposes Swift methods to the Objective-C runtime**/ @@ -19,14 +19,14 @@ class AppReviewModule: NSObject { let activeWindowScene = UIApplication.shared.connectedScenes.first { scene in return scene.activationState == .foregroundActive && scene is UIWindowScene } - + if #available(iOS 16.0, *) { if let scene = activeWindowScene as? UIWindowScene { AppStore.requestReview(in: scene) return } } - + if #available(iOS 14.0, *) { if let scene = activeWindowScene as? UIWindowScene { SKStoreReviewController.requestReview(in: scene) From 5ac95d50d969b17d318c6150c1b799e40bf13df2 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Thu, 16 Oct 2025 11:55:28 +0200 Subject: [PATCH 02/12] base Profile --- locales/it/index.json | 6 +- ts/features/toyProfile/navigation/params.ts | 5 + ts/features/toyProfile/navigation/routes.ts | 3 + .../navigation/toyProfileNavigator.tsx | 24 +++++ ts/features/toyProfile/saga/fetch.ts | 54 +++++++++++ .../toyProfile/screens/ProfileHomeScreen.tsx | 94 +++++++++++++++++++ ts/features/toyProfile/store/actions/index.ts | 10 ++ .../toyProfile/store/reducers/index.ts | 23 +++++ .../toyProfile/store/selectors/index.ts | 3 + ts/features/toyProfile/types/index.ts | 7 ++ ts/navigation/AuthenticatedStackNavigator.tsx | 3 +- ts/sagas/startup.ts | 3 + ts/store/reducers/index.ts | 3 + ts/store/reducers/types.ts | 2 + 14 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 ts/features/toyProfile/navigation/params.ts create mode 100644 ts/features/toyProfile/navigation/routes.ts create mode 100644 ts/features/toyProfile/navigation/toyProfileNavigator.tsx create mode 100644 ts/features/toyProfile/saga/fetch.ts create mode 100644 ts/features/toyProfile/screens/ProfileHomeScreen.tsx create mode 100644 ts/features/toyProfile/store/actions/index.ts create mode 100644 ts/features/toyProfile/store/reducers/index.ts create mode 100644 ts/features/toyProfile/store/selectors/index.ts create mode 100644 ts/features/toyProfile/types/index.ts diff --git a/locales/it/index.json b/locales/it/index.json index 7453bb7f8cf..c79cf987b45 100644 --- a/locales/it/index.json +++ b/locales/it/index.json @@ -664,7 +664,8 @@ "email": "Indirizzo email", "need_validate": "Da validare", "nameSurname": "Nome e cognome", - "fiscalCode": "Codice Fiscale" + "fiscalCode": "Codice Fiscale", + "birthDate" : "Data di nascita" } }, "security": { @@ -3772,6 +3773,9 @@ } }, "features": { + "profile" : { + "title" : "Profilo" + }, "messages": { "pushNotifications": { "banner": { diff --git a/ts/features/toyProfile/navigation/params.ts b/ts/features/toyProfile/navigation/params.ts new file mode 100644 index 00000000000..42dc36a6d4e --- /dev/null +++ b/ts/features/toyProfile/navigation/params.ts @@ -0,0 +1,5 @@ +import { TOY_PROFILE_ROUTES } from "./routes.ts"; + +export type ToyProfileParamsList = { + [TOY_PROFILE_ROUTES.PROFILE_MAIN]: undefined; +}; diff --git a/ts/features/toyProfile/navigation/routes.ts b/ts/features/toyProfile/navigation/routes.ts new file mode 100644 index 00000000000..25e6447a872 --- /dev/null +++ b/ts/features/toyProfile/navigation/routes.ts @@ -0,0 +1,3 @@ +export const TOY_PROFILE_ROUTES = { + PROFILE_MAIN: "PROFILE_MAIN" +} as const; diff --git a/ts/features/toyProfile/navigation/toyProfileNavigator.tsx b/ts/features/toyProfile/navigation/toyProfileNavigator.tsx new file mode 100644 index 00000000000..dff52a52afa --- /dev/null +++ b/ts/features/toyProfile/navigation/toyProfileNavigator.tsx @@ -0,0 +1,24 @@ +import { createStackNavigator } from "@react-navigation/stack"; +import { ProfileHomeScreen } from "../screens/ProfileHomeScreen.tsx"; +import { isGestureEnabled } from "../../../utils/navigation.ts"; +import { TOY_PROFILE_ROUTES } from "./routes"; +import { ToyProfileParamsList } from "./params.ts"; + +const Stack = createStackNavigator(); + +/** + * A navigator for all the screens of the Settings section + */ +const ToyProfileNavigator = () => ( + + + +); + +export default ToyProfileNavigator; diff --git a/ts/features/toyProfile/saga/fetch.ts b/ts/features/toyProfile/saga/fetch.ts new file mode 100644 index 00000000000..73a62b6694e --- /dev/null +++ b/ts/features/toyProfile/saga/fetch.ts @@ -0,0 +1,54 @@ +import * as E from "fp-ts/lib/Either"; +import * as O from "fp-ts/lib/Option"; +import { call, put, takeLatest } from "typed-redux-saga/macro"; +import { getType } from "typesafe-actions"; +import { BackendClient } from "../../../api/backend.ts"; +import { ReduxSagaEffect, SagaCallReturnType } from "../../../types/utils.ts"; +import { ToyProfileResponse } from "../types"; +import { withRefreshApiCall } from "../../authentication/fastLogin/saga/utils"; +import { getToyProfileDetailsAction } from "../store/actions"; +import { getNetworkError } from "../../../utils/errors.ts"; + +export function* loadToyProfileSaga( + getProfile: ReturnType["getProfile"] +): Generator< + ReduxSagaEffect, + O.Option, + SagaCallReturnType +> { + try { + const resp = (yield* call( + withRefreshApiCall, + getProfile({}) + )) as unknown as SagaCallReturnType; + + if (E.isLeft(resp)) { + throw Error(`error: ${resp.left.map(e => e.message).join(", ")}`); + } + + if (resp.right.status === 200) { + yield* put( + getToyProfileDetailsAction.success( + resp.right.value as ToyProfileResponse + ) + ); + return O.some(resp.right.value as ToyProfileResponse); + } + + throw resp ? Error(`response status ${resp.right.status}`) : "No data"; + } catch (err) { + // FAILED ACTION + yield* put(getToyProfileDetailsAction.failure(getNetworkError(err))); + } + return O.none; +} + +export function* watchToyProfileSaga( + getProfile: ReturnType["getProfile"] +): Iterator { + yield* takeLatest( + getType(getToyProfileDetailsAction.request), + loadToyProfileSaga, + getProfile + ); +} diff --git a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx new file mode 100644 index 00000000000..3dfc2d384c8 --- /dev/null +++ b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx @@ -0,0 +1,94 @@ +import { + ContentWrapper, + Divider, + ListItemInfo +} from "@pagopa/io-app-design-system"; +import I18n from "i18next"; +import { useDispatch, useSelector } from "react-redux"; +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { useEffect } from "react"; +import { emptyContextualHelp } from "../../../utils/emptyContextualHelp.tsx"; +import { IOScrollViewWithLargeHeader } from "../../../components/ui/IOScrollViewWithLargeHeader.tsx"; +import { getToyProfileDetailsAction } from "../store/actions"; +import { toyProfileSelector } from "../store/selectors"; +import { useIOSelector } from "../../../store/hooks.ts"; + +const ProfileHomeScreen = () => { + const dispatch = useDispatch(); + const toyProfilePot = useSelector(toyProfileSelector); + + useEffect(() => { + dispatch(getToyProfileDetailsAction.request()); + }, [dispatch]); + + const isLoading = pot.isLoading(toyProfilePot); + const isError = pot.isError(toyProfilePot); + const isSome = pot.isSome(toyProfilePot); + + return ( + + + {isError && ( + + )} + {isSome && ( + <> + {toyProfilePot.value.name && toyProfilePot.value.family_name && ( + <> + + + + )} + {toyProfilePot.value.fiscal_code && ( + <> + + + + )} + {toyProfilePot.value.email && ( + <> + + + + )} + {toyProfilePot.value.date_of_birth && ( + <> + + + + )} + + )} + + + ); +}; + +export { ProfileHomeScreen }; diff --git a/ts/features/toyProfile/store/actions/index.ts b/ts/features/toyProfile/store/actions/index.ts new file mode 100644 index 00000000000..44b9fe6fa1c --- /dev/null +++ b/ts/features/toyProfile/store/actions/index.ts @@ -0,0 +1,10 @@ +import { createAsyncAction } from "typesafe-actions"; +import { NetworkError } from "../../../../utils/errors.ts"; +import { ToyProfileResponse } from "../../types"; + +export const getToyProfileDetailsAction = createAsyncAction( + "TOY_PROFILE_DETAILS_REQUEST", + "TOY_PROFILE_DETAILS_SUCCESS", + "TOY_PROFILE_DETAILS_FAILURE", + "TOY_PROFILE_DETAILS_CANCEL" +)(); diff --git a/ts/features/toyProfile/store/reducers/index.ts b/ts/features/toyProfile/store/reducers/index.ts new file mode 100644 index 00000000000..9c709cda5c7 --- /dev/null +++ b/ts/features/toyProfile/store/reducers/index.ts @@ -0,0 +1,23 @@ +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { getType } from "typesafe-actions"; + +import { ToyProfileResponse } from "../../types"; +import { getToyProfileDetailsAction } from "../actions"; + +export type ToyProfileState = pot.Pot; // forse era più corretto un RemoteValue, ma la traccia dice Pot + +export const toyProfileReducer = ( + state: ToyProfileState = pot.none, + action: any +): ToyProfileState => { + switch (action.type) { + case getType(getToyProfileDetailsAction.request): + return pot.toLoading(state); + case getType(getToyProfileDetailsAction.success): + return pot.some(action.payload); + case getType(getToyProfileDetailsAction.failure): + return pot.toError(state, action.payload); + default: + return state; + } +}; diff --git a/ts/features/toyProfile/store/selectors/index.ts b/ts/features/toyProfile/store/selectors/index.ts new file mode 100644 index 00000000000..b94daeaaa48 --- /dev/null +++ b/ts/features/toyProfile/store/selectors/index.ts @@ -0,0 +1,3 @@ +import { GlobalState } from "../../../../store/reducers/types.ts"; + +export const toyProfileSelector = (state: GlobalState) => state.toyProfile; diff --git a/ts/features/toyProfile/types/index.ts b/ts/features/toyProfile/types/index.ts new file mode 100644 index 00000000000..72c76553193 --- /dev/null +++ b/ts/features/toyProfile/types/index.ts @@ -0,0 +1,7 @@ +export type ToyProfileResponse = { + name: string; + family_name: string; + fiscal_code: string; + email: string; + date_of_birth?: Date; +}; diff --git a/ts/navigation/AuthenticatedStackNavigator.tsx b/ts/navigation/AuthenticatedStackNavigator.tsx index 53384007264..9416340974f 100644 --- a/ts/navigation/AuthenticatedStackNavigator.tsx +++ b/ts/navigation/AuthenticatedStackNavigator.tsx @@ -71,6 +71,7 @@ import { } from "../store/reducers/backendStatus/remoteConfig"; import { isGestureEnabled } from "../utils/navigation"; import OnboardingNavigator from "../features/onboarding/navigation/OnboardingNavigator.tsx"; +import ToyProfileNavigator from "../features/toyProfile/navigation/toyProfileNavigator.tsx"; import { AppParamsList } from "./params/AppParamsList"; import ROUTES from "./routes"; import { MainTabNavigator } from "./TabNavigator"; @@ -177,7 +178,7 @@ const AuthenticatedStackNavigator = () => { ...TransitionPresets.SlideFromRightIOS, gestureEnabled: isGestureEnabled }} - component={SettingsStackNavigator} + component={ToyProfileNavigator} /> = combineReducers< // // ephemeral state // + toyProfile: toyProfileReducer, appState: appStateReducer, navigation: navigationReducer, versionInfo: versionInfoReducer, @@ -198,6 +200,7 @@ export function createRootReducer( // eslint-disable-next-line no-param-reassign state = state ? ({ + toyProfile: state.toyProfile, authentication: { ...authenticationInitialState, diff --git a/ts/store/reducers/types.ts b/ts/store/reducers/types.ts index c27fee41b4a..c8dff4f9548 100644 --- a/ts/store/reducers/types.ts +++ b/ts/store/reducers/types.ts @@ -13,6 +13,7 @@ import { PersistedNotificationsState } from "../../features/pushNotifications/st import { ProfileState } from "../../features/settings/common/store/reducers"; import { UserDataProcessingState } from "../../features/settings/common/store/reducers/userDataProcessing"; import { TrialSystemState } from "../../features/trialSystem/store/reducers"; +import { ToyProfileState } from "../../features/toyProfile/store/reducers"; import { AppState } from "./appState"; import { AssistanceToolsState } from "./assistanceTools"; import { BackedInfoState } from "./backendStatus/backendInfo"; @@ -60,6 +61,7 @@ export type GlobalState = Readonly<{ startup: StartupState; lollipop: PersistedLollipopState; trialSystem: TrialSystemState; + toyProfile: ToyProfileState; }>; export type PersistedGlobalState = GlobalState & PersistPartial; From 6b05d704cf1935315ff89c308f42131f5e839c94 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Fri, 17 Oct 2025 07:33:16 +0200 Subject: [PATCH 03/12] fix startup saga --- ts/sagas/startup.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ts/sagas/startup.ts b/ts/sagas/startup.ts index caafa3acf0b..431cd5e3503 100644 --- a/ts/sagas/startup.ts +++ b/ts/sagas/startup.ts @@ -463,6 +463,7 @@ export function* initializeApplicationSaga( // Start watching for requests of refresh the profile yield* fork(watchProfileRefreshRequestsSaga, backendClient.getProfile); + yield* fork(watchToyProfileSaga, backendClient.getProfile); // Start watching for requests about session and support token yield* fork( @@ -502,8 +503,6 @@ export function* initializeApplicationSaga( yield* call(setLanguageFromProfileIfExists); } - yield* fork(watchToyProfileSaga, backendClient.getProfile); - const isFastLoginEnabled = yield* select(isFastLoginEnabledSelector); if (isFastLoginEnabled) { // At application startup, the state of the refresh token is "idle". From 76231483a03a079887b0159aa19ca84917793a8a Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Fri, 17 Oct 2025 08:21:34 +0200 Subject: [PATCH 04/12] step 2 --- locales/it/index.json | 3 +- .../toyProfile/screens/ProfileHomeScreen.tsx | 159 +++++++++++------- 2 files changed, 103 insertions(+), 59 deletions(-) diff --git a/locales/it/index.json b/locales/it/index.json index c79cf987b45..776d6b6472e 100644 --- a/locales/it/index.json +++ b/locales/it/index.json @@ -666,7 +666,8 @@ "nameSurname": "Nome e cognome", "fiscalCode": "Codice Fiscale", "birthDate" : "Data di nascita" - } + }, + "deleteState" : "Stato cancellazione profilo" }, "security": { "title": "Sicurezza e accesso", diff --git a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx index 3dfc2d384c8..6e031f72b76 100644 --- a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx +++ b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx @@ -1,10 +1,11 @@ import { ContentWrapper, Divider, - ListItemInfo + ListItemInfo, + ListItemSwitch } from "@pagopa/io-app-design-system"; import I18n from "i18next"; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; import * as pot from "@pagopa/ts-commons/lib/pot"; import { useEffect } from "react"; import { emptyContextualHelp } from "../../../utils/emptyContextualHelp.tsx"; @@ -12,18 +13,44 @@ import { IOScrollViewWithLargeHeader } from "../../../components/ui/IOScrollView import { getToyProfileDetailsAction } from "../store/actions"; import { toyProfileSelector } from "../store/selectors"; import { useIOSelector } from "../../../store/hooks.ts"; +import { loadUserDataProcessing } from "../../settings/common/store/actions/userDataProcessing.ts"; +import { UserDataProcessingChoiceEnum } from "../../../../definitions/backend/UserDataProcessingChoice.ts"; +import { userDataProcessingSelector } from "../../settings/common/store/selectors/userDataProcessing.ts"; +import LoadingSpinnerOverlay from "../../../components/LoadingSpinnerOverlay.tsx"; +import { UserDataProcessingStatusEnum } from "../../../../definitions/backend/UserDataProcessingStatus.ts"; const ProfileHomeScreen = () => { const dispatch = useDispatch(); - const toyProfilePot = useSelector(toyProfileSelector); + + const toyProfilePot = useIOSelector(toyProfileSelector); + const deletePot = useIOSelector( + s => userDataProcessingSelector(s)[UserDataProcessingChoiceEnum.DELETE] + ); useEffect(() => { dispatch(getToyProfileDetailsAction.request()); + dispatch( + loadUserDataProcessing.request(UserDataProcessingChoiceEnum.DELETE) + ); }, [dispatch]); - const isLoading = pot.isLoading(toyProfilePot); - const isError = pot.isError(toyProfilePot); - const isSome = pot.isSome(toyProfilePot); + const profile = pot.isSome(toyProfilePot) ? toyProfilePot.value : undefined; + + const delIsLoading = pot.isLoading(deletePot); + const delIsSome = pot.isSome(deletePot); + const delStatus = delIsSome ? deletePot.value?.status : undefined; + const delDisabled = delIsLoading || pot.isNone(deletePot); + const delValue = + !!delStatus && + [ + UserDataProcessingStatusEnum.PENDING, + UserDataProcessingStatusEnum.WIP, + UserDataProcessingStatusEnum.CLOSED + ].includes(delStatus); + + const onDeleteSwitchChange = (checked: boolean) => { + // TODO + }; return ( { faqCategories={["profile"]} headerActionsProp={{ showHelp: true }} > - - {isError && ( - - )} - {isSome && ( - <> - {toyProfilePot.value.name && toyProfilePot.value.family_name && ( - <> - - - - )} - {toyProfilePot.value.fiscal_code && ( - <> - - - - )} - {toyProfilePot.value.email && ( - <> - - - - )} - {toyProfilePot.value.date_of_birth && ( - <> - + + {pot.isError(toyProfilePot) && ( + + )} + + {profile && ( + <> + {profile.name && profile.family_name && ( + <> + + + + )} + + {profile.fiscal_code && ( + <> + + + + )} + + {profile.email && ( + <> + + + + )} + + {profile.date_of_birth && ( + <> + + + + )} + {delIsSome && ( + - - - )} - - )} - + )} + + )} + + ); }; From 379ee16fb49254eba5e617bd7991ecd4042f4349 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Fri, 17 Oct 2025 16:05:30 +0200 Subject: [PATCH 05/12] step 3 --- locales/it/index.json | 30 ++++- .../settings/common/navigation/routes.ts | 2 + .../toyProfile/components/ProfileFields.tsx | 75 ++++++++++++ ts/features/toyProfile/navigation/params.ts | 2 + ts/features/toyProfile/navigation/routes.ts | 4 +- .../navigation/toyProfileNavigator.tsx | 10 ++ .../screens/ProfileConfirmDeleteScreen.tsx | 103 ++++++++++++++++ .../toyProfile/screens/ProfileHomeScreen.tsx | 115 +++++------------- .../toyProfile/screens/ProfileWarnScreen.tsx | 58 +++++++++ 9 files changed, 310 insertions(+), 89 deletions(-) create mode 100644 ts/features/toyProfile/components/ProfileFields.tsx create mode 100644 ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx create mode 100644 ts/features/toyProfile/screens/ProfileWarnScreen.tsx diff --git a/locales/it/index.json b/locales/it/index.json index 8ff22ab9c09..89b5b4b279f 100644 --- a/locales/it/index.json +++ b/locales/it/index.json @@ -666,8 +666,36 @@ "nameSurname": "Nome e cognome", "fiscalCode": "Codice Fiscale", "birthDate" : "Data di nascita" + } + }, + "toy" : { + "main" : { + "email": "Indirizzo email", + "nameSurname": "Nome e cognome", + "fiscalCode": "Codice Fiscale", + "birthDate" : "Data di nascita", + "deleteState" : "Stato cancellazione profilo" + }, + "warn" : { + "header" : "Sei avvisato", + "headerTitle" : "Sicuro di voler procedere?", + "message" : "Sicuro **sicuro** Sicuro?", + "buttons" : { + "continue" : "Continua", + "cancel" : "Annulla" + } }, - "deleteState" : "Stato cancellazione profilo" + "confirm_delete" : { + "header" : "Ecco cosa stai per rimuovere...", + "header_deleted" : "Ecco cosa stai per rimuovere...", + "deleted" : "Hai richiesto correttamente la cancellazione", + "buttons" : { + "continue" : "Sono Sicuro", + "cancel" : "Annulla", + "retry" : "Riprova", + "close" : "Chiudi" + } + } }, "security": { "title": "Sicurezza e accesso", diff --git a/ts/features/settings/common/navigation/routes.ts b/ts/features/settings/common/navigation/routes.ts index ac3a234ca92..dd558f5918f 100644 --- a/ts/features/settings/common/navigation/routes.ts +++ b/ts/features/settings/common/navigation/routes.ts @@ -2,6 +2,8 @@ export const SETTINGS_ROUTES = { SETTINGS_MAIN: "SETTINGS_MAIN", PROFILE_NAVIGATOR: "PROFILE_NAVIGATOR", PROFILE_MAIN: "PROFILE_MAIN", + PROFILE_WARN: "PROFILE_WARN", + PROFILE_CONFIRM_DELETE: "PROFILE_CONFIRM_DELETE", PROFILE_PRIVACY: "PROFILE_PRIVACY", PROFILE_PRIVACY_MAIN: "PROFILE_PRIVACY_MAIN", PROFILE_PRIVACY_SHARE_DATA: "PROFILE_PRIVACY_SHARE_DATA", diff --git a/ts/features/toyProfile/components/ProfileFields.tsx b/ts/features/toyProfile/components/ProfileFields.tsx new file mode 100644 index 00000000000..871424f701d --- /dev/null +++ b/ts/features/toyProfile/components/ProfileFields.tsx @@ -0,0 +1,75 @@ +import { + Divider, + ListItemInfo, + LoadingSpinner +} from "@pagopa/io-app-design-system"; +import I18n from "i18next"; +import { IOIcons } from "@pagopa/io-app-design-system/src/components/icons/Icon.tsx"; +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { useIOSelector } from "../../../store/hooks.ts"; +import { toyProfileSelector } from "../store/selectors"; +import LoadingSpinnerOverlay from "../../../components/LoadingSpinnerOverlay.tsx"; + +const ProfileFieldItem = ({ + label, + icon, + value +}: { + label: string; + icon: IOIcons; + value?: string; +}) => + value ? ( + <> + + + + ) : null; + +export const ProfileFields = () => { + const toyProfilePot = useIOSelector(toyProfileSelector); + + const profile = pot.isSome(toyProfilePot) ? toyProfilePot.value : undefined; + + if (!profile) { + return ; + } + + return ( + + {pot.isError(toyProfilePot) && ( + + )} + {pot.isSome(toyProfilePot) && ( + <> + + + + {toyProfilePot.value.date_of_birth && ( + + )} + + )} + + ); +}; diff --git a/ts/features/toyProfile/navigation/params.ts b/ts/features/toyProfile/navigation/params.ts index 42dc36a6d4e..3946af0b153 100644 --- a/ts/features/toyProfile/navigation/params.ts +++ b/ts/features/toyProfile/navigation/params.ts @@ -2,4 +2,6 @@ import { TOY_PROFILE_ROUTES } from "./routes.ts"; export type ToyProfileParamsList = { [TOY_PROFILE_ROUTES.PROFILE_MAIN]: undefined; + [TOY_PROFILE_ROUTES.PROFILE_WARN]: undefined; + [TOY_PROFILE_ROUTES.PROFILE_CONFIRM_DELETE]: undefined; }; diff --git a/ts/features/toyProfile/navigation/routes.ts b/ts/features/toyProfile/navigation/routes.ts index 25e6447a872..f1e74faa164 100644 --- a/ts/features/toyProfile/navigation/routes.ts +++ b/ts/features/toyProfile/navigation/routes.ts @@ -1,3 +1,5 @@ export const TOY_PROFILE_ROUTES = { - PROFILE_MAIN: "PROFILE_MAIN" + PROFILE_MAIN: "PROFILE_MAIN", + PROFILE_WARN: "PROFILE_WARN", + PROFILE_CONFIRM_DELETE: "PROFILE_CONFIRM_DELETE" } as const; diff --git a/ts/features/toyProfile/navigation/toyProfileNavigator.tsx b/ts/features/toyProfile/navigation/toyProfileNavigator.tsx index dff52a52afa..81be2de6b85 100644 --- a/ts/features/toyProfile/navigation/toyProfileNavigator.tsx +++ b/ts/features/toyProfile/navigation/toyProfileNavigator.tsx @@ -1,6 +1,8 @@ import { createStackNavigator } from "@react-navigation/stack"; import { ProfileHomeScreen } from "../screens/ProfileHomeScreen.tsx"; import { isGestureEnabled } from "../../../utils/navigation.ts"; +import { ProfileWarnScreen } from "../screens/ProfileWarnScreen.tsx"; +import { ProfileConfirmDeleteScreen } from "../screens/ProfileConfirmDeleteScreen.tsx"; import { TOY_PROFILE_ROUTES } from "./routes"; import { ToyProfileParamsList } from "./params.ts"; @@ -18,6 +20,14 @@ const ToyProfileNavigator = () => ( name={TOY_PROFILE_ROUTES.PROFILE_MAIN} component={ProfileHomeScreen} /> + + ); diff --git a/ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx b/ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx new file mode 100644 index 00000000000..49579713f93 --- /dev/null +++ b/ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx @@ -0,0 +1,103 @@ +import { ComponentProps } from "react"; +import I18n from "i18next"; +import { useDispatch } from "react-redux"; +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { ListItemInfo, LoadingSpinner } from "@pagopa/io-app-design-system"; +import { IOScrollView } from "../../../components/ui/IOScrollView.tsx"; +import { IOStackNavigationProp } from "../../../navigation/params/AppParamsList.ts"; +import { ToyProfileParamsList } from "../navigation/params.ts"; +import { IOScrollViewWithLargeHeader } from "../../../components/ui/IOScrollViewWithLargeHeader.tsx"; +import { ProfileFields } from "../components/ProfileFields.tsx"; +import { useIOSelector } from "../../../store/hooks.ts"; +import { userDataProcessingSelector } from "../../settings/common/store/selectors/userDataProcessing.ts"; +import { UserDataProcessingChoiceEnum } from "../../../../definitions/backend/UserDataProcessingChoice.ts"; +import { upsertUserDataProcessing } from "../../settings/common/store/actions/userDataProcessing.ts"; + +type Props = { + navigation: IOStackNavigationProp< + ToyProfileParamsList, + "PROFILE_CONFIRM_DELETE" + >; +}; + +export const ProfileConfirmDeleteScreen = ({ navigation }: Props) => { + const dispatch = useDispatch(); + + const deletePot = useIOSelector( + s => userDataProcessingSelector(s)[UserDataProcessingChoiceEnum.DELETE] + ); + + const delHasValue = pot.isSome(deletePot) && deletePot.value?.status; + + const renderActionProps = (): ComponentProps< + typeof IOScrollView + >["actions"] => { + const onDeletePress = () => { + dispatch( + upsertUserDataProcessing.request(UserDataProcessingChoiceEnum.DELETE) + ); + }; + + if (pot.isError(deletePot)) { + return { + type: "TwoButtons", + primary: { + label: I18n.t("profile.toy.confirm_delete.buttons.cancel"), + onPress: navigation.popToTop + }, + secondary: { + label: I18n.t("profile.toy.confirm_delete.buttons.retry"), + onPress: onDeletePress + } + }; + } + + if (delHasValue) { + return { + type: "SingleButton", + primary: { + label: I18n.t("profile.toy.confirm_delete.buttons.close"), + onPress: navigation.popToTop + } + }; + } + + return { + type: "TwoButtons", + primary: { + label: I18n.t("profile.toy.confirm_delete.buttons.cancel"), + onPress: navigation.popToTop + }, + secondary: { + label: I18n.t("profile.toy.confirm_delete.buttons.continue"), + onPress: onDeletePress + } + }; + }; + + return ( + + {pot.isError(deletePot) && ( + + )} + {pot.isLoading(deletePot) && } + {!delHasValue && } + {delHasValue && ( + + )} + + ); +}; diff --git a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx index 6e031f72b76..bbebffffc39 100644 --- a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx +++ b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx @@ -1,28 +1,28 @@ -import { - ContentWrapper, - Divider, - ListItemInfo, - ListItemSwitch -} from "@pagopa/io-app-design-system"; +import { ContentWrapper, ListItemSwitch } from "@pagopa/io-app-design-system"; import I18n from "i18next"; import { useDispatch } from "react-redux"; import * as pot from "@pagopa/ts-commons/lib/pot"; import { useEffect } from "react"; import { emptyContextualHelp } from "../../../utils/emptyContextualHelp.tsx"; import { IOScrollViewWithLargeHeader } from "../../../components/ui/IOScrollViewWithLargeHeader.tsx"; -import { getToyProfileDetailsAction } from "../store/actions"; -import { toyProfileSelector } from "../store/selectors"; import { useIOSelector } from "../../../store/hooks.ts"; import { loadUserDataProcessing } from "../../settings/common/store/actions/userDataProcessing.ts"; import { UserDataProcessingChoiceEnum } from "../../../../definitions/backend/UserDataProcessingChoice.ts"; import { userDataProcessingSelector } from "../../settings/common/store/selectors/userDataProcessing.ts"; -import LoadingSpinnerOverlay from "../../../components/LoadingSpinnerOverlay.tsx"; import { UserDataProcessingStatusEnum } from "../../../../definitions/backend/UserDataProcessingStatus.ts"; +import { SETTINGS_ROUTES } from "../../settings/common/navigation/routes.ts"; +import { IOStackNavigationProp } from "../../../navigation/params/AppParamsList.ts"; +import { ToyProfileParamsList } from "../navigation/params.ts"; +import { ProfileFields } from "../components/ProfileFields.tsx"; +import { getToyProfileDetailsAction } from "../store/actions"; -const ProfileHomeScreen = () => { +type Props = { + navigation: IOStackNavigationProp; +}; + +const ProfileHomeScreen = ({ navigation }: Props) => { const dispatch = useDispatch(); - const toyProfilePot = useIOSelector(toyProfileSelector); const deletePot = useIOSelector( s => userDataProcessingSelector(s)[UserDataProcessingChoiceEnum.DELETE] ); @@ -34,12 +34,9 @@ const ProfileHomeScreen = () => { ); }, [dispatch]); - const profile = pot.isSome(toyProfilePot) ? toyProfilePot.value : undefined; - - const delIsLoading = pot.isLoading(deletePot); - const delIsSome = pot.isSome(deletePot); - const delStatus = delIsSome ? deletePot.value?.status : undefined; - const delDisabled = delIsLoading || pot.isNone(deletePot); + const delStatus = pot.isSome(deletePot) ? deletePot.value?.status : undefined; + const delDisabled = + pot.isLoading(deletePot) || pot.isNone(deletePot) || !!delStatus; const delValue = !!delStatus && [ @@ -49,7 +46,9 @@ const ProfileHomeScreen = () => { ].includes(delStatus); const onDeleteSwitchChange = (checked: boolean) => { - // TODO + if (checked) { + navigation.navigate(SETTINGS_ROUTES.PROFILE_WARN); + } }; return ( @@ -61,75 +60,17 @@ const ProfileHomeScreen = () => { faqCategories={["profile"]} headerActionsProp={{ showHelp: true }} > - - - {pot.isError(toyProfilePot) && ( - - )} - - {profile && ( - <> - {profile.name && profile.family_name && ( - <> - - - - )} - - {profile.fiscal_code && ( - <> - - - - )} - - {profile.email && ( - <> - - - - )} - - {profile.date_of_birth && ( - <> - - - - )} - {delIsSome && ( - - )} - - )} - - + + + + ); }; diff --git a/ts/features/toyProfile/screens/ProfileWarnScreen.tsx b/ts/features/toyProfile/screens/ProfileWarnScreen.tsx new file mode 100644 index 00000000000..d40d862d5ff --- /dev/null +++ b/ts/features/toyProfile/screens/ProfileWarnScreen.tsx @@ -0,0 +1,58 @@ +import { FeatureInfo } from "@pagopa/io-app-design-system"; +import { ComponentProps } from "react"; +import I18n from "i18next"; +import { IOScrollViewWithLargeHeader } from "../../../components/ui/IOScrollViewWithLargeHeader.tsx"; +import { IOScrollView } from "../../../components/ui/IOScrollView.tsx"; +import IOMarkdown from "../../../components/IOMarkdown"; +import { IOStackNavigationProp } from "../../../navigation/params/AppParamsList.ts"; +import { ToyProfileParamsList } from "../navigation/params.ts"; +import { SETTINGS_ROUTES } from "../../settings/common/navigation/routes.ts"; + +type Props = { + navigation: IOStackNavigationProp; +}; + +export const ProfileWarnScreen = ({ navigation }: Props) => { + const onCancelPress = () => { + navigation.navigate(SETTINGS_ROUTES.PROFILE_MAIN); + }; + + const renderActionProps = (): ComponentProps< + typeof IOScrollView + >["actions"] => ({ + type: "TwoButtons", + primary: { + label: I18n.t("profile.toy.warn.buttons.cancel"), + onPress: onCancelPress, + testID: "addIbanButtonTestID" + }, + secondary: { + label: I18n.t("profile.toy.warn.buttons.continue"), + onPress: () => { + navigation.navigate(SETTINGS_ROUTES.PROFILE_CONFIRM_DELETE); + }, + testID: "continueButtonTestID" + } + }); + + return ( + + } + variant={"neutral"} + /> + + ); +}; From ef2d8277a1669b93f8062136c80c67cebe04f048 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Mon, 20 Oct 2025 12:51:30 +0200 Subject: [PATCH 06/12] Revert "microfix ios" This reverts commit e5648f0630e5a3bc465911ae24471ed34f631a52. --- ios/AppReviewModule.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/AppReviewModule.swift b/ios/AppReviewModule.swift index c064b183f77..c132fc700c2 100644 --- a/ios/AppReviewModule.swift +++ b/ios/AppReviewModule.swift @@ -8,7 +8,7 @@ import Foundation import UIKit import StoreKit -import SwiftUI +import SwiftUICore /** `@objc` attribute exposes Swift methods to the Objective-C runtime**/ @@ -19,14 +19,14 @@ class AppReviewModule: NSObject { let activeWindowScene = UIApplication.shared.connectedScenes.first { scene in return scene.activationState == .foregroundActive && scene is UIWindowScene } - + if #available(iOS 16.0, *) { if let scene = activeWindowScene as? UIWindowScene { AppStore.requestReview(in: scene) return } } - + if #available(iOS 14.0, *) { if let scene = activeWindowScene as? UIWindowScene { SKStoreReviewController.requestReview(in: scene) From 50523476c4b06a999f4bf371bd2e3f3de46eeb5c Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Wed, 22 Oct 2025 12:42:59 +0200 Subject: [PATCH 07/12] fix: split toyProfile saga in two files --- .../{fetch.ts => handleLoadToyProfileSaga.ts} | 17 +++-------------- ts/features/toyProfile/saga/index.ts | 16 ++++++++++++++++ ts/sagas/startup.ts | 2 +- 3 files changed, 20 insertions(+), 15 deletions(-) rename ts/features/toyProfile/saga/{fetch.ts => handleLoadToyProfileSaga.ts} (77%) create mode 100644 ts/features/toyProfile/saga/index.ts diff --git a/ts/features/toyProfile/saga/fetch.ts b/ts/features/toyProfile/saga/handleLoadToyProfileSaga.ts similarity index 77% rename from ts/features/toyProfile/saga/fetch.ts rename to ts/features/toyProfile/saga/handleLoadToyProfileSaga.ts index 73a62b6694e..649c171ec0c 100644 --- a/ts/features/toyProfile/saga/fetch.ts +++ b/ts/features/toyProfile/saga/handleLoadToyProfileSaga.ts @@ -1,15 +1,14 @@ import * as E from "fp-ts/lib/Either"; import * as O from "fp-ts/lib/Option"; -import { call, put, takeLatest } from "typed-redux-saga/macro"; -import { getType } from "typesafe-actions"; +import { call, put } from "typed-redux-saga/macro"; import { BackendClient } from "../../../api/backend.ts"; import { ReduxSagaEffect, SagaCallReturnType } from "../../../types/utils.ts"; -import { ToyProfileResponse } from "../types"; import { withRefreshApiCall } from "../../authentication/fastLogin/saga/utils"; import { getToyProfileDetailsAction } from "../store/actions"; import { getNetworkError } from "../../../utils/errors.ts"; +import { ToyProfileResponse } from "../types"; -export function* loadToyProfileSaga( +export function* handleLoadToyProfileSaga( getProfile: ReturnType["getProfile"] ): Generator< ReduxSagaEffect, @@ -42,13 +41,3 @@ export function* loadToyProfileSaga( } return O.none; } - -export function* watchToyProfileSaga( - getProfile: ReturnType["getProfile"] -): Iterator { - yield* takeLatest( - getType(getToyProfileDetailsAction.request), - loadToyProfileSaga, - getProfile - ); -} diff --git a/ts/features/toyProfile/saga/index.ts b/ts/features/toyProfile/saga/index.ts new file mode 100644 index 00000000000..4322740bc5e --- /dev/null +++ b/ts/features/toyProfile/saga/index.ts @@ -0,0 +1,16 @@ +import { takeLatest } from "typed-redux-saga/macro"; +import { getType } from "typesafe-actions"; +import { BackendClient } from "../../../api/backend.ts"; +import { ReduxSagaEffect } from "../../../types/utils.ts"; +import { getToyProfileDetailsAction } from "../store/actions"; +import { handleLoadToyProfileSaga } from "./handleLoadToyProfileSaga.ts"; + +export function* watchToyProfileSaga( + getProfile: ReturnType["getProfile"] +): Iterator { + yield* takeLatest( + getType(getToyProfileDetailsAction.request), + handleLoadToyProfileSaga, + getProfile + ); +} diff --git a/ts/sagas/startup.ts b/ts/sagas/startup.ts index c681dd6a9f0..8ba9d2a10b0 100644 --- a/ts/sagas/startup.ts +++ b/ts/sagas/startup.ts @@ -139,7 +139,7 @@ import { waitForNavigatorServiceInitialization } from "../navigation/saga/navigation"; import { checkShouldDisplaySendEngagementScreen } from "../features/pn/loginEngagement/sagas/checkShouldDisplaySendEngagementScreen"; -import { watchToyProfileSaga } from "../features/toyProfile/saga/fetch.ts"; +import { watchToyProfileSaga } from "../features/toyProfile/saga"; import { previousInstallationDataDeleteSaga } from "./installation"; import { askMixpanelOptIn, From 53a1693945288987697599467d047cb50e9cdec3 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Wed, 22 Oct 2025 12:46:34 +0200 Subject: [PATCH 08/12] fix: toyProfile navigation replaced with useIONavigation --- ts/features/settings/common/navigation/routes.ts | 2 -- .../screens/ProfileConfirmDeleteScreen.tsx | 12 +++--------- .../toyProfile/screens/ProfileHomeScreen.tsx | 13 +++++-------- .../toyProfile/screens/ProfileWarnScreen.tsx | 15 ++++++--------- ts/navigation/params/AppParamsList.ts | 5 +++++ 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/ts/features/settings/common/navigation/routes.ts b/ts/features/settings/common/navigation/routes.ts index dd558f5918f..ac3a234ca92 100644 --- a/ts/features/settings/common/navigation/routes.ts +++ b/ts/features/settings/common/navigation/routes.ts @@ -2,8 +2,6 @@ export const SETTINGS_ROUTES = { SETTINGS_MAIN: "SETTINGS_MAIN", PROFILE_NAVIGATOR: "PROFILE_NAVIGATOR", PROFILE_MAIN: "PROFILE_MAIN", - PROFILE_WARN: "PROFILE_WARN", - PROFILE_CONFIRM_DELETE: "PROFILE_CONFIRM_DELETE", PROFILE_PRIVACY: "PROFILE_PRIVACY", PROFILE_PRIVACY_MAIN: "PROFILE_PRIVACY_MAIN", PROFILE_PRIVACY_SHARE_DATA: "PROFILE_PRIVACY_SHARE_DATA", diff --git a/ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx b/ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx index 49579713f93..5a067bef520 100644 --- a/ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx +++ b/ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx @@ -4,8 +4,7 @@ import { useDispatch } from "react-redux"; import * as pot from "@pagopa/ts-commons/lib/pot"; import { ListItemInfo, LoadingSpinner } from "@pagopa/io-app-design-system"; import { IOScrollView } from "../../../components/ui/IOScrollView.tsx"; -import { IOStackNavigationProp } from "../../../navigation/params/AppParamsList.ts"; -import { ToyProfileParamsList } from "../navigation/params.ts"; +import { useIONavigation } from "../../../navigation/params/AppParamsList.ts"; import { IOScrollViewWithLargeHeader } from "../../../components/ui/IOScrollViewWithLargeHeader.tsx"; import { ProfileFields } from "../components/ProfileFields.tsx"; import { useIOSelector } from "../../../store/hooks.ts"; @@ -13,14 +12,9 @@ import { userDataProcessingSelector } from "../../settings/common/store/selector import { UserDataProcessingChoiceEnum } from "../../../../definitions/backend/UserDataProcessingChoice.ts"; import { upsertUserDataProcessing } from "../../settings/common/store/actions/userDataProcessing.ts"; -type Props = { - navigation: IOStackNavigationProp< - ToyProfileParamsList, - "PROFILE_CONFIRM_DELETE" - >; -}; +export const ProfileConfirmDeleteScreen = () => { + const navigation = useIONavigation(); -export const ProfileConfirmDeleteScreen = ({ navigation }: Props) => { const dispatch = useDispatch(); const deletePot = useIOSelector( diff --git a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx index bbebffffc39..5aebfbdb2cf 100644 --- a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx +++ b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx @@ -10,17 +10,14 @@ import { loadUserDataProcessing } from "../../settings/common/store/actions/user import { UserDataProcessingChoiceEnum } from "../../../../definitions/backend/UserDataProcessingChoice.ts"; import { userDataProcessingSelector } from "../../settings/common/store/selectors/userDataProcessing.ts"; import { UserDataProcessingStatusEnum } from "../../../../definitions/backend/UserDataProcessingStatus.ts"; -import { SETTINGS_ROUTES } from "../../settings/common/navigation/routes.ts"; -import { IOStackNavigationProp } from "../../../navigation/params/AppParamsList.ts"; -import { ToyProfileParamsList } from "../navigation/params.ts"; +import { useIONavigation } from "../../../navigation/params/AppParamsList.ts"; import { ProfileFields } from "../components/ProfileFields.tsx"; import { getToyProfileDetailsAction } from "../store/actions"; +import { TOY_PROFILE_ROUTES } from "../navigation/routes.ts"; -type Props = { - navigation: IOStackNavigationProp; -}; +const ProfileHomeScreen = () => { + const navigation = useIONavigation(); -const ProfileHomeScreen = ({ navigation }: Props) => { const dispatch = useDispatch(); const deletePot = useIOSelector( @@ -47,7 +44,7 @@ const ProfileHomeScreen = ({ navigation }: Props) => { const onDeleteSwitchChange = (checked: boolean) => { if (checked) { - navigation.navigate(SETTINGS_ROUTES.PROFILE_WARN); + navigation.navigate(TOY_PROFILE_ROUTES.PROFILE_WARN); } }; diff --git a/ts/features/toyProfile/screens/ProfileWarnScreen.tsx b/ts/features/toyProfile/screens/ProfileWarnScreen.tsx index d40d862d5ff..15b4301b173 100644 --- a/ts/features/toyProfile/screens/ProfileWarnScreen.tsx +++ b/ts/features/toyProfile/screens/ProfileWarnScreen.tsx @@ -4,17 +4,14 @@ import I18n from "i18next"; import { IOScrollViewWithLargeHeader } from "../../../components/ui/IOScrollViewWithLargeHeader.tsx"; import { IOScrollView } from "../../../components/ui/IOScrollView.tsx"; import IOMarkdown from "../../../components/IOMarkdown"; -import { IOStackNavigationProp } from "../../../navigation/params/AppParamsList.ts"; -import { ToyProfileParamsList } from "../navigation/params.ts"; -import { SETTINGS_ROUTES } from "../../settings/common/navigation/routes.ts"; +import { useIONavigation } from "../../../navigation/params/AppParamsList.ts"; +import { TOY_PROFILE_ROUTES } from "../navigation/routes.ts"; -type Props = { - navigation: IOStackNavigationProp; -}; +export const ProfileWarnScreen = () => { + const navigation = useIONavigation(); -export const ProfileWarnScreen = ({ navigation }: Props) => { const onCancelPress = () => { - navigation.navigate(SETTINGS_ROUTES.PROFILE_MAIN); + navigation.navigate(TOY_PROFILE_ROUTES.PROFILE_MAIN); }; const renderActionProps = (): ComponentProps< @@ -29,7 +26,7 @@ export const ProfileWarnScreen = ({ navigation }: Props) => { secondary: { label: I18n.t("profile.toy.warn.buttons.continue"), onPress: () => { - navigation.navigate(SETTINGS_ROUTES.PROFILE_CONFIRM_DELETE); + navigation.navigate(TOY_PROFILE_ROUTES.PROFILE_CONFIRM_DELETE); }, testID: "continueButtonTestID" } diff --git a/ts/navigation/params/AppParamsList.ts b/ts/navigation/params/AppParamsList.ts index 0b06e8f42bf..5796fefcafa 100644 --- a/ts/navigation/params/AppParamsList.ts +++ b/ts/navigation/params/AppParamsList.ts @@ -64,6 +64,7 @@ import { AUTHENTICATION_ROUTES } from "../../features/authentication/common/navi import { SettingsParamsList } from "../../features/settings/common/navigation/params/SettingsParamsList.ts"; import { SETTINGS_ROUTES } from "../../features/settings/common/navigation/routes.ts"; import { OnboardingParamsList } from "../../features/onboarding/navigation/params/OnboardingParamsList.ts"; +import { TOY_PROFILE_ROUTES } from "../../features/toyProfile/navigation/routes.ts"; import { CheckEmailParamsList } from "./CheckEmailParamsList"; import { MainTabParamsList } from "./MainTabParamsList"; @@ -118,6 +119,10 @@ export type AppParamsList = { [ITW_ROUTES.MAIN]: NavigatorScreenParams; [ITW_REMOTE_ROUTES.MAIN]: NavigatorScreenParams; [SERVICES_ROUTES.SERVICES_HOME]: undefined; + + [TOY_PROFILE_ROUTES.PROFILE_MAIN]: undefined; + [TOY_PROFILE_ROUTES.PROFILE_WARN]: undefined; + [TOY_PROFILE_ROUTES.PROFILE_CONFIRM_DELETE]: undefined; }; /** From 9578b657eae7f535eb94c9999d2c969c7d6990b3 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Wed, 22 Oct 2025 12:50:41 +0200 Subject: [PATCH 09/12] fix: toyProfileReduce's action is typed --- ts/features/toyProfile/store/actions/index.ts | 4 +++- ts/features/toyProfile/store/reducers/index.ts | 3 ++- ts/store/actions/types.ts | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ts/features/toyProfile/store/actions/index.ts b/ts/features/toyProfile/store/actions/index.ts index 44b9fe6fa1c..8e7ce3bc3bd 100644 --- a/ts/features/toyProfile/store/actions/index.ts +++ b/ts/features/toyProfile/store/actions/index.ts @@ -1,4 +1,4 @@ -import { createAsyncAction } from "typesafe-actions"; +import { ActionType, createAsyncAction } from "typesafe-actions"; import { NetworkError } from "../../../../utils/errors.ts"; import { ToyProfileResponse } from "../../types"; @@ -8,3 +8,5 @@ export const getToyProfileDetailsAction = createAsyncAction( "TOY_PROFILE_DETAILS_FAILURE", "TOY_PROFILE_DETAILS_CANCEL" )(); + +export type ToyProfileActions = ActionType; diff --git a/ts/features/toyProfile/store/reducers/index.ts b/ts/features/toyProfile/store/reducers/index.ts index 9c709cda5c7..c0d06416d2a 100644 --- a/ts/features/toyProfile/store/reducers/index.ts +++ b/ts/features/toyProfile/store/reducers/index.ts @@ -3,12 +3,13 @@ import { getType } from "typesafe-actions"; import { ToyProfileResponse } from "../../types"; import { getToyProfileDetailsAction } from "../actions"; +import { Action } from "../../../../store/actions/types.ts"; export type ToyProfileState = pot.Pot; // forse era più corretto un RemoteValue, ma la traccia dice Pot export const toyProfileReducer = ( state: ToyProfileState = pot.none, - action: any + action: Action ): ToyProfileState => { switch (action.type) { case getType(getToyProfileDetailsAction.request): diff --git a/ts/store/actions/types.ts b/ts/store/actions/types.ts index 6f9309aad84..37977b717fa 100644 --- a/ts/store/actions/types.ts +++ b/ts/store/actions/types.ts @@ -53,6 +53,7 @@ import { WhatsNewActions } from "../../features/whatsnew/store/actions"; import { ZendeskSupportActions } from "../../features/zendesk/store/actions"; import { GlobalState } from "../reducers/types"; import { SENDLoginEngagementActions } from "../../features/pn/loginEngagement/store/actions"; +import { ToyProfileActions } from "../../features/toyProfile/store/actions"; import { AnalyticsActions } from "./analytics"; import { ApplicationActions } from "./application"; import { BackendStatusActions } from "./backendStatus"; @@ -125,7 +126,8 @@ export type Action = | LoginPreferencesActions | AARFlowStateActions | BackgroundLinkingActions - | SENDLoginEngagementActions; + | SENDLoginEngagementActions + | ToyProfileActions; export type Dispatch = DispatchAPI; From 86fcc67a97cf9af52fa5f011feef3c093046e304 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Thu, 23 Oct 2025 16:09:04 +0200 Subject: [PATCH 10/12] fix: managed none state for profile fields --- locales/it/index.json | 1 + .../toyProfile/components/ProfileFields.tsx | 86 +++++++++---------- .../toyProfile/store/reducers/index.ts | 3 +- 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/locales/it/index.json b/locales/it/index.json index 89b5b4b279f..cdce7773d1f 100644 --- a/locales/it/index.json +++ b/locales/it/index.json @@ -674,6 +674,7 @@ "nameSurname": "Nome e cognome", "fiscalCode": "Codice Fiscale", "birthDate" : "Data di nascita", + "error_none" : "Nessun Profilo Caricato", "deleteState" : "Stato cancellazione profilo" }, "warn" : { diff --git a/ts/features/toyProfile/components/ProfileFields.tsx b/ts/features/toyProfile/components/ProfileFields.tsx index 871424f701d..41515f84406 100644 --- a/ts/features/toyProfile/components/ProfileFields.tsx +++ b/ts/features/toyProfile/components/ProfileFields.tsx @@ -8,7 +8,8 @@ import { IOIcons } from "@pagopa/io-app-design-system/src/components/icons/Icon. import * as pot from "@pagopa/ts-commons/lib/pot"; import { useIOSelector } from "../../../store/hooks.ts"; import { toyProfileSelector } from "../store/selectors"; -import LoadingSpinnerOverlay from "../../../components/LoadingSpinnerOverlay.tsx"; +import { OperationResultScreenContent } from "../../../components/screens/OperationResultScreenContent.tsx"; +import { getNetworkErrorMessage } from "../../../utils/errors.ts"; const ProfileFieldItem = ({ label, @@ -19,57 +20,56 @@ const ProfileFieldItem = ({ icon: IOIcons; value?: string; }) => - value ? ( + value && ( <> - ) : null; + ); export const ProfileFields = () => { const toyProfilePot = useIOSelector(toyProfileSelector); - const profile = pot.isSome(toyProfilePot) ? toyProfilePot.value : undefined; - - if (!profile) { - return ; - } - - return ( - - {pot.isError(toyProfilePot) && ( - - )} - {pot.isSome(toyProfilePot) && ( - <> - - + return pot.fold( + toyProfilePot, + () => ( + + ), + () => , + () => , + e => , + profileData => ( + <> + + + + {profileData.date_of_birth && ( - {toyProfilePot.value.date_of_birth && ( - - )} - - )} - + )} + + ), + () => , + () => , + (_profileData, e) => ( + + ) ); }; diff --git a/ts/features/toyProfile/store/reducers/index.ts b/ts/features/toyProfile/store/reducers/index.ts index c0d06416d2a..1b7535e625c 100644 --- a/ts/features/toyProfile/store/reducers/index.ts +++ b/ts/features/toyProfile/store/reducers/index.ts @@ -4,8 +4,9 @@ import { getType } from "typesafe-actions"; import { ToyProfileResponse } from "../../types"; import { getToyProfileDetailsAction } from "../actions"; import { Action } from "../../../../store/actions/types.ts"; +import { NetworkError } from "../../../../utils/errors.ts"; -export type ToyProfileState = pot.Pot; // forse era più corretto un RemoteValue, ma la traccia dice Pot +export type ToyProfileState = pot.Pot; export const toyProfileReducer = ( state: ToyProfileState = pot.none, From df1949a84e0a9dd50b43dcfebc0f295a7383da32 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Thu, 23 Oct 2025 16:30:06 +0200 Subject: [PATCH 11/12] feat: refactor variables --- .../toyProfile/screens/ProfileHomeScreen.tsx | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx index 5aebfbdb2cf..2d699208f13 100644 --- a/ts/features/toyProfile/screens/ProfileHomeScreen.tsx +++ b/ts/features/toyProfile/screens/ProfileHomeScreen.tsx @@ -15,12 +15,17 @@ import { ProfileFields } from "../components/ProfileFields.tsx"; import { getToyProfileDetailsAction } from "../store/actions"; import { TOY_PROFILE_ROUTES } from "../navigation/routes.ts"; +const DELETE_ACTIVE_STATUSES = [ + UserDataProcessingStatusEnum.PENDING, + UserDataProcessingStatusEnum.WIP, + UserDataProcessingStatusEnum.CLOSED +]; + const ProfileHomeScreen = () => { const navigation = useIONavigation(); - const dispatch = useDispatch(); - const deletePot = useIOSelector( + const deleteUserDataPot = useIOSelector( s => userDataProcessingSelector(s)[UserDataProcessingChoiceEnum.DELETE] ); @@ -31,18 +36,20 @@ const ProfileHomeScreen = () => { ); }, [dispatch]); - const delStatus = pot.isSome(deletePot) ? deletePot.value?.status : undefined; - const delDisabled = - pot.isLoading(deletePot) || pot.isNone(deletePot) || !!delStatus; - const delValue = - !!delStatus && - [ - UserDataProcessingStatusEnum.PENDING, - UserDataProcessingStatusEnum.WIP, - UserDataProcessingStatusEnum.CLOSED - ].includes(delStatus); + const deleteRequestStatus = pot.isSome(deleteUserDataPot) + ? deleteUserDataPot.value?.status + : undefined; + + const isDeleteSwitchDisabled = + pot.isLoading(deleteUserDataPot) || + pot.isNone(deleteUserDataPot) || + !!deleteRequestStatus; + + const isDeleteInProgress = + !!deleteRequestStatus && + DELETE_ACTIVE_STATUSES.includes(deleteRequestStatus); - const onDeleteSwitchChange = (checked: boolean) => { + const handleDeleteSwitchToggle = (checked: boolean) => { if (checked) { navigation.navigate(TOY_PROFILE_ROUTES.PROFILE_WARN); } @@ -60,12 +67,12 @@ const ProfileHomeScreen = () => { From 1a5252a55b4f4c602f3058193d34a29c7a0f4567 Mon Sep 17 00:00:00 2001 From: pawelPrzywara Date: Thu, 23 Oct 2025 16:36:44 +0200 Subject: [PATCH 12/12] fix: separated success and error paths for toyProfileSaga --- .../saga/handleLoadToyProfileSaga.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ts/features/toyProfile/saga/handleLoadToyProfileSaga.ts b/ts/features/toyProfile/saga/handleLoadToyProfileSaga.ts index 649c171ec0c..c397b355c23 100644 --- a/ts/features/toyProfile/saga/handleLoadToyProfileSaga.ts +++ b/ts/features/toyProfile/saga/handleLoadToyProfileSaga.ts @@ -21,20 +21,21 @@ export function* handleLoadToyProfileSaga( getProfile({}) )) as unknown as SagaCallReturnType; - if (E.isLeft(resp)) { - throw Error(`error: ${resp.left.map(e => e.message).join(", ")}`); - } + if (E.isRight(resp)) { + const { status, value } = resp.right; + + if (status === 200) { + const payload = value as ToyProfileResponse; + yield* put(getToyProfileDetailsAction.success(payload)); + return O.some(payload); + } - if (resp.right.status === 200) { - yield* put( - getToyProfileDetailsAction.success( - resp.right.value as ToyProfileResponse - ) - ); - return O.some(resp.right.value as ToyProfileResponse); + throw new Error(`response status ${status}`); } - throw resp ? Error(`response status ${resp.right.status}`) : "No data"; + const messages = resp.left.map(e => e.message).join(", "); + + throw new Error(`error: ${messages}`); } catch (err) { // FAILED ACTION yield* put(getToyProfileDetailsAction.failure(getNetworkError(err)));