merchi_cart 0.0.7-c → 0.0.7-e
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 +4 -3
- package/src/Cart.tsx +100 -0
- package/src/CartAlert.tsx +34 -0
- package/src/CartHeader.tsx +27 -0
- package/src/CartProvider.tsx +386 -0
- package/src/CartTotals.tsx +106 -0
- package/src/assets/merchi-monster-loader.gif +0 -0
- package/src/assets/merchi-swipe-outline.png +0 -0
- package/src/assets/product-not-found.png +0 -0
- package/src/buttons/Button.tsx +35 -0
- package/src/buttons/ButtonBack.tsx +37 -0
- package/src/buttons/ButtonClearCart.tsx +18 -0
- package/src/buttons/ButtonInvoiceDownload.tsx +63 -0
- package/src/buttons/ButtonNext.tsx +38 -0
- package/src/buttons/ButtonNextDynamic.tsx +31 -0
- package/src/buttons/ButtonPay.tsx +32 -0
- package/src/buttons/ButtonShipmentTabNext.tsx +25 -0
- package/src/buttons/index.ts +15 -0
- package/src/components/Alert.tsx +31 -0
- package/src/components/CartBody.tsx +16 -0
- package/src/components/CartClient.tsx +48 -0
- package/src/components/CartFooter.tsx +15 -0
- package/src/components/CartItemRow.tsx +145 -0
- package/src/components/CartPaymentSettingsInvalid.tsx +12 -0
- package/src/components/CartShipment.tsx +55 -0
- package/src/components/CartTabContent.tsx +15 -0
- package/src/components/CartTabPanel.tsx +25 -0
- package/src/components/CartTableContainer.tsx +15 -0
- package/src/components/Collapse.tsx +23 -0
- package/src/components/LoadingTemplate.tsx +22 -0
- package/src/components/NoCartItems.tsx +15 -0
- package/src/components/ShipmentGroupCard.tsx +156 -0
- package/src/components/Table.tsx +14 -0
- package/src/components/Title.tsx +22 -0
- package/src/components/containers.tsx +40 -0
- package/src/components/index.ts +30 -0
- package/src/forms/FormNewCustomer.tsx +119 -0
- package/src/forms/FormReturningCustomer.tsx +70 -0
- package/src/forms/FormShipmentAddressAndNotes.tsx +142 -0
- package/src/forms/FormSquarePayment.tsx +14 -0
- package/src/forms/FormStripePayment.tsx +14 -0
- package/src/forms/InputAcceptUserTermsAndConditions.tsx +43 -0
- package/src/forms/InputError.tsx +23 -0
- package/src/forms/InputSelect.tsx +58 -0
- package/src/forms/InputText.tsx +56 -0
- package/src/forms/InputsAddress.tsx +205 -0
- package/src/index.tsx +13 -0
- package/src/panels/PanelCartItems.tsx +61 -0
- package/src/panels/PanelCartShipment.tsx +89 -0
- package/src/panels/PanelClearCart.tsx +47 -0
- package/src/panels/PanelClientCheckout.tsx +92 -0
- package/src/panels/PanelEditCartItem.tsx +58 -0
- package/src/panels/PanelPaymentSuccess.tsx +69 -0
- package/src/panels/index.ts +15 -0
- package/src/slices/sliceCart.ts +159 -0
- package/src/slices/sliceCartAlert.ts +39 -0
- package/src/slices/sliceCartItem.ts +29 -0
- package/src/slices/sliceCartPayment.ts +45 -0
- package/src/slices/sliceCartShipment.ts +67 -0
- package/src/slices/sliceFormReturningCustomer.ts +25 -0
- package/src/slices/sliceNewCustomerForm.ts +24 -0
- package/src/square/SquareCard.tsx +58 -0
- package/src/square/actions.ts +40 -0
- package/src/square/slices/reducersSquare.ts +9 -0
- package/src/square/slices/sliceSquareAlerts.ts +29 -0
- package/src/square/slices/sliceSquareForm.ts +29 -0
- package/src/store.ts +499 -0
- package/src/stripe/StripeCardFields.tsx +164 -0
- package/src/stripe/StripeCardForm.tsx +86 -0
- package/src/stripe/StripePaymentButtons.tsx +117 -0
- package/src/stripe/actions.ts +94 -0
- package/src/stripe/slices/sliceStripeAlerts.ts +29 -0
- package/src/stripe/slices/sliceStripeCardForm.ts +9 -0
- package/src/stripe/slices/sliceStripeForm.ts +56 -0
- package/src/stripe/utils.ts +21 -0
- package/src/styles/globals.css +191 -0
- package/src/styles/globals.css.map +1 -0
- package/src/styles/globals.scss +197 -0
- package/src/tabs/CartNav.tsx +20 -0
- package/src/tabs/NavTab.tsx +38 -0
- package/src/utilities/address.ts +351 -0
- package/src/utilities/cart.ts +73 -0
- package/src/utilities/company.ts +8 -0
- package/src/utilities/cookie.ts +54 -0
- package/src/utilities/currency.ts +216 -0
- package/src/utilities/helpers.ts +209 -0
- package/src/utilities/invoice.ts +60 -0
- package/src/utilities/product.ts +7 -0
- package/src/utilities/user.ts +38 -0
- package/src/utilities/variations.ts +56 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux';
|
|
2
|
+
import MerchiProductForm from 'merchi_product_form';
|
|
3
|
+
import { tabIdItem } from '../slices/sliceCart';
|
|
4
|
+
import { patchCartItem } from '../store';
|
|
5
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
6
|
+
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons';
|
|
7
|
+
import { Button, ButtonBack } from '../buttons';
|
|
8
|
+
import {
|
|
9
|
+
CartBody,
|
|
10
|
+
CartFooter,
|
|
11
|
+
CartTabPanel,
|
|
12
|
+
} from '../components';
|
|
13
|
+
import { useCartContext } from '../CartProvider';
|
|
14
|
+
|
|
15
|
+
function PanelEditCartItem() {
|
|
16
|
+
const {
|
|
17
|
+
productFormClassNames,
|
|
18
|
+
classNameBtnPrimary,
|
|
19
|
+
urlApi,
|
|
20
|
+
} = useCartContext();
|
|
21
|
+
const { cartItem, savingCartItem } = useSelector((s: any) => s.stateCartItem);
|
|
22
|
+
const formId = 'edit-cart-item-form';
|
|
23
|
+
function onSubmit(data: any) {
|
|
24
|
+
patchCartItem(data.job);
|
|
25
|
+
}
|
|
26
|
+
return (
|
|
27
|
+
<CartTabPanel tabId={tabIdItem}>
|
|
28
|
+
<CartBody style={{padding: '2rem'}}>
|
|
29
|
+
{cartItem.id &&
|
|
30
|
+
<MerchiProductForm
|
|
31
|
+
apiHost={urlApi}
|
|
32
|
+
isCartItem={true}
|
|
33
|
+
job={cartItem}
|
|
34
|
+
initProduct={cartItem.product}
|
|
35
|
+
onSubmit={onSubmit}
|
|
36
|
+
productFormId={formId}
|
|
37
|
+
hideRequestQuotationButton={true}
|
|
38
|
+
hidePaymentUpfrontButton={true}
|
|
39
|
+
{...productFormClassNames}
|
|
40
|
+
/>
|
|
41
|
+
}
|
|
42
|
+
</CartBody>
|
|
43
|
+
<CartFooter>
|
|
44
|
+
<ButtonBack />
|
|
45
|
+
<Button
|
|
46
|
+
className={classNameBtnPrimary}
|
|
47
|
+
disabled={savingCartItem}
|
|
48
|
+
form={formId}
|
|
49
|
+
>
|
|
50
|
+
{savingCartItem && <FontAwesomeIcon icon={faCircleNotch} spin />}
|
|
51
|
+
{savingCartItem ? ' Loading...' : 'Save'}
|
|
52
|
+
</Button>
|
|
53
|
+
</CartFooter>
|
|
54
|
+
</CartTabPanel>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default PanelEditCartItem;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux';
|
|
2
|
+
import {
|
|
3
|
+
CheckoutContainer,
|
|
4
|
+
InnerContainer,
|
|
5
|
+
} from '../components/containers';
|
|
6
|
+
import { tabIdPaymentSuccess } from '../slices/sliceCart';
|
|
7
|
+
import { doCartComplete } from '../store';
|
|
8
|
+
import { companyPrimaryAddress } from '../utilities/company';
|
|
9
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
10
|
+
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons';
|
|
11
|
+
import { Button, ButtonInvoiceDownload } from '../buttons';
|
|
12
|
+
import { CartBody, CartTabPanel } from '../components';
|
|
13
|
+
import { useCartContext } from '../CartProvider';
|
|
14
|
+
|
|
15
|
+
function PanelPaymentSuccess() {
|
|
16
|
+
const {
|
|
17
|
+
classNameBtnPrimary,
|
|
18
|
+
customSuccessMessage,
|
|
19
|
+
urlTrackingPage,
|
|
20
|
+
} = useCartContext();
|
|
21
|
+
const {
|
|
22
|
+
cartCompleteLoading,
|
|
23
|
+
invoice,
|
|
24
|
+
} = useSelector((s: any) => s.stateCartPayment);
|
|
25
|
+
const { domain, id, unpaid } = invoice;
|
|
26
|
+
const email = domain && domain.company ? companyPrimaryAddress(domain.company) : null;
|
|
27
|
+
return (
|
|
28
|
+
<CartTabPanel tabId={tabIdPaymentSuccess}>
|
|
29
|
+
<CartBody>
|
|
30
|
+
<CheckoutContainer>
|
|
31
|
+
<InnerContainer>
|
|
32
|
+
<h2><FontAwesomeIcon icon={faCheckCircle} /></h2>
|
|
33
|
+
<h3>Order #{id}</h3>
|
|
34
|
+
<h4>Payment Received!</h4>
|
|
35
|
+
{customSuccessMessage ? (
|
|
36
|
+
<p className='merchi-cart-success-message '>
|
|
37
|
+
{customSuccessMessage}
|
|
38
|
+
</p>
|
|
39
|
+
) : (
|
|
40
|
+
<p className='merchi-cart-success-message '>
|
|
41
|
+
Your order #{id} is being processed and you should
|
|
42
|
+
receive an email from us shortly. If you haven't heard from us within 24
|
|
43
|
+
hours please feel free to contact us{email ? ` at ${email.emailAddress}.` : '.'}
|
|
44
|
+
</p>
|
|
45
|
+
)}
|
|
46
|
+
{!unpaid && <p><ButtonInvoiceDownload /></p>}
|
|
47
|
+
<p>
|
|
48
|
+
<Button
|
|
49
|
+
className={classNameBtnPrimary}
|
|
50
|
+
disabled={cartCompleteLoading}
|
|
51
|
+
onClick={doCartComplete}
|
|
52
|
+
>
|
|
53
|
+
{cartCompleteLoading ? 'Loading...' : 'Done'}
|
|
54
|
+
</Button>
|
|
55
|
+
</p>
|
|
56
|
+
</InnerContainer>
|
|
57
|
+
</CheckoutContainer>
|
|
58
|
+
{urlTrackingPage &&
|
|
59
|
+
<iframe
|
|
60
|
+
style={{height: '1px', left: '0px', width: '1px'}}
|
|
61
|
+
src={urlTrackingPage}
|
|
62
|
+
/>
|
|
63
|
+
}
|
|
64
|
+
</CartBody>
|
|
65
|
+
</CartTabPanel>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default PanelPaymentSuccess;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import PanelCartItems from "./PanelCartItems";
|
|
2
|
+
import PanelCartShipment from "./PanelCartShipment";
|
|
3
|
+
import PanelClearCart from "./PanelClearCart";
|
|
4
|
+
import PanelClientCheckout from "./PanelClientCheckout";
|
|
5
|
+
import PanelEditCartItem from "./PanelEditCartItem";
|
|
6
|
+
import PanelPaymentSuccess from "./PanelPaymentSuccess";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
PanelCartItems,
|
|
10
|
+
PanelCartShipment,
|
|
11
|
+
PanelClearCart,
|
|
12
|
+
PanelClientCheckout,
|
|
13
|
+
PanelPaymentSuccess,
|
|
14
|
+
PanelEditCartItem,
|
|
15
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
import {
|
|
3
|
+
faCheckCircle,
|
|
4
|
+
faCreditCard,
|
|
5
|
+
faMapMarkerAlt,
|
|
6
|
+
faShoppingCart,
|
|
7
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
8
|
+
|
|
9
|
+
export const tabIdClearCart = -2;
|
|
10
|
+
export const tabIdItem = -1;
|
|
11
|
+
export const tabIdItems = 0;
|
|
12
|
+
export const tabIdShipment = 1;
|
|
13
|
+
export const tabIdCheckout = 2;
|
|
14
|
+
export const tabIdPaymentSuccess = 3;
|
|
15
|
+
|
|
16
|
+
const tabItems = {disabled: false, icon: faShoppingCart, tabId: tabIdItems, name: 'Items'};
|
|
17
|
+
const tabShipment = {disabled: true, icon: faMapMarkerAlt, tabId: tabIdShipment, name: 'Address'};
|
|
18
|
+
const tabCheckout = {disabled: true, icon: faCreditCard, tabId: tabIdCheckout, name: 'Checkout'};
|
|
19
|
+
const tabCheckoutSuccess = {
|
|
20
|
+
disabled: true,
|
|
21
|
+
icon: faCheckCircle,
|
|
22
|
+
tabId: tabIdPaymentSuccess,
|
|
23
|
+
name: 'Success',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const initTabs: Array<any> = [tabItems, tabCheckout, tabCheckoutSuccess];
|
|
27
|
+
const tabsWithShipment: Array<any> = [tabItems, tabShipment, tabCheckout, tabCheckoutSuccess];
|
|
28
|
+
|
|
29
|
+
function setDisableTab(tab: any, disabled: boolean) {
|
|
30
|
+
return {...tab, disabled};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cartHasShippmentGroupsAndAllHaveSelectedGroups(cart: any) {
|
|
34
|
+
const shipmentGroups = cart.shipmentGroups ? cart.shipmentGroups : [];
|
|
35
|
+
const haveSelectedQuotes = shipmentGroups.map((g: any) => Boolean(g.selectedQuote));
|
|
36
|
+
return shipmentGroups.length && !haveSelectedQuotes.includes(false);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function cartItemsNeedShipment(cart: any) {
|
|
40
|
+
const { cartItems = [] } = cart;
|
|
41
|
+
for (let i = 0; i < cartItems.length; i++) {
|
|
42
|
+
if (cartItems[i].product && cartItems[i].product.needsShipping) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const sliceCart = createSlice({
|
|
50
|
+
initialState: {
|
|
51
|
+
activeTab: 0,
|
|
52
|
+
cart: {},
|
|
53
|
+
cartSettingsInvalid: false,
|
|
54
|
+
deletingCartItemIndex: undefined,
|
|
55
|
+
domainId: undefined,
|
|
56
|
+
fetchingCart: false,
|
|
57
|
+
loading: false,
|
|
58
|
+
modalCartOpen: false,
|
|
59
|
+
modalClearCartOpen: false,
|
|
60
|
+
needsShipping: false,
|
|
61
|
+
tabs: initTabs,
|
|
62
|
+
},
|
|
63
|
+
name: 'stateCart',
|
|
64
|
+
reducers: {
|
|
65
|
+
cartSettingsInvalid: (state: any) => {
|
|
66
|
+
state.cartSettingsInvalid = true;
|
|
67
|
+
state.fetchingCart = false;
|
|
68
|
+
},
|
|
69
|
+
deleteCartItem: (state: any, action: PayloadAction<any>) => {
|
|
70
|
+
state.deletingCartItemIndex = action.payload;
|
|
71
|
+
},
|
|
72
|
+
deleteCartItemError: (state: any) => {
|
|
73
|
+
state.deletingCartItemIndex = undefined;
|
|
74
|
+
},
|
|
75
|
+
deleteCartItemSuccess: (state: any, action: PayloadAction<any>) => {
|
|
76
|
+
const cart = action.payload;
|
|
77
|
+
const needsShipping = cartItemsNeedShipment(cart);
|
|
78
|
+
state.deletingCartItemIndex = undefined;
|
|
79
|
+
state.cart = cart;
|
|
80
|
+
state.needsShipping = needsShipping;
|
|
81
|
+
if (cart.cartItems.length === 0) {
|
|
82
|
+
state.tabs = initTabs;
|
|
83
|
+
} else {
|
|
84
|
+
if (needsShipping) {
|
|
85
|
+
state.tabs = tabsWithShipment;
|
|
86
|
+
if (cart.cartItems.length) {
|
|
87
|
+
state.tabs[1] = setDisableTab(tabShipment, false);
|
|
88
|
+
if (cartHasShippmentGroupsAndAllHaveSelectedGroups(cart)) {
|
|
89
|
+
state.tabs[2] = setDisableTab(tabCheckout, false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
state.tabs = initTabs;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
fetchCart: (state: any) => {
|
|
98
|
+
state.fetchingCart = true;
|
|
99
|
+
},
|
|
100
|
+
fetchCartError: (state: any) => {
|
|
101
|
+
state.fetchingCart = false;
|
|
102
|
+
},
|
|
103
|
+
fetchCartSuccess: (state: any, action: PayloadAction<any>) => {
|
|
104
|
+
const cart = action.payload;
|
|
105
|
+
const needsShipping = cartItemsNeedShipment({...cart});
|
|
106
|
+
state.cart = cart;
|
|
107
|
+
state.needsShipping = needsShipping;
|
|
108
|
+
if (needsShipping) {
|
|
109
|
+
const initShipment = [...tabsWithShipment];
|
|
110
|
+
if (cart.cartItems.length) {
|
|
111
|
+
initShipment[1] = setDisableTab(tabShipment, false);
|
|
112
|
+
if (cartHasShippmentGroupsAndAllHaveSelectedGroups(cart)) {
|
|
113
|
+
initShipment[2] = setDisableTab(tabCheckout, false);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
state.tabs = initShipment;
|
|
117
|
+
} else {
|
|
118
|
+
state.tabs = initTabs;
|
|
119
|
+
}
|
|
120
|
+
state.activeTab = 0;
|
|
121
|
+
state.fetchingCart = false;
|
|
122
|
+
},
|
|
123
|
+
fetchTheme: (state: any) => {
|
|
124
|
+
state.loading = true;
|
|
125
|
+
},
|
|
126
|
+
fetchThemeError: (state: any) => {
|
|
127
|
+
state.loading = false;
|
|
128
|
+
},
|
|
129
|
+
fetchThemeSuccess: (state: any) => {
|
|
130
|
+
state.loading = false;
|
|
131
|
+
},
|
|
132
|
+
setActiveTab: (state: any, action: PayloadAction<number>) => {
|
|
133
|
+
state.activeTab = action.payload;
|
|
134
|
+
},
|
|
135
|
+
setActiveTabAndEditDisabled: (state: any, action: PayloadAction<any>) => {
|
|
136
|
+
const { tabId, tabIndexToSet, disabled } = action.payload;
|
|
137
|
+
state.activeTab = tabId;
|
|
138
|
+
state.tabs[tabIndexToSet].disabled = disabled;
|
|
139
|
+
},
|
|
140
|
+
setCart: (state: any, action: PayloadAction<any>) => {
|
|
141
|
+
state.cart = action.payload;
|
|
142
|
+
},
|
|
143
|
+
setCartShipmentGroups: (state: any, action: PayloadAction<any>) => {
|
|
144
|
+
state.cart.shipmentGroups = action.payload;
|
|
145
|
+
},
|
|
146
|
+
setCartClient: (state: any, action: PayloadAction<any>) => {
|
|
147
|
+
state.cart.client = action.payload;
|
|
148
|
+
},
|
|
149
|
+
setDomainId: (state: any, action: PayloadAction<number>) => {
|
|
150
|
+
state.domainId = action.payload;
|
|
151
|
+
},
|
|
152
|
+
toggleCartOpen: (state: any) => {
|
|
153
|
+
state.modalCartOpen = !state.modalCartOpen;
|
|
154
|
+
},
|
|
155
|
+
toggleClearCart: (state: any) => {
|
|
156
|
+
state.modalClearCartOpen = !state.modalClearCartOpen;
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
import { faCheckCircle, faExclamationTriangle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
|
3
|
+
|
|
4
|
+
export const sliceCartAlert = createSlice({
|
|
5
|
+
initialState: {
|
|
6
|
+
alert: {
|
|
7
|
+
icon: faInfoCircle,
|
|
8
|
+
message: '',
|
|
9
|
+
show: false,
|
|
10
|
+
title: '',
|
|
11
|
+
type: 'info',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
name: 'stateCartAlert',
|
|
15
|
+
reducers: {
|
|
16
|
+
closeAlert: (state: any) => {
|
|
17
|
+
state.alert.show = false;
|
|
18
|
+
},
|
|
19
|
+
setAlert: (state: any, action: PayloadAction<any>) => {
|
|
20
|
+
state.alert = action.payload;
|
|
21
|
+
},
|
|
22
|
+
showAlertDanger: (state: any, action: PayloadAction<any>) => {
|
|
23
|
+
state.alert = {
|
|
24
|
+
...action.payload,
|
|
25
|
+
icon: faExclamationTriangle,
|
|
26
|
+
show: true,
|
|
27
|
+
type: 'danger',
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
showAlertSuccess: (state: any, action: PayloadAction<any>) => {
|
|
31
|
+
state.alert = {
|
|
32
|
+
...action.payload,
|
|
33
|
+
icon: faCheckCircle,
|
|
34
|
+
show: true,
|
|
35
|
+
type: 'success',
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
const sliceCartItem = createSlice({
|
|
4
|
+
initialState: {
|
|
5
|
+
cartItem: {},
|
|
6
|
+
fetchingPrice: false,
|
|
7
|
+
index: 0,
|
|
8
|
+
savingCartItem: false,
|
|
9
|
+
},
|
|
10
|
+
name: 'stateCartItem',
|
|
11
|
+
reducers: {
|
|
12
|
+
patchCartItem: (state: any) => {
|
|
13
|
+
state.savingCartItem = true;
|
|
14
|
+
},
|
|
15
|
+
patchCartItemError: (state: any) => {
|
|
16
|
+
state.savingCartItem = false;
|
|
17
|
+
},
|
|
18
|
+
patchCartItemSuccess: (state: any) => {
|
|
19
|
+
state.savingCartItem = false;
|
|
20
|
+
},
|
|
21
|
+
setCartItem: (state: any, action: PayloadAction<any>) => {
|
|
22
|
+
const { cartItem, index } = action.payload;
|
|
23
|
+
state.cartItem = cartItem;
|
|
24
|
+
state.index = index;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default sliceCartItem;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
function setLoadingKeys(state: any, action: any, newState: boolean) {
|
|
4
|
+
const {
|
|
5
|
+
loadingKey = null,
|
|
6
|
+
loadingKeys = null,
|
|
7
|
+
} = action.payload;
|
|
8
|
+
if (Array.isArray(loadingKeys)) {
|
|
9
|
+
loadingKeys.map((lK: string) => state[lK] = newState);
|
|
10
|
+
}
|
|
11
|
+
if (typeof loadingKey === 'string') {
|
|
12
|
+
state[loadingKey] = newState;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const sliceCartPayment = createSlice({
|
|
17
|
+
initialState: {
|
|
18
|
+
cartCompleteLoading: false,
|
|
19
|
+
invoice: {},
|
|
20
|
+
loading: false,
|
|
21
|
+
},
|
|
22
|
+
name: 'stateCartPayment',
|
|
23
|
+
reducers: {
|
|
24
|
+
doCardComplete: (state: any) => {
|
|
25
|
+
state.cartCompleteLoading = true;
|
|
26
|
+
},
|
|
27
|
+
fetchInvoice: (state: any, action: any) => {
|
|
28
|
+
setLoadingKeys(state, action, true);
|
|
29
|
+
},
|
|
30
|
+
fetchInvoiceError: (state: any, action: any) => {
|
|
31
|
+
setLoadingKeys(state, action, false);
|
|
32
|
+
},
|
|
33
|
+
fetchInvoiceSuccess: (state: any, action: any) => {
|
|
34
|
+
const { invoice } = action.payload;
|
|
35
|
+
state.invoice = invoice;
|
|
36
|
+
setLoadingKeys(state, action, false);
|
|
37
|
+
},
|
|
38
|
+
setInvoice: (state: any, action: any) => {
|
|
39
|
+
state.invoice = action.payload;
|
|
40
|
+
},
|
|
41
|
+
creditCardPaySuccess: (state: any, action: PayloadAction<any>) => {
|
|
42
|
+
state.invoice = action.payload;
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
export const shipmentFormId = '_shipment-address-form-id';
|
|
4
|
+
|
|
5
|
+
export const sliceCartShipment = createSlice({
|
|
6
|
+
initialState: {
|
|
7
|
+
addressFieldsOpen: false,
|
|
8
|
+
fetchingShipmentGroups: false,
|
|
9
|
+
fetchingShipmentQuote: false,
|
|
10
|
+
googleMapsInitialised: false,
|
|
11
|
+
receiverAddress: {},
|
|
12
|
+
receiverNotes: '',
|
|
13
|
+
savingShipmentAddress: false,
|
|
14
|
+
shipmentGroups: [],
|
|
15
|
+
selectedQuotes: [],
|
|
16
|
+
},
|
|
17
|
+
name: 'stateCartShipment',
|
|
18
|
+
reducers: {
|
|
19
|
+
initCartShipmentSlice: (state: any, action: PayloadAction<any>) => {
|
|
20
|
+
const cart = action.payload;
|
|
21
|
+
const groups = cart.shipmentGroups;
|
|
22
|
+
state.receiverAddress = cart.receiverAddress;
|
|
23
|
+
state.receiverNotes = cart.receiverNotes;
|
|
24
|
+
state.shipmentGroups = groups;
|
|
25
|
+
state.selectedQuotes = groups.map((g: any) => g.selectedQuote);
|
|
26
|
+
},
|
|
27
|
+
saveShipmentAddress: (state: any) => {
|
|
28
|
+
state.savingShipmentAddress = true;
|
|
29
|
+
},
|
|
30
|
+
saveShipmentAddressError: (state: any) => {
|
|
31
|
+
state.savingShipmentAddress = false;
|
|
32
|
+
},
|
|
33
|
+
saveShipmentAddressSuccess: (state: any) => {
|
|
34
|
+
state.savingShipmentAddress = false;
|
|
35
|
+
},
|
|
36
|
+
setGoogleMapsInitialised: (state: any, action: PayloadAction<boolean>) => {
|
|
37
|
+
state.googleMapsInitialised = action.payload;
|
|
38
|
+
},
|
|
39
|
+
setSelectedShipmentQuote: (state: any, action: PayloadAction<any>) => {
|
|
40
|
+
const { groupIndex, quote } = action.payload;
|
|
41
|
+
state.selectedQuotes[groupIndex] = quote;
|
|
42
|
+
state.fetchingShipmentQuote = true;
|
|
43
|
+
},
|
|
44
|
+
setSelectedShipmentQuoteError: (state: any) => {
|
|
45
|
+
state.fetchingShipmentQuote = false;
|
|
46
|
+
},
|
|
47
|
+
setSelectedShipmentQuoteSuccess: (state: any) => {
|
|
48
|
+
state.fetchingShipmentQuote = false;
|
|
49
|
+
},
|
|
50
|
+
toggleAddressFields: (state: any) => {
|
|
51
|
+
state.addressFieldsOpen = !state.addressFieldsOpen;
|
|
52
|
+
},
|
|
53
|
+
updateShipmentAddress: (state: any, action: PayloadAction<any>) => {
|
|
54
|
+
state.receiverAddress = action.payload;
|
|
55
|
+
state.fetchingShipmentGroups = true;
|
|
56
|
+
},
|
|
57
|
+
updateShipmentAddressError: (state: any) => {
|
|
58
|
+
state.fetchingShipmentGroups = false;
|
|
59
|
+
},
|
|
60
|
+
updateShipmentAddressSuccess: (state: any, action: PayloadAction<any>) => {
|
|
61
|
+
const groups = action.payload;
|
|
62
|
+
state.fetchingShipmentGroups = false;
|
|
63
|
+
state.shipmentGroups = groups;
|
|
64
|
+
state.selectedQuotes = groups.map((g: any) => g.selectedQuote);
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
const sliceFormReturningCustomer = createSlice({
|
|
4
|
+
initialState: {
|
|
5
|
+
returningCustomerError: false,
|
|
6
|
+
returningCustomerLoading: false
|
|
7
|
+
},
|
|
8
|
+
name: 'stateFormReturningCustomer',
|
|
9
|
+
reducers: {
|
|
10
|
+
setReturningCustomerError: (state: any, action: PayloadAction<boolean>) => {
|
|
11
|
+
state.returningCustomerError = action.payload;
|
|
12
|
+
},
|
|
13
|
+
tryReturningCustomer: (state: any) => {
|
|
14
|
+
state.returningCustomerLoading = true;
|
|
15
|
+
},
|
|
16
|
+
tryReturningCustomerError: (state: any) => {
|
|
17
|
+
state.returningCustomerLoading = false;
|
|
18
|
+
},
|
|
19
|
+
tryReturningCustomerSuccess: (state: any) => {
|
|
20
|
+
state.returningCustomerLoading = false;
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default sliceFormReturningCustomer;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
const sliceNewCustomerForm = createSlice({
|
|
4
|
+
initialState: {
|
|
5
|
+
creatingNewCustomer: false,
|
|
6
|
+
serverError: '',
|
|
7
|
+
},
|
|
8
|
+
name: 'stateNewCustomerForm',
|
|
9
|
+
reducers: {
|
|
10
|
+
createNewCustomer: (state: any) => {
|
|
11
|
+
state.creatingNewCustomer = true;
|
|
12
|
+
state.serverError = '';
|
|
13
|
+
},
|
|
14
|
+
createNewCustomerError: (state: any, action: PayloadAction<string>) => {
|
|
15
|
+
state.creatingNewCustomer = false;
|
|
16
|
+
state.serverError = action.payload;
|
|
17
|
+
},
|
|
18
|
+
createNewCustomerSuccess: (state: any) => {
|
|
19
|
+
state.creatingNewCustomer = false;
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export default sliceNewCustomerForm;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
3
|
+
import { CreditCard, PaymentForm } from 'react-square-web-payments-sdk';
|
|
4
|
+
import { actionSquarePaymentProcess } from './actions';
|
|
5
|
+
import { currencyMap } from '../utilities/currency';
|
|
6
|
+
import { callbackCreditCardPaymentSuccess } from '../store';
|
|
7
|
+
|
|
8
|
+
declare const SQUARE_APPLICATION_ID: string;
|
|
9
|
+
|
|
10
|
+
function SquareCard() {
|
|
11
|
+
const { cart } = useSelector((s: any) => s.stateCart);
|
|
12
|
+
const { currency, domain, totalCost } = cart;
|
|
13
|
+
const company = domain.company;
|
|
14
|
+
const dispatch = useDispatch();
|
|
15
|
+
function ButtonText() {
|
|
16
|
+
return (
|
|
17
|
+
<span>
|
|
18
|
+
Pay {currencyMap[currency]}
|
|
19
|
+
{totalCost}<small> / inc tax</small>
|
|
20
|
+
</span>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const squareWebLocationId = company.squareWebLocationId;
|
|
24
|
+
return squareWebLocationId ?
|
|
25
|
+
(
|
|
26
|
+
<PaymentForm
|
|
27
|
+
/**
|
|
28
|
+
* Identifies the calling form with a verified application ID generated from
|
|
29
|
+
* the Square Application Dashboard.
|
|
30
|
+
*/
|
|
31
|
+
applicationId={SQUARE_APPLICATION_ID}
|
|
32
|
+
/**
|
|
33
|
+
* Invoked when payment form receives the result of a tokenize generation
|
|
34
|
+
* request. The result will be a valid credit card or wallet token, or an error.
|
|
35
|
+
*/
|
|
36
|
+
cardTokenizeResponseReceived={(token: any, buyer: any) =>
|
|
37
|
+
actionSquarePaymentProcess(
|
|
38
|
+
dispatch,
|
|
39
|
+
cart,
|
|
40
|
+
token.token,
|
|
41
|
+
callbackCreditCardPaymentSuccess)
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Identifies the location of the merchant that is taking the payment.
|
|
45
|
+
* Obtained from the Square Application Dashboard - Locations tab.
|
|
46
|
+
*/
|
|
47
|
+
locationId={squareWebLocationId}
|
|
48
|
+
>
|
|
49
|
+
<CreditCard>
|
|
50
|
+
<ButtonText />
|
|
51
|
+
</CreditCard>
|
|
52
|
+
</PaymentForm>
|
|
53
|
+
)
|
|
54
|
+
:
|
|
55
|
+
<></>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default SquareCard;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Merchi } from 'merchi_sdk_ts';
|
|
2
|
+
import sliceSquareAlerts from './slices/sliceSquareAlerts';
|
|
3
|
+
import sliceSquareForm from './slices/sliceSquareForm';
|
|
4
|
+
|
|
5
|
+
const { alertError } = sliceSquareAlerts.actions;
|
|
6
|
+
|
|
7
|
+
type DispatchInterface = (action: any) => void;
|
|
8
|
+
|
|
9
|
+
export function actionAlertError(dispatch: DispatchInterface, message: string) {
|
|
10
|
+
dispatch(alertError(message));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const merchi = new Merchi();
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
actionSquarePaymentEnd,
|
|
17
|
+
actionSquarePaymentStart,
|
|
18
|
+
} = sliceSquareForm.actions;
|
|
19
|
+
|
|
20
|
+
export async function actionSquarePaymentProcess(
|
|
21
|
+
dispatch: any,
|
|
22
|
+
cart: any,
|
|
23
|
+
sourceId: string,
|
|
24
|
+
callbackPaymentSuccess: any,
|
|
25
|
+
) {
|
|
26
|
+
dispatch(actionSquarePaymentStart());
|
|
27
|
+
let url = `/cart/${cart.id}/square/payment/`;
|
|
28
|
+
const fetchOptions: any = {
|
|
29
|
+
method: 'GET',
|
|
30
|
+
query: [['sourceId', sourceId], ['cart_token', cart.token]],
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
const r = await merchi.authenticatedFetch(url, fetchOptions);
|
|
34
|
+
callbackPaymentSuccess(r.invoice);
|
|
35
|
+
dispatch(actionSquarePaymentEnd());
|
|
36
|
+
} catch(e: any) {
|
|
37
|
+
actionAlertError(dispatch, e);
|
|
38
|
+
dispatch(actionSquarePaymentEnd());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import sliceSquareAlerts from './sliceSquareAlerts';
|
|
2
|
+
import sliceSquareForm from './sliceSquareForm';
|
|
3
|
+
|
|
4
|
+
const reducersSquare = {
|
|
5
|
+
stateSquareAlerts: sliceSquareAlerts.reducer,
|
|
6
|
+
stateSquareForm: sliceSquareForm.reducer,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default reducersSquare;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
const sliceSquareAlerts = createSlice({
|
|
4
|
+
initialState: {
|
|
5
|
+
alerts: [],
|
|
6
|
+
},
|
|
7
|
+
name: 'stateSquareAlerts',
|
|
8
|
+
reducers: {
|
|
9
|
+
alertError: (state: any, action: PayloadAction<any>) => {
|
|
10
|
+
state.alerts =
|
|
11
|
+
[...state.alerts,
|
|
12
|
+
{ alertType: 'danger',
|
|
13
|
+
message: action.payload,
|
|
14
|
+
title: 'Error!' }];
|
|
15
|
+
},
|
|
16
|
+
alertRemove: (state: any, action: PayloadAction<any>) => {
|
|
17
|
+
state.alerts.splice(action.payload, 1);
|
|
18
|
+
},
|
|
19
|
+
alertSuccess: (state: any, action: PayloadAction<any>) => {
|
|
20
|
+
state.alerts =
|
|
21
|
+
[...state.alerts,
|
|
22
|
+
{ alertType: 'success',
|
|
23
|
+
message: action.payload,
|
|
24
|
+
title: 'Success!' }];
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default sliceSquareAlerts;
|