Skip to content
36 changes: 35 additions & 1 deletion locales/it/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,38 @@
"email": "Indirizzo email",
"need_validate": "Da validare",
"nameSurname": "Nome e cognome",
"fiscalCode": "Codice Fiscale"
"fiscalCode": "Codice Fiscale",
"birthDate" : "Data di nascita"
}
},
"toy" : {
"main" : {
"email": "Indirizzo email",
"nameSurname": "Nome e cognome",
"fiscalCode": "Codice Fiscale",
"birthDate" : "Data di nascita",
"error_none" : "Nessun Profilo Caricato",
"deleteState" : "Stato cancellazione profilo"
},
"warn" : {
"header" : "Sei avvisato",
"headerTitle" : "Sicuro di voler procedere?",
"message" : "Sicuro **sicuro** Sicuro?",
"buttons" : {
"continue" : "Continua",
"cancel" : "Annulla"
}
},
"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": {
Expand Down Expand Up @@ -3772,6 +3803,9 @@
}
},
"features": {
"profile" : {
"title" : "Profilo"
},
"messages": {
"pushNotifications": {
"banner": {
Expand Down
75 changes: 75 additions & 0 deletions ts/features/toyProfile/components/ProfileFields.tsx
Original file line number Diff line number Diff line change
@@ -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 { OperationResultScreenContent } from "../../../components/screens/OperationResultScreenContent.tsx";
import { getNetworkErrorMessage } from "../../../utils/errors.ts";

const ProfileFieldItem = ({
label,
icon,
value
}: {
label: string;
icon: IOIcons;
value?: string;
}) =>
value && (
<>
<ListItemInfo label={label} icon={icon} value={value} />
<Divider />
</>
);

export const ProfileFields = () => {
const toyProfilePot = useIOSelector(toyProfileSelector);

return pot.fold(
toyProfilePot,
() => (
<OperationResultScreenContent
title={I18n.t("profile.toy.main.error_none")}
/>
),
() => <LoadingSpinner />,
() => <LoadingSpinner />,
e => <OperationResultScreenContent title={getNetworkErrorMessage(e)} />,
profileData => (
<>
<ProfileFieldItem
label={I18n.t("profile.toy.main.nameSurname")}
icon="profile"
value={`${profileData.name} ${profileData.family_name}`}
/>
<ProfileFieldItem
label={I18n.t("profile.toy.main.fiscalCode")}
icon="fiscalCodeIndividual"
value={profileData.fiscal_code}
/>
<ProfileFieldItem
label={I18n.t("profile.toy.main.email")}
icon="email"
value={profileData.email}
/>
{profileData.date_of_birth && (
<ProfileFieldItem
label={I18n.t("profile.toy.main.birthDate")}
icon="calendar"
value={profileData.date_of_birth.toLocaleDateString(I18n.language)}
/>
)}
</>
),
() => <LoadingSpinner />,
() => <LoadingSpinner />,
(_profileData, e) => (
<OperationResultScreenContent title={getNetworkErrorMessage(e)} />
)
);
};
7 changes: 7 additions & 0 deletions ts/features/toyProfile/navigation/params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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;
};
5 changes: 5 additions & 0 deletions ts/features/toyProfile/navigation/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const TOY_PROFILE_ROUTES = {
PROFILE_MAIN: "PROFILE_MAIN",
PROFILE_WARN: "PROFILE_WARN",
PROFILE_CONFIRM_DELETE: "PROFILE_CONFIRM_DELETE"
} as const;
34 changes: 34 additions & 0 deletions ts/features/toyProfile/navigation/toyProfileNavigator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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";

const Stack = createStackNavigator<ToyProfileParamsList>();

/**
* A navigator for all the screens of the Settings section
*/
const ToyProfileNavigator = () => (
<Stack.Navigator
initialRouteName={TOY_PROFILE_ROUTES.PROFILE_MAIN}
screenOptions={{ gestureEnabled: isGestureEnabled, headerMode: "screen" }}
>
<Stack.Screen
name={TOY_PROFILE_ROUTES.PROFILE_MAIN}
component={ProfileHomeScreen}
/>
<Stack.Screen
name={TOY_PROFILE_ROUTES.PROFILE_WARN}
component={ProfileWarnScreen}
/>
<Stack.Screen
name={TOY_PROFILE_ROUTES.PROFILE_CONFIRM_DELETE}
component={ProfileConfirmDeleteScreen}
/>
</Stack.Navigator>
);

export default ToyProfileNavigator;
44 changes: 44 additions & 0 deletions ts/features/toyProfile/saga/handleLoadToyProfileSaga.ts
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case since the file contains both the handlers and the watcher, consider renaming the file to index.ts

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, just to follow our conventions, it will be better to separate this into two different files:

  • in index we can keep the watchToyProfileSaga
  • in another file the loadToyProfileSaga and I think it will be better renamed it handleLoadToyProfileSaga

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

solved with 5052347

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

solved with 5052347

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as E from "fp-ts/lib/Either";
import * as O from "fp-ts/lib/Option";
import { call, put } from "typed-redux-saga/macro";
import { BackendClient } from "../../../api/backend.ts";
import { ReduxSagaEffect, SagaCallReturnType } from "../../../types/utils.ts";
import { withRefreshApiCall } from "../../authentication/fastLogin/saga/utils";
import { getToyProfileDetailsAction } from "../store/actions";
import { getNetworkError } from "../../../utils/errors.ts";
import { ToyProfileResponse } from "../types";

export function* handleLoadToyProfileSaga(
getProfile: ReturnType<typeof BackendClient>["getProfile"]
): Generator<
ReduxSagaEffect,
O.Option<ToyProfileResponse>,
SagaCallReturnType<typeof getProfile>
> {
try {
const resp = (yield* call(
withRefreshApiCall,
getProfile({})
)) as unknown as SagaCallReturnType<typeof getProfile>;

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);
}

throw new Error(`response status ${status}`);
}

const messages = resp.left.map(e => e.message).join(", ");

throw new Error(`error: ${messages}`);
} catch (err) {
// FAILED ACTION
yield* put(getToyProfileDetailsAction.failure(getNetworkError(err)));
}
return O.none;
}
16 changes: 16 additions & 0 deletions ts/features/toyProfile/saga/index.ts
Original file line number Diff line number Diff line change
@@ -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<typeof BackendClient>["getProfile"]
): Iterator<ReduxSagaEffect> {
yield* takeLatest(
getType(getToyProfileDetailsAction.request),
handleLoadToyProfileSaga,
getProfile
);
}
97 changes: 97 additions & 0 deletions ts/features/toyProfile/screens/ProfileConfirmDeleteScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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 { 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";
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";

export const ProfileConfirmDeleteScreen = () => {
const navigation = useIONavigation();

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 (
<IOScrollViewWithLargeHeader
includeContentMargins
title={{
label: I18n.t(
delHasValue
? "profile.toy.confirm_delete.header_deleted"
: "profile.toy.confirm_delete.header"
)
}}
headerActionsProp={{
showHelp: true
}}
actions={renderActionProps()}
>
{pot.isError(deletePot) && (
<ListItemInfo value={deletePot.error.message} />
)}
{pot.isLoading(deletePot) && <LoadingSpinner />}
{!delHasValue && <ProfileFields />}
{delHasValue && (
<ListItemInfo value={I18n.t("profile.toy.confirm_delete.deleted")} />
)}
</IOScrollViewWithLargeHeader>
);
};
Loading