diff --git a/locales/en/index.yml b/locales/en/index.yml index eefede14497..9e6b43f426b 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -3068,7 +3068,8 @@ features: status: expired: Scaduta transactions: - title: Storico operazioni + multiplePayment: Pagamento multiplo + title: Ricevute pagoPA button: Vedi tutte empty: title: Qui vedrai le tue ricevute pagoPA @@ -3778,6 +3779,8 @@ bonusCard: removed: Rimossa transaction: details: + error: + title: É stato riscontrato un errore title: Dettaglio operazione totalAmount: Totale totalFee: Il totale comprende @@ -3788,12 +3791,19 @@ transaction: pspName: Gestore della transazione (PSP) dateAndHour: Data e ora transactionId: ID transazione + paymentMethod: Metodo di pagamento + authCode: Codice autorizzativo + rrn: RRN + executedBy: Eseguito da + headedTo: Intestato a operation: amount: Importo creditor: Ente creditore debtor: Debitore iuv: IUV subject: Oggetto del pagamento + noticeCode: Codice avviso + taxCode: Codice Fiscale Ente permissionRequest: gallery: title: Consenti a IO di accedere alle tue foto diff --git a/locales/it/index.yml b/locales/it/index.yml index 6826572d9a7..b51796adcf7 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -3068,7 +3068,8 @@ features: status: expired: Scaduta transactions: - title: Storico operazioni + multiplePayment: Pagamento multiplo + title: Ricevute pagoPA button: Vedi tutte empty: title: Qui vedrai le tue ricevute pagoPA @@ -3778,6 +3779,8 @@ bonusCard: removed: Rimossa transaction: details: + error: + title: É stato riscontrato un errore title: Dettaglio operazione totalAmount: Totale totalFee: Il totale comprende @@ -3788,12 +3791,19 @@ transaction: pspName: Gestore della transazione (PSP) dateAndHour: Data e ora transactionId: ID transazione + paymentMethod: Metodo di pagamento + authCode: Codice autorizzativo + rrn: RRN + executedBy: Eseguito da + headedTo: Intestato a operation: amount: Importo creditor: Ente creditore debtor: Debitore iuv: IUV subject: Oggetto del pagamento + noticeCode: Codice avviso + taxCode: Codice Fiscale Ente permissionRequest: gallery: title: Consenti a IO di accedere alle tue foto diff --git a/package.json b/package.json index 7d70bbf4f32..8f25577431f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.35.0-RELEASE/openapi/generated/api_fast_login.yaml", "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.35.1/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl", "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.35.1/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl", + "pagopa_api_biz_events": "https://raw.githubusercontent.com/pagopa/pagopa-infra/0a6784276fd43aaff7709dd90e0d379e77326f28/src/domains/bizevents-app/api/transaction-service/v1/_openapi-jwt.json.tpl", "trial_system": "https://raw.githubusercontent.com/pagopa/io-backend/features/add-openapi-trial-service/api_trial_system.yaml", "private": true, "scripts": { @@ -82,7 +83,8 @@ "generate:trial-system-api": "rimraf definitions/trial_systwem && mkdir -p definitions/trial_systwem && gen-api-models --api-spec $npm_package_trial_system --out-dir ./definitions/trial_systwem --no-strict --response-decoders --request-types --client", "generate:pagopa-walletv3-api": "rimraf definitions/pagopa/walletv3 && mkdir -p definitions/pagopa/walletv3 && gen-api-models --api-spec $npm_package_pagopa_api_walletv3 --out-dir ./definitions/pagopa/walletv3 --no-strict --response-decoders --request-types --client", "generate:pagopa-ecommerce-api": "rimraf definitions/pagopa/ecommerce && mkdir -p definitions/pagopa/ecommerce && gen-api-models --api-spec $npm_package_pagopa_api_ecommerce --out-dir ./definitions/pagopa/ecommerce --no-strict --response-decoders --request-types --client", - "generate:payments": "npm-run-all generate:pagopa-walletv3-api generate:pagopa-ecommerce-api", + "generate:pagopa-biz-events-api": "rimraf definitions/pagopa/biz-events && mkdir -p definitions/pagopa/biz-events && gen-api-models --api-spec $npm_package_pagopa_api_biz_events --out-dir ./definitions/pagopa/biz-events --no-strict --response-decoders --request-types --client", + "generate:payments": "npm-run-all generate:pagopa-walletv3-api generate:pagopa-ecommerce-api generate:pagopa-biz-events-api", "generate": "npm-run-all generate:*", "locales_unused": "ts-node --skip-project -O '{\"lib\":[\"es2015\"]}' scripts/unused-locales.ts", "remove_unused_locales": "ts-node --skip-project -O '{\"lib\":[\"es2015\"]}' scripts/remove-unused-locales.ts", @@ -227,7 +229,7 @@ "@babel/preset-typescript": "^7.16.7", "@babel/runtime": "^7.15.3", "@jambit/eslint-plugin-typed-redux-saga": "^0.4.0", - "@pagopa/openapi-codegen-ts": "^12.2.1", + "@pagopa/openapi-codegen-ts": "^13.2.0", "@react-native-community/eslint-config": "^3.0.1", "@testing-library/jest-native": "^3.4.3", "@testing-library/react-native": "^8.0.0", diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsListItemTransaction.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsListItemTransaction.tsx new file mode 100644 index 00000000000..98f7bb299a5 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsListItemTransaction.tsx @@ -0,0 +1,86 @@ +import { Avatar, ListItemTransaction } from "@pagopa/io-app-design-system"; +import * as O from "fp-ts/lib/Option"; +import { pipe } from "fp-ts/lib/function"; +import React from "react"; +import { getAccessibleAmountText } from "../../../../utils/accessibility"; +import { format } from "../../../../utils/dates"; +import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem"; +import { getTransactionLogo } from "../../common/utils"; +import { formatAmountText } from "../utils"; +import I18n from "../../../../i18n"; + +type Props = { + transaction: TransactionListItem; + onPress?: () => void; +}; + +const PaymentsBizEventsListItemTransaction = ({ + transaction, + onPress +}: Props) => { + const recipient = transaction.payeeName || ""; + + const amountText = pipe( + transaction.amount, + O.fromNullable, + O.map(amount => formatAmountText(amount)), + O.getOrElse(() => "") + ); + + const datetime: string = pipe( + transaction.transactionDate, + O.fromNullable, + O.map(transactionDate => format(transactionDate, "DD MMM YYYY, HH:mm")), + O.getOrElse(() => "") + ); + + const accessibleDatetime: string = pipe( + transaction.transactionDate, + O.fromNullable, + O.map(transactionDate => format(transactionDate, "DD MMMM YYYY, HH:mm")), + O.getOrElse(() => "") + ); + + const transactionPayeeLogoUri = getTransactionLogo(transaction); + + const accessibleAmountText = getAccessibleAmountText(amountText); + const accessibilityLabel = `${recipient}; ${accessibleDatetime}; ${accessibleAmountText}`; + + const TransactionEmptyIcon = () => ; + + const transactionLogo = pipe( + transactionPayeeLogoUri, + O.map(uri => ({ uri })), + O.getOrElseW(() => ) + ); + + if (transaction.isCart) { + return ( + } + onPress={onPress} + accessible={true} + title={I18n.t("features.payments.transactions.multiplePayment")} + subtitle={datetime} + transactionAmount={amountText} + accessibilityLabel={accessibilityLabel} + transactionStatus="success" + /> + ); + } + + return ( + + ); +}; + +export { PaymentsBizEventsListItemTransaction }; diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionCartList.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionCartList.tsx new file mode 100644 index 00000000000..b759dc7ff86 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionCartList.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { View } from "react-native"; +import Placeholder from "rn-placeholder"; +import { + HSpacer, + IOStyles, + ListItemTransaction, + VSpacer +} from "@pagopa/io-app-design-system"; +import { CartItem } from "../../../../../definitions/pagopa/biz-events/CartItem"; +import { formatAmountText } from "../utils"; + +type Props = { + carts?: ReadonlyArray; + loading: boolean; + onPress: (cartItem: CartItem) => void; +}; + +/** + * This component renders a list of transaction cart details + */ +export const PaymentsBizEventsTransactionCartList = ({ + carts, + loading, + onPress +}: Props) => { + if (loading) { + return ; + } + if (!carts) { + return null; + } + + return ( + <> + {carts.map((cartItem, index) => ( + onPress(cartItem)} + /> + ))} + + ); +}; + +const SkeletonTransactionDetailsList = () => ( + + + + + + + + + + +); diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionHeadingSection.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionHeadingSection.tsx new file mode 100644 index 00000000000..ce692fc1e3f --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionHeadingSection.tsx @@ -0,0 +1,99 @@ +import _ from "lodash"; +import { Body, IOStyles, VSpacer } from "@pagopa/io-app-design-system"; +import { useNavigation } from "@react-navigation/native"; +import React from "react"; +import { View } from "react-native"; +import Placeholder from "rn-placeholder"; +import { CartItem } from "../../../../../definitions/pagopa/biz-events/CartItem"; +import I18n from "../../../../i18n"; +import { TransactionDetailResponse } from "../../../../../definitions/pagopa/biz-events/TransactionDetailResponse"; +import { Psp } from "../../../../types/pagopa"; +import { formatAmountText } from "../utils"; +import { PaymentsTransactionBizEventsStackNavigation } from "../navigation/navigator"; +import { PaymentsTransactionBizEventsRoutes } from "../navigation/routes"; +import { PaymentsBizEventsTransactionCartList } from "./PaymentsBizEventsTransactionCartList"; +import { PaymentsBizEventsTransactionTotalAmount } from "./PaymentsBizEventsTransactionTotalAmount"; + +type Props = { + transaction?: TransactionDetailResponse; + psp?: Psp; + isLoading: boolean; +}; + +export const PaymentsBizEventsTransactionHeadingSection = ({ + transaction, + isLoading +}: Props) => { + const navigation = + useNavigation(); + + const transactionInfo = transaction?.infoTransaction; + + const handlePressTransactionDetails = (cartItem: CartItem) => { + if (transaction) { + navigation.navigate( + PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS, + { + cartItem + } + ); + } + }; + + const FeeAmountSection = () => { + if (isLoading) { + return ( + + + + + + + ); + } + if (transactionInfo?.fee !== undefined) { + const formattedFee = formatAmountText(transactionInfo.fee); + return ( + + {I18n.t("transaction.details.totalFee")}{" "} + {formattedFee}{" "} + {transactionInfo?.pspName + ? // we want to make sure no empty string is passed either + I18n.t("transaction.details.totalFeePsp", { + pspName: transactionInfo.pspName + }) + : I18n.t("transaction.details.totalFeeNoPsp")} + + ); + } + return null; + }; + + const calculateTotalAmount = () => { + if (transactionInfo?.amount && transactionInfo?.fee) { + return ( + _.toNumber(transactionInfo.amount) + _.toNumber(transactionInfo.fee) + ).toString(); + } + return transactionInfo?.amount; + }; + + return ( + + + + + + + + + + ); +}; diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionInfoSection.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionInfoSection.tsx new file mode 100644 index 00000000000..9f464c7dd2f --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionInfoSection.tsx @@ -0,0 +1,218 @@ +/* eslint-disable functional/immutable-data */ +import { capitalize } from "lodash"; +import * as React from "react"; +import { StyleSheet, View } from "react-native"; +import Placeholder from "rn-placeholder"; +import { + Divider, + IOLogoPaymentType, + IORadiusScale, + IOVisualCostants, + ListItemHeader, + ListItemInfo, + ListItemInfoCopy, + VSpacer +} from "@pagopa/io-app-design-system"; +import { IOStyles } from "../../../../components/core/variables/IOStyles"; +import I18n from "../../../../i18n"; +import { format } from "../../../../utils/dates"; +import { clipboardSetStringWithFeedback } from "../../../../utils/clipboard"; +import TransactionReceiptDivider from "../../../../../img/features/wallet/transaction-receipt-divider.svg"; +import { TransactionDetailResponse } from "../../../../../definitions/pagopa/biz-events/TransactionDetailResponse"; +import { WalletInfo } from "../../../../../definitions/pagopa/biz-events/WalletInfo"; + +type PaymentsBizEventsTransactionInfoSectionProps = { + transaction?: TransactionDetailResponse; + loading?: boolean; +}; + +const styles = StyleSheet.create({ + container: { + flexGrow: 1, + ...IOStyles.horizontalContentPadding + }, + contentCard: { + ...IOStyles.horizontalContentPadding, + ...IOStyles.bgWhite, + borderRadius: IORadiusScale["1"], + marginVertical: IOVisualCostants.appMarginDefault + } +}); + +/** + * Component that shows the biz-events transaction info + */ +const PaymentsBizEventsTransactionInfoSection = ({ + transaction, + loading +}: PaymentsBizEventsTransactionInfoSectionProps) => { + const transactionInfo = transaction?.infoTransaction; + return ( + <> + + + + + {loading && ( + <> + + + + + + + + + + + )} + {!loading && transactionInfo && ( + <> + {transactionInfo.payer && ( + <> + + + + )} + {transactionInfo.paymentMethod && transactionInfo.walletInfo && ( + <> + {renderPaymentMethod(transactionInfo.walletInfo)} + + + )} + {(transactionInfo.walletInfo?.maskedEmail || + transactionInfo.walletInfo?.accountHolder) && ( + <> + + + + )} + {transactionInfo.pspName && ( + <> + + + + )} + {transactionInfo.transactionDate && ( + <> + + + + )} + {transactionInfo.rrn && ( + <> + + clipboardSetStringWithFeedback(transactionInfo.rrn ?? "") + } + accessibilityLabel={`${I18n.t( + "transaction.details.info.rrn" + )}: ${transactionInfo.rrn}`} + label={I18n.t("transaction.details.info.rrn")} + value={transactionInfo.rrn} + /> + + + )} + {transactionInfo.authCode && ( + <> + + clipboardSetStringWithFeedback( + transactionInfo.authCode ?? "" + ) + } + accessibilityLabel={`${I18n.t( + "transaction.details.info.authCode" + )}: ${transactionInfo.authCode}`} + label={I18n.t("transaction.details.info.authCode")} + value={transactionInfo.authCode} + /> + + + )} + {transactionInfo.transactionId && ( + + clipboardSetStringWithFeedback( + transactionInfo.transactionId ?? "" + ) + } + accessibilityLabel={`${I18n.t( + "transaction.details.info.transactionId" + )}: ${transactionInfo.transactionId}`} + label={I18n.t("transaction.details.info.transactionId")} + value={transactionInfo.transactionId} + /> + )} + + )} + + + + ); +}; + +const renderPaymentMethod = (walletInfo: WalletInfo) => { + if (walletInfo.blurredNumber && walletInfo.brand) { + return ( + + ); + } + if (walletInfo.maskedEmail) { + return ( + + ); + } + return <>; +}; + +const SkeletonItem = () => ( + + + + + +); + +export default PaymentsBizEventsTransactionInfoSection; diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionTotalAmount.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionTotalAmount.tsx new file mode 100644 index 00000000000..ef1c2584b95 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionTotalAmount.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import Placeholder from "rn-placeholder"; +import { View } from "react-native"; +import { H3, H6, IOStyles } from "@pagopa/io-app-design-system"; +import I18n from "../../../../i18n"; +import { formatAmountText } from "../utils"; + +type TotalAmountSectionProps = { + totalAmount?: string; + loading?: boolean; +}; + +export const PaymentsBizEventsTransactionTotalAmount = ({ + totalAmount, + loading +}: TotalAmountSectionProps) => ( + +
{I18n.t("transaction.details.totalAmount")}
+ {loading && ( + + + + )} + {!loading && totalAmount &&

{formatAmountText(totalAmount)}

} +
+); diff --git a/ts/features/payments/bizEventsTransaction/navigation/navigator.tsx b/ts/features/payments/bizEventsTransaction/navigation/navigator.tsx new file mode 100644 index 00000000000..35a2fb76dbe --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/navigation/navigator.tsx @@ -0,0 +1,59 @@ +import { ParamListBase } from "@react-navigation/native"; +import { + createStackNavigator, + StackNavigationProp +} from "@react-navigation/stack"; +import React from "react"; +import { isGestureEnabled } from "../../../../utils/navigation"; +import { PaymentsTransactionBizEventsDetailsScreen } from "../screens/PaymentsTransactionBizEventsDetailsScreen"; +import { PaymentsTransactionBizEventsListScreen } from "../screens/PaymentsTransactionBizEventsListScreen"; +import WalletTransactionCartItemDetailsScreen from "../screens/PaymentsTransactionBizEventsCartItemDetailsScreen"; +import { PaymentsTransactionBizEventsParamsList } from "./params"; +import { PaymentsTransactionBizEventsRoutes } from "./routes"; + +const Stack = createStackNavigator(); + +export const PaymentsTransactionBizEventsNavigator = () => ( + + + + + +); + +export type PaymentsTransactionBizEventsStackNavigationProp< + ParamList extends ParamListBase, + RouteName extends keyof ParamList = string +> = StackNavigationProp< + PaymentsTransactionBizEventsParamsList & ParamList, + RouteName +>; + +export type PaymentsTransactionBizEventsStackNavigation = + PaymentsTransactionBizEventsStackNavigationProp< + PaymentsTransactionBizEventsParamsList, + keyof PaymentsTransactionBizEventsParamsList + >; diff --git a/ts/features/payments/bizEventsTransaction/navigation/params.ts b/ts/features/payments/bizEventsTransaction/navigation/params.ts new file mode 100644 index 00000000000..28da72bc85e --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/navigation/params.ts @@ -0,0 +1,10 @@ +import { PaymentsTransactionBizEventsDetailsScreenParams } from "../screens/PaymentsTransactionBizEventsDetailsScreen"; +import { PaymentsTransactionBizEventsCartItemDetailsScreenParams } from "../screens/PaymentsTransactionBizEventsCartItemDetailsScreen"; +import { PaymentsTransactionBizEventsRoutes } from "./routes"; + +export type PaymentsTransactionBizEventsParamsList = { + [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR]: undefined; + [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS]: PaymentsTransactionBizEventsDetailsScreenParams; + [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS]: PaymentsTransactionBizEventsCartItemDetailsScreenParams; + [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_LIST_SCREEN]: undefined; +}; diff --git a/ts/features/payments/bizEventsTransaction/navigation/routes.ts b/ts/features/payments/bizEventsTransaction/navigation/routes.ts new file mode 100644 index 00000000000..422db8fae7c --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/navigation/routes.ts @@ -0,0 +1,10 @@ +export const PaymentsTransactionBizEventsRoutes = { + PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR: + "PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR", + PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS: + "PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS", + PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS: + "PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS", + PAYMENT_TRANSACTION_BIZ_EVENTS_LIST_SCREEN: + "PAYMENT_TRANSACTION_BIZ_EVENTS_LIST_SCREEN" +} as const; diff --git a/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactionDetails.ts b/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactionDetails.ts new file mode 100644 index 00000000000..7af433f9d15 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactionDetails.ts @@ -0,0 +1,81 @@ +import * as E from "fp-ts/lib/Either"; +import { call, put } from "typed-redux-saga/macro"; +import { ActionType } from "typesafe-actions"; +import { getPaymentsBizEventsTransactionDetailsAction } from "../store/actions"; +import { getGenericError, getNetworkError } from "../../../../utils/errors"; +import { TransactionClient } from "../../common/api/client"; +import { getOrFetchWalletSessionToken } from "../../checkout/saga/networking/handleWalletPaymentNewSessionToken"; +import { withRefreshApiCall } from "../../../fastLogin/saga/utils"; +import { readablePrivacyReport } from "../../../../utils/reporters"; +import { SagaCallReturnType } from "../../../../types/utils"; + +/** + * Handle the remote call to get the transaction details from the biz events API + @param getPaymentMethods + * @param action + */ +export function* handleGetBizEventsTransactionDetails( + getTransactionDetails: TransactionClient["getTransactionDetails"], + action: ActionType< + (typeof getPaymentsBizEventsTransactionDetailsAction)["request"] + > +) { + const sessionToken = yield* getOrFetchWalletSessionToken(); + + if (sessionToken === undefined) { + yield* put( + getPaymentsBizEventsTransactionDetailsAction.failure({ + ...getGenericError(new Error(`Missing session token`)) + }) + ); + return; + } + const getTransactionDetailsRequest = getTransactionDetails({ + Authorization: sessionToken, + "transaction-id": action.payload.transactionId + }); + + try { + const getTransactionDetailsResult = (yield* call( + withRefreshApiCall, + getTransactionDetailsRequest, + action + )) as unknown as SagaCallReturnType; + + if (E.isLeft(getTransactionDetailsResult)) { + yield* put( + getPaymentsBizEventsTransactionDetailsAction.failure({ + ...getGenericError( + new Error(readablePrivacyReport(getTransactionDetailsResult.left)) + ) + }) + ); + return; + } + + if (getTransactionDetailsResult.right.status === 200) { + yield* put( + getPaymentsBizEventsTransactionDetailsAction.success( + getTransactionDetailsResult.right.value + ) + ); + } else if (getTransactionDetailsResult.right.status !== 401) { + // The 401 status is handled by the withRefreshApiCall + yield* put( + getPaymentsBizEventsTransactionDetailsAction.failure({ + ...getGenericError( + new Error( + `response status code ${getTransactionDetailsResult.right.status}` + ) + ) + }) + ); + } + } catch (e) { + yield* put( + getPaymentsBizEventsTransactionDetailsAction.failure({ + ...getNetworkError(e) + }) + ); + } +} diff --git a/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactions.ts b/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactions.ts new file mode 100644 index 00000000000..1e65a4a9ba9 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactions.ts @@ -0,0 +1,86 @@ +import { pipe } from "fp-ts/lib/function"; +import * as E from "fp-ts/lib/Either"; +import { call, put } from "typed-redux-saga/macro"; +import { ActionType } from "typesafe-actions"; +import { getGenericError, getNetworkError } from "../../../../utils/errors"; +import { getPaymentsBizEventsTransactionsAction } from "../store/actions"; +import { TransactionClient } from "../../common/api/client"; +import { getOrFetchWalletSessionToken } from "../../checkout/saga/networking/handleWalletPaymentNewSessionToken"; +import { withRefreshApiCall } from "../../../fastLogin/saga/utils"; +import { SagaCallReturnType } from "../../../../types/utils"; +import { readablePrivacyReport } from "../../../../utils/reporters"; +import { BizEventsHeaders } from "../utils/types"; + +const DEFAULT_TRANSACTION_LIST_SIZE = 10; + +export function* handleGetBizEventsTransactions( + getTransactionList: TransactionClient["getTransactionList"], + action: ActionType<(typeof getPaymentsBizEventsTransactionsAction)["request"]> +) { + const sessionToken = yield* getOrFetchWalletSessionToken(); + + if (sessionToken === undefined) { + yield* put( + getPaymentsBizEventsTransactionsAction.failure({ + ...getGenericError(new Error(`Missing session token`)) + }) + ); + return; + } + const getTransactionListRequest = getTransactionList({ + size: action.payload.size || DEFAULT_TRANSACTION_LIST_SIZE, + Authorization: sessionToken, + "x-continuation-token": action.payload.continuationToken + }); + + try { + const getTransactionListResult = (yield* call( + withRefreshApiCall, + getTransactionListRequest, + action + )) as unknown as SagaCallReturnType; + + if (E.isLeft(getTransactionListResult)) { + yield* put( + getPaymentsBizEventsTransactionsAction.failure({ + ...getGenericError( + new Error(readablePrivacyReport(getTransactionListResult.left)) + ) + }) + ); + return; + } + if (getTransactionListResult.right.status === 200) { + const continuationToken = pipe( + getTransactionListResult.right.headers, + BizEventsHeaders.decode, + E.map(headers => headers.map["x-continuation-token"]), + E.getOrElseW(() => undefined) + ); + action.payload.onSuccess?.(continuationToken); + yield* put( + getPaymentsBizEventsTransactionsAction.success({ + data: getTransactionListResult.right.value, + appendElements: action.payload.firstLoad + }) + ); + } else if (getTransactionListResult.right.status === 404) { + yield* put(getPaymentsBizEventsTransactionsAction.success({ data: [] })); + } else if (getTransactionListResult.right.status !== 401) { + // The 401 status is handled by the withRefreshApiCall + yield* put( + getPaymentsBizEventsTransactionsAction.failure({ + ...getGenericError( + new Error( + `response status code ${getTransactionListResult.right.status}` + ) + ) + }) + ); + } + } catch (e) { + yield* put( + getPaymentsBizEventsTransactionsAction.failure({ ...getNetworkError(e) }) + ); + } +} diff --git a/ts/features/payments/bizEventsTransaction/saga/handleGetLatestBizEventsTransactions.ts b/ts/features/payments/bizEventsTransaction/saga/handleGetLatestBizEventsTransactions.ts new file mode 100644 index 00000000000..90a4075fb67 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/saga/handleGetLatestBizEventsTransactions.ts @@ -0,0 +1,79 @@ +import * as E from "fp-ts/lib/Either"; +import { call, put } from "typed-redux-saga/macro"; +import { ActionType } from "typesafe-actions"; +import { getGenericError, getNetworkError } from "../../../../utils/errors"; +import { getPaymentsLatestBizEventsTransactionsAction } from "../store/actions"; +import { TransactionClient } from "../../common/api/client"; +import { getOrFetchWalletSessionToken } from "../../checkout/saga/networking/handleWalletPaymentNewSessionToken"; +import { withRefreshApiCall } from "../../../fastLogin/saga/utils"; +import { SagaCallReturnType } from "../../../../types/utils"; +import { readablePrivacyReport } from "../../../../utils/reporters"; + +const DEFAULT_LATEST_TRANSACTION_LIST_SIZE = 5; + +export function* handleGetLatestBizEventsTransactions( + getTransactionList: TransactionClient["getTransactionList"], + action: ActionType< + (typeof getPaymentsLatestBizEventsTransactionsAction)["request"] + > +) { + const sessionToken = yield* getOrFetchWalletSessionToken(); + + if (sessionToken === undefined) { + yield* put( + getPaymentsLatestBizEventsTransactionsAction.failure({ + ...getGenericError(new Error(`Missing session token`)) + }) + ); + return; + } + const getTransactionListRequest = getTransactionList({ + size: DEFAULT_LATEST_TRANSACTION_LIST_SIZE, + Authorization: sessionToken + }); + + try { + const getTransactionListResult = (yield* call( + withRefreshApiCall, + getTransactionListRequest, + action + )) as unknown as SagaCallReturnType; + + if (E.isLeft(getTransactionListResult)) { + yield* put( + getPaymentsLatestBizEventsTransactionsAction.failure({ + ...getGenericError( + new Error(readablePrivacyReport(getTransactionListResult.left)) + ) + }) + ); + return; + } + if (getTransactionListResult.right.status === 200) { + yield* put( + getPaymentsLatestBizEventsTransactionsAction.success( + getTransactionListResult.right.value + ) + ); + } else if (getTransactionListResult.right.status === 404) { + yield* put(getPaymentsLatestBizEventsTransactionsAction.success([])); + } else if (getTransactionListResult.right.status !== 401) { + // The 401 status is handled by the withRefreshApiCall + yield* put( + getPaymentsLatestBizEventsTransactionsAction.failure({ + ...getGenericError( + new Error( + `response status code ${getTransactionListResult.right.status}` + ) + ) + }) + ); + } + } catch (e) { + yield* put( + getPaymentsLatestBizEventsTransactionsAction.failure({ + ...getNetworkError(e) + }) + ); + } +} diff --git a/ts/features/payments/bizEventsTransaction/saga/index.ts b/ts/features/payments/bizEventsTransaction/saga/index.ts new file mode 100644 index 00000000000..81cd46e4334 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/saga/index.ts @@ -0,0 +1,38 @@ +import { SagaIterator } from "redux-saga"; +import { takeLatest } from "typed-redux-saga/macro"; + +import { TransactionClient } from "../../common/api/client"; +import { + getPaymentsLatestBizEventsTransactionsAction, + getPaymentsBizEventsTransactionDetailsAction, + getPaymentsBizEventsTransactionsAction +} from "../store/actions"; +import { handleGetLatestBizEventsTransactions } from "./handleGetLatestBizEventsTransactions"; +import { handleGetBizEventsTransactions } from "./handleGetBizEventsTransactions"; +import { handleGetBizEventsTransactionDetails } from "./handleGetBizEventsTransactionDetails"; + +/** + * Handle Wallet transaction requests + * @param bearerToken + */ +export function* watchPaymentsBizEventsTransactionSaga( + transactionClient: TransactionClient +): SagaIterator { + yield* takeLatest( + getPaymentsBizEventsTransactionsAction.request, + handleGetBizEventsTransactions, + transactionClient.getTransactionList + ); + + yield* takeLatest( + getPaymentsLatestBizEventsTransactionsAction.request, + handleGetLatestBizEventsTransactions, + transactionClient.getTransactionList + ); + + yield* takeLatest( + getPaymentsBizEventsTransactionDetailsAction.request, + handleGetBizEventsTransactionDetails, + transactionClient.getTransactionDetails + ); +} diff --git a/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsCartItemDetailsScreen.tsx b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsCartItemDetailsScreen.tsx new file mode 100644 index 00000000000..719d9dcff33 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsCartItemDetailsScreen.tsx @@ -0,0 +1,94 @@ +import * as React from "react"; +import { ScrollView } from "react-native"; +import { + Divider, + H6, + IOStyles, + ListItemInfo +} from "@pagopa/io-app-design-system"; +import { RouteProp, useRoute } from "@react-navigation/native"; +import { PaymentsTransactionBizEventsParamsList } from "../navigation/params"; +import I18n from "../../../../i18n"; +import { CartItem } from "../../../../../definitions/pagopa/biz-events/CartItem"; +import { UserDetail } from "../../../../../definitions/pagopa/biz-events/UserDetail"; +import { formatAmountText } from "../utils"; +import { IOScrollViewWithLargeHeader } from "../../../../components/ui/IOScrollViewWithLargeHeader"; + +export type PaymentsTransactionBizEventsCartItemDetailsScreenParams = { + cartItem: CartItem; +}; + +export type PaymentsTransactionBizEventsCartItemDetailsScreenProps = RouteProp< + PaymentsTransactionBizEventsParamsList, + "PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS" +>; + +const PaymentsTransactionBizEventsCartItemDetailsScreen = () => { + const route = + useRoute(); + const { cartItem } = route.params; + + const getDebtorText = (debtor: UserDetail) => ( + <> + {debtor.name ?
{debtor.name}
: null} + {debtor.taxCode ?
({debtor.taxCode})
: null} + + ); + + return ( + + + {cartItem.amount && ( + <> + + + + )} + {cartItem.payee && ( + <> + + + + )} + {cartItem.debtor && + (cartItem.debtor.name || cartItem.debtor.taxCode) && ( + <> + + + + )} + {cartItem.refNumberValue && ( + <> + + + + )} + {cartItem.payee && ( + + )} + + + ); +}; + +export default PaymentsTransactionBizEventsCartItemDetailsScreen; diff --git a/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsDetailsScreen.tsx b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsDetailsScreen.tsx new file mode 100644 index 00000000000..eecd71ebef9 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsDetailsScreen.tsx @@ -0,0 +1,118 @@ +import { IOColors } from "@pagopa/io-app-design-system"; +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { RouteProp, useRoute } from "@react-navigation/native"; +import * as React from "react"; +import { Dimensions, StyleSheet, View } from "react-native"; +import FocusAwareStatusBar from "../../../../components/ui/FocusAwareStatusBar"; +import I18n from "../../../../i18n"; +import { useIODispatch, useIOSelector } from "../../../../store/hooks"; +import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; +import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender"; +import { PaymentsBizEventsTransactionHeadingSection } from "../components/PaymentsBizEventsTransactionHeadingSection"; +import WalletTransactionInfoSection from "../components/PaymentsBizEventsTransactionInfoSection"; +import { PaymentsTransactionBizEventsParamsList } from "../navigation/params"; +import { getPaymentsBizEventsTransactionDetailsAction } from "../store/actions"; +import { walletTransactionBizEventsDetailsPotSelector } from "../store/selectors"; +import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent"; +import { useIONavigation } from "../../../../navigation/params/AppParamsList"; +import { IOScrollViewWithLargeHeader } from "../../../../components/ui/IOScrollViewWithLargeHeader"; + +export type PaymentsTransactionBizEventsDetailsScreenParams = { + transactionId: string; +}; + +export type PaymentsTransactionBizEventsDetailsScreenProps = RouteProp< + PaymentsTransactionBizEventsParamsList, + "PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS" +>; + +const windowHeight = Dimensions.get("window").height; + +const styles = StyleSheet.create({ + bottomBackground: { + position: "absolute", + height: windowHeight, + bottom: -windowHeight, + left: 0, + right: 0, + backgroundColor: IOColors["grey-50"] + }, + wrapper: { + flexGrow: 1, + alignContent: "flex-start", + backgroundColor: IOColors["grey-50"] + } +}); + +const PaymentsTransactionBizEventsDetailsScreen = () => { + const dispatch = useIODispatch(); + const navigation = useIONavigation(); + const route = useRoute(); + const { transactionId } = route.params; + const transactionDetailsPot = useIOSelector( + walletTransactionBizEventsDetailsPotSelector + ); + + const isLoading = pot.isLoading(transactionDetailsPot); + const isError = pot.isError(transactionDetailsPot); + const transactionDetails = pot.toUndefined(transactionDetailsPot); + + useOnFirstRender(() => { + dispatch( + getPaymentsBizEventsTransactionDetailsAction.request({ transactionId }) + ); + }); + + // eslint-disable-next-line sonarjs/no-identical-functions + const handleOnRetry = () => { + dispatch( + getPaymentsBizEventsTransactionDetailsAction.request({ transactionId }) + ); + }; + + if (isError) { + return ( + + ); + } + + return ( + + + + {/* The following line is used to show the background color gray that overlay the basic one which is white */} + + + + + + ); +}; + +export { PaymentsTransactionBizEventsDetailsScreen }; diff --git a/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsListScreen.tsx b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsListScreen.tsx new file mode 100644 index 00000000000..ddf45a66712 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsListScreen.tsx @@ -0,0 +1,203 @@ +import { + Divider, + H2, + IOStyles, + ListItemHeader, + ListItemTransaction +} from "@pagopa/io-app-design-system"; +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { RouteProp } from "@react-navigation/native"; +import * as React from "react"; +import { + LayoutChangeEvent, + SectionList, + SectionListData, + View +} from "react-native"; +import Animated, { + useAnimatedScrollHandler, + useSharedValue +} from "react-native-reanimated"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { useIODispatch, useIOSelector } from "../../../../store/hooks"; +import { PaymentsTransactionBizEventsParamsList } from "../navigation/params"; +import { getPaymentsBizEventsTransactionsAction } from "../store/actions"; +import { walletTransactionBizEventsListPotSelector } from "../store/selectors"; +import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem"; +import { useIONavigation } from "../../../../navigation/params/AppParamsList"; +import { isPaymentsTransactionsEmptySelector } from "../../home/store/selectors"; +import { PaymentsBizEventsListItemTransaction } from "../components/PaymentsBizEventsListItemTransaction"; +import { PaymentsHomeEmptyScreenContent } from "../../home/components/PaymentsHomeEmptyScreenContent"; +import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel"; +import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender"; +import { groupTransactionsByMonth } from "../utils"; +import I18n from "../../../../i18n"; +import { PaymentsTransactionBizEventsRoutes } from "../navigation/routes"; + +export type PaymentsTransactionBizEventsListScreenProps = RouteProp< + PaymentsTransactionBizEventsParamsList, + "PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS" +>; + +const AnimatedSectionList = Animated.createAnimatedComponent( + SectionList as new () => SectionList +); + +const PaymentsTransactionBizEventsListScreen = () => { + const dispatch = useIODispatch(); + const navigation = useIONavigation(); + + const scrollTranslationY = useSharedValue(0); + const [titleHeight, setTitleHeight] = React.useState(0); + const [continuationToken, setContinuationToken] = React.useState< + string | undefined + >(); + const [groupedTransactions, setGroupedTransactions] = + React.useState>>(); + const insets = useSafeAreaInsets(); + + const transactionsPot = useIOSelector( + walletTransactionBizEventsListPotSelector + ); + const isEmpty = useIOSelector(isPaymentsTransactionsEmptySelector); + + const isLoading = pot.isLoading(transactionsPot); + + const handleNavigateToTransactionDetails = ( + transaction: TransactionListItem + ) => { + if (transaction.transactionId === undefined) { + return; + } + navigation.navigate( + PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR, + { + screen: + PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS, + params: { + transactionId: transaction.transactionId + } + } + ); + }; + + const scrollHandler = useAnimatedScrollHandler(({ contentOffset }) => { + // eslint-disable-next-line functional/immutable-data + scrollTranslationY.value = contentOffset.y; + }); + + const getTitleHeight = (event: LayoutChangeEvent) => { + const { height } = event.nativeEvent.layout; + setTitleHeight(height); + }; + + const handleOnSuccess = (continuationToken?: string) => { + setContinuationToken(continuationToken); + }; + + useOnFirstRender( + React.useCallback(() => { + dispatch( + getPaymentsBizEventsTransactionsAction.request({ + firstLoad: true, + onSuccess: handleOnSuccess + }) + ); + }, [dispatch]) + ); + + React.useEffect(() => { + if (pot.isSome(transactionsPot)) { + setGroupedTransactions(groupTransactionsByMonth(transactionsPot.value)); + } + }, [transactionsPot]); + + useHeaderSecondLevel({ + title: I18n.t("features.payments.transactions.title"), + canGoBack: true, + supportRequest: true, + scrollValues: { + contentOffsetY: scrollTranslationY, + triggerOffset: titleHeight + } + }); + + const SectionListHeaderTitle = ( + +

+ {I18n.t("features.payments.transactions.title")} +

+
+ ); + + const renderLoadingFooter = () => ( + <> + {isLoading && + Array.from({ length: 5 }).map((_, index) => ( + + ))} + + ); + + if (isEmpty) { + return ; + } + + const fetchNextPage = () => { + if (!continuationToken || isLoading) { + return; + } + dispatch( + getPaymentsBizEventsTransactionsAction.request({ + continuationToken, + onSuccess: handleOnSuccess + }) + ); + }; + + return ( + ( + + )} + ListFooterComponent={renderLoadingFooter} + keyExtractor={item => `transaction_${item.transactionId}`} + renderItem={({ item }) => ( + handleNavigateToTransactionDetails(item)} + transaction={item} + /> + )} + /> + ); +}; + +export { PaymentsTransactionBizEventsListScreen }; diff --git a/ts/features/payments/bizEventsTransaction/store/actions/index.ts b/ts/features/payments/bizEventsTransaction/store/actions/index.ts new file mode 100644 index 00000000000..7c33ef7e972 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/store/actions/index.ts @@ -0,0 +1,47 @@ +import { ActionType, createAsyncAction } from "typesafe-actions"; +import { NetworkError } from "../../../../../utils/errors"; +import { TransactionDetailResponse } from "../../../../../../definitions/pagopa/biz-events/TransactionDetailResponse"; +import { TransactionListWrapResponse } from "../../../../../../definitions/pagopa/biz-events/TransactionListWrapResponse"; + +export type PaymentsGetBizEventsTransactionPayload = { + firstLoad?: boolean; + size?: number; + continuationToken?: string; + onSuccess?: (continuationToken?: string) => void; +}; + +export type PaymentsGetBizEventsTransactionSuccessPayload = { + data: TransactionListWrapResponse; + appendElements?: boolean; +}; + +export const getPaymentsBizEventsTransactionsAction = createAsyncAction( + "PAYMENTS_TRANSACTIONS_LIST_REQUEST", + "PAYMENTS_TRANSACTIONS_LIST_SUCCESS", + "PAYMENTS_TRANSACTIONS_LIST_FAILURE", + "PAYMENTS_TRANSACTIONS_LIST_CANCEL" +)< + PaymentsGetBizEventsTransactionPayload, + PaymentsGetBizEventsTransactionSuccessPayload, + NetworkError, + void +>(); + +export const getPaymentsLatestBizEventsTransactionsAction = createAsyncAction( + "PAYMENTS_LATEST_TRANSACTIONS_LIST_REQUEST", + "PAYMENTS_LATEST_TRANSACTIONS_LIST_SUCCESS", + "PAYMENTS_LATEST_TRANSACTIONS_LIST_FAILURE", + "PAYMENTS_LATEST_TRANSACTIONS_LIST_CANCEL" +)(); + +export const getPaymentsBizEventsTransactionDetailsAction = createAsyncAction( + "PAYMENTS_BIZ_EVENTS_TRANSACTION_DETAILS_REQUEST", + "PAYMENTS_BIZ_EVENTS_TRANSACTION_DETAILS_SUCCESS", + "PAYMENTS_BIZ_EVENTS_TRANSACTION_DETAILS_FAILURE", + "PAYMENTS_BIZ_EVENTS_TRANSACTION_DETAILS_CANCEL" +)<{ transactionId: string }, TransactionDetailResponse, NetworkError, void>(); + +export type PaymentsTransactionBizEventsActions = + | ActionType + | ActionType + | ActionType; diff --git a/ts/features/payments/bizEventsTransaction/store/reducers/index.ts b/ts/features/payments/bizEventsTransaction/store/reducers/index.ts new file mode 100644 index 00000000000..4ae878209d5 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/store/reducers/index.ts @@ -0,0 +1,105 @@ +import * as pot from "@pagopa/ts-commons/lib/pot"; +import { getType } from "typesafe-actions"; +import { Action } from "../../../../../store/actions/types"; +import { NetworkError } from "../../../../../utils/errors"; + +import { + getPaymentsBizEventsTransactionDetailsAction, + getPaymentsLatestBizEventsTransactionsAction, + getPaymentsBizEventsTransactionsAction +} from "../actions"; +import { TransactionListItem } from "../../../../../../definitions/pagopa/biz-events/TransactionListItem"; +import { TransactionDetailResponse } from "../../../../../../definitions/pagopa/biz-events/TransactionDetailResponse"; + +export type PaymentsBizEventsTransactionState = { + transactions: pot.Pot, NetworkError>; + latestTransactions: pot.Pot, NetworkError>; + details: pot.Pot; +}; + +const INITIAL_STATE: PaymentsBizEventsTransactionState = { + transactions: pot.noneLoading, + latestTransactions: pot.noneLoading, + details: pot.noneLoading +}; + +const reducer = ( + state: PaymentsBizEventsTransactionState = INITIAL_STATE, + action: Action +): PaymentsBizEventsTransactionState => { + switch (action.type) { + // GET LATEST TRANSACTIONS LIST + case getType(getPaymentsLatestBizEventsTransactionsAction.request): + return { + ...state, + latestTransactions: pot.toLoading(state.latestTransactions) + }; + case getType(getPaymentsLatestBizEventsTransactionsAction.success): + return { + ...state, + latestTransactions: pot.some(action.payload.transactions || []) + }; + case getType(getPaymentsLatestBizEventsTransactionsAction.failure): + return { + ...state, + latestTransactions: pot.toError( + state.latestTransactions, + action.payload + ) + }; + case getType(getPaymentsLatestBizEventsTransactionsAction.cancel): + return { + ...state, + latestTransactions: pot.none + }; + // GET TRANSACTIONS LIST + case getType(getPaymentsBizEventsTransactionsAction.request): + return { + ...state, + transactions: pot.toLoading(state.transactions) + }; + case getType(getPaymentsBizEventsTransactionsAction.success): + const previousTransactions = pot.getOrElse(state.transactions, []); + const maybeTransactions = action.payload.data.transactions || []; + return { + ...state, + transactions: !action.payload.appendElements + ? pot.some([...previousTransactions, ...maybeTransactions]) + : pot.some(maybeTransactions) + }; + case getType(getPaymentsBizEventsTransactionsAction.failure): + return { + ...state, + transactions: pot.toError(state.transactions, action.payload) + }; + case getType(getPaymentsBizEventsTransactionsAction.cancel): + return { + ...state, + transactions: pot.none + }; + // GET BIZ-EVENTS TRANSACTION DETAILS + case getType(getPaymentsBizEventsTransactionDetailsAction.request): + return { + ...state, + details: pot.toLoading(state.details) + }; + case getType(getPaymentsBizEventsTransactionDetailsAction.success): + return { + ...state, + details: pot.some(action.payload) + }; + case getType(getPaymentsBizEventsTransactionDetailsAction.failure): + return { + ...state, + details: pot.toError(state.details, action.payload) + }; + case getType(getPaymentsBizEventsTransactionDetailsAction.cancel): + return { + ...state, + details: pot.none + }; + } + return state; +}; + +export default reducer; diff --git a/ts/features/payments/bizEventsTransaction/store/selectors/index.ts b/ts/features/payments/bizEventsTransaction/store/selectors/index.ts new file mode 100644 index 00000000000..a962ed3b1c0 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/store/selectors/index.ts @@ -0,0 +1,19 @@ +import { GlobalState } from "../../../../../store/reducers/types"; + +const walletTransactionBizEventsSelector = (state: GlobalState) => + state.features.payments.bizEventsTransaction; + +export const walletTransactionBizEventsDetailsPotSelector = ( + state: GlobalState +) => walletTransactionBizEventsSelector(state).details; + +export const walletTransactionBizEventsBizEventsDetailsPotSelector = ( + state: GlobalState +) => walletTransactionBizEventsSelector(state).details; + +export const walletTransactionBizEventsListPotSelector = (state: GlobalState) => + walletTransactionBizEventsSelector(state).transactions; + +export const walletLatestTransactionsBizEventsListPotSelector = ( + state: GlobalState +) => walletTransactionBizEventsSelector(state).latestTransactions; diff --git a/ts/features/payments/bizEventsTransaction/utils/index.ts b/ts/features/payments/bizEventsTransaction/utils/index.ts new file mode 100644 index 00000000000..b6dc0959906 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/utils/index.ts @@ -0,0 +1,46 @@ +import { SectionListData } from "react-native"; +import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem"; + +/** + * Function that groups the transactions by month and returns an array of objects with the month as title and the transactions as data + * - The year is shown only if it's different from the current year + */ +export const groupTransactionsByMonth = ( + transactions: ReadonlyArray +): Array> => { + const groups = transactions.reduce((acc, element) => { + if (element.transactionDate !== undefined) { + const isCurrentYear = + new Date().getFullYear() === + new Date(element.transactionDate).getFullYear(); + const month = new Date(element.transactionDate).toLocaleString( + "default", + { + month: "long", + year: isCurrentYear ? undefined : "numeric" + } + ); + return { + ...acc, + [month]: [...(acc[month] || []), element] + }; + } + return acc; + }, {} as { [month: string]: Array }); + + return Object.keys(groups).map(month => ({ + title: month, + data: groups[month] + })); +}; + +// This function formats the text of the amount that is a string composed by "1000.00" in the format "1.000,00 €" allowing only two decimal digits +export const formatAmountText = (amount: string): string => { + const amountNumber = parseFloat(amount); + return amountNumber.toLocaleString("it-IT", { + style: "currency", + currency: "EUR", + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); +}; diff --git a/ts/features/payments/bizEventsTransaction/utils/types.ts b/ts/features/payments/bizEventsTransaction/utils/types.ts new file mode 100644 index 00000000000..8604fce0182 --- /dev/null +++ b/ts/features/payments/bizEventsTransaction/utils/types.ts @@ -0,0 +1,14 @@ +import * as t from "io-ts"; + +/** + * BizEvents headers + * This is the type of the headers returned by the BizEvents API + * due to resolve wrong type error in the auto-generated client + */ +export const BizEventsHeaders = t.type({ + map: t.type({ + "x-continuation-token": t.string + }) +}); + +export type BizEventsHeaders = t.TypeOf; diff --git a/ts/features/payments/common/api/client.ts b/ts/features/payments/common/api/client.ts index 353050b65cc..6b13f87ff5c 100644 --- a/ts/features/payments/common/api/client.ts +++ b/ts/features/payments/common/api/client.ts @@ -1,5 +1,6 @@ import { createClient } from "../../../../../definitions/pagopa/walletv3/client"; import { createClient as createECommerceClient } from "../../../../../definitions/pagopa/ecommerce/client"; +import { createClient as createBizEventsClient } from "../../../../../definitions/pagopa/biz-events/client"; import { defaultRetryingFetch } from "../../../../utils/fetch"; export const createWalletClient = (baseUrl: string, bearerAuth: string) => @@ -32,5 +33,21 @@ export const createPaymentClient = (baseUrl: string, token: string) => } }); +export const createTransactionClient = (baseUrl: string, token: string) => + createBizEventsClient<"walletId">({ + baseUrl, + basePath: "/bizevents/bizevents/tx-service-jwt/v1", + fetchApi: defaultRetryingFetch(), + withDefaults: op => params => { + const paramsWithDefaults = { + ...params, + walletId: token + } as Parameters[0]; + + return op(paramsWithDefaults); + } + }); + export type PaymentClient = ReturnType; export type WalletClient = ReturnType; +export type TransactionClient = ReturnType; diff --git a/ts/features/payments/common/saga/index.ts b/ts/features/payments/common/saga/index.ts index c68b76f3a6a..2907d30fbfc 100644 --- a/ts/features/payments/common/saga/index.ts +++ b/ts/features/payments/common/saga/index.ts @@ -3,11 +3,16 @@ import { fork, select } from "typed-redux-saga/macro"; import { isPagoPATestEnabledSelector } from "../../../../store/reducers/persistedPreferences"; import { watchPaymentsOnboardingSaga } from "../../onboarding/saga"; import { watchPaymentsCheckoutSaga } from "../../checkout/saga"; -import { createPaymentClient, createWalletClient } from "../api/client"; +import { + createPaymentClient, + createTransactionClient, + createWalletClient +} from "../api/client"; import { walletApiBaseUrl, walletApiUatBaseUrl } from "../../../../config"; import { watchPaymentsMethodDetailsSaga } from "../../details/saga"; import { watchPaymentsTransactionSaga } from "../../transaction/saga"; import { watchPaymentsWalletSaga } from "../../wallet/saga"; +import { watchPaymentsBizEventsTransactionSaga } from "../../bizEventsTransaction/saga"; export function* watchPaymentsSaga(walletToken: string): SagaIterator { const isPagoPATestEnabled = yield* select(isPagoPATestEnabledSelector); @@ -18,10 +23,12 @@ export function* watchPaymentsSaga(walletToken: string): SagaIterator { const walletClient = createWalletClient(walletBaseUrl, walletToken); const paymentClient = createPaymentClient(walletBaseUrl, walletToken); + const transactionClient = createTransactionClient(walletBaseUrl, walletToken); yield* fork(watchPaymentsWalletSaga, walletClient); yield* fork(watchPaymentsOnboardingSaga, walletClient); yield* fork(watchPaymentsMethodDetailsSaga, walletClient); yield* fork(watchPaymentsTransactionSaga, walletClient); + yield* fork(watchPaymentsBizEventsTransactionSaga, transactionClient); yield* fork(watchPaymentsCheckoutSaga, paymentClient); } diff --git a/ts/features/payments/common/store/actions/index.ts b/ts/features/payments/common/store/actions/index.ts index 5a94e81f706..ebcb68f8cd0 100644 --- a/ts/features/payments/common/store/actions/index.ts +++ b/ts/features/payments/common/store/actions/index.ts @@ -5,6 +5,7 @@ import { PaymentsCheckoutActions } from "../../../checkout/store/actions"; import { PaymentsTransactionActions } from "../../../transaction/store/actions"; import { PaymentsHomeActions } from "../../../home/store/actions"; import { PaymentsWalletActions } from "../../../wallet/store/actions"; +import { PaymentsTransactionBizEventsActions } from "../../../bizEventsTransaction/store/actions"; export type PaymentsActions = | PaymentsOnboardingActions @@ -13,4 +14,5 @@ export type PaymentsActions = | PaymentsTransactionActions | PaymentsHistoryActions | PaymentsHomeActions - | PaymentsWalletActions; + | PaymentsWalletActions + | PaymentsTransactionBizEventsActions; diff --git a/ts/features/payments/common/store/reducers/index.ts b/ts/features/payments/common/store/reducers/index.ts index 2d627b2e7cb..00a4377d6cb 100644 --- a/ts/features/payments/common/store/reducers/index.ts +++ b/ts/features/payments/common/store/reducers/index.ts @@ -19,6 +19,9 @@ import homeReducer, { PaymentsHomeState } from "../../../home/store/reducers"; import paymentsWalletReducer, { PaymentsWalletState } from "../../../wallet/store/reducers"; +import paymentsBizEventsTransactionReducer, { + PaymentsBizEventsTransactionState +} from "../../../bizEventsTransaction/store/reducers"; export type PaymentsState = { onboarding: PaymentsOnboardingState; @@ -28,6 +31,7 @@ export type PaymentsState = { history: PaymentsHistoryState & PersistPartial; home: PaymentsHomeState & PersistPartial; wallet: PaymentsWalletState; + bizEventsTransaction: PaymentsBizEventsTransactionState; }; const paymentsReducer = combineReducers({ @@ -37,7 +41,8 @@ const paymentsReducer = combineReducers({ transaction: transactionReducer, history: historyReducer, home: homeReducer, - wallet: paymentsWalletReducer + wallet: paymentsWalletReducer, + bizEventsTransaction: paymentsBizEventsTransactionReducer }); export default paymentsReducer; diff --git a/ts/features/payments/common/utils/index.ts b/ts/features/payments/common/utils/index.ts index 01f4efef7ac..9fa1f376857 100644 --- a/ts/features/payments/common/utils/index.ts +++ b/ts/features/payments/common/utils/index.ts @@ -17,6 +17,10 @@ import { PaymentCardProps } from "../components/PaymentCard"; import { UIWalletInfoDetails } from "../types/UIWalletInfoDetails"; import { findFirstCaseInsensitive } from "../../../../utils/object"; import { WalletCard } from "../../../newWallet/types"; +import { contentRepoUrl } from "../../../../config"; +import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem"; + +export const TRANSACTION_LOGO_CDN = `${contentRepoUrl}/logos/organizations`; /** * A simple function to get the corresponding translated badge text, @@ -162,6 +166,15 @@ export const getPaymentLogoFromWalletDetails = ( } }; +export const getTransactionLogo = (transaction: TransactionListItem) => + pipe( + transaction.payeeTaxCode, + O.fromNullable, + O.map( + taxCode => `${TRANSACTION_LOGO_CDN}/${taxCode.replace(/^0+/, "")}.png` + ) + ); + export const mapWalletIdToCardKey = (walletId: string) => `method_${walletId}`; export const mapWalletsToCards = ( diff --git a/ts/features/payments/home/components/PaymentsHomeTransactionsList.tsx b/ts/features/payments/home/components/PaymentsHomeTransactionsList.tsx index f5fe17648b4..47c2cd33030 100644 --- a/ts/features/payments/home/components/PaymentsHomeTransactionsList.tsx +++ b/ts/features/payments/home/components/PaymentsHomeTransactionsList.tsx @@ -1,4 +1,5 @@ import { + Divider, IOStyles, ListItemHeader, ListItemTransaction @@ -9,12 +10,15 @@ import * as React from "react"; import { View } from "react-native"; import Animated, { Layout } from "react-native-reanimated"; import { default as I18n } from "../../../../i18n"; -import { fetchTransactionsRequestWithExpBackoff } from "../../../../store/actions/wallet/transactions"; import { useIODispatch, useIOSelector } from "../../../../store/hooks"; -import { latestTransactionsSelector } from "../../../../store/reducers/wallet/transactions"; -import { isPaymentsTransactionsEmptySelector } from "../store/selectors"; +import { isPaymentsLatestTransactionsEmptySelector } from "../store/selectors"; +import { walletLatestTransactionsBizEventsListPotSelector } from "../../bizEventsTransaction/store/selectors"; +import { getPaymentsLatestBizEventsTransactionsAction } from "../../bizEventsTransaction/store/actions"; +import { PaymentsBizEventsListItemTransaction } from "../../bizEventsTransaction/components/PaymentsBizEventsListItemTransaction"; +import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem"; +import { useIONavigation } from "../../../../navigation/params/AppParamsList"; +import { PaymentsTransactionBizEventsRoutes } from "../../bizEventsTransaction/navigation/routes"; import { PaymentsHomeEmptyScreenContent } from "./PaymentsHomeEmptyScreenContent"; -import { PaymentsListItemTransaction } from "./PaymentsListItemTransaction"; type Props = { enforcedLoadingState?: boolean; @@ -22,27 +26,67 @@ type Props = { const PaymentsHomeTransactionsList = ({ enforcedLoadingState }: Props) => { const dispatch = useIODispatch(); + const navigation = useIONavigation(); - const transactionsPot = useIOSelector(latestTransactionsSelector); + const latestTransactionsPot = useIOSelector( + walletLatestTransactionsBizEventsListPotSelector + ); - const isLoading = pot.isLoading(transactionsPot) || enforcedLoadingState; - const isEmpty = useIOSelector(isPaymentsTransactionsEmptySelector); + const isLoading = + pot.isLoading(latestTransactionsPot) || enforcedLoadingState; + const isEmpty = useIOSelector(isPaymentsLatestTransactionsEmptySelector); useFocusEffect( React.useCallback(() => { - dispatch(fetchTransactionsRequestWithExpBackoff({ start: 0 })); + dispatch(getPaymentsLatestBizEventsTransactionsAction.request()); }, [dispatch]) ); + const handleNavigateToTransactionDetails = ( + transaction: TransactionListItem + ) => { + if (transaction.transactionId === undefined) { + return; + } + navigation.navigate( + PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR, + { + screen: + PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS, + params: { + transactionId: transaction.transactionId + } + } + ); + }; + + const handleNavigateToTransactionList = () => { + navigation.navigate( + PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR, + { + screen: + PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_LIST_SCREEN + } + ); + }; + const renderItems = () => { - if (!isLoading && pot.isSome(transactionsPot)) { + if (!isLoading && pot.isSome(latestTransactionsPot)) { return ( - {transactionsPot.value.map(transaction => ( - + {latestTransactionsPot.value.map((latestTransaction, index) => ( + + + handleNavigateToTransactionDetails(latestTransaction) + } + transaction={latestTransaction} + /> + {index < latestTransactionsPot.value.length - 1 && } + ))} ); @@ -73,6 +117,17 @@ const PaymentsHomeTransactionsList = ({ enforcedLoadingState }: Props) => { {renderItems()} diff --git a/ts/features/payments/home/screens/PaymentsHomeScreen.tsx b/ts/features/payments/home/screens/PaymentsHomeScreen.tsx index 67fc4d18854..dd3ed8a845b 100644 --- a/ts/features/payments/home/screens/PaymentsHomeScreen.tsx +++ b/ts/features/payments/home/screens/PaymentsHomeScreen.tsx @@ -9,9 +9,9 @@ import { PaymentsHomeEmptyScreenContent } from "../components/PaymentsHomeEmptyS import { PaymentsHomeTransactionsList } from "../components/PaymentsHomeTransactionsList"; import { PaymentsHomeUserMethodsList } from "../components/PaymentsHomeUserMethodsList"; import { + isPaymentsLatestTransactionsEmptySelector, isPaymentsSectionEmptySelector, - isPaymentsSectionLoadingSelector, - isPaymentsTransactionsEmptySelector + isPaymentsSectionLoadingSelector } from "../store/selectors"; const PaymentsHomeScreen = () => { @@ -19,7 +19,7 @@ const PaymentsHomeScreen = () => { const isLoading = useIOSelector(isPaymentsSectionLoadingSelector); const isTransactionsEmpty = useIOSelector( - isPaymentsTransactionsEmptySelector + isPaymentsLatestTransactionsEmptySelector ); const handleOnPayNoticedPress = () => { diff --git a/ts/features/payments/home/screens/__tests__/PaymentsHomeScreen.test.tsx b/ts/features/payments/home/screens/__tests__/PaymentsHomeScreen.test.tsx index 2d13966009a..acd30caac4e 100644 --- a/ts/features/payments/home/screens/__tests__/PaymentsHomeScreen.test.tsx +++ b/ts/features/payments/home/screens/__tests__/PaymentsHomeScreen.test.tsx @@ -4,16 +4,14 @@ import configureMockStore from "redux-mock-store"; import { WalletInfo } from "../../../../../../definitions/pagopa/walletv3/WalletInfo"; import { WalletStatusEnum } from "../../../../../../definitions/pagopa/walletv3/WalletStatus"; import { Wallets } from "../../../../../../definitions/pagopa/walletv3/Wallets"; -import { validTransaction } from "../../../../../__mocks__/paymentPayloads"; import ROUTES from "../../../../../navigation/routes"; import { applicationChangeState } from "../../../../../store/actions/application"; -import { IndexedById } from "../../../../../store/helpers/indexer"; import { appReducer } from "../../../../../store/reducers"; import { GlobalState } from "../../../../../store/reducers/types"; -import { Transaction } from "../../../../../types/pagopa"; import { NetworkError } from "../../../../../utils/errors"; import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; import { PaymentsHomeScreen } from "../PaymentsHomeScreen"; +import { TransactionListItem } from "../../../../../../definitions/pagopa/biz-events/TransactionListItem"; jest.mock("react-native-reanimated", () => ({ ...require("react-native-reanimated/mock"), @@ -22,6 +20,15 @@ jest.mock("react-native-reanimated", () => ({ } })); +const validTransaction: TransactionListItem = { + transactionId: "0e208420-19dc-490c-8f3f-5772b7249643", + payeeName: "Hessel, Muller and Kilback", + payeeTaxCode: "262700362", + amount: "246.53", + transactionDate: "2024-05-08T14:32:45.927Z", + isCart: true +}; + const MOCK_WALLET: WalletInfo = { walletId: "1", creationDate: new Date(), @@ -57,7 +64,7 @@ describe("PaymentsHomeScreen", () => { it("should render full empty screen content", () => { const { queryByTestId } = renderComponent({ - transactions: pot.some({}), + transactions: pot.some([]), userMethods: pot.some({ wallets: [] }) }); @@ -76,7 +83,7 @@ describe("PaymentsHomeScreen", () => { it("should render empty transactions content", () => { const { queryByTestId } = renderComponent({ - transactions: pot.some({}), + transactions: pot.some([]), userMethods: pot.some({ wallets: [MOCK_WALLET, MOCK_WALLET] }) }); @@ -95,7 +102,7 @@ describe("PaymentsHomeScreen", () => { it("should render empty methods content", () => { const { queryByTestId } = renderComponent({ - transactions: pot.some({ [validTransaction.id]: validTransaction }), + transactions: pot.some([validTransaction]), userMethods: pot.some({ wallets: [] }), shouldShowAddMethodsBanner: true }); @@ -113,7 +120,7 @@ describe("PaymentsHomeScreen", () => { it("should not render empty methods content", () => { const { queryByTestId } = renderComponent({ - transactions: pot.some({ [validTransaction.id]: validTransaction }), + transactions: pot.some([validTransaction]), userMethods: pot.some({ wallets: [] }), shouldShowAddMethodsBanner: false }); @@ -131,7 +138,7 @@ describe("PaymentsHomeScreen", () => { it("should render wallets and transactions content", () => { const { queryByTestId } = renderComponent({ - transactions: pot.some({ [validTransaction.id]: validTransaction }), + transactions: pot.some([validTransaction]), userMethods: pot.some({ wallets: [MOCK_WALLET] }), shouldShowAddMethodsBanner: false }); @@ -154,18 +161,13 @@ const renderComponent = ({ userMethods = pot.none, shouldShowAddMethodsBanner = true }: { - transactions?: pot.Pot, Error>; + transactions?: pot.Pot, Error>; userMethods?: pot.Pot; shouldShowAddMethodsBanner?: boolean; }) => { const globalState = appReducer(undefined, applicationChangeState("active")); const state: GlobalState = _.merge(null, globalState, { - wallet: { - transactions: { - transactions - } - }, features: { payments: { wallet: { @@ -173,6 +175,9 @@ const renderComponent = ({ }, home: { shouldShowAddMethodsBanner + }, + bizEventsTransaction: { + latestTransactions: transactions } } } diff --git a/ts/features/payments/home/store/selectors/index.ts b/ts/features/payments/home/store/selectors/index.ts index 272043757c3..626e82679ca 100644 --- a/ts/features/payments/home/store/selectors/index.ts +++ b/ts/features/payments/home/store/selectors/index.ts @@ -2,14 +2,17 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; import _ from "lodash"; import { createSelector } from "reselect"; import { GlobalState } from "../../../../../store/reducers/types"; -import { latestTransactionsSelector } from "../../../../../store/reducers/wallet/transactions"; import { paymentsWalletUserMethodsSelector } from "../../../wallet/store/selectors"; +import { + walletLatestTransactionsBizEventsListPotSelector, + walletTransactionBizEventsListPotSelector +} from "../../../bizEventsTransaction/store/selectors"; export const isPaymentsSectionLoadingSelector = createSelector( paymentsWalletUserMethodsSelector, - latestTransactionsSelector, - (methodsPot, transactionsPot) => - pot.isLoading(methodsPot) || pot.isLoading(transactionsPot) + walletLatestTransactionsBizEventsListPotSelector, + (methodsPot, latestTransactionsPot) => + pot.isLoading(methodsPot) || pot.isLoading(latestTransactionsPot) ); export const isPaymentsMethodsEmptySelector = createSelector( @@ -21,21 +24,27 @@ export const isPaymentsMethodsEmptySelector = createSelector( ) ); +export const isPaymentsLatestTransactionsEmptySelector = createSelector( + walletLatestTransactionsBizEventsListPotSelector, + latestTransactionsPot => + pot.getOrElse( + pot.map(latestTransactionsPot, transactions => transactions.length === 0), + false + ) +); + export const isPaymentsTransactionsEmptySelector = createSelector( - latestTransactionsSelector, + walletTransactionBizEventsListPotSelector, transactionsPot => pot.getOrElse( - pot.map( - transactionsPot, - transactions => _.values(transactions).length === 0 - ), + pot.map(transactionsPot, transactions => transactions.length === 0), false ) ); export const isPaymentsSectionEmptySelector = createSelector( isPaymentsMethodsEmptySelector, - isPaymentsTransactionsEmptySelector, + isPaymentsLatestTransactionsEmptySelector, (isMethodsEmpty, isTransactionsEmpty) => isMethodsEmpty && isTransactionsEmpty ); diff --git a/ts/navigation/AuthenticatedStackNavigator.tsx b/ts/navigation/AuthenticatedStackNavigator.tsx index 885c0a702e1..248b674e8d0 100644 --- a/ts/navigation/AuthenticatedStackNavigator.tsx +++ b/ts/navigation/AuthenticatedStackNavigator.tsx @@ -64,6 +64,8 @@ import { UAWebViewScreen } from "../features/uaDonations/screens/UAWebViewScreen import { ZendeskStackNavigator } from "../features/zendesk/navigation/navigator"; import ZENDESK_ROUTES from "../features/zendesk/navigation/routes"; import { GalleryPermissionInstructionsScreen } from "../screens/misc/GalleryPermissionInstructionsScreen"; +import { PaymentsTransactionBizEventsRoutes } from "../features/payments/bizEventsTransaction/navigation/routes"; +import { PaymentsTransactionBizEventsNavigator } from "../features/payments/bizEventsTransaction/navigation/navigator"; import { useIOSelector } from "../store/hooks"; import { isCdcEnabledSelector, @@ -340,6 +342,16 @@ const AuthenticatedStackNavigator = () => { ...hideHeaderOptions }} /> + ; [PaymentsMethodDetailsRoutes.PAYMENT_METHOD_DETAILS_NAVIGATOR]: NavigatorScreenParams; [PaymentsTransactionRoutes.PAYMENT_TRANSACTION_NAVIGATOR]: NavigatorScreenParams; + [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR]: NavigatorScreenParams; [ITW_ROUTES.MAIN]: NavigatorScreenParams; }; diff --git a/yarn.lock b/yarn.lock index 2a8bdb3ce5c..1ae3c69526b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3340,10 +3340,10 @@ resolved "https://registry.yarnpkg.com/@pagopa/io-react-native-zendesk/-/io-react-native-zendesk-0.3.29.tgz#ada8dab8a9ef15e126275baace2084491148bac8" integrity sha512-CskFyF0Sz+EC/ZyJNNMFEX/Avjyn6cKRwE2K+XSTGWDKYLImPraA8YozTljclCdv3DOxk3ZklUsieMG25PfnSw== -"@pagopa/openapi-codegen-ts@^12.2.1": - version "12.2.1" - resolved "https://registry.yarnpkg.com/@pagopa/openapi-codegen-ts/-/openapi-codegen-ts-12.2.1.tgz#e2cc9cedcbb23077b4f3f80e970ebd9c55e71afe" - integrity sha512-NO+z8+FUuR04Kc6I6fZk2mh+PWc1wrrBiTmcmnwuGGAWcObPqt4oF7b0fpAiZF91YuAMf5BOaXsFIIeSQv3i8g== +"@pagopa/openapi-codegen-ts@^13.2.0": + version "13.2.0" + resolved "https://registry.yarnpkg.com/@pagopa/openapi-codegen-ts/-/openapi-codegen-ts-13.2.0.tgz#ed55405c979b582dc11a8a9b900adc45b6c7379d" + integrity sha512-EKo4CJReQPT8z3a6P35p3eW86VIy9gcLOVrxIj8VR5bWRhVDfFjwAmpth9pIGQ0QXPmGuylbJbWQmWHrbGLnaA== dependencies: "@pagopa/ts-commons" "^10.15.0" fs-extra "^6.0.0"