Skip to content

Commit 306fd90

Browse files
authored
GPR | Route errors to error views (#972)
1 parent 5fbb066 commit 306fd90

File tree

8 files changed

+105
-108
lines changed

8 files changed

+105
-108
lines changed

packages/checkout/widgets-lib/src/context/view-context/SaleViewContextTypes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SaleErrorTypes } from '../../widgets/sale/types';
12
import { ViewType } from './ViewType';
23

34
export enum SaleWidgetViews {
@@ -35,7 +36,10 @@ interface SaleSuccessView extends ViewType {
3536
}
3637
interface SaleFailView extends ViewType {
3738
type: SaleWidgetViews.SALE_FAIL;
38-
reason?: string;
39+
data?: {
40+
errorType: SaleErrorTypes;
41+
[key: string]: unknown;
42+
};
3943
}
4044

4145
export enum FundWithSmartCheckoutSubViews {

packages/checkout/widgets-lib/src/widgets/sale/SaleWebView.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,24 @@ const defaultPassportConfig = {
2222
const defaultItems: Item[] = [
2323
{
2424
productId: 'P0001',
25-
qty: 1,
26-
name: 'Poliwag',
27-
image: 'https://pokemon-nfts.s3.ap-southeast-2.amazonaws.com/images/60.png',
28-
description: 'Poliwag',
25+
qty: 3,
26+
name: 'Bulbasaur',
27+
image: 'https://pokemon-nfts.s3.ap-southeast-2.amazonaws.com/images/1.png',
28+
description: 'Bulbasaur',
2929
},
3030
{
3131
productId: 'P0002',
32-
qty: 1,
33-
name: 'Poliwhirl',
34-
image: 'https://pokemon-nfts.s3.ap-southeast-2.amazonaws.com/images/61.png',
35-
description: 'Poliwhirl',
32+
qty: 2,
33+
name: 'Ivyasaur',
34+
image: 'https://pokemon-nfts.s3.ap-southeast-2.amazonaws.com/images/2.png',
35+
description: 'Ivyasaur',
3636
},
3737
{
3838
productId: 'P0003',
3939
qty: 1,
40-
name: 'Poliwrath',
41-
image: 'https://pokemon-nfts.s3.ap-southeast-2.amazonaws.com/images/62.png',
42-
description: 'Poliwrath',
40+
name: 'Venusaur',
41+
image: 'https://pokemon-nfts.s3.ap-southeast-2.amazonaws.com/images/3.png',
42+
description: 'Venusaur',
4343
},
4444
];
4545

packages/checkout/widgets-lib/src/widgets/sale/SaleWidget.tsx

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable no-console */
21
import {
32
useCallback, useContext, useEffect, useMemo, useReducer,
43
} from 'react';
@@ -22,7 +21,7 @@ import { StatusType } from '../../components/Status/StatusType';
2221
import { StatusView, StatusViewProps } from '../../components/Status/StatusView';
2322
import { EventTargetContext } from '../../context/event-target-context/EventTargetContext';
2423
import { SaleWidgetViews } from '../../context/view-context/SaleViewContextTypes';
25-
import { Item, SaleErrorTypes } from './types';
24+
import { Item, SaleErrorTypes, PaymentTypes } from './types';
2625
import { widgetTheme } from '../../lib/theme';
2726
import { SaleContextProvider } from './context/SaleContextProvider';
2827
import { FundWithSmartCheckout } from './views/FundWithSmartCheckout';
@@ -67,16 +66,6 @@ export function SaleWidget(props: SaleWidgetProps) {
6766
connectLoaderParams,
6867
} = props;
6968

70-
console.log(
71-
'@@@ SaleWidget',
72-
config,
73-
amount,
74-
items,
75-
fromContractAddress,
76-
env,
77-
environmentId,
78-
);
79-
8069
const { connectLoaderState } = useContext(ConnectLoaderContext);
8170
const { checkout, provider } = connectLoaderState;
8271

@@ -89,10 +78,7 @@ export function SaleWidget(props: SaleWidgetProps) {
8978
const loadingText = viewState.view.data?.loadingText
9079
|| text.views[SharedViews.LOADING_VIEW].text;
9180

92-
const {
93-
eventTargetState: { eventTarget },
94-
} = useContext(EventTargetContext);
95-
81+
const { eventTargetState: { eventTarget } } = useContext(EventTargetContext);
9682
const onMount = useCallback(() => {
9783
if (!checkout || !provider) return;
9884

@@ -112,12 +98,13 @@ export function SaleWidget(props: SaleWidgetProps) {
11298
onMount();
11399
}, [checkout, provider]);
114100

115-
const updateToPaymentMethods = () => {
101+
const goBackToPaymentMethods = (type?: PaymentTypes | undefined) => {
116102
viewDispatch({
117103
payload: {
118104
type: ViewActions.UPDATE_VIEW,
119105
view: {
120106
type: SaleWidgetViews.PAYMENT_METHODS,
107+
data: { paymentMethod: type },
121108
},
122109
},
123110
});
@@ -129,9 +116,10 @@ export function SaleWidget(props: SaleWidgetProps) {
129116

130117
const errorHandlersConfig: Record<SaleErrorTypes, ErrorHandlerConfig> = {
131118
[SaleErrorTypes.TRANSACTION_FAILED]: {
132-
onActionClick: updateToPaymentMethods,
119+
onActionClick: goBackToPaymentMethods,
133120
onSecondaryActionClick: () => {
134-
/* TODO: redirects to Immutascan to check the transaction */
121+
/* TODO: redirects to Immutascan to check the transaction if has is given */
122+
console.log({ transactionHash: viewState.view?.data?.transactionHash }); // eslint-disable-line no-console
135123
},
136124
statusType: StatusType.FAILURE,
137125
statusIconStyles: {
@@ -153,36 +141,37 @@ export function SaleWidget(props: SaleWidgetProps) {
153141
statusType: StatusType.INFORMATION,
154142
},
155143
[SaleErrorTypes.WALLET_FAILED]: {
156-
onActionClick: updateToPaymentMethods,
144+
onActionClick: goBackToPaymentMethods,
157145
onSecondaryActionClick: closeWidget,
158146
statusType: StatusType.INFORMATION,
159147
statusIconStyles: {
160148
fill: biomeTheme.color.status.fatal.dim,
161149
},
162150
},
163151
[SaleErrorTypes.WALLET_REJECTED_NO_FUNDS]: {
164-
onActionClick: updateToPaymentMethods,
152+
onActionClick: goBackToPaymentMethods,
165153
onSecondaryActionClick: closeWidget,
166154
statusType: StatusType.INFORMATION,
167155
},
168156
[SaleErrorTypes.WALLET_REJECTED]: {
169157
onActionClick: () => {
170-
/* TODO: trigger the approve and execute flow pop up flow again */
158+
goBackToPaymentMethods(PaymentTypes.CRYPTO);
171159
},
172160
onSecondaryActionClick: closeWidget,
173161
statusType: StatusType.INFORMATION,
174162
},
175163
[SaleErrorTypes.DEFAULT]: {
176-
onActionClick: updateToPaymentMethods,
164+
onActionClick: goBackToPaymentMethods,
177165
onSecondaryActionClick: closeWidget,
178166
statusType: StatusType.INFORMATION,
179167
},
180168
};
181169

182-
const errorViewProps = useMemo<StatusViewProps>(() => {
170+
const getErrorViewProps = (): StatusViewProps => {
183171
const errorTextConfig: AllErrorTextConfigs = text.views[SaleWidgetViews.SALE_FAIL].errors;
184-
const errorType = viewState.view.data?.error || SaleErrorTypes.DEFAULT;
172+
const errorType = viewState.view.data.errorType || SaleErrorTypes.DEFAULT;
185173
const handlers = errorHandlersConfig[errorType] || {};
174+
186175
return {
187176
testId: 'fail-view',
188177
statusText: errorTextConfig[errorType].description,
@@ -198,7 +187,7 @@ export function SaleWidget(props: SaleWidgetProps) {
198187
...handlers.statusIconStyles,
199188
},
200189
};
201-
}, [viewState.view.data?.error]);
190+
};
202191

203192
return (
204193
<BiomeCombinedProviders theme={{ base: biomeTheme }}>
@@ -219,9 +208,6 @@ export function SaleWidget(props: SaleWidgetProps) {
219208
{viewState.view.type === SharedViews.LOADING_VIEW && (
220209
<LoadingView loadingText={loadingText} />
221210
)}
222-
{viewState.view.type === SharedViews.ERROR_VIEW && (
223-
<div>{viewState.view.error.message}</div>
224-
)}
225211
{viewState.view.type
226212
=== SaleWidgetViews.PAYMENT_METHODS && <PaymentMethods />}
227213
{viewState.view.type === SaleWidgetViews.PAY_WITH_CARD && (
@@ -231,7 +217,7 @@ export function SaleWidget(props: SaleWidgetProps) {
231217
<PayWithCoins />
232218
)}
233219
{viewState.view.type === SaleWidgetViews.SALE_FAIL && (
234-
<StatusView {...errorViewProps} />
220+
<StatusView {...getErrorViewProps()} />
235221
)}
236222
{viewState.view.type === SaleWidgetViews.SALE_SUCCESS
237223
&& provider && (
Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1-
/* eslint-disable no-console */
2-
import { useCallback, useMemo } from 'react';
1+
import { useMemo } from 'react';
32

43
import { useSaleContext } from '../context/SaleContextProvider';
54
import { TransakIframe } from '../../../components/Transak/TransakIframe';
65
import { TransakNFTData } from '../../../components/Transak/TransakTypes';
76
import { text as textConfig } from '../../../resources/text/textConfig';
87
import { SaleWidgetViews } from '../../../context/view-context/SaleViewContextTypes';
8+
import { SaleErrorTypes } from '../types';
99

10-
export function WithCard() {
10+
export interface WithCardProps {
11+
onInit?: () => void;
12+
onOpen?: () => void;
13+
onOrderCreated?: () => void;
14+
onOrderProcessing?: () => void;
15+
onOrderCompleted?: () => void;
16+
onOrderFailed?: () => void;
17+
}
18+
19+
export function WithCard(props: WithCardProps) {
20+
const {
21+
onInit, onOpen, onOrderCreated, onOrderProcessing, onOrderCompleted, onOrderFailed,
22+
} = props;
1123
const { screenTitle, loading } = textConfig.views[SaleWidgetViews.PAY_WITH_CARD];
1224

1325
const {
14-
recipientEmail, recipientAddress, isPassportWallet, signResponse,
26+
recipientEmail, recipientAddress, isPassportWallet, signResponse, goToErrorView,
1527
} = useSaleContext();
1628
const executeTxn = signResponse?.transactions.find((txn) => txn.methodCall.startsWith('execute'));
1729

@@ -33,28 +45,11 @@ export function WithCard() {
3345
[signResponse],
3446
);
3547

36-
const onOpen = useCallback(() => {
37-
console.log('onOpen');
38-
}, []);
39-
40-
const onOrderCreated = useCallback(() => {
41-
console.log('onOrderCreated');
42-
}, []);
43-
44-
const onOrderProcessing = useCallback(() => {
45-
console.log('onOrderProcessing');
46-
}, []);
47-
48-
const onOrderCompleted = useCallback(() => {
49-
console.log('onOrderCompleted');
50-
}, []);
51-
52-
const onOrderFailed = useCallback(() => {
53-
console.log('onOrderFailed');
54-
}, []);
48+
const onFailedToLoad = () => {
49+
goToErrorView(SaleErrorTypes.TRANSACTION_FAILED);
50+
};
5551

5652
return (
57-
5853
<TransakIframe
5954
id="transak-iframe"
6055
type="nft-checkout"
@@ -69,11 +64,13 @@ export function WithCard() {
6964
estimatedGasLimit={executeTxn.gasEstimate}
7065
smartContractAddress={executeTxn.contractAddress}
7166
partnerOrderId={executeTxn.params.reference}
67+
onInit={onInit}
7268
onOpen={onOpen}
7369
onOrderCreated={onOrderCreated}
7470
onOrderProcessing={onOrderProcessing}
7571
onOrderCompleted={onOrderCompleted}
7672
onOrderFailed={onOrderFailed}
73+
onFailedToLoad={onFailedToLoad}
7774
/>
7875
);
7976
}

packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Passport } from '@imtbl/passport';
1313
import { SaleSuccess } from '@imtbl/checkout-widgets';
1414

1515
import {
16-
Item, PaymentTypes, SignResponse, SaleErrorTypes,
16+
Item, PaymentTypes, SignResponse, SaleErrorTypes, SignOrderError,
1717
} from '../types';
1818
import { useSignOrder } from '../hooks/useSignOrder';
1919
import { ConnectLoaderState } from '../../../context/connect-loader-context/ConnectLoaderContext';
@@ -42,6 +42,7 @@ type SaleContextValues = SaleContextProps & {
4242
recipientAddress: string;
4343
recipientEmail: string;
4444
signResponse: SignResponse | undefined;
45+
signError: SignOrderError | undefined;
4546
isPassportWallet: boolean;
4647
paymentMethod: PaymentTypes | undefined;
4748
setPaymentMethod: (paymentMethod: PaymentTypes) => void;
@@ -63,6 +64,7 @@ const SaleContext = createContext<SaleContextValues>({
6364
sign: () => Promise.resolve(undefined),
6465
execute: () => Promise.resolve({} as SaleSuccess),
6566
signResponse: undefined,
67+
signError: undefined,
6668
passport: undefined,
6769
isPassportWallet: false,
6870
paymentMethod: undefined,
@@ -151,7 +153,9 @@ export function SaleContextProvider(props: {
151153
getUserInfo();
152154
}, [provider]);
153155

154-
const { sign: signOrder, execute, signResponse } = useSignOrder({
156+
const {
157+
sign: signOrder, execute, signResponse, signError,
158+
} = useSignOrder({
155159
items,
156160
provider,
157161
fromContractAddress,
@@ -174,6 +178,11 @@ export function SaleContextProvider(props: {
174178
[signOrder],
175179
);
176180

181+
useEffect(() => {
182+
if (!signError) return;
183+
goToErrorView(signError.type, signError.data);
184+
}, [signError]);
185+
177186
const values = useMemo(
178187
() => ({
179188
config,
@@ -183,6 +192,7 @@ export function SaleContextProvider(props: {
183192
sign,
184193
execute,
185194
signResponse,
195+
signError,
186196
environmentId,
187197
env,
188198
provider,
@@ -209,6 +219,7 @@ export function SaleContextProvider(props: {
209219
signResponse,
210220
paymentMethod,
211221
signResponse,
222+
signError,
212223
goBackToPaymentMethods,
213224
goToErrorView,
214225
],

packages/checkout/widgets-lib/src/widgets/sale/hooks/useSignOrder.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,26 @@ const toSignResponse = (
111111
name: order.currency.name,
112112
erc20Address: order.currency.erc20_address,
113113
},
114-
products: order.products.map((product) => toSignedProduct(
115-
product,
116-
order.currency.name,
117-
items.find((item) => item.productId === product.product_id),
118-
)),
114+
products: order.products
115+
.map((product) => toSignedProduct(
116+
product,
117+
order.currency.name,
118+
items.find((item) => item.productId === product.product_id),
119+
))
120+
.reduce((acc, product) => {
121+
const index = acc.findIndex((n) => n.name === product.name);
122+
123+
if (index === -1) {
124+
acc.push({ ...product });
125+
}
126+
127+
if (index > -1) {
128+
acc[index].amount = [...acc[index].amount, ...product.amount];
129+
acc[index].tokenId = [...acc[index].tokenId, ...product.tokenId];
130+
}
131+
132+
return acc;
133+
}, [] as SignedOrderProduct[]),
119134
totalAmount: Number(order.total_amount),
120135
},
121136
transactions: transactions.map((transaction) => ({

0 commit comments

Comments
 (0)