Skip to content

Commit

Permalink
feat(Bonus Pagamenti Digitali): [#177142358] Added privative issuers …
Browse files Browse the repository at this point in the history
…configuration and choice screen (#2876)

* [#177142358] add privative config import

* [#177142358] add api

* [#177142358] add networking for privative config

* [#177142358] add privative trigger

* [#177142358] renaming and fixing

* [#177142358] fix import

* [#177142358] add screens

* [#177142358] search brandissuer

* [#177142358] update text

* [#177142358] add issuers

* [#177142358] add tests

* [#177142358] add fidelty name to privative issuer item

* copy suggestions

* [#177142358] fix typescript

* [#177142358] update comment

Co-authored-by: Simone <[email protected]>
Co-authored-by: Jacopo Pompilii <[email protected]>
  • Loading branch information
3 people authored Mar 10, 2021
1 parent 572f28e commit e8b559a
Show file tree
Hide file tree
Showing 25 changed files with 734 additions and 103 deletions.
10 changes: 10 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,16 @@ wallet:
description: "We will search for all cards registered to you at participating banks"
privative:
headerTitle: "Add supermarket card"
choosePrivativeIssuer:
loading: "Loading settings\n\nPlease wait a few seconds"
title: "Choose your card brand"
body: "You can only add cards that allow you to pay in supermarkets."
koDisabled:
title: "This supermarket doesn't offer this feature yet"
body: "We are working to allow you to add your card soon."
koUnavailable:
title: "We're unable to contact the server"
body: "We are working to solve the problem."
alert:
supportedCardPageLinkError: An error occurred while opening the supported cards page.
msgErrorUpdateApp: "An error occurred while opening the app store"
Expand Down
10 changes: 10 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,16 @@ wallet:
description: "Cercheremo tutte le carte intestate a te presso le banche aderenti al servizio"
privative:
headerTitle: "Aggiungi carta supermercato"
choosePrivativeIssuer:
loading: "Stiamo caricando le impostazioni\n\nAttendi qualche secondo"
title: "Scegli il marchio della tua carta"
body: "Puoi aggiungere soltanto carte che ti permettono di pagare nei supermercati."
koDisabled:
title: "Questo supermercato non offre ancora questa funzionalità"
body: "Siamo al lavoro per permetterti presto di poter aggiungere la tua carta."
koUnavailable:
title: "Non riusciamo a contattare il server"
body: "Siamo al lavoro per risolvere il problema."
alert:
supportedCardPageLinkError: Si è verificato un errore durante l'apertura della pagina di riferimento per le carte supportate.
msgErrorUpdateApp: "Si è verificato un errore durante l'apertura dello store delle app"
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"pagopa_api": "https://raw.githubusercontent.com/pagopa/io-services-metadata/master/pagopa/pm/spec.json",
"pagopa_api_walletv2": "https://raw.githubusercontent.com/pagopa/io-services-metadata/master/bonus/specs/bpd/pm/walletv2.json",
"pagopa_cobadge_configuration": "https://raw.githubusercontent.com/pagopa/io-services-metadata/master/pagopa/cobadge/abi_definitions.yml",
"pagopa_privative_configuration": "https://raw.githubusercontent.com/pagopa/io-services-metadata/master/pagopa/privative/definitions.yml",
"private": true,
"scripts": {
"start": "react-native start",
Expand Down Expand Up @@ -46,11 +47,12 @@
"generate:bpd-winning-transactions": "rimraf definitions/bpd/winning_transactions && mkdir -p definitions/bpd/winning_transactions && gen-api-models --api-spec $npm_package_io_bpd_winning_transactions --out-dir ./definitions/bpd/winning_transactions --no-strict --request-types --response-decoders",
"generate:pagopa-api-walletv2": "rimraf definitions/pagopa/walletv2 && mkdir -p definitions/pagopa/walletv2 && gen-api-models --api-spec $npm_package_pagopa_api_walletv2 --out-dir ./definitions/pagopa/walletv2 --no-strict --request-types --response-decoders",
"generate:pagopa-cobadge-configuration": "rimraf definitions/pagopa/cobadge/configuration && mkdir -p definitions/pagopa/cobadge/configuration && gen-api-models --api-spec $npm_package_pagopa_cobadge_configuration --out-dir ./definitions/pagopa/cobadge/configuration",
"generate:pagopa-privative-configuration": "rimraf definitions/pagopa/privative/configuration && mkdir -p definitions/pagopa/privative/configuration && gen-api-models --api-spec $npm_package_pagopa_privative_configuration --out-dir ./definitions/pagopa/privative/configuration",
"bundle:android-release": "node node_modules/react-native/local-cli/cli.js bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/",
"generate:locales": "ts-node --skip-project -O '{\"lib\":[\"es2015\"]}' scripts/make-locales.ts",
"generate:content-definitions": "rimraf definitions/content && mkdir -p definitions/content && gen-api-models --api-spec $npm_package_io_content_specs --out-dir ./definitions/content",
"generate:bpd": "npm-run-all generate:bpd-citizen generate:bpd-payment generate:bpd-award-periods generate:bpd-winning-transactions",
"generate:all": "npm-run-all generate:mock-google-services-json generate:api-definitions generate:pagopa-api-definitions generate:pagopa-api-walletv2 generate:pagopa-cobadge-configuration generate:content-definitions generate:bonus-vacanze-definitions generate:bpd generate:cgn-definitions generate:locales",
"generate:all": "npm-run-all generate:mock-google-services-json generate:api-definitions generate:pagopa-api-definitions generate:pagopa-api-walletv2 generate:pagopa-cobadge-configuration generate:pagopa-privative-configuration generate:content-definitions generate:bonus-vacanze-definitions generate:bpd generate:cgn-definitions generate:locales",
"locales_unused": "ts-node --skip-project -O '{\"lib\":[\"es2015\"]}' scripts/unused-locales.ts",
"generate-myportal-readme": "jsdoc2md ts/utils/webviewScripts/*.js > MYPORTAL_README.md"
},
Expand Down
21 changes: 20 additions & 1 deletion ts/api/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Municipality as MunicipalityMedadata } from "../../definitions/content/
import { Service as ServiceMetadata } from "../../definitions/content/Service";
import { ServicesByScope } from "../../definitions/content/ServicesByScope";
import { CoBadgeServices } from "../../definitions/pagopa/cobadge/configuration/CoBadgeServices";
import { PrivativeServices } from "../../definitions/pagopa/privative/configuration/PrivativeServices";
import { contentRepoUrl } from "../config";
import { CodiceCatastale } from "../types/MunicipalityCodiceCatastale";
import { defaultRetryingFetch } from "../utils/fetch";
Expand Down Expand Up @@ -129,6 +130,20 @@ const getCobadgeServicesT: GetCoBadgeServicesT = {
response_decoder: basicResponseDecoder(CoBadgeServices)
};

type GetPrivativeServicesT = IGetApiRequestType<
void,
never,
never,
BasicResponseType<PrivativeServices>
>;
const getPrivativeServicesT: GetPrivativeServicesT = {
method: "get",
url: () => "/status/privativeServices.json",
query: _ => ({}),
headers: () => ({}),
response_decoder: basicResponseDecoder(PrivativeServices)
};

/**
* A client for the static content
*/
Expand All @@ -145,6 +160,10 @@ export function ContentClient(fetchApi: typeof fetch = defaultRetryingFetch()) {
getServicesByScope: createFetchRequestForApi(getServicesByScopeT, options),
getContextualHelp: createFetchRequestForApi(getContextualHelpT, options),
getAbiList: createFetchRequestForApi(getAbisListT, options),
getCobadgeServices: createFetchRequestForApi(getCobadgeServicesT, options)
getCobadgeServices: createFetchRequestForApi(getCobadgeServicesT, options),
getPrivativeServices: createFetchRequestForApi(
getPrivativeServicesT,
options
)
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import IconFont from "../../../../../components/ui/IconFont";
import { useImageResize } from "../screens/hooks/useImageResize";

type Props = {
// TODO: change bank in info and use a generic type
bank: Abi;
inList: boolean;
onPress: (abi: string) => void;
Expand Down Expand Up @@ -46,6 +47,7 @@ const styles = StyleSheet.create({
}
});

// TODO: rename the component, in order to have a generic list item that accepts an image with a text
export const BankPreviewItem: React.FunctionComponent<Props> = (
props: Props
) => {
Expand Down
14 changes: 12 additions & 2 deletions ts/features/wallet/onboarding/privative/navigation/action.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { NavigationActions } from "react-navigation";
import WALLET_ONBOARDING_PRIVATIVE_ROUTES from "./routes";

export const navigateToOnboardingPrivativeChooseBrandScreen = () =>
export const navigateToOnboardingPrivativeChooseIssuerScreen = () =>
NavigationActions.navigate({
routeName: WALLET_ONBOARDING_PRIVATIVE_ROUTES.CHOOSE_BRAND
routeName: WALLET_ONBOARDING_PRIVATIVE_ROUTES.CHOOSE_ISSUER
});

export const navigateToOnboardingPrivativeInsertCardNumberScreen = () =>
NavigationActions.navigate({
routeName: WALLET_ONBOARDING_PRIVATIVE_ROUTES.INSERT_CARD_NUMBER
});

export const navigateToOnboardingPrivativeKoDisabledScreen = () =>
NavigationActions.navigate({
routeName: WALLET_ONBOARDING_PRIVATIVE_ROUTES.DISABLED_ISSUER
});

export const navigateToOnboardingPrivativeKoUnavailableScreen = () =>
NavigationActions.navigate({
routeName: WALLET_ONBOARDING_PRIVATIVE_ROUTES.UNAVAILABLE_ISSUER
});

export const navigateToOnboardingPrivativeSearchAvailable = () =>
NavigationActions.navigate({
routeName: WALLET_ONBOARDING_PRIVATIVE_ROUTES.SEARCH_AVAILABLE
Expand Down
14 changes: 11 additions & 3 deletions ts/features/wallet/onboarding/privative/navigation/navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@ import { createStackNavigator } from "react-navigation";
import ActivateBpdOnNewPrivativeScreen from "../screens/ActivateBpdOnNewPrivativeScreen";
import AddPrivativeCardScreen from "../screens/add/AddPrivativeCardScreen";
import AddPrivativeCardNumberScreen from "../screens/AddPrivativeCardNumberScreen";
import ChoosePrivativeBrandScreen from "../screens/ChoosePrivativeBrandScreen";
import ChoosePrivativeIssuerScreen from "../screens/choosePrivativeIssuer/ChoosePrivativeIssuerScreen";
import PrivativeIssuerKoDisabled from "../screens/choosePrivativeIssuer/ko/PrivativeIssuerKoDisabled";
import PrivativeIssuerKoUnavailable from "../screens/choosePrivativeIssuer/ko/PrivativeIssuerKoUnavailable";
import SearchPrivativeCardScreen from "../screens/search/SearchPrivativeCardScreen";
import WALLET_ONBOARDING_PRIVATIVE_ROUTES from "./routes";

const PaymentMethodOnboardingPrivativeNavigator = createStackNavigator(
{
[WALLET_ONBOARDING_PRIVATIVE_ROUTES.CHOOSE_BRAND]: {
screen: ChoosePrivativeBrandScreen
[WALLET_ONBOARDING_PRIVATIVE_ROUTES.CHOOSE_ISSUER]: {
screen: ChoosePrivativeIssuerScreen
},
[WALLET_ONBOARDING_PRIVATIVE_ROUTES.INSERT_CARD_NUMBER]: {
screen: AddPrivativeCardNumberScreen
},
[WALLET_ONBOARDING_PRIVATIVE_ROUTES.DISABLED_ISSUER]: {
screen: PrivativeIssuerKoDisabled
},
[WALLET_ONBOARDING_PRIVATIVE_ROUTES.UNAVAILABLE_ISSUER]: {
screen: PrivativeIssuerKoUnavailable
},
[WALLET_ONBOARDING_PRIVATIVE_ROUTES.SEARCH_AVAILABLE]: {
screen: SearchPrivativeCardScreen
},
Expand Down
5 changes: 4 additions & 1 deletion ts/features/wallet/onboarding/privative/navigation/routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const WALLET_ONBOARDING_PRIVATIVE_ROUTES = {
MAIN: "WALLET_ONBOARDING_PRIVATIVE_MAIN",

CHOOSE_BRAND: "WALLET_ONBOARDING_PRIVATIVE_CHOOSE_BRAND",
CHOOSE_ISSUER: "WALLET_ONBOARDING_PRIVATIVE_CHOOSE_ISSUER",
DISABLED_ISSUER: "WALLET_ONBOARDING_PRIVATIVE_DISABLED_ISSUER",
UNAVAILABLE_ISSUER: "WALLET_ONBOARDING_PRIVATIVE_UNAVAILABLE_ISSUER",

INSERT_CARD_NUMBER: "WALLET_ONBOARDING_PRIVATIVE_INSERT_CARD_NUMBER",

SEARCH_AVAILABLE: "WALLET_ONBOARDING_PRIVATIVE_SEARCH_AVAILABLE",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function* handleSearchUserPrivative(
const result = yield call(
searchUserCobadge,
{
abiCode: searchAction.payload.brandId,
abiCode: searchAction.payload.id,
panCode: searchAction.payload.cardNumber
},
getCobadgePans,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { readableReport } from "italia-ts-commons/lib/reporters";
import { call, put } from "redux-saga/effects";
import { ActionType } from "typesafe-actions";
import { ContentClient } from "../../../../../../api/content";
import { SagaCallReturnType } from "../../../../../../types/utils";
import { getNetworkError } from "../../../../../../utils/errors";
import { loadPrivativeIssuers } from "../../store/actions";

/**
* Load Privative Issuers configuration
*/
export function* handleLoadPrivativeConfiguration(
getPrivativeServices: ReturnType<
typeof ContentClient
>["getPrivativeServices"],
_: ActionType<typeof loadPrivativeIssuers.request>
) {
try {
const getPrivativeServicesResult: SagaCallReturnType<typeof getPrivativeServices> = yield call(
getPrivativeServices
);
if (getPrivativeServicesResult.isRight()) {
if (getPrivativeServicesResult.value.status === 200) {
yield put(
loadPrivativeIssuers.success(getPrivativeServicesResult.value.value)
);
} else {
throw new Error(
`response status ${getPrivativeServicesResult.value.status}`
);
}
} else {
throw new Error(readableReport(getPrivativeServicesResult.value));
}
} catch (e) {
yield put(loadPrivativeIssuers.failure(getNetworkError(e)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { SagaCallReturnType } from "../../../../../../types/utils";
import { activateBpdOnNewPaymentMethods } from "../../../../../bonus/bpd/saga/orchestration/activateBpdOnNewAddedPaymentMethods";
import {
navigateToActivateBpdOnNewPrivative,
navigateToOnboardingPrivativeChooseBrandScreen
navigateToOnboardingPrivativeChooseIssuerScreen
} from "../../navigation/action";
import WALLET_ONBOARDING_PRIVATIVE_ROUTES from "../../navigation/routes";
import {
Expand All @@ -31,8 +31,8 @@ import { onboardingPrivativeAddedSelector } from "../../store/reducers/addedPriv
*/
function* privativeWorkUnit() {
return yield call(executeWorkUnit, {
startScreenNavigation: navigateToOnboardingPrivativeChooseBrandScreen(),
startScreenName: WALLET_ONBOARDING_PRIVATIVE_ROUTES.CHOOSE_BRAND,
startScreenNavigation: navigateToOnboardingPrivativeChooseIssuerScreen(),
startScreenName: WALLET_ONBOARDING_PRIVATIVE_ROUTES.CHOOSE_ISSUER,
complete: walletAddPrivativeCompleted,
back: walletAddPrivativeBack,
cancel: walletAddPrivativeCancel
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { View } from "native-base";
import * as React from "react";
import { FlatList, ListRenderItemInfo, SafeAreaView } from "react-native";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { PrivativeServiceStatusEnum } from "../../../../../../../definitions/pagopa/privative/configuration/PrivativeServiceStatus";
import { Body } from "../../../../../../components/core/typography/Body";
import { H1 } from "../../../../../../components/core/typography/H1";
import { IOStyles } from "../../../../../../components/core/variables/IOStyles";
import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent";
import FooterWithButtons from "../../../../../../components/ui/FooterWithButtons";
import I18n from "../../../../../../i18n";
import { GlobalState } from "../../../../../../store/reducers/types";
import { emptyContextualHelp } from "../../../../../../utils/emptyContextualHelp";
import { cancelButtonProps } from "../../../../../bonus/bonusVacanze/components/buttons/ButtonConfigurations";
import { BankPreviewItem } from "../../../bancomat/components/BankPreviewItem";
import {
navigateToOnboardingPrivativeInsertCardNumberScreen,
navigateToOnboardingPrivativeKoDisabledScreen,
navigateToOnboardingPrivativeKoUnavailableScreen
} from "../../navigation/action";
import {
walletAddPrivativeCancel,
walletAddPrivativeChooseIssuer
} from "../../store/actions";
import { PrivativeIssuer } from "../../store/reducers/privativeIssuers";
import { PrivativeIssuerId } from "../../store/reducers/searchedPrivative";

type OwnProps = {
privativeIssuers: ReadonlyArray<PrivativeIssuer>;
};

type Props = OwnProps &
ReturnType<typeof mapDispatchToProps> &
ReturnType<typeof mapStateToProps>;

const loadLocales = () => ({
headerTitle: I18n.t("wallet.onboarding.privative.headerTitle"),
title: I18n.t("wallet.onboarding.privative.choosePrivativeIssuer.title"),
body: I18n.t("wallet.onboarding.privative.choosePrivativeIssuer.body")
});

const handlePress = (pi: PrivativeIssuer, props: Props) => {
switch (pi.status) {
case PrivativeServiceStatusEnum.enabled:
props.chooseIssuer(pi.id);
break;
case PrivativeServiceStatusEnum.disabled:
props.navigateToDisabled();
break;
case PrivativeServiceStatusEnum.unavailable:
props.navigateToUnavailable();
break;
}
};

const renderItem = (pi: ListRenderItemInfo<PrivativeIssuer>, props: Props) => (
<BankPreviewItem
bank={{
abi: pi.item.id,
name: `${pi.item.gdo} - ${pi.item.loyalty}`,
logoUrl: pi.item.gdoLogo
}}
inList={true}
onPress={_ => handlePress(pi.item, props)}
/>
);

const ChoosePrivativeIssuerComponent = (props: Props): React.ReactElement => {
const { headerTitle, title, body } = loadLocales();
return (
<BaseScreenComponent
goBack={true}
headerTitle={headerTitle}
contextualHelp={emptyContextualHelp}
>
<SafeAreaView
style={IOStyles.flex}
testID={"ChoosePrivativeIssuerComponent"}
>
<View style={[IOStyles.horizontalContentPadding, IOStyles.flex]}>
<H1>{title}</H1>
<View spacer={true} />
<Body>{body}</Body>
<FlatList
data={props.privativeIssuers}
renderItem={v => renderItem(v, props)}
keyExtractor={privativeIssuer => privativeIssuer.id}
keyboardShouldPersistTaps={"handled"}
/>
</View>
<FooterWithButtons
type={"SingleButton"}
leftButton={cancelButtonProps(props.cancel)}
/>
</SafeAreaView>
</BaseScreenComponent>
);
};

const mapDispatchToProps = (dispatch: Dispatch) => ({
cancel: () => dispatch(walletAddPrivativeCancel()),
chooseIssuer: (issuerId: PrivativeIssuerId) => {
dispatch(walletAddPrivativeChooseIssuer(issuerId));
dispatch(navigateToOnboardingPrivativeInsertCardNumberScreen());
},
navigateToDisabled: () =>
dispatch(navigateToOnboardingPrivativeKoDisabledScreen()),
navigateToUnavailable: () =>
dispatch(navigateToOnboardingPrivativeKoUnavailableScreen())
});

const mapStateToProps = (_: GlobalState) => ({});

export default connect(
mapStateToProps,
mapDispatchToProps
)(ChoosePrivativeIssuerComponent);
Loading

0 comments on commit e8b559a

Please sign in to comment.