@lmnto/h-mall-shared 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/shared/components/payments/RequestProcessingWithCancelConfirm.tsx +29 -13
- package/shared/hooks/useCart.ts +36 -3
- package/shared/hooks/useUsaHomeStationErrorMessage.ts +18 -0
- package/shared/i18n/locales/en.ts +23 -0
- package/shared/i18n/locales/it.ts +23 -0
- package/shared/utils/usa-home-station-error.constants.ts +6 -0
- package/shared/utils/usa-home-station-error.util.ts +64 -0
package/package.json
CHANGED
|
@@ -26,6 +26,8 @@ export type RequestProcessingWithCancelConfirmProps = {
|
|
|
26
26
|
confirmBeforeClose?: boolean;
|
|
27
27
|
/** Called when the user confirms leaving the payment flow (e.g. navigate home). */
|
|
28
28
|
onConfirmLeavePayment?: () => void;
|
|
29
|
+
/** Optional copy variant for cancel confirmation dialog. Defaults to nowpayments wording. */
|
|
30
|
+
cancelConfirmVariant?: "nowpayments" | "stripe";
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -41,9 +43,11 @@ export function RequestProcessingWithCancelConfirm({
|
|
|
41
43
|
paymentDetails,
|
|
42
44
|
confirmBeforeClose,
|
|
43
45
|
onConfirmLeavePayment,
|
|
46
|
+
cancelConfirmVariant = "nowpayments",
|
|
44
47
|
}: RequestProcessingWithCancelConfirmProps) {
|
|
45
48
|
const scopeT = _useScopedI18n("payment.requestProcessingDialog");
|
|
46
49
|
const cancelT = _useScopedI18n("payment.cancelPaymentConfirm");
|
|
50
|
+
const stripeCancelT = _useScopedI18n("payment.cancelPaymentConfirmStripe");
|
|
47
51
|
const hasPaymentDetails = Boolean(paymentDetails);
|
|
48
52
|
const [cancelConfirmOpen, setCancelConfirmOpen] = useState(false);
|
|
49
53
|
|
|
@@ -69,6 +73,8 @@ export function RequestProcessingWithCancelConfirm({
|
|
|
69
73
|
setCancelConfirmOpen(false);
|
|
70
74
|
}, []);
|
|
71
75
|
|
|
76
|
+
const isStripeVariant = cancelConfirmVariant === "stripe";
|
|
77
|
+
|
|
72
78
|
return (
|
|
73
79
|
<>
|
|
74
80
|
<Dialog
|
|
@@ -200,21 +206,31 @@ export function RequestProcessingWithCancelConfirm({
|
|
|
200
206
|
variant="h4"
|
|
201
207
|
className="text-base md:text-xl font-semibold text-black-500"
|
|
202
208
|
>
|
|
203
|
-
{cancelT("title")}
|
|
209
|
+
{isStripeVariant ? stripeCancelT("title") : cancelT("title")}
|
|
204
210
|
</Typography>
|
|
205
211
|
<ul className="list-disc space-y-3 pl-5 text-sm leading-relaxed text-slate-600 marker:text-slate-500">
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
<li>{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
{isStripeVariant ? (
|
|
213
|
+
<>
|
|
214
|
+
<li>{stripeCancelT("item1")}</li>
|
|
215
|
+
<li>{stripeCancelT("item2")}</li>
|
|
216
|
+
<li>{stripeCancelT("item3")}</li>
|
|
217
|
+
</>
|
|
218
|
+
) : (
|
|
219
|
+
<>
|
|
220
|
+
<li>{cancelT("completedItem")}</li>
|
|
221
|
+
<li>
|
|
222
|
+
<span className="block">{cancelT("notSentIntro")}</span>
|
|
223
|
+
<ul className="mt-2 list-[circle] space-y-2 pl-5 marker:text-slate-400">
|
|
224
|
+
<li>{cancelT("notSentSub1")}</li>
|
|
225
|
+
<li>{cancelT("notSentSub2")}</li>
|
|
226
|
+
<li>{cancelT("notSentSub3")}</li>
|
|
227
|
+
</ul>
|
|
228
|
+
</li>
|
|
229
|
+
</>
|
|
230
|
+
)}
|
|
215
231
|
</ul>
|
|
216
232
|
<Typography className="text-sm leading-relaxed text-slate-600">
|
|
217
|
-
{cancelT("closing")}
|
|
233
|
+
{isStripeVariant ? stripeCancelT("closing") : cancelT("closing")}
|
|
218
234
|
</Typography>
|
|
219
235
|
</DialogBody>
|
|
220
236
|
<DialogFooter className="flex flex-col-reverse gap-2 p-0 pt-2 sm:flex-row sm:justify-end">
|
|
@@ -224,10 +240,10 @@ export function RequestProcessingWithCancelConfirm({
|
|
|
224
240
|
className="normal-case rounded-xl border-slate-300 px-4 py-2.5 text-sm font-semibold text-slate-700 shadow-sm hover:border-slate-400 hover:bg-slate-50"
|
|
225
241
|
onClick={handleStayOnPayment}
|
|
226
242
|
>
|
|
227
|
-
{cancelT("stayBtn")}
|
|
243
|
+
{isStripeVariant ? stripeCancelT("stayBtn") : cancelT("stayBtn")}
|
|
228
244
|
</Button>
|
|
229
245
|
<ButtonCustom icon={undefined} onClick={handleConfirmLeave}>
|
|
230
|
-
{cancelT("leaveBtn")}
|
|
246
|
+
{isStripeVariant ? stripeCancelT("leaveBtn") : cancelT("leaveBtn")}
|
|
231
247
|
</ButtonCustom>
|
|
232
248
|
</DialogFooter>
|
|
233
249
|
</Dialog>
|
package/shared/hooks/useCart.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
2
4
|
import { useCallback, useEffect, useState } from "react";
|
|
3
5
|
|
|
@@ -8,11 +10,24 @@ import { ShippingMethodsService } from "../services/api";
|
|
|
8
10
|
import { useCartStore } from "../stores/cartStore";
|
|
9
11
|
import { countriesStore } from "../stores/countriesStore";
|
|
10
12
|
import { withTimeout } from "../utils/app.util";
|
|
13
|
+
import { notification } from "../utils/notifications.util";
|
|
14
|
+
import { UsaHomeStationErrorCode } from "../utils/usa-home-station-error.constants";
|
|
15
|
+
import { useUsaHomeStationErrorMessage } from "./useUsaHomeStationErrorMessage";
|
|
16
|
+
|
|
17
|
+
function isUsaHomeStationApiError(error: { body?: { code?: string }; code?: string }) {
|
|
18
|
+
const code = error?.body?.code ?? error?.code;
|
|
19
|
+
return (
|
|
20
|
+
code === UsaHomeStationErrorCode.QUOTA_EXCEEDED ||
|
|
21
|
+
code === UsaHomeStationErrorCode.ELIGIBILITY_CHECK_FAILED ||
|
|
22
|
+
code === UsaHomeStationErrorCode.USER_NOT_FOUND
|
|
23
|
+
);
|
|
24
|
+
}
|
|
11
25
|
|
|
12
26
|
export function useCart() {
|
|
13
27
|
const [revalidateCartSuccess, setRevalidateCartSuccess] = useState(false);
|
|
14
28
|
const { triggerInvalidDialog } = useMainContext();
|
|
15
29
|
const { setShippingCountries } = countriesStore();
|
|
30
|
+
const resolveUsaHomeStationError = useUsaHomeStationErrorMessage();
|
|
16
31
|
|
|
17
32
|
//
|
|
18
33
|
const { cart, commissionNonEligibleFound, setLoading, setCart } =
|
|
@@ -51,6 +66,9 @@ export function useCart() {
|
|
|
51
66
|
triggerInvalidDialog();
|
|
52
67
|
return;
|
|
53
68
|
}
|
|
69
|
+
if (isUsaHomeStationApiError(error)) {
|
|
70
|
+
notification.notifyError(resolveUsaHomeStationError(error));
|
|
71
|
+
}
|
|
54
72
|
},
|
|
55
73
|
});
|
|
56
74
|
|
|
@@ -81,7 +99,12 @@ export function useCart() {
|
|
|
81
99
|
onSuccess: async () => {
|
|
82
100
|
revalidateCart();
|
|
83
101
|
},
|
|
84
|
-
onError: () =>
|
|
102
|
+
onError: (error: any) => {
|
|
103
|
+
setLoading(false);
|
|
104
|
+
if (isUsaHomeStationApiError(error)) {
|
|
105
|
+
notification.notifyError(resolveUsaHomeStationError(error));
|
|
106
|
+
}
|
|
107
|
+
},
|
|
85
108
|
});
|
|
86
109
|
|
|
87
110
|
const updateCartAddresses = useMutation({
|
|
@@ -89,7 +112,12 @@ export function useCart() {
|
|
|
89
112
|
mutationFn: CartsService.cartsControllerUpdateAddresses,
|
|
90
113
|
onMutate: () => setLoading(true),
|
|
91
114
|
onSuccess: () => revalidateCart(),
|
|
92
|
-
onError: () =>
|
|
115
|
+
onError: (error: any) => {
|
|
116
|
+
setLoading(false);
|
|
117
|
+
if (isUsaHomeStationApiError(error)) {
|
|
118
|
+
notification.notifyError(resolveUsaHomeStationError(error));
|
|
119
|
+
}
|
|
120
|
+
},
|
|
93
121
|
});
|
|
94
122
|
|
|
95
123
|
//#TODO Update with the Update shipping address API
|
|
@@ -98,7 +126,12 @@ export function useCart() {
|
|
|
98
126
|
mutationFn: CartsService.cartsControllerShippingAddresses,
|
|
99
127
|
onMutate: () => setLoading(true),
|
|
100
128
|
onSuccess: () => revalidateCart(),
|
|
101
|
-
onError: () =>
|
|
129
|
+
onError: (error: any) => {
|
|
130
|
+
setLoading(false);
|
|
131
|
+
if (isUsaHomeStationApiError(error)) {
|
|
132
|
+
notification.notifyError(resolveUsaHomeStationError(error));
|
|
133
|
+
}
|
|
134
|
+
},
|
|
102
135
|
});
|
|
103
136
|
|
|
104
137
|
const updatePaymentMethod = useMutation({
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
|
|
5
|
+
import { _useScopedI18n } from "../i18n/client";
|
|
6
|
+
import { resolveUsaHomeStationErrorMessage } from "../utils/usa-home-station-error.util";
|
|
7
|
+
|
|
8
|
+
export function useUsaHomeStationErrorMessage() {
|
|
9
|
+
const t = _useScopedI18n("cart.usaHomeStation");
|
|
10
|
+
|
|
11
|
+
return useCallback(
|
|
12
|
+
(error: unknown) =>
|
|
13
|
+
resolveUsaHomeStationErrorMessage(error, (key, params) =>
|
|
14
|
+
t(key as "quotaExceeded", params),
|
|
15
|
+
),
|
|
16
|
+
[t],
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -206,6 +206,16 @@ export default {
|
|
|
206
206
|
addNewShippingAddress: "Add New Shipping Address",
|
|
207
207
|
},
|
|
208
208
|
},
|
|
209
|
+
usaHomeStation: {
|
|
210
|
+
quotaExceeded:
|
|
211
|
+
"Shipping to the United States allows {defaultAllowance} Nx1 Home Station by default (max {max} based on {downline} direct first-line referral(s) on NodeLink). You already have {past} from previous US shipped order(s) and {cart} in your cart.",
|
|
212
|
+
userNotFound:
|
|
213
|
+
"We could not verify your account for US Home Station shipping. Please sign in again.",
|
|
214
|
+
eligibilityCheckFailed:
|
|
215
|
+
"We could not verify your Nx1 Home Station purchase limit. Please try again.",
|
|
216
|
+
generic:
|
|
217
|
+
"Unable to complete this action because of US Home Station shipping limits.",
|
|
218
|
+
},
|
|
209
219
|
},
|
|
210
220
|
sharedComponents: {
|
|
211
221
|
addressFormComponent: {
|
|
@@ -364,6 +374,19 @@ export default {
|
|
|
364
374
|
stayBtn: "Stay and pay",
|
|
365
375
|
leaveBtn: "Leave without paying",
|
|
366
376
|
},
|
|
377
|
+
cancelPaymentConfirmStripe: {
|
|
378
|
+
title: "Leave card payment?",
|
|
379
|
+
item1:
|
|
380
|
+
"If your card was declined, no funds were captured and you can retry with another card.",
|
|
381
|
+
item2:
|
|
382
|
+
"If authentication is still pending (e.g. 3D Secure), leaving now may interrupt payment completion.",
|
|
383
|
+
item3:
|
|
384
|
+
"You can return to checkout anytime to complete payment for this order.",
|
|
385
|
+
closing:
|
|
386
|
+
"If you are unsure whether payment went through, check your order status before trying again.",
|
|
387
|
+
stayBtn: "Stay and complete payment",
|
|
388
|
+
leaveBtn: "Leave checkout",
|
|
389
|
+
},
|
|
367
390
|
paybyWalletItemComponent: {
|
|
368
391
|
insufficientBalanceMsg: "Insufficient wallet balance. Please recharge.",
|
|
369
392
|
avlBalance: "Avl Balance",
|
|
@@ -193,6 +193,16 @@ export default {
|
|
|
193
193
|
addNewShippingAddress: "Aggiungi un nuovo indirizzo di spedizione",
|
|
194
194
|
},
|
|
195
195
|
},
|
|
196
|
+
usaHomeStation: {
|
|
197
|
+
quotaExceeded:
|
|
198
|
+
"La spedizione negli Stati Uniti consente {defaultAllowance} Nx1 Home Station di default (max {max} in base a {downline} referral diretti di prima linea su NodeLink). Hai già {past} da ordini precedenti spediti negli USA e {cart} nel carrello.",
|
|
199
|
+
userNotFound:
|
|
200
|
+
"Impossibile verificare il tuo account per la spedizione US Home Station. Accedi di nuovo.",
|
|
201
|
+
eligibilityCheckFailed:
|
|
202
|
+
"Impossibile verificare il limite di acquisto Nx1 Home Station. Riprova.",
|
|
203
|
+
generic:
|
|
204
|
+
"Impossibile completare l'azione a causa dei limiti di spedizione US Home Station.",
|
|
205
|
+
},
|
|
196
206
|
},
|
|
197
207
|
sharedComponents: {
|
|
198
208
|
addressFormComponent: {
|
|
@@ -353,6 +363,19 @@ export default {
|
|
|
353
363
|
stayBtn: "Resta e paga",
|
|
354
364
|
leaveBtn: "Esci senza pagare",
|
|
355
365
|
},
|
|
366
|
+
cancelPaymentConfirmStripe: {
|
|
367
|
+
title: "Vuoi uscire dal pagamento con carta?",
|
|
368
|
+
item1:
|
|
369
|
+
"Se la carta è stata rifiutata, non è stato addebitato alcun importo e puoi riprovare con un'altra carta.",
|
|
370
|
+
item2:
|
|
371
|
+
"Se l'autenticazione è ancora in corso (es. 3D Secure), uscire ora può interrompere il completamento del pagamento.",
|
|
372
|
+
item3:
|
|
373
|
+
"Puoi tornare al checkout in qualsiasi momento per completare il pagamento di questo ordine.",
|
|
374
|
+
closing:
|
|
375
|
+
"Se non sei sicuro che il pagamento sia andato a buon fine, controlla lo stato dell'ordine prima di riprovare.",
|
|
376
|
+
stayBtn: "Resta e completa il pagamento",
|
|
377
|
+
leaveBtn: "Esci dal checkout",
|
|
378
|
+
},
|
|
356
379
|
paybyWalletItemComponent: {
|
|
357
380
|
insufficientBalanceMsg:
|
|
358
381
|
"Saldo del portafoglio insufficiente. Si prega di ricaricare.",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Keep in sync with h-mall-api `usa-home-station.errors.ts` */
|
|
2
|
+
export const UsaHomeStationErrorCode = {
|
|
3
|
+
QUOTA_EXCEEDED: 'USA_HOME_STATION_QUOTA_EXCEEDED',
|
|
4
|
+
ELIGIBILITY_CHECK_FAILED: 'USA_HOME_STATION_ELIGIBILITY_CHECK_FAILED',
|
|
5
|
+
USER_NOT_FOUND: 'USA_HOME_STATION_USER_NOT_FOUND',
|
|
6
|
+
} as const;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { UsaHomeStationErrorCode } from './usa-home-station-error.constants';
|
|
2
|
+
|
|
3
|
+
export type UsaHomeStationErrorPayload = {
|
|
4
|
+
maxMachinesAllowed?: number;
|
|
5
|
+
downlineUsersCount?: number;
|
|
6
|
+
pastPurchasedQuantity?: number;
|
|
7
|
+
cartQuantity?: number;
|
|
8
|
+
totalRequestedQuantity?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type ApiErrorShape = {
|
|
12
|
+
code?: string;
|
|
13
|
+
msg?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
data?: UsaHomeStationErrorPayload;
|
|
16
|
+
error?: UsaHomeStationErrorPayload;
|
|
17
|
+
body?: {
|
|
18
|
+
code?: string;
|
|
19
|
+
msg?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
data?: UsaHomeStationErrorPayload;
|
|
22
|
+
error?: UsaHomeStationErrorPayload;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type TranslateFn = (
|
|
27
|
+
key: string,
|
|
28
|
+
params?: Record<string, string | number>,
|
|
29
|
+
) => string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Maps US Nx1 Home Station API error codes to i18n messages.
|
|
33
|
+
* Falls back to API msg when code is unknown.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveUsaHomeStationErrorMessage(
|
|
36
|
+
error: unknown,
|
|
37
|
+
translate: TranslateFn,
|
|
38
|
+
): string {
|
|
39
|
+
const err = error as ApiErrorShape;
|
|
40
|
+
const code = err?.body?.code ?? err?.code;
|
|
41
|
+
const payload = (err?.body?.data ??
|
|
42
|
+
err?.body?.error ??
|
|
43
|
+
err?.data ??
|
|
44
|
+
err?.error) as UsaHomeStationErrorPayload | undefined;
|
|
45
|
+
const fallback =
|
|
46
|
+
err?.body?.msg ?? err?.body?.message ?? err?.msg ?? err?.message;
|
|
47
|
+
|
|
48
|
+
switch (code) {
|
|
49
|
+
case UsaHomeStationErrorCode.QUOTA_EXCEEDED:
|
|
50
|
+
return translate("quotaExceeded", {
|
|
51
|
+
defaultAllowance: 1,
|
|
52
|
+
max: payload?.maxMachinesAllowed ?? 0,
|
|
53
|
+
downline: payload?.downlineUsersCount ?? 0,
|
|
54
|
+
past: payload?.pastPurchasedQuantity ?? 0,
|
|
55
|
+
cart: payload?.cartQuantity ?? 0,
|
|
56
|
+
});
|
|
57
|
+
case UsaHomeStationErrorCode.USER_NOT_FOUND:
|
|
58
|
+
return translate("userNotFound");
|
|
59
|
+
case UsaHomeStationErrorCode.ELIGIBILITY_CHECK_FAILED:
|
|
60
|
+
return translate("eligibilityCheckFailed");
|
|
61
|
+
default:
|
|
62
|
+
return fallback ?? translate("generic");
|
|
63
|
+
}
|
|
64
|
+
}
|