@springmicro/cart 0.7.1 → 0.7.2
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 +4706 -4609
- package/dist/index.umd.cjs +60 -60
- package/package.json +3 -3
- package/src/CartButton.tsx +27 -11
- package/src/ProductCard.tsx +6 -1
- package/src/checkout/ReviewCartAndCalculateTaxes.tsx +158 -58
- package/src/checkout/components/CartList.tsx +2 -2
- package/src/checkout/index.tsx +15 -9
- package/src/types.d.ts +2 -0
- package/src/utils/storage.ts +31 -11
- package/springmicro-cart-0.6.1.tgz +0 -0
- package/springmicro-cart-0.6.2.tgz +0 -0
- package/springmicro-cart-0.6.4.tgz +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springmicro/cart",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@nanostores/persistent": "^0.10.1",
|
|
25
25
|
"@nanostores/query": "^0.3.3",
|
|
26
26
|
"@nanostores/react": "^0.7.2",
|
|
27
|
-
"@springmicro/utils": "0.7.
|
|
27
|
+
"@springmicro/utils": "0.7.2",
|
|
28
28
|
"dotenv": "^16.4.5",
|
|
29
29
|
"nanostores": "^0.10.3",
|
|
30
30
|
"react": "^18.2.0",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"vite-plugin-css-injected-by-js": "^3.5.1",
|
|
50
50
|
"yup": "^1.4.0"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "baedb0de00a8f238a2356a747e69eb1754606d58"
|
|
53
53
|
}
|
package/src/CartButton.tsx
CHANGED
|
@@ -160,6 +160,7 @@ function CartModal({
|
|
|
160
160
|
})
|
|
161
161
|
)
|
|
162
162
|
.catch(() => {
|
|
163
|
+
console.error("Couldn't get prices");
|
|
163
164
|
setPrices(null);
|
|
164
165
|
});
|
|
165
166
|
}, [cart]);
|
|
@@ -214,17 +215,7 @@ function CartModal({
|
|
|
214
215
|
{prices === null
|
|
215
216
|
? "Price not found."
|
|
216
217
|
: price
|
|
217
|
-
?
|
|
218
|
-
price.recurring
|
|
219
|
-
? `/${
|
|
220
|
-
price.recurring.interval_count > 1
|
|
221
|
-
? `${price.recurring.interval_count} `
|
|
222
|
-
: ""
|
|
223
|
-
}${price.recurring.interval}${
|
|
224
|
-
price.recurring.interval_count > 1 ? "s" : ""
|
|
225
|
-
}`
|
|
226
|
-
: ""
|
|
227
|
-
}`
|
|
218
|
+
? formatPrice(price)
|
|
228
219
|
: "Loading price..."}
|
|
229
220
|
</h3>
|
|
230
221
|
</Box>
|
|
@@ -255,3 +246,28 @@ function CartModal({
|
|
|
255
246
|
</Box>
|
|
256
247
|
);
|
|
257
248
|
}
|
|
249
|
+
|
|
250
|
+
function formatPrice(price) {
|
|
251
|
+
let baseUnitAmount = (price.unit_amount / 100).toFixed(2);
|
|
252
|
+
if (baseUnitAmount.endsWith("00"))
|
|
253
|
+
baseUnitAmount = baseUnitAmount.replace(".00", "");
|
|
254
|
+
|
|
255
|
+
const unitAmountStr = `$${baseUnitAmount}`;
|
|
256
|
+
|
|
257
|
+
if (!price.recurring) return unitAmountStr;
|
|
258
|
+
|
|
259
|
+
let { interval, interval_count } = price.recurring;
|
|
260
|
+
if (interval === "month" && interval_count % 12 === 0) {
|
|
261
|
+
interval = "year";
|
|
262
|
+
interval_count /= 12;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const SLASH = "/";
|
|
266
|
+
let count = "";
|
|
267
|
+
if (interval_count != 1) {
|
|
268
|
+
interval += "s";
|
|
269
|
+
count = `${interval_count} `;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return unitAmountStr + SLASH + count + interval;
|
|
273
|
+
}
|
package/src/ProductCard.tsx
CHANGED
|
@@ -166,7 +166,12 @@ function DefaultProductCard({
|
|
|
166
166
|
|
|
167
167
|
function formatPricing({ unit_amount, recurring }: any) {
|
|
168
168
|
if (recurring) {
|
|
169
|
-
|
|
169
|
+
let { interval, interval_count } = recurring;
|
|
170
|
+
if (interval === "month" && interval_count % 12 === 0) {
|
|
171
|
+
interval = "year";
|
|
172
|
+
interval_count /= 12;
|
|
173
|
+
}
|
|
174
|
+
|
|
170
175
|
return `$${(unit_amount / 100).toFixed(2)}/${
|
|
171
176
|
interval_count === 1 ? interval : `${interval_count} ${interval}s`
|
|
172
177
|
}`;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import "./ReviewCartAndCalculateTaxes.css";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
useTheme,
|
|
5
|
+
useMediaQuery,
|
|
6
|
+
Button,
|
|
7
|
+
TextField,
|
|
8
|
+
Modal,
|
|
9
|
+
Backdrop,
|
|
10
|
+
Fade,
|
|
11
|
+
Typography,
|
|
12
|
+
IconButton,
|
|
13
|
+
} from "@mui/material";
|
|
3
14
|
import { setOrder as cartSetOrder } from "../utils/storage";
|
|
4
15
|
import { AddCard, CCFocus, CreditCardValues } from "./components/AddCard";
|
|
5
16
|
import { CartList } from "./components/CartList";
|
|
@@ -26,6 +37,7 @@ import { AddressValues } from "./components/Address";
|
|
|
26
37
|
import { useFormik } from "formik";
|
|
27
38
|
import { postCheckout } from "../utils/api";
|
|
28
39
|
import { CheckoutFieldsType } from ".";
|
|
40
|
+
import CloseIcon from "@mui/icons-material/Close";
|
|
29
41
|
|
|
30
42
|
async function createOrder(cart, apiBaseUrl, dev) {
|
|
31
43
|
dev && console.log("Creating order");
|
|
@@ -144,6 +156,8 @@ export default function ReviewAndCalculateTaxes({
|
|
|
144
156
|
const [tax, setTax] = taxState;
|
|
145
157
|
const [order, setOrder] = orderState;
|
|
146
158
|
|
|
159
|
+
const [precheckErrors, setPrecheckErrors] = useState([]);
|
|
160
|
+
|
|
147
161
|
async function createOrderAndGetTaxes(values) {
|
|
148
162
|
setFormError(undefined);
|
|
149
163
|
setFormDisabled(true);
|
|
@@ -190,11 +204,15 @@ export default function ReviewAndCalculateTaxes({
|
|
|
190
204
|
"stripe",
|
|
191
205
|
invoiceId
|
|
192
206
|
)
|
|
193
|
-
.then((data) => {
|
|
207
|
+
.then(async (data) => {
|
|
194
208
|
dev && console.log("Checkout submitted");
|
|
195
209
|
if (data instanceof Response) {
|
|
196
210
|
setFormError(true);
|
|
197
|
-
|
|
211
|
+
const { detail: errorData } = await data.json();
|
|
212
|
+
// dev && alert(data.statusText);
|
|
213
|
+
// console.log(errorData);
|
|
214
|
+
// TODO display issues
|
|
215
|
+
if (!errorData.valid) setPrecheckErrors(errorData.errors);
|
|
198
216
|
} else {
|
|
199
217
|
// success
|
|
200
218
|
setSuccessData(data);
|
|
@@ -362,61 +380,69 @@ export default function ReviewAndCalculateTaxes({
|
|
|
362
380
|
}, [formik.errors]);
|
|
363
381
|
|
|
364
382
|
return (
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
>
|
|
376
|
-
<CartList
|
|
377
|
-
{...{
|
|
378
|
-
statusState: [status, setStatus],
|
|
379
|
-
cart,
|
|
380
|
-
subtotal,
|
|
381
|
-
tax,
|
|
382
|
-
shipping,
|
|
383
|
-
discount,
|
|
384
|
-
prices,
|
|
385
|
-
products,
|
|
386
|
-
disableMissingImage,
|
|
387
|
-
disableProductLink,
|
|
388
|
-
formik,
|
|
389
|
-
formDisabled,
|
|
390
|
-
formErrorState: [formError, setFormError],
|
|
383
|
+
<>
|
|
384
|
+
<Box
|
|
385
|
+
sx={{
|
|
386
|
+
mt: 2,
|
|
387
|
+
display: "flex",
|
|
388
|
+
flexDirection: { xs: "column", xl: "row" },
|
|
389
|
+
justifyContent: "center",
|
|
390
|
+
alignItems: { xs: undefined, xl: "flex-start" },
|
|
391
|
+
px: { xs: 32, xl: 16 },
|
|
392
|
+
"&>div": { width: { xs: undefined, xl: "50%" } },
|
|
391
393
|
}}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
394
|
+
>
|
|
395
|
+
<CartList
|
|
396
|
+
{...{
|
|
397
|
+
statusState: [status, setStatus],
|
|
398
|
+
cart,
|
|
399
|
+
subtotal,
|
|
400
|
+
tax,
|
|
401
|
+
shipping,
|
|
402
|
+
discount,
|
|
403
|
+
prices,
|
|
404
|
+
products,
|
|
405
|
+
disableMissingImage,
|
|
406
|
+
disableProductLink,
|
|
407
|
+
formik,
|
|
408
|
+
formDisabled,
|
|
409
|
+
formErrorState: [formError, setFormError],
|
|
410
|
+
}}
|
|
411
|
+
/>
|
|
412
|
+
{status === 0 && (
|
|
413
|
+
<Box
|
|
414
|
+
sx={{
|
|
415
|
+
flexGrow: 1,
|
|
416
|
+
display: "flex",
|
|
417
|
+
flexDirection: "column",
|
|
418
|
+
gap: 2,
|
|
414
419
|
}}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
+
>
|
|
421
|
+
{CollectExtraInfo && <CollectExtraInfo formik={formik} />}
|
|
422
|
+
<CheckoutActionFields
|
|
423
|
+
formik={formik}
|
|
424
|
+
prices={prices}
|
|
425
|
+
products={products}
|
|
426
|
+
hideFields={hideFields}
|
|
427
|
+
/>
|
|
428
|
+
<AddCard
|
|
429
|
+
cardRequired={cardRequired}
|
|
430
|
+
onSubmit={createOrderAndGetTaxes}
|
|
431
|
+
stacked={stacked}
|
|
432
|
+
formData={{
|
|
433
|
+
formik,
|
|
434
|
+
handleInputChange,
|
|
435
|
+
handleInputFocus,
|
|
436
|
+
formDisabled,
|
|
437
|
+
formError,
|
|
438
|
+
}}
|
|
439
|
+
hideFields={hideFields}
|
|
440
|
+
/>
|
|
441
|
+
</Box>
|
|
442
|
+
)}
|
|
443
|
+
</Box>
|
|
444
|
+
<PrecheckModal errors={precheckErrors} setErrors={setPrecheckErrors} />
|
|
445
|
+
</>
|
|
420
446
|
);
|
|
421
447
|
}
|
|
422
448
|
|
|
@@ -454,8 +480,15 @@ function CheckoutActionFields({ formik, products, prices, hideFields = [] }) {
|
|
|
454
480
|
// console.log({ checkoutFields });
|
|
455
481
|
return (
|
|
456
482
|
<Box sx={{ display: "flex", flexDirection: "column", mx: 3, gap: 2 }}>
|
|
457
|
-
{Object.keys(checkoutFields)
|
|
458
|
-
.filter(
|
|
483
|
+
{Object.keys((console.log(checkoutFields), checkoutFields))
|
|
484
|
+
.filter(
|
|
485
|
+
(key) =>
|
|
486
|
+
!["email", ...hideFields].includes(key) &&
|
|
487
|
+
!(
|
|
488
|
+
checkoutFields[key].hidden === true ||
|
|
489
|
+
checkoutFields[key].hidden === "true"
|
|
490
|
+
)
|
|
491
|
+
)
|
|
459
492
|
.map((key) => {
|
|
460
493
|
const {
|
|
461
494
|
name,
|
|
@@ -483,3 +516,70 @@ function CheckoutActionFields({ formik, products, prices, hideFields = [] }) {
|
|
|
483
516
|
</Box>
|
|
484
517
|
);
|
|
485
518
|
}
|
|
519
|
+
|
|
520
|
+
function PrecheckModal({ errors, setErrors }) {
|
|
521
|
+
function onClose() {
|
|
522
|
+
setErrors([]);
|
|
523
|
+
}
|
|
524
|
+
return (
|
|
525
|
+
<Modal
|
|
526
|
+
open={errors.length > 0}
|
|
527
|
+
onClose={onClose} // called when backdrop clicked or escape pressed
|
|
528
|
+
closeAfterTransition
|
|
529
|
+
slots={{ backdrop: Backdrop }}
|
|
530
|
+
slotProps={{
|
|
531
|
+
backdrop: {
|
|
532
|
+
timeout: 300,
|
|
533
|
+
},
|
|
534
|
+
}}
|
|
535
|
+
aria-labelledby="centered-modal-title"
|
|
536
|
+
aria-describedby="centered-modal-description"
|
|
537
|
+
>
|
|
538
|
+
<Fade in={errors.length > 0}>
|
|
539
|
+
<Box
|
|
540
|
+
sx={{
|
|
541
|
+
position: "absolute",
|
|
542
|
+
top: "50%",
|
|
543
|
+
left: "50%",
|
|
544
|
+
transform: "translate(-50%, -50%)",
|
|
545
|
+
minWidth: 300,
|
|
546
|
+
maxWidth: "90vw",
|
|
547
|
+
bgcolor: "background.paper",
|
|
548
|
+
boxShadow: 24,
|
|
549
|
+
borderRadius: 2,
|
|
550
|
+
p: 3,
|
|
551
|
+
outline: "none",
|
|
552
|
+
}}
|
|
553
|
+
>
|
|
554
|
+
<Box
|
|
555
|
+
sx={{
|
|
556
|
+
display: "flex",
|
|
557
|
+
alignItems: "center",
|
|
558
|
+
justifyContent: "space-between",
|
|
559
|
+
mb: 1,
|
|
560
|
+
gap: 4,
|
|
561
|
+
}}
|
|
562
|
+
>
|
|
563
|
+
<Typography id="centered-modal-title" variant="h5">
|
|
564
|
+
Your purchase was unsuccessful.
|
|
565
|
+
</Typography>
|
|
566
|
+
<IconButton aria-label="close" onClick={onClose} size="small">
|
|
567
|
+
<CloseIcon fontSize="small" />
|
|
568
|
+
</IconButton>
|
|
569
|
+
</Box>
|
|
570
|
+
|
|
571
|
+
<Box id="centered-modal-description">
|
|
572
|
+
<Typography variant="h6" component="h2">
|
|
573
|
+
Errors:
|
|
574
|
+
</Typography>
|
|
575
|
+
<ul>
|
|
576
|
+
{errors.map((error) => (
|
|
577
|
+
<Typography component="li">{error}</Typography>
|
|
578
|
+
))}
|
|
579
|
+
</ul>
|
|
580
|
+
</Box>
|
|
581
|
+
</Box>
|
|
582
|
+
</Fade>
|
|
583
|
+
</Modal>
|
|
584
|
+
);
|
|
585
|
+
}
|
|
@@ -138,10 +138,10 @@ export function CartList({
|
|
|
138
138
|
alignItems: "center",
|
|
139
139
|
}}
|
|
140
140
|
>
|
|
141
|
-
<Typography color="red">
|
|
141
|
+
{/* <Typography color="red">
|
|
142
142
|
Could not confirm payment. Your card info may have been entered
|
|
143
143
|
incorrectly.
|
|
144
|
-
</Typography>
|
|
144
|
+
</Typography> */}
|
|
145
145
|
<Button
|
|
146
146
|
onClick={() => {
|
|
147
147
|
setFormError(undefined);
|
package/src/checkout/index.tsx
CHANGED
|
@@ -13,6 +13,7 @@ export type CheckoutFieldsType = {
|
|
|
13
13
|
label?: string; // Defaults to a formated version of the name
|
|
14
14
|
required?: boolean | string; // Defaults to true
|
|
15
15
|
type?: "string"; // Defaults to "string" (text)
|
|
16
|
+
hidden?: boolean | string; // Defaults to true
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
export default function Checkout({
|
|
@@ -60,15 +61,20 @@ export default function Checkout({
|
|
|
60
61
|
if (oid && or) {
|
|
61
62
|
fetch(`${apiBaseUrl}/api/ecommerce/orders/${oid}/reference/${or}`).then(
|
|
62
63
|
async (res) =>
|
|
63
|
-
await res
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
order
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
await res
|
|
65
|
+
.json()
|
|
66
|
+
.then((order) => {
|
|
67
|
+
setOrder(order);
|
|
68
|
+
setStatus(
|
|
69
|
+
order.charge_id ||
|
|
70
|
+
!["pending", "awaiting_payment"].includes(order.status)
|
|
71
|
+
? 2
|
|
72
|
+
: 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.
|
|
73
|
+
);
|
|
74
|
+
})
|
|
75
|
+
.catch(() => {
|
|
76
|
+
console.error("Failed to get order");
|
|
77
|
+
})
|
|
72
78
|
);
|
|
73
79
|
}
|
|
74
80
|
}, []);
|
package/src/types.d.ts
CHANGED
package/src/utils/storage.ts
CHANGED
|
@@ -37,20 +37,31 @@ function apiCartToLocalCart(cart) {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
function jsonOrObj(items) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(items);
|
|
43
|
+
} catch {
|
|
44
|
+
return items;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
apiPathDetails.listen((pathDetails, oldPathDetails) => {
|
|
41
49
|
const localCartData = JSON.parse(cartStore.get());
|
|
42
50
|
if (pathDetailsIsFullyDefined(pathDetails)) {
|
|
43
51
|
// Runs on init if there is a user id and api key. Automatically logs in if userId is updated from undefined.
|
|
44
|
-
fetchFromCartApi("GET", pathDetails).then(async (
|
|
52
|
+
fetchFromCartApi("GET", pathDetails).then(async (res) => {
|
|
53
|
+
const { status } = res;
|
|
45
54
|
if (status === 200) {
|
|
46
|
-
const cart = await json();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
const cart = await res.json();
|
|
56
|
+
|
|
57
|
+
const cartStr = JSON.stringify({
|
|
58
|
+
authentication: { loggedIn: true, user_id: cart.user_id },
|
|
59
|
+
items: jsonOrObj(cart.items),
|
|
60
|
+
order: cart.order ? jsonOrObj(cart.order) : undefined,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
cartStore.set(cartStr);
|
|
64
|
+
console.log(cartStr);
|
|
54
65
|
} else if (status === 404 && localCartData.items.length > 0) {
|
|
55
66
|
fetchFromCartApi(
|
|
56
67
|
"PUT",
|
|
@@ -59,11 +70,19 @@ apiPathDetails.listen((pathDetails, oldPathDetails) => {
|
|
|
59
70
|
).then(async ({ ok, json }) => {
|
|
60
71
|
if (!ok) return;
|
|
61
72
|
const cart = await json();
|
|
73
|
+
console.log(
|
|
74
|
+
"Update local cart",
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
authentication: { loggedIn: true, user_id: cart.user_id },
|
|
77
|
+
items: jsonOrObj(cart.items),
|
|
78
|
+
order: cart.order ? jsonOrObj(cart.order) : undefined,
|
|
79
|
+
})
|
|
80
|
+
);
|
|
62
81
|
cartStore.set(
|
|
63
82
|
JSON.stringify({
|
|
64
83
|
authentication: { loggedIn: true, user_id: cart.user_id },
|
|
65
|
-
items:
|
|
66
|
-
order: cart.order ?
|
|
84
|
+
items: jsonOrObj(cart.items),
|
|
85
|
+
order: cart.order ? jsonOrObj(cart.order) : undefined,
|
|
67
86
|
})
|
|
68
87
|
);
|
|
69
88
|
});
|
|
@@ -96,6 +115,7 @@ export function logout(apiBaseUrl: string | undefined) {
|
|
|
96
115
|
}
|
|
97
116
|
|
|
98
117
|
export function addToCart(p: CartProduct) {
|
|
118
|
+
console.log("ADD TO CART", p);
|
|
99
119
|
const cart: Cart = JSON.parse(cartStore.get());
|
|
100
120
|
const newCart: Cart = {
|
|
101
121
|
...cart,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|