@lmnto/h-mall-shared 1.0.14 → 1.0.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lmnto/h-mall-shared",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "private": false,
5
5
  "sideEffects": false,
6
6
  "scripts": {
@@ -17,12 +17,14 @@ export type AddressFormComponentProps = {
17
17
  isLoading: boolean;
18
18
  proceedSubmit: FormEventHandler<HTMLButtonElement>;
19
19
  onCancel?: () => void;
20
+ enforceCheckoutEnabled?: boolean;
20
21
  };
21
22
 
22
23
  export default function AddressFormComponent({
23
24
  formId,
24
25
  isLoading,
25
26
  proceedSubmit,
27
+ enforceCheckoutEnabled = false,
26
28
  }: AddressFormComponentProps) {
27
29
  //
28
30
  const scopeT = _useScopedI18n("sharedComponents.addressFormComponent");
@@ -126,6 +128,7 @@ export default function AddressFormComponent({
126
128
  name="countryId"
127
129
  formId={formId}
128
130
  onCountrySelected={handleGetState}
131
+ enforceCheckoutEnabled={enforceCheckoutEnabled}
129
132
  />
130
133
  </div>
131
134
  <div className="col-span-12 md:col-span-6 relative">
@@ -18,12 +18,14 @@ export type AddressShippingFormComponentProps = {
18
18
  isLoading: boolean;
19
19
  proceedSubmit: FormEventHandler<HTMLButtonElement>;
20
20
  onCancel?: () => void;
21
+ enforceCheckoutEnabled?: boolean;
21
22
  };
22
23
 
23
24
  export default function AddressShippingFormComponent({
24
25
  formId,
25
26
  isLoading,
26
27
  proceedSubmit,
28
+ enforceCheckoutEnabled = false,
27
29
  }: AddressShippingFormComponentProps) {
28
30
  //
29
31
  const scopeT = _useScopedI18n("sharedComponents.addressFormComponent");
@@ -134,6 +136,7 @@ export default function AddressShippingFormComponent({
134
136
  formId={formId}
135
137
  onCountrySelected={handleGetState}
136
138
  forShipping={true}
139
+ enforceCheckoutEnabled={enforceCheckoutEnabled}
137
140
  />
138
141
  </div>
139
142
  <div className="col-span-12 md:col-span-6 relative">
@@ -18,6 +18,8 @@ export interface InputFieldProps {
18
18
  formId: string;
19
19
  forShipping?: boolean;
20
20
  forPickUp?: boolean;
21
+ /** When true, only countries with isCheckoutEnabled are shown (checkout flows). */
22
+ enforceCheckoutEnabled?: boolean;
21
23
  onCountrySelected?: (value: string | number) => void;
22
24
  }
23
25
 
@@ -29,10 +31,12 @@ export function SelectCountries({
29
31
  formId,
30
32
  forShipping = true,
31
33
  forPickUp = false,
34
+ enforceCheckoutEnabled = false,
32
35
  onCountrySelected,
33
36
  }: InputFieldProps) {
34
37
  const {
35
38
  countries,
39
+ shippingCountries,
36
40
  setStates,
37
41
  setShippingStates,
38
42
  setCities,
@@ -50,8 +54,14 @@ export function SelectCountries({
50
54
  const [error, setError] = useState<any>(null);
51
55
  const [search, setSearch] = useState<string>("");
52
56
 
53
- const countriesType = countries;
54
- const filteredCountries = countriesType?.filter((country) =>
57
+ const baseCountries =
58
+ forShipping && shippingCountries?.length
59
+ ? shippingCountries
60
+ : countries;
61
+ const checkoutFiltered = enforceCheckoutEnabled
62
+ ? baseCountries?.filter((country) => country.isCheckoutEnabled !== false)
63
+ : baseCountries;
64
+ const filteredCountries = checkoutFiltered?.filter((country) =>
55
65
  country.name.toLowerCase().includes(search.toLowerCase()),
56
66
  );
57
67
 
@@ -73,7 +83,7 @@ export function SelectCountries({
73
83
  // Handle change logic for country selection
74
84
  const handleOnChange = (value: any) => {
75
85
  const countryId = +value.id; // Ensure it's a number
76
- const selectedCountry = countries.find((c) => c.id === countryId);
86
+ const selectedCountry = checkoutFiltered?.find((c) => c.id === countryId);
77
87
 
78
88
  if (!selectedCountry) return; // Handle invalid selection safely
79
89
 
@@ -138,7 +148,7 @@ export function SelectCountries({
138
148
  focus:bg-offWhite
139
149
  shadow-none ${className}
140
150
  `}
141
- items={filteredCountries}
151
+ items={filteredCountries ?? []}
142
152
  searchKey="name"
143
153
  placeholder={placeholder}
144
154
  renderOption={(_item, country) => {
@@ -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: () => setLoading(false),
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: () => setLoading(false),
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: () => setLoading(false),
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: {
@@ -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: {
@@ -24,4 +24,6 @@ export interface ICountries {
24
24
  zipCodeExists: boolean;
25
25
  zipCodeRegex: string;
26
26
  zipCodeExample: string;
27
+ /** Partner Hub checkout eligibility; used to filter checkout country pickers only. */
28
+ isCheckoutEnabled?: boolean;
27
29
  }
@@ -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
+ }