merchi_cart 1.3.2 → 1.3.4
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/esm/CartProvider.js +239 -154
- package/esm/components/CustomCartProductForm.js +129 -0
- package/esm/components/product-form-sdk-externals.js +35 -0
- package/esm/forms/FormNewCustomer.js +22 -13
- package/esm/forms/FormReturningCustomer.js +18 -16
- package/esm/forms/FormShipmentAddressAndNotes.js +14 -11
- package/esm/utilities/customForm.js +57 -0
- package/esm/utilities/index.js +1 -0
- package/esm/utilities/local_storage.js +85 -0
- package/lib/CartProvider.js +239 -154
- package/lib/components/CustomCartProductForm.js +137 -0
- package/lib/components/product-form-sdk-externals.js +42 -0
- package/lib/forms/FormNewCustomer.js +22 -13
- package/lib/forms/FormReturningCustomer.js +18 -16
- package/lib/forms/FormShipmentAddressAndNotes.js +14 -11
- package/lib/utilities/customForm.js +64 -0
- package/lib/utilities/index.js +11 -0
- package/lib/utilities/local_storage.js +96 -0
- package/package.json +1 -1
- package/src/CartProvider.tsx +39 -3
- package/src/forms/FormNewCustomer.tsx +19 -3
- package/src/forms/FormReturningCustomer.tsx +15 -8
- package/src/forms/FormShipmentAddressAndNotes.tsx +12 -1
- package/src/utilities/index.ts +1 -0
- package/src/utilities/local_storage.ts +130 -0
package/src/CartProvider.tsx
CHANGED
|
@@ -15,6 +15,11 @@ import { makeAddress } from './utilities/address';
|
|
|
15
15
|
import { appendStyleSheetText } from './utilities/helpers';
|
|
16
16
|
import { makeJob } from './utilities/job';
|
|
17
17
|
import { makeProduct } from './utilities/product';
|
|
18
|
+
import {
|
|
19
|
+
getSavedCheckoutCustomer,
|
|
20
|
+
saveCheckoutAddress,
|
|
21
|
+
} from './utilities/local_storage';
|
|
22
|
+
import { makeUser } from './utilities/user';
|
|
18
23
|
|
|
19
24
|
export interface PropsCart {
|
|
20
25
|
cart: any;
|
|
@@ -577,15 +582,44 @@ const CartProvider = ({
|
|
|
577
582
|
const [fetchingCart, setFetchingCart] = useState(false);
|
|
578
583
|
const [cartSettingsInvalid, setCartSettingsInvalid] = useState(false);
|
|
579
584
|
|
|
585
|
+
async function attachSavedClientIfNeeded(cartJson: any) {
|
|
586
|
+
if (cartJson?.client?.id) {
|
|
587
|
+
setCartClient(cartJson.client);
|
|
588
|
+
return cartJson;
|
|
589
|
+
}
|
|
590
|
+
const savedCustomer = getSavedCheckoutCustomer(domainId);
|
|
591
|
+
if (!savedCustomer?.id) {
|
|
592
|
+
setCartClient(null);
|
|
593
|
+
return cartJson;
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const cartToken =
|
|
597
|
+
cartJson.token || (await getCartCookieToken(domainId as number));
|
|
598
|
+
const clientEnt = makeUser({ id: savedCustomer.id }, true);
|
|
599
|
+
const cartEnt = makeCart({ ...cartJson }, false, cartToken);
|
|
600
|
+
cartEnt.client = clientEnt;
|
|
601
|
+
const savedCart = await cartEnt.save({ embed: cartEmbed });
|
|
602
|
+
const nextCart = savedCart.toJson();
|
|
603
|
+
if (nextCart?.client?.id) {
|
|
604
|
+
setCartClient(nextCart.client);
|
|
605
|
+
return nextCart;
|
|
606
|
+
}
|
|
607
|
+
} catch {
|
|
608
|
+
// Stale saved customer id — leave customer forms available.
|
|
609
|
+
}
|
|
610
|
+
setCartClient(null);
|
|
611
|
+
return cartJson;
|
|
612
|
+
}
|
|
613
|
+
|
|
580
614
|
// Create cart and save cart cookie
|
|
581
615
|
async function createCartAndCookie(callback?: () => void) {
|
|
582
616
|
setFetchingCart(true);
|
|
583
617
|
try {
|
|
584
618
|
const cart = makeCart({domain: {id: domainId}}, true);
|
|
585
619
|
await cart.create({embed: cartEmbed});
|
|
586
|
-
|
|
620
|
+
let cartJson = await cart.toJson();
|
|
587
621
|
if (domainId) setCartCookie(Number(domainId), cartJson, undefined);
|
|
588
|
-
|
|
622
|
+
cartJson = await attachSavedClientIfNeeded(cartJson);
|
|
589
623
|
setCart({...cartJson});
|
|
590
624
|
if (callback) callback();
|
|
591
625
|
} catch (e: any) {
|
|
@@ -603,8 +637,9 @@ const CartProvider = ({
|
|
|
603
637
|
setFetchingCart(true);
|
|
604
638
|
try {
|
|
605
639
|
const cart = await merchi.Cart.get(id, {embed: cartEmbed});
|
|
606
|
-
|
|
640
|
+
let cartJson = await cart.toJson();
|
|
607
641
|
if (stripeIsValidAndActive(cartJson)) {
|
|
642
|
+
cartJson = await attachSavedClientIfNeeded(cartJson);
|
|
608
643
|
setCart({...cartJson});
|
|
609
644
|
} else {
|
|
610
645
|
console.error(
|
|
@@ -648,6 +683,7 @@ const CartProvider = ({
|
|
|
648
683
|
cartEnt.receiverAddress = receiverAddress;
|
|
649
684
|
cartEnt = await cartEnt.save({embed: cartEmbed});
|
|
650
685
|
const cartJson = cartEnt.toJson();
|
|
686
|
+
saveCheckoutAddress(domainId, address);
|
|
651
687
|
const query: Array<any> = [];
|
|
652
688
|
query.push(['cart_token', cart.token]);
|
|
653
689
|
const cartWithShipmentGroups = await merchi.authenticatedFetch(
|
|
@@ -10,6 +10,10 @@ import InputError from './InputError';
|
|
|
10
10
|
import { createNewCustomer, makeUser } from '../utilities/user';
|
|
11
11
|
import { makeCart } from '../utilities/cart';
|
|
12
12
|
import { getCartCookieToken } from '../utilities/cookie';
|
|
13
|
+
import {
|
|
14
|
+
getSavedCheckoutCustomer,
|
|
15
|
+
saveCheckoutCustomer,
|
|
16
|
+
} from '../utilities/local_storage';
|
|
13
17
|
|
|
14
18
|
export const emailValidation = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]/i;
|
|
15
19
|
|
|
@@ -27,6 +31,7 @@ export function FormCustomerNew() {
|
|
|
27
31
|
const [loading, setLoading] = React.useState(false);
|
|
28
32
|
const [error, setError] = React.useState(({} as any));
|
|
29
33
|
const [acceptConditions, setAcceptConditions] = React.useState(true);
|
|
34
|
+
const savedCustomer = getSavedCheckoutCustomer(domainId);
|
|
30
35
|
|
|
31
36
|
async function actionCreateNewCustomer(customerJson: any) {
|
|
32
37
|
setError({});
|
|
@@ -50,6 +55,12 @@ export function FormCustomerNew() {
|
|
|
50
55
|
if (!cartJson?.client?.id) {
|
|
51
56
|
throw new Error('Unable to attach client to cart.');
|
|
52
57
|
}
|
|
58
|
+
saveCheckoutCustomer(domainId, {
|
|
59
|
+
id: user.id,
|
|
60
|
+
name: customerJson.name,
|
|
61
|
+
emailAddress: customerJson.emailAddresses?.[0]?.emailAddress,
|
|
62
|
+
phoneNumbers: customerJson.phoneNumbers,
|
|
63
|
+
});
|
|
53
64
|
setCartClient(cartJson.client);
|
|
54
65
|
setCart(cartJson);
|
|
55
66
|
} catch (e: any) {
|
|
@@ -61,9 +72,14 @@ export function FormCustomerNew() {
|
|
|
61
72
|
|
|
62
73
|
const { control, handleSubmit, watch } = useForm({
|
|
63
74
|
defaultValues: {
|
|
64
|
-
emailAddresses: [{
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
emailAddresses: [{
|
|
76
|
+
emailAddress: savedCustomer?.emailAddress || '',
|
|
77
|
+
}],
|
|
78
|
+
name: savedCustomer?.name || '',
|
|
79
|
+
phoneNumbers: [{
|
|
80
|
+
code: savedCustomer?.phoneNumbers?.[0]?.code || 'AU',
|
|
81
|
+
number: savedCustomer?.phoneNumbers?.[0]?.number || '',
|
|
82
|
+
}],
|
|
67
83
|
registeredAsGuest: false,
|
|
68
84
|
},
|
|
69
85
|
});
|
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
import { makeCart } from '../utilities/cart';
|
|
11
11
|
import { getCartCookieToken } from '../utilities/cookie';
|
|
12
12
|
import { cartEmbed } from '../utilities/helpers';
|
|
13
|
+
import {
|
|
14
|
+
getSavedCheckoutCustomer,
|
|
15
|
+
saveCheckoutCustomer,
|
|
16
|
+
} from '../utilities/local_storage';
|
|
13
17
|
|
|
14
18
|
const emailValidation = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]/i;
|
|
15
19
|
|
|
@@ -27,8 +31,11 @@ function FormReturningCustomer() {
|
|
|
27
31
|
} = useCartContext();
|
|
28
32
|
const [loading, setLoading] = React.useState(false);
|
|
29
33
|
const [error, setError] = React.useState(({} as any));
|
|
34
|
+
const savedCustomer = getSavedCheckoutCustomer(domainId);
|
|
30
35
|
|
|
31
|
-
const hookForm = useForm({
|
|
36
|
+
const hookForm = useForm({
|
|
37
|
+
defaultValues: { emailAddress: savedCustomer?.emailAddress || '' },
|
|
38
|
+
});
|
|
32
39
|
const {
|
|
33
40
|
handleSubmit,
|
|
34
41
|
formState: { errors },
|
|
@@ -46,15 +53,10 @@ function FormReturningCustomer() {
|
|
|
46
53
|
if (!userId) {
|
|
47
54
|
throw new Error('Unable to find returning customer.');
|
|
48
55
|
}
|
|
49
|
-
const userJson = {
|
|
50
|
-
id: userId,
|
|
51
|
-
emailAddresses: [{emailAddress}],
|
|
52
|
-
name: user.name || 'Hidden for privacy'
|
|
53
|
-
};
|
|
54
56
|
|
|
55
|
-
//
|
|
57
|
+
// Attach by id only — extra user fields trigger a forbidden user PATCH.
|
|
56
58
|
const cartToken = await getCartCookieToken((domainId as number));
|
|
57
|
-
const clientEnt = makeUser(
|
|
59
|
+
const clientEnt = makeUser({id: userId}, true);
|
|
58
60
|
const cartEnt = makeCart({...cart}, false, cartToken);
|
|
59
61
|
cartEnt.client = clientEnt;
|
|
60
62
|
const _cart = await cartEnt.save({embed: cartEmbed});
|
|
@@ -62,6 +64,11 @@ function FormReturningCustomer() {
|
|
|
62
64
|
if (!cartJson?.client?.id) {
|
|
63
65
|
throw new Error('Unable to attach client to cart.');
|
|
64
66
|
}
|
|
67
|
+
saveCheckoutCustomer(domainId, {
|
|
68
|
+
id: userId,
|
|
69
|
+
name: user.name || savedCustomer?.name,
|
|
70
|
+
emailAddress,
|
|
71
|
+
});
|
|
65
72
|
setCartClient(cartJson.client);
|
|
66
73
|
setCart(cartJson);
|
|
67
74
|
} catch(e: any) {
|
|
@@ -11,6 +11,11 @@ import { makeAddress } from "../utilities/address";
|
|
|
11
11
|
import { makeCart } from "../utilities/cart";
|
|
12
12
|
import { cartEmbed } from "../utilities/helpers";
|
|
13
13
|
import { tabIdCheckout } from "../utilities/tabs";
|
|
14
|
+
import {
|
|
15
|
+
addressHasContent,
|
|
16
|
+
getSavedCheckoutAddress,
|
|
17
|
+
saveCheckoutAddress,
|
|
18
|
+
} from "../utilities/local_storage";
|
|
14
19
|
|
|
15
20
|
interface PropsAddress {
|
|
16
21
|
defaultAddress?: any;
|
|
@@ -110,7 +115,12 @@ export function ActiveFormShipmentAddressAndNotes() {
|
|
|
110
115
|
setLoadingTotals,
|
|
111
116
|
setActiveTabAndEditDisabled,
|
|
112
117
|
} = useCartContext();
|
|
113
|
-
const
|
|
118
|
+
const saved = getSavedCheckoutAddress(domainId);
|
|
119
|
+
const cartAddress = cart?.receiverAddress;
|
|
120
|
+
const receiverAddress = addressHasContent(cartAddress)
|
|
121
|
+
? cartAddress
|
|
122
|
+
: saved.address || {};
|
|
123
|
+
const receiverNotes = cart?.receiverNotes || saved.receiverNotes || "";
|
|
114
124
|
const [addressFieldsOpen, setAddressFieldsOpen] = useState(false);
|
|
115
125
|
|
|
116
126
|
async function saveCartShipmentAddressAndGoToNextTab(values: any) {
|
|
@@ -123,6 +133,7 @@ export function ActiveFormShipmentAddressAndNotes() {
|
|
|
123
133
|
cartEnt.receiverNotes = receiverNotes;
|
|
124
134
|
try {
|
|
125
135
|
await cartEnt.save({ embed: cartEmbed });
|
|
136
|
+
saveCheckoutAddress(domainId, address, receiverNotes);
|
|
126
137
|
setCart(cartEnt.toJson()); // Update cart context
|
|
127
138
|
setActiveTabAndEditDisabled({
|
|
128
139
|
tabId: tabIdCheckout,
|
package/src/utilities/index.ts
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { isBrowser } from 'browser-or-node';
|
|
2
|
+
|
|
3
|
+
export interface SavedCheckoutCustomer {
|
|
4
|
+
id?: number;
|
|
5
|
+
name?: string;
|
|
6
|
+
emailAddress?: string;
|
|
7
|
+
phoneNumbers?: Array<{ code?: string; number?: string }>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SavedCheckoutAddress {
|
|
11
|
+
city?: string;
|
|
12
|
+
country?: string;
|
|
13
|
+
lineOne?: string;
|
|
14
|
+
lineTwo?: string;
|
|
15
|
+
postcode?: string;
|
|
16
|
+
state?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SavedCheckoutDraft {
|
|
20
|
+
customer?: SavedCheckoutCustomer;
|
|
21
|
+
address?: SavedCheckoutAddress;
|
|
22
|
+
receiverNotes?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function storageKey(domainId: number | string) {
|
|
26
|
+
return `merchi-cart-checkout-${domainId}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readDraft(domainId?: number | string | null): SavedCheckoutDraft {
|
|
30
|
+
if (!isBrowser || domainId === undefined || domainId === null) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const raw = localStorage.getItem(storageKey(domainId));
|
|
35
|
+
if (!raw) return {};
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeDraft(domainId: number | string | undefined | null, draft: SavedCheckoutDraft) {
|
|
44
|
+
if (!isBrowser || domainId === undefined || domainId === null) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
localStorage.setItem(storageKey(domainId), JSON.stringify(draft));
|
|
49
|
+
} catch {
|
|
50
|
+
// Ignore quota / private-mode failures.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getSavedCheckoutDraft(domainId?: number | string | null): SavedCheckoutDraft {
|
|
55
|
+
return readDraft(domainId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getSavedCheckoutCustomer(
|
|
59
|
+
domainId?: number | string | null
|
|
60
|
+
): SavedCheckoutCustomer | undefined {
|
|
61
|
+
return readDraft(domainId).customer;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getSavedCheckoutAddress(
|
|
65
|
+
domainId?: number | string | null
|
|
66
|
+
): { address?: SavedCheckoutAddress; receiverNotes?: string } {
|
|
67
|
+
const draft = readDraft(domainId);
|
|
68
|
+
return {
|
|
69
|
+
address: draft.address,
|
|
70
|
+
receiverNotes: draft.receiverNotes,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function saveCheckoutCustomer(
|
|
75
|
+
domainId: number | string | undefined | null,
|
|
76
|
+
customer: SavedCheckoutCustomer
|
|
77
|
+
) {
|
|
78
|
+
const draft = readDraft(domainId);
|
|
79
|
+
const nextCustomer: SavedCheckoutCustomer = {
|
|
80
|
+
...draft.customer,
|
|
81
|
+
...customer,
|
|
82
|
+
};
|
|
83
|
+
// Drop empty phone entries.
|
|
84
|
+
if (nextCustomer.phoneNumbers) {
|
|
85
|
+
nextCustomer.phoneNumbers = nextCustomer.phoneNumbers.filter(
|
|
86
|
+
(phone) => phone && (phone.number || phone.code)
|
|
87
|
+
);
|
|
88
|
+
if (!nextCustomer.phoneNumbers.length) {
|
|
89
|
+
delete nextCustomer.phoneNumbers;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
writeDraft(domainId, { ...draft, customer: nextCustomer });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function saveCheckoutAddress(
|
|
96
|
+
domainId: number | string | undefined | null,
|
|
97
|
+
address: SavedCheckoutAddress | null | undefined,
|
|
98
|
+
receiverNotes?: string
|
|
99
|
+
) {
|
|
100
|
+
if (!address) return;
|
|
101
|
+
const draft = readDraft(domainId);
|
|
102
|
+
const nextAddress: SavedCheckoutAddress = {
|
|
103
|
+
city: address.city || '',
|
|
104
|
+
country: address.country || '',
|
|
105
|
+
lineOne: address.lineOne || '',
|
|
106
|
+
lineTwo: address.lineTwo || '',
|
|
107
|
+
postcode: address.postcode || '',
|
|
108
|
+
state: address.state || '',
|
|
109
|
+
};
|
|
110
|
+
const next: SavedCheckoutDraft = {
|
|
111
|
+
...draft,
|
|
112
|
+
address: nextAddress,
|
|
113
|
+
};
|
|
114
|
+
if (receiverNotes !== undefined) {
|
|
115
|
+
next.receiverNotes = receiverNotes;
|
|
116
|
+
}
|
|
117
|
+
writeDraft(domainId, next);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function addressHasContent(address?: SavedCheckoutAddress | null) {
|
|
121
|
+
if (!address) return false;
|
|
122
|
+
return Boolean(
|
|
123
|
+
address.lineOne ||
|
|
124
|
+
address.lineTwo ||
|
|
125
|
+
address.city ||
|
|
126
|
+
address.state ||
|
|
127
|
+
address.postcode ||
|
|
128
|
+
address.country
|
|
129
|
+
);
|
|
130
|
+
}
|