@springmicro/cart 0.3.4 → 0.4.0
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/dist/index.js +9663 -9576
- package/dist/index.umd.cjs +64 -90
- package/package.json +3 -3
- package/src/checkout/{CheckoutList.css → ReviewCartAndCalculateTaxes.css} +1 -1
- package/src/checkout/ReviewCartAndCalculateTaxes.tsx +370 -0
- package/src/checkout/components/AddCard.tsx +267 -0
- package/src/checkout/components/Address.tsx +7 -11
- package/src/checkout/components/CartList.tsx +151 -0
- package/src/checkout/components/CartProductCard.css +1 -1
- package/src/checkout/components/CartProductCard.tsx +28 -18
- package/src/checkout/components/Invoice.tsx +145 -0
- package/src/checkout/components/StatusBar.tsx +32 -0
- package/src/checkout/index.tsx +165 -0
- package/src/index.ts +7 -6
- package/src/checkout/CheckoutList.tsx +0 -272
- package/src/checkout/components/Billing.tsx +0 -353
- package/src/checkout/components/Order.tsx +0 -95
- package/src/checkout/components/index.tsx +0 -104
|
@@ -43,9 +43,9 @@ export function AddressCountryField({ formik, name, sx }: AddressFieldProps) {
|
|
|
43
43
|
return (
|
|
44
44
|
<Autocomplete
|
|
45
45
|
sx={{ width: 300, ...sx }}
|
|
46
|
-
options={Object.
|
|
46
|
+
options={Object.keys(allCountries)}
|
|
47
47
|
autoHighlight
|
|
48
|
-
getOptionLabel={(option) => option}
|
|
48
|
+
getOptionLabel={(option) => allCountries[option]}
|
|
49
49
|
value={formik.values[name] as string}
|
|
50
50
|
onChange={(e, newValue) => {
|
|
51
51
|
// console.log(newValue)
|
|
@@ -75,14 +75,14 @@ export function AddressCountryField({ formik, name, sx }: AddressFieldProps) {
|
|
|
75
75
|
loading="lazy"
|
|
76
76
|
width="20"
|
|
77
77
|
src={`https://flagcdn.com/w20/${allCountriesReverse[
|
|
78
|
-
option
|
|
78
|
+
allCountries[option]
|
|
79
79
|
].toLowerCase()}.png`}
|
|
80
80
|
srcSet={`https://flagcdn.com/w40/${allCountriesReverse[
|
|
81
|
-
option
|
|
81
|
+
allCountries[option]
|
|
82
82
|
].toLowerCase()}.png 2x`}
|
|
83
83
|
alt=""
|
|
84
84
|
/>
|
|
85
|
-
{option}
|
|
85
|
+
{allCountries[option]}
|
|
86
86
|
</Box>
|
|
87
87
|
)}
|
|
88
88
|
renderInput={(params) => (
|
|
@@ -100,9 +100,7 @@ export function AddressCountryField({ formik, name, sx }: AddressFieldProps) {
|
|
|
100
100
|
export function AddressRegionField({ formik, name, sx }: AddressFieldProps) {
|
|
101
101
|
const [addressRegionFieldOptional, setAddressRegionFieldOptional] =
|
|
102
102
|
useState<boolean>(false);
|
|
103
|
-
const country2code =
|
|
104
|
-
formik.values["country"] as string
|
|
105
|
-
] as Alpha2Code;
|
|
103
|
+
const country2code = formik.values["country"] as Alpha2Code;
|
|
106
104
|
|
|
107
105
|
useEffect(() => {
|
|
108
106
|
setAddressRegionFieldOptional(
|
|
@@ -233,9 +231,7 @@ export function AddressPostalCodeField({
|
|
|
233
231
|
name,
|
|
234
232
|
sx,
|
|
235
233
|
}: AddressFieldProps) {
|
|
236
|
-
const country2code =
|
|
237
|
-
formik.values["country"] as string
|
|
238
|
-
] as Alpha2Code;
|
|
234
|
+
const country2code = formik.values["country"] as Alpha2Code;
|
|
239
235
|
|
|
240
236
|
React.useEffect(() => {
|
|
241
237
|
if (getPostalCodeDefault(country2code)) {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Box, Button, Tooltip, Typography } from "@mui/material";
|
|
2
|
+
import { CartProductCard } from "./CartProductCard";
|
|
3
|
+
|
|
4
|
+
function formatPrice(cents) {
|
|
5
|
+
if (typeof cents === "string") return cents;
|
|
6
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function CartList({
|
|
10
|
+
status,
|
|
11
|
+
cart,
|
|
12
|
+
subtotal,
|
|
13
|
+
tax,
|
|
14
|
+
shipping,
|
|
15
|
+
discount,
|
|
16
|
+
prices,
|
|
17
|
+
disableMissingImage,
|
|
18
|
+
disableProductLink,
|
|
19
|
+
formik,
|
|
20
|
+
formDisabled,
|
|
21
|
+
formError,
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<form
|
|
25
|
+
style={{ flexGrow: 1 }}
|
|
26
|
+
onSubmit={(e) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
formik.handleSubmit(e);
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<Box
|
|
32
|
+
sx={{
|
|
33
|
+
display: "flex",
|
|
34
|
+
justifyContent: "center",
|
|
35
|
+
flexGrow: 1,
|
|
36
|
+
mb: { xs: 4, xl: undefined },
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<Box
|
|
40
|
+
sx={{
|
|
41
|
+
display: "flex",
|
|
42
|
+
flexDirection: "column",
|
|
43
|
+
maxWidth: 650,
|
|
44
|
+
alignItems: "center",
|
|
45
|
+
boxShadow: "0px 2px 5px 2px #dfdfdfff",
|
|
46
|
+
margin: "4px",
|
|
47
|
+
padding: "2rem 0",
|
|
48
|
+
borderRadius: "8px",
|
|
49
|
+
flexGrow: 1,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<Typography style={{ fontSize: "30px", fontWeight: "bold" }}>
|
|
53
|
+
Checkout
|
|
54
|
+
</Typography>
|
|
55
|
+
<Box
|
|
56
|
+
sx={{
|
|
57
|
+
width: "50%",
|
|
58
|
+
display: "flex",
|
|
59
|
+
justifyContent: "space-between",
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<Typography sx={{ fontSize: "20px", p: 1 }}>
|
|
63
|
+
Subtotal: {formatPrice(subtotal)}
|
|
64
|
+
</Typography>
|
|
65
|
+
{tax.error ? (
|
|
66
|
+
<Tooltip
|
|
67
|
+
disableInteractive
|
|
68
|
+
title={`Tax could not be calculated. Error: ${tax.error}`}
|
|
69
|
+
>
|
|
70
|
+
<Typography
|
|
71
|
+
sx={{
|
|
72
|
+
fontSize: "20px",
|
|
73
|
+
bgcolor: "#ff000040",
|
|
74
|
+
borderRadius: 2,
|
|
75
|
+
p: 1,
|
|
76
|
+
textDecoration: "underline dotted",
|
|
77
|
+
cursor: "help",
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
Taxes: {formatPrice(tax.tax_amount)}
|
|
81
|
+
</Typography>
|
|
82
|
+
</Tooltip>
|
|
83
|
+
) : (
|
|
84
|
+
<Typography sx={{ fontSize: "20px", p: 1 }}>
|
|
85
|
+
Taxes: {formatPrice(tax.tax_amount)}
|
|
86
|
+
</Typography>
|
|
87
|
+
)}
|
|
88
|
+
</Box>
|
|
89
|
+
{(discount != 0 || shipping != 0) && (
|
|
90
|
+
<Box
|
|
91
|
+
sx={{
|
|
92
|
+
width: "50%",
|
|
93
|
+
display: "flex",
|
|
94
|
+
justifyContent: "space-between",
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<Typography sx={{ fontSize: "20px", p: 1 }}>
|
|
98
|
+
{shipping && <>Shipping: {formatPrice(shipping)}</>}
|
|
99
|
+
</Typography>
|
|
100
|
+
<Typography sx={{ fontSize: "20px", p: 1 }}>
|
|
101
|
+
{discount && <>Discount: {formatPrice(discount)}</>}
|
|
102
|
+
</Typography>
|
|
103
|
+
</Box>
|
|
104
|
+
)}
|
|
105
|
+
<Box>
|
|
106
|
+
<Typography style={{ fontSize: "26px" }}>
|
|
107
|
+
Total:{" "}
|
|
108
|
+
{typeof subtotal === "number"
|
|
109
|
+
? formatPrice(
|
|
110
|
+
subtotal +
|
|
111
|
+
(tax.tax_amount === "TBD" ? 0 : tax.tax_amount) +
|
|
112
|
+
shipping -
|
|
113
|
+
discount
|
|
114
|
+
)
|
|
115
|
+
: subtotal}
|
|
116
|
+
</Typography>
|
|
117
|
+
</Box>
|
|
118
|
+
{formError && (
|
|
119
|
+
<Typography color="red">
|
|
120
|
+
Could not confirm payment. Your card info may have been entered
|
|
121
|
+
incorrectly.
|
|
122
|
+
</Typography>
|
|
123
|
+
)}
|
|
124
|
+
{status === 1 && (
|
|
125
|
+
<Button
|
|
126
|
+
variant="contained"
|
|
127
|
+
sx={{ mt: 2 }}
|
|
128
|
+
type="submit"
|
|
129
|
+
disabled={formDisabled}
|
|
130
|
+
color={formError ? "error" : undefined}
|
|
131
|
+
>
|
|
132
|
+
Confirm Order
|
|
133
|
+
</Button>
|
|
134
|
+
)}
|
|
135
|
+
<Box className="checkout-list">
|
|
136
|
+
{cart.items.map((p, i) => (
|
|
137
|
+
<CartProductCard
|
|
138
|
+
product={p}
|
|
139
|
+
i={i}
|
|
140
|
+
price={prices[p.price_id]}
|
|
141
|
+
disableProductLink={disableProductLink}
|
|
142
|
+
disableMissingImage={disableMissingImage}
|
|
143
|
+
disableModification={status != 0}
|
|
144
|
+
/>
|
|
145
|
+
))}
|
|
146
|
+
</Box>
|
|
147
|
+
</Box>
|
|
148
|
+
</Box>
|
|
149
|
+
</form>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -10,14 +10,18 @@ export function CartProductCard({
|
|
|
10
10
|
i,
|
|
11
11
|
price,
|
|
12
12
|
disableProductLink = false,
|
|
13
|
+
disableMissingImage = false,
|
|
14
|
+
disableModification = false,
|
|
13
15
|
}: {
|
|
14
16
|
product: CartProduct;
|
|
15
17
|
i: number;
|
|
16
18
|
price: any;
|
|
17
19
|
disableProductLink?: boolean;
|
|
20
|
+
disableMissingImage?: boolean;
|
|
21
|
+
disableModification?: boolean;
|
|
18
22
|
}) {
|
|
19
23
|
const [hoverDelete, setHoverDelete] = React.useState(false);
|
|
20
|
-
|
|
24
|
+
|
|
21
25
|
return (
|
|
22
26
|
<a
|
|
23
27
|
className="product-card"
|
|
@@ -28,7 +32,11 @@ export function CartProductCard({
|
|
|
28
32
|
}
|
|
29
33
|
>
|
|
30
34
|
<div className="left-section">
|
|
31
|
-
{product.image ?
|
|
35
|
+
{product.image ? (
|
|
36
|
+
<img />
|
|
37
|
+
) : (
|
|
38
|
+
!disableMissingImage && <div className="missing-image" />
|
|
39
|
+
)}
|
|
32
40
|
<div className="two-row">
|
|
33
41
|
<Typography>{product.name}</Typography>
|
|
34
42
|
<Typography>
|
|
@@ -56,24 +64,26 @@ export function CartProductCard({
|
|
|
56
64
|
alignItems: "center",
|
|
57
65
|
}}
|
|
58
66
|
>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
>
|
|
68
|
-
<IconButton
|
|
69
|
-
className="remove-button"
|
|
70
|
-
onClick={() => {
|
|
71
|
-
removeFromCart(i);
|
|
67
|
+
{!disableModification && (
|
|
68
|
+
<Tooltip
|
|
69
|
+
title="Remove from cart"
|
|
70
|
+
onMouseEnter={() => {
|
|
71
|
+
setHoverDelete(true);
|
|
72
|
+
}}
|
|
73
|
+
onMouseLeave={() => {
|
|
74
|
+
setHoverDelete(false);
|
|
72
75
|
}}
|
|
73
76
|
>
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
<IconButton
|
|
78
|
+
className="remove-button"
|
|
79
|
+
onClick={() => {
|
|
80
|
+
removeFromCart(i);
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<CloseIcon />
|
|
84
|
+
</IconButton>
|
|
85
|
+
</Tooltip>
|
|
86
|
+
)}
|
|
77
87
|
</div>
|
|
78
88
|
</a>
|
|
79
89
|
);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { AddCard, type AddCardProps } from "./AddCard";
|
|
2
|
+
import { postCheckout } from "../../utils/api";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
Container,
|
|
7
|
+
Typography,
|
|
8
|
+
CircularProgress,
|
|
9
|
+
TableContainer,
|
|
10
|
+
Table,
|
|
11
|
+
TableHead,
|
|
12
|
+
TableRow,
|
|
13
|
+
TableCell,
|
|
14
|
+
Paper,
|
|
15
|
+
TableBody,
|
|
16
|
+
} from "@mui/material";
|
|
17
|
+
|
|
18
|
+
type CheckoutProps = AddCardProps & {
|
|
19
|
+
order: any;
|
|
20
|
+
invoiceId?: string;
|
|
21
|
+
successData;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default function Invoice({
|
|
25
|
+
order,
|
|
26
|
+
invoiceId,
|
|
27
|
+
successData,
|
|
28
|
+
}: CheckoutProps) {
|
|
29
|
+
const currentUrl = new URL(window.location.href);
|
|
30
|
+
const formatter = new Intl.NumberFormat("en-US", {
|
|
31
|
+
style: "currency",
|
|
32
|
+
currency: "USD",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
successData !== null ||
|
|
37
|
+
// currentUrl.searchParams.get("showReceipt")
|
|
38
|
+
order.charge_id ||
|
|
39
|
+
!["pending", "awaiting_payment"].includes(order.status)
|
|
40
|
+
) {
|
|
41
|
+
const print = (
|
|
42
|
+
<a
|
|
43
|
+
href="#"
|
|
44
|
+
onClick={() => {
|
|
45
|
+
window.print();
|
|
46
|
+
return false;
|
|
47
|
+
}}
|
|
48
|
+
style={{ textDecoration: "underline" }}
|
|
49
|
+
>
|
|
50
|
+
print
|
|
51
|
+
</a>
|
|
52
|
+
);
|
|
53
|
+
const text =
|
|
54
|
+
successData !== null ? (
|
|
55
|
+
<>
|
|
56
|
+
Payment received! An email was sent to{" "}
|
|
57
|
+
<strong>{successData.billing_information.email}</strong>. You can also{" "}
|
|
58
|
+
{print} this page for your records.
|
|
59
|
+
</>
|
|
60
|
+
) : (
|
|
61
|
+
<>Payment received! You can {print} this page for your records.</>
|
|
62
|
+
);
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<Container>
|
|
66
|
+
<Alert severity="success">{text}</Alert>
|
|
67
|
+
</Container>
|
|
68
|
+
<Container>
|
|
69
|
+
<Typography variant="h4" gutterBottom>
|
|
70
|
+
{invoiceId ? `Invoice #${invoiceId}` : "Order"}
|
|
71
|
+
</Typography>
|
|
72
|
+
<Typography variant="subtitle1">
|
|
73
|
+
Reference: {order.reference}
|
|
74
|
+
</Typography>
|
|
75
|
+
<Typography variant="subtitle1">
|
|
76
|
+
Date: {new Date(order.date).toLocaleDateString()}
|
|
77
|
+
</Typography>
|
|
78
|
+
<Typography variant="subtitle1">Status: {order.status}</Typography>
|
|
79
|
+
{order.customer ? (
|
|
80
|
+
<Typography variant="subtitle1">
|
|
81
|
+
{order.customer.first_name} {order.customer.last_name}
|
|
82
|
+
</Typography>
|
|
83
|
+
) : (
|
|
84
|
+
<Typography variant="subtitle1">
|
|
85
|
+
Customer ID: {order.customer_id}
|
|
86
|
+
</Typography>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
<TableContainer component={Paper} sx={{ my: 2 }}>
|
|
90
|
+
<Table>
|
|
91
|
+
<TableHead>
|
|
92
|
+
<TableRow>
|
|
93
|
+
<TableCell>Item ID</TableCell>
|
|
94
|
+
<TableCell>Name</TableCell>
|
|
95
|
+
<TableCell>Description</TableCell>
|
|
96
|
+
<TableCell>Quantity</TableCell>
|
|
97
|
+
<TableCell>Unit Price</TableCell>
|
|
98
|
+
<TableCell>Total Price</TableCell>
|
|
99
|
+
</TableRow>
|
|
100
|
+
</TableHead>
|
|
101
|
+
<TableBody>
|
|
102
|
+
{order.basket.map((item) => (
|
|
103
|
+
<TableRow key={item.item_id}>
|
|
104
|
+
<TableCell>{item.item_id}</TableCell>
|
|
105
|
+
<TableCell>{item.name}</TableCell>
|
|
106
|
+
<TableCell>{item.description || "-"}</TableCell>
|
|
107
|
+
<TableCell>{item.quantity}</TableCell>
|
|
108
|
+
<TableCell>
|
|
109
|
+
{formatter.format(item.unit_price_cents / 100)}
|
|
110
|
+
</TableCell>
|
|
111
|
+
<TableCell>
|
|
112
|
+
{formatter.format(
|
|
113
|
+
(item.quantity * item.unit_price_cents) / 100
|
|
114
|
+
)}
|
|
115
|
+
</TableCell>
|
|
116
|
+
</TableRow>
|
|
117
|
+
))}
|
|
118
|
+
</TableBody>
|
|
119
|
+
</Table>
|
|
120
|
+
</TableContainer>
|
|
121
|
+
</Container>
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// return (
|
|
127
|
+
// <>
|
|
128
|
+
// <Order order={order} invoiceId={invoiceId} />
|
|
129
|
+
// {isSubmitting ? (
|
|
130
|
+
// <Container>
|
|
131
|
+
// <Typography>
|
|
132
|
+
// <CircularProgress color="primary" /> Submitting...
|
|
133
|
+
// </Typography>
|
|
134
|
+
// </Container>
|
|
135
|
+
// ) : (
|
|
136
|
+
// <AddCard
|
|
137
|
+
// contact={order.customer}
|
|
138
|
+
// onSubmit={onSubmit}
|
|
139
|
+
// PriceDetails={PriceDetails}
|
|
140
|
+
// />
|
|
141
|
+
// )
|
|
142
|
+
// }
|
|
143
|
+
// </>
|
|
144
|
+
// );
|
|
145
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Box, IconButton, Typography } from "@mui/material";
|
|
2
|
+
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
|
3
|
+
|
|
4
|
+
export function StatusBar({ status, backlink = "" }) {
|
|
5
|
+
return (
|
|
6
|
+
<Box
|
|
7
|
+
style={{
|
|
8
|
+
display: "flex",
|
|
9
|
+
justifyContent: "center",
|
|
10
|
+
flexDirection: "row",
|
|
11
|
+
}}
|
|
12
|
+
>
|
|
13
|
+
<Box sx={{ flex: "1 1" }}>
|
|
14
|
+
<IconButton href={`/${backlink}`}>
|
|
15
|
+
<ArrowBackIcon />
|
|
16
|
+
</IconButton>
|
|
17
|
+
</Box>
|
|
18
|
+
<div id="status-bar">
|
|
19
|
+
<Typography className="status-text active">PAYMENT DETAILS</Typography>
|
|
20
|
+
<div className={"status-bar" + (status > 0 ? " active" : "")}></div>
|
|
21
|
+
<Typography className={"status-text" + (status > 0 ? " active" : "")}>
|
|
22
|
+
CONFIRMATION
|
|
23
|
+
</Typography>
|
|
24
|
+
<div className={"status-bar" + (status > 1 ? " active" : "")}></div>
|
|
25
|
+
<Typography className={"status-text" + (status > 1 ? " active" : "")}>
|
|
26
|
+
ORDER PLACED
|
|
27
|
+
</Typography>
|
|
28
|
+
</div>
|
|
29
|
+
<Box sx={{ flex: "1 1" }} />
|
|
30
|
+
</Box>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import "./ReviewCartAndCalculateTaxes.css";
|
|
2
|
+
import { useStore } from "@nanostores/react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Typography } from "@mui/material";
|
|
5
|
+
import { cartStore, clearCart } from "../utils/storage";
|
|
6
|
+
import { StatusBar } from "./components/StatusBar";
|
|
7
|
+
import ReviewAndCalculateTaxes from "./ReviewCartAndCalculateTaxes";
|
|
8
|
+
import Invoice from "./components/Invoice";
|
|
9
|
+
import { FormikConfig } from "formik";
|
|
10
|
+
|
|
11
|
+
export default function Checkout({
|
|
12
|
+
apiBaseUrl,
|
|
13
|
+
taxProvider,
|
|
14
|
+
emptyCartLink = "",
|
|
15
|
+
disableProductLink = false,
|
|
16
|
+
disableMissingImage = false,
|
|
17
|
+
CollectExtraInfo,
|
|
18
|
+
}: {
|
|
19
|
+
apiBaseUrl: string;
|
|
20
|
+
taxProvider: string;
|
|
21
|
+
emptyCartLink?: string;
|
|
22
|
+
disableProductLink?: boolean;
|
|
23
|
+
disableMissingImage?: boolean;
|
|
24
|
+
CollectExtraInfo?: React.FC<{ formik: FormikConfig<any> }>;
|
|
25
|
+
}) {
|
|
26
|
+
const cart = JSON.parse(useStore(cartStore));
|
|
27
|
+
|
|
28
|
+
const [prices, setPrices] = useState({});
|
|
29
|
+
const [subtotal, setSubtotal] = useState("Loading prices...");
|
|
30
|
+
const taxState = useState({ tax_amount: "TBD" });
|
|
31
|
+
const shipping = 0;
|
|
32
|
+
const discount = 0;
|
|
33
|
+
|
|
34
|
+
const [status, setStatus] = useState(0);
|
|
35
|
+
const [order, setOrder] = useState(undefined);
|
|
36
|
+
const [successData, setSuccessData] = useState(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const params = new URLSearchParams(window.location.search);
|
|
40
|
+
|
|
41
|
+
const sr = params.get("showReceipt");
|
|
42
|
+
const oid = params.get("orderId");
|
|
43
|
+
const or = params.get("orderRef");
|
|
44
|
+
if (oid && or) {
|
|
45
|
+
fetch(`${apiBaseUrl}/api/ecommerce/orders/${oid}/reference/${or}`).then(
|
|
46
|
+
async (res) =>
|
|
47
|
+
await res.json().then((order) => {
|
|
48
|
+
setOrder(order);
|
|
49
|
+
setStatus(
|
|
50
|
+
order.charge_id ||
|
|
51
|
+
!["pending", "awaiting_payment"].includes(order.status)
|
|
52
|
+
? 2
|
|
53
|
+
: 0 // normally this should be set to 1 but we need to ensure it sends card data to the payment provider and that data is likely lost on refresh.
|
|
54
|
+
);
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
// build pricing list
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
// filter out prices that have already been queried
|
|
63
|
+
const pricesToGet = cart.items
|
|
64
|
+
.map((c) => c.price_id)
|
|
65
|
+
.filter(
|
|
66
|
+
(pId) => Object.keys(prices).findIndex((pKey) => pKey == pId) === -1
|
|
67
|
+
);
|
|
68
|
+
if (pricesToGet.length === 0) return;
|
|
69
|
+
|
|
70
|
+
const url = `${apiBaseUrl}/api/ecommerce/price?filter={'ids':[${pricesToGet.join(
|
|
71
|
+
","
|
|
72
|
+
)}]}`;
|
|
73
|
+
fetch(url, {
|
|
74
|
+
method: "GET",
|
|
75
|
+
headers: {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
.then((res) =>
|
|
80
|
+
res.json().then((data) => {
|
|
81
|
+
const pricingData = { ...prices };
|
|
82
|
+
|
|
83
|
+
data.forEach((p) => {
|
|
84
|
+
pricingData[p.id] = p;
|
|
85
|
+
});
|
|
86
|
+
setPrices(pricingData);
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
.catch(() => {});
|
|
90
|
+
}, [cart]);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
setSubtotal(
|
|
94
|
+
cart.items
|
|
95
|
+
.map((product) => prices[product.price_id]?.unit_amount)
|
|
96
|
+
.reduce((p, c) => (c ? p + c : p), 0)
|
|
97
|
+
);
|
|
98
|
+
}, [cart, prices]);
|
|
99
|
+
|
|
100
|
+
if (status === 0 && cart.items.length === 0)
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
style={{
|
|
104
|
+
width: "100%",
|
|
105
|
+
display: "flex",
|
|
106
|
+
alignItems: "center",
|
|
107
|
+
flexDirection: "column",
|
|
108
|
+
gap: 8,
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<Typography style={{ fontSize: 32, fontWeight: 600 }}>
|
|
112
|
+
Cart is empty
|
|
113
|
+
</Typography>
|
|
114
|
+
<a className="shopping-button" href={`/${emptyCartLink}`}>
|
|
115
|
+
<Typography>BACK</Typography>
|
|
116
|
+
</a>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div>
|
|
122
|
+
<StatusBar status={status} />
|
|
123
|
+
<div>
|
|
124
|
+
{/**
|
|
125
|
+
*
|
|
126
|
+
* status === 0
|
|
127
|
+
* Manage cart and enter in payment details
|
|
128
|
+
*
|
|
129
|
+
* status === 1
|
|
130
|
+
* Review taxes and shipping
|
|
131
|
+
*
|
|
132
|
+
* status === 2
|
|
133
|
+
* Order has been place. Invoice for printing.
|
|
134
|
+
*
|
|
135
|
+
*/}
|
|
136
|
+
|
|
137
|
+
{status != 2 && (
|
|
138
|
+
<ReviewAndCalculateTaxes
|
|
139
|
+
subtotal={subtotal}
|
|
140
|
+
discount={discount}
|
|
141
|
+
shipping={shipping}
|
|
142
|
+
taxProvider={taxProvider}
|
|
143
|
+
taxState={taxState}
|
|
144
|
+
statusState={[status, setStatus]}
|
|
145
|
+
cart={cart}
|
|
146
|
+
prices={prices}
|
|
147
|
+
apiBaseUrl={apiBaseUrl}
|
|
148
|
+
orderState={[order, setOrder]}
|
|
149
|
+
disableProductLink={disableProductLink}
|
|
150
|
+
disableMissingImage={disableMissingImage}
|
|
151
|
+
setSuccessData={setSuccessData}
|
|
152
|
+
onPlacement={() => {
|
|
153
|
+
clearCart();
|
|
154
|
+
setStatus(2);
|
|
155
|
+
}}
|
|
156
|
+
CollectExtraInfo={CollectExtraInfo}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
159
|
+
{status === 2 && order !== undefined && (
|
|
160
|
+
<Invoice order={order} successData={successData} />
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,17 +8,18 @@ import {
|
|
|
8
8
|
} from "./utils/storage";
|
|
9
9
|
import type { Cart, CartProduct } from "./types";
|
|
10
10
|
import AddToCartForm from "./AddToCartForm";
|
|
11
|
-
import { AddCard, AddCardProps } from "./checkout/components/
|
|
12
|
-
import Order from "./checkout/components/Order";
|
|
13
|
-
import
|
|
11
|
+
import { AddCard, AddCardProps } from "./checkout/components/AddCard";
|
|
12
|
+
// import Order from "./checkout/components/Order";
|
|
13
|
+
import Invoice from "./checkout/components/Invoice";
|
|
14
14
|
import "react-credit-cards-2/dist/es/styles-compiled.css";
|
|
15
15
|
import "./index.css";
|
|
16
16
|
import ProductCard from "./ProductCard";
|
|
17
|
-
import
|
|
17
|
+
import ReviewCartAndCalculateTaxes from "./checkout/ReviewCartAndCalculateTaxes";
|
|
18
|
+
import Checkout from "./checkout";
|
|
18
19
|
|
|
19
20
|
export {
|
|
20
21
|
Checkout,
|
|
21
|
-
|
|
22
|
+
Invoice,
|
|
22
23
|
AddCard,
|
|
23
24
|
type AddCardProps,
|
|
24
25
|
cartStore,
|
|
@@ -31,5 +32,5 @@ export {
|
|
|
31
32
|
AddToCartForm,
|
|
32
33
|
setOrder,
|
|
33
34
|
ProductCard,
|
|
34
|
-
|
|
35
|
+
ReviewCartAndCalculateTaxes,
|
|
35
36
|
};
|