Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Bonus Pagamenti Digitali): [#176455937] Error and loading screen transaction details #2755

Merged
merged 45 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b22eca2
[#176455937] remove unused load prop
debiff Jan 18, 2021
501b56d
[#176455937] refresh transactions list on go to transaction button press
debiff Jan 18, 2021
09f9997
[#176455937] renamed BpdTransactionsScreen in BpdAvailableTransaction…
debiff Jan 18, 2021
e38a5f7
[#176455937] refactor BpdTransactionsScreen as proxy screen
debiff Jan 19, 2021
6e35217
[#176455937] add LoadTransactions component
debiff Jan 19, 2021
c60017e
[#176455937] move BpdAvailableTransactionsScreen in detail dir
debiff Jan 19, 2021
5f6b04a
[#176455937] add loading and error labels
debiff Jan 19, 2021
a1aea0b
[#176455937] move screen file outside details dir
debiff Jan 19, 2021
8967e97
[#176455937] add transactions unavailable screen
debiff Jan 19, 2021
5e8b1ff
[#176455937] refactor BpdTransactionsScreen taking into account lastU…
debiff Jan 19, 2021
8a3de01
[#176455937] fix lint error loadingScreen
debiff Jan 19, 2021
9557822
[#176455937] move transactions reload inside BpdTransactionsScreen
debiff Jan 20, 2021
ef09eee
[#176455937] Merge branch 'master' into 176455937-error-screen-transa…
debiff Jan 20, 2021
2fe143a
[#176455937] remove reactotron
debiff Jan 21, 2021
c44dc51
[#176455937] add test files
debiff Jan 21, 2021
2e5ce1e
[#176455937] add testId to the image
debiff Jan 21, 2021
7c3d92a
[#176455937] add TransactionsUnavailable tests
debiff Jan 21, 2021
3c51bfc
[#176455937] add testId
debiff Jan 21, 2021
c1b7dbd
[#176455937] refactor TransactionsUnavailable tests
debiff Jan 21, 2021
c6fc602
[#176455937] LoadTransactions tests
debiff Jan 21, 2021
adff477
[#176455937] add testId to identify different screen
debiff Jan 21, 2021
2d50d6e
[#176455937] add bpdTransactionsScreen tests
debiff Jan 22, 2021
3694901
[#176455937] add some transactionsScreen tests
debiff Jan 22, 2021
6800ea9
[#176455937] refactor tests
debiff Jan 26, 2021
2de29be
[#176455937] refactor BpdTransactionsScreen
debiff Jan 26, 2021
aae49e0
[#176455937] Merge branch 'master' into 176455937-error-screen-transa…
debiff Jan 26, 2021
7172ed9
[#176455937] fix test
debiff Jan 26, 2021
3496e14
[#176455937] remove async on test
debiff Jan 26, 2021
2f21d10
Merge branch 'master' into 176455937-error-screen-transaction-details
debiff Jan 29, 2021
9a5a746
[#176455937] Merge branch 'master' into 176455937-error-screen-transa…
debiff Feb 2, 2021
01eb23e
[#176455937] remove useless brackets
debiff Feb 2, 2021
6e0e882
Merge branch 'master' into 176455937-error-screen-transaction-details
fabriziofff Feb 4, 2021
a6079ff
Merge branch 'master' into 176455937-error-screen-transaction-details
debiff Feb 5, 2021
716bcb1
Merge branch 'master' into 176455937-error-screen-transaction-details
debiff Feb 9, 2021
1e857e6
Merge branch 'master' into 176455937-error-screen-transaction-details
fabriziofff Feb 9, 2021
f52e5d6
Merge branch 'master' into 176455937-error-screen-transaction-details
fabriziofff Feb 9, 2021
2b70b1c
Merge branch 'master' into 176455937-error-screen-transaction-details
fabriziofff Feb 9, 2021
6286c1f
Merge branch 'master' into 176455937-error-screen-transaction-details
fabriziofff Feb 9, 2021
5e940eb
Merge branch 'master' into 176455937-error-screen-transaction-details
fabriziofff Feb 10, 2021
b05dd5f
Merge branch 'master' into 176455937-error-screen-transaction-details
fabriziofff Feb 10, 2021
f05e33e
[#176455937] remove old comment
debiff Feb 10, 2021
156a52a
[#176455937] fix comment typo
debiff Feb 10, 2021
144984f
[#176455937] Merge remote-tracking branch 'origin/176455937-error-scr…
debiff Feb 10, 2021
eca6a13
Merge branch 'master' into 176455937-error-screen-transaction-details
fabriziofff Feb 10, 2021
c8880a3
[#176455937] remove callback chain
fabriziofff Feb 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2047,13 +2047,16 @@ bonus:
body: "The ranking is based on the **number of transactions** made with the payment methods on which you have Cashback enabled.\n\n
We will show your ranking **when the distribution of participants will be more meaningful**. Currently, many users collected the same number of transactions.\n\n
Once published, the ranking will **continue to change** depending on the number of transactions among all participants, until the end of the 6 months."

transaction:
label:
one: "1 transaction"
other: "{{transactions}} transactions"
title: "Your transactions"
goToButton: "Transactions detail"
loading: "We're retrieving your transactions\n\nPlease hold on..."
error:
title: "Systems are taking longer than expected"
body: "We're currently unable to retrieve your transactions. Don't worry! The list will be available again soon."
noPaymentMethod:
text1: "You must activate "
text2: "at least one payment method "
Expand Down
4 changes: 4 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2085,6 +2085,10 @@ bonus:
other: "{{transactions}} transazioni"
title: "Le tue transazioni"
goToButton: "Dettaglio transazioni"
loading: "Stiamo recuperando la lista delle tue transazioni\n\nAttendi qualche secondo..."
error:
title: "I sistemi stanno impiegando più tempo del previsto"
body: "In questo momento non riusciamo a recuperare le tue transazioni. Non preoccuparti, a breve l’elenco tornerà nuovamente disponibile."
noPaymentMethod:
text1: "Devi attivare "
text2: "almeno un metodo di pagamento "
Expand Down
14 changes: 12 additions & 2 deletions ts/components/infoScreen/InfoScreenComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export const InfoScreenStyle = styles;

const renderNode = (body: string | React.ReactNode) => {
if (typeof body === "string") {
return <Text style={styles.body}>{body}</Text>;
return (
<Text style={styles.body} testID={"infoScreenBody"}>
{body}
</Text>
);
}

return body;
Expand All @@ -53,7 +57,13 @@ export const InfoScreenComponent: React.FunctionComponent<Props> = props => {
<NavigationEvents onDidFocus={() => setAccessibilityFocus(elementRef)} />
{props.image}
<View spacer={true} large={true} />
<Text style={styles.title} bold={true} ref={elementRef} accessible={true}>
<Text
style={styles.title}
bold={true}
ref={elementRef}
accessible={true}
testID={"infoScreenTitle"}
>
{props.title}
</Text>
<View spacer={true} />
Expand Down
7 changes: 6 additions & 1 deletion ts/components/infoScreen/imageRendering.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ const styles = StyleSheet.create({
* @param image
*/
export const renderInfoRasterImage = (image: ImageSourcePropType) => (
<Image source={image} resizeMode={"contain"} style={styles.raster} />
<Image
source={image}
resizeMode={"contain"}
style={styles.raster}
testID={"rasterImage"}
/>
);

export const renderInfoIconImage = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ const BpdDetailsScreen: React.FunctionComponent<Props> = props => {
};

const mapDispatchToProps = (dispatch: Dispatch) => ({
load: () => dispatch(bpdAllData.request()),
completeUnsubscription: () => {
dispatch(bpdAllData.request());
dispatch(bpdUnsubscribeCompleted());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
import { compareDesc } from "date-fns";
import { index, reverse } from "fp-ts/lib/Array";
import { fromNullable } from "fp-ts/lib/Option";
import * as pot from "italia-ts-commons/lib/pot";
import { View } from "native-base";
import * as React from "react";
import {
SafeAreaView,
ScrollView,
SectionList,
SectionListData,
SectionListRenderItem
} from "react-native";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { InfoBox } from "../../../../../../components/box/InfoBox";
import { H1 } from "../../../../../../components/core/typography/H1";
import { H4 } from "../../../../../../components/core/typography/H4";
import { IOStyles } from "../../../../../../components/core/variables/IOStyles";
import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent";
import I18n from "../../../../../../i18n";
import { GlobalState } from "../../../../../../store/reducers/types";
import { emptyContextualHelp } from "../../../../../../utils/emptyContextualHelp";
import { localeDateFormat } from "../../../../../../utils/locale";
import BaseDailyTransactionHeader from "../../../components/BaseDailyTransactionHeader";
import BpdTransactionSummaryComponent from "../../../components/BpdTransactionSummaryComponent";
import {
BpdTransactionItem,
EnhancedBpdTransaction
} from "../../../components/transactionItem/BpdTransactionItem";
import {
atLeastOnePaymentMethodHasBpdEnabledSelector,
bpdDisplayTransactionsSelector,
paymentMethodsWithActivationStatusSelector
} from "../../../store/reducers/details/combiner";
import { bpdSelectedPeriodSelector } from "../../../store/reducers/details/selectedPeriod";
import BpdCashbackMilestoneComponent from "./BpdCashbackMilestoneComponent";
import BpdEmptyTransactionsList from "./BpdEmptyTransactionsList";

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

type TotalCashbackPerDate = {
trxDate: Date;
totalCashBack: number;
};

const dataForFlatList = (
transactions: pot.Pot<ReadonlyArray<EnhancedBpdTransaction>, Error>
) => pot.getOrElse(transactions, []);

export const isTotalCashback = (item: any): item is TotalCashbackPerDate =>
item.totalCashBack !== undefined;

/**
* Builds the array of objects needed to show the sectionsList grouped by transaction day.
*
* We check the subtotal of TotalCashback earned on each transaction to check when the user reaches the cashback.
*
* When creating the final array if we reached the cashback amount we set all the following transaction cashback value to 0
*
* If the sum of cashback comes over the award we remove the exceeding part on the transaction.
* @param transactions
* @param cashbackAward
*/
const getTransactionsByDaySections = (
transactions: ReadonlyArray<EnhancedBpdTransaction>,
cashbackAward: number
): ReadonlyArray<
SectionListData<EnhancedBpdTransaction | TotalCashbackPerDate>
> => {
const dates = [
...new Set(
transactions.map(trx =>
localeDateFormat(trx.trxDate, I18n.t("global.dateFormats.dayFullMonth"))
)
)
];

const transactionsAsc = reverse([...transactions]);

// accumulator to define when the user reached the cashback award amount
// and tracing the sum of all the cashback value to check if any negative trx may cause a revoke of cashback award
const amountWinnerAccumulator = transactionsAsc.reduce(
(
acc: {
winner?: {
amount: number;
index: number;
date: Date;
};
sumAmount: number;
},
t: EnhancedBpdTransaction,
currIndex: number
) => {
const sum = acc.sumAmount + t.cashback;
if (sum >= cashbackAward && !acc.winner) {
return {
winner: {
amount: sum,
index: currIndex,
date: new Date(t.trxDate)
},
sumAmount: sum
};
} else if (sum < cashbackAward) {
return {
sumAmount: sum
};
}
return {
...acc,
sumAmount: sum
};
},
{
sumAmount: 0
}
);

const maybeWinner = fromNullable(amountWinnerAccumulator.winner);

// If the user reached the cashback amount within transactions we actualize all the cashback value starting from the index of winning transaction
// if the winning transaction makes cashback value exceed the limit we set the amount to the difference of transaction cashback value, total amout at winnign transaction and cashback award limit.
// all the following transactions will be set to 0 cashback value, since the limit has been reached (a dedicated item will be displayed)
const updatedTransactions = [...transactionsAsc].map((t, i) => {
if (maybeWinner.isSome()) {
if (
i === maybeWinner.value.index &&
maybeWinner.value.amount > cashbackAward
) {
return {
...t,
cashback: t.cashback - (maybeWinner.value.amount - cashbackAward)
};
} else if (i > maybeWinner.value.index) {
return {
...t,
cashback: 0
};
}
}
return t;
});

return dates.map(d => ({
title: d,
data: [
...updatedTransactions.filter(
t =>
localeDateFormat(
t.trxDate,
I18n.t("global.dateFormats.dayFullMonth")
) === d
),
// we add the the data array an item to display the milestone reached
// in order to display the milestone after the latest transaction summed in the total we add 1 ms so that the ordering will set it correctly
...maybeWinner.fold([], w => {
if (
localeDateFormat(
w.date,
I18n.t("global.dateFormats.dayFullMonth")
) === d
) {
return [
{
totalCashBack: w.amount,
trxDate: new Date(
w.date.setMilliseconds(w.date.getMilliseconds() + 1)
)
}
];
}
return [];
})
].sort((trx1, trx2) => compareDesc(trx1.trxDate, trx2.trxDate))
}));
};

const renderSectionHeader = (info: {
section: SectionListData<EnhancedBpdTransaction | TotalCashbackPerDate>;
}): React.ReactNode => (
<BaseDailyTransactionHeader
date={info.section.title}
transactionsNumber={
[...info.section.data].filter(i => !isTotalCashback(i)).length
}
/>
);

export const NoPaymentMethodAreActiveWarning = () => (
<View>
<InfoBox iconName={"io-warning"}>
<H4 weight={"Regular"}>
{I18n.t("bonus.bpd.details.transaction.noPaymentMethod.text1")}
<H4 weight={"Bold"}>
{I18n.t("bonus.bpd.details.transaction.noPaymentMethod.text2")}
</H4>
{I18n.t("bonus.bpd.details.transaction.noPaymentMethod.text3")}
</H4>
</InfoBox>
<View spacer={true} small={true} />
</View>
);

/**
* Display all the transactions for a specific period
* TODO: scroll to refresh, display error, display loading
* @constructor
*/
const BpdAvailableTransactionsScreen: React.FunctionComponent<Props> = props => {
const transactions = dataForFlatList(props.transactionForSelectedPeriod);

const trxSortByDate = [...transactions].sort((trx1, trx2) =>
compareDesc(trx1.trxDate, trx2.trxDate)
);

const maybeLastUpdateDate = index(0, [...trxSortByDate]).map(t => t.trxDate);

const renderTransactionItem: SectionListRenderItem<
EnhancedBpdTransaction | TotalCashbackPerDate
> = info => {
if (isTotalCashback(info.item)) {
return (
<BpdCashbackMilestoneComponent
cashbackValue={fromNullable(props.selectedPeriod).fold(
0,
p => p.maxPeriodCashback
)}
/>
);
}
return <BpdTransactionItem transaction={info.item} />;
};

return (
<BaseScreenComponent
goBack={true}
headerTitle={I18n.t("bonus.bpd.title")}
contextualHelp={emptyContextualHelp}
>
<SafeAreaView
style={IOStyles.flex}
testID={"BpdAvailableTransactionsScreen"}
>
<View spacer={true} />
<View style={IOStyles.horizontalContentPadding}>
<H1>{I18n.t("bonus.bpd.details.transaction.title")}</H1>
</View>
<ScrollView style={[IOStyles.flex]}>
<View style={IOStyles.horizontalContentPadding}>
<View spacer={true} />
{props.selectedPeriod && maybeLastUpdateDate.isSome() && (
<>
<BpdTransactionSummaryComponent
lastUpdateDate={localeDateFormat(
maybeLastUpdateDate.value,
I18n.t("global.dateFormats.fullFormatFullMonthLiteral")
)}
period={props.selectedPeriod}
totalAmount={props.selectedPeriod.amount}
/>
<View spacer={true} />
</>
)}
</View>
{props.selectedPeriod &&
(transactions.length > 0 ? (
<SectionList
renderSectionHeader={renderSectionHeader}
scrollEnabled={true}
stickySectionHeadersEnabled={true}
sections={getTransactionsByDaySections(
trxSortByDate,
props.selectedPeriod.maxPeriodCashback
)}
renderItem={renderTransactionItem}
keyExtractor={t =>
isTotalCashback(t)
? `awarded_cashback_item${t.totalCashBack}`
: t.keyId
}
/>
) : !props.atLeastOnePaymentMethodActive &&
pot.isSome(props.potWallets) &&
props.potWallets.value.length > 0 ? (
<View style={IOStyles.horizontalContentPadding}>
<NoPaymentMethodAreActiveWarning />
</View>
) : (
<View style={IOStyles.horizontalContentPadding}>
<BpdEmptyTransactionsList />
</View>
))}
</ScrollView>
</SafeAreaView>
</BaseScreenComponent>
);
};

const mapDispatchToProps = (_: Dispatch) => ({});

const mapStateToProps = (state: GlobalState) => ({
transactionForSelectedPeriod: bpdDisplayTransactionsSelector(state),
selectedPeriod: bpdSelectedPeriodSelector(state),
potWallets: paymentMethodsWithActivationStatusSelector(state),
atLeastOnePaymentMethodActive: atLeastOnePaymentMethodHasBpdEnabledSelector(
state
)
});

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