@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@springmicro/cart",
3
3
  "private": false,
4
- "version": "0.7.1",
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.1",
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": "3fba722cf2e25b298d1b774c2b39ae8ac5743076"
52
+ "gitHead": "baedb0de00a8f238a2356a747e69eb1754606d58"
53
53
  }
@@ -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
- ? `$${(price.unit_amount / 100).toFixed(2)}${
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
+ }
@@ -166,7 +166,12 @@ function DefaultProductCard({
166
166
 
167
167
  function formatPricing({ unit_amount, recurring }: any) {
168
168
  if (recurring) {
169
- const { interval, interval_count } = recurring;
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 { Box, useTheme, useMediaQuery, Button, TextField } from "@mui/material";
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
- dev && alert(data.statusText);
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
- <Box
366
- sx={{
367
- mt: 2,
368
- display: "flex",
369
- flexDirection: { xs: "column", xl: "row" },
370
- justifyContent: "center",
371
- alignItems: { xs: undefined, xl: "flex-start" },
372
- px: { xs: 32, xl: 16 },
373
- "&>div": { width: { xs: undefined, xl: "50%" } },
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
- {status === 0 && (
394
- <Box
395
- sx={{ flexGrow: 1, display: "flex", flexDirection: "column", gap: 2 }}
396
- >
397
- {CollectExtraInfo && <CollectExtraInfo formik={formik} />}
398
- <CheckoutActionFields
399
- formik={formik}
400
- prices={prices}
401
- products={products}
402
- hideFields={hideFields}
403
- />
404
- <AddCard
405
- cardRequired={cardRequired}
406
- onSubmit={createOrderAndGetTaxes}
407
- stacked={stacked}
408
- formData={{
409
- formik,
410
- handleInputChange,
411
- handleInputFocus,
412
- formDisabled,
413
- formError,
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
- hideFields={hideFields}
416
- />
417
- </Box>
418
- )}
419
- </Box>
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((key) => !["email", ...hideFields].includes(key))
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);
@@ -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.json().then((order) => {
64
- setOrder(order);
65
- setStatus(
66
- order.charge_id ||
67
- !["pending", "awaiting_payment"].includes(order.status)
68
- ? 2
69
- : 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.
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
@@ -15,6 +15,8 @@ export type CartProduct = {
15
15
  quantity?: number;
16
16
  // Used in local cart, delete when sent to api
17
17
  name: string;
18
+ action_data?: object;
19
+ hidden_id?: string;
18
20
  image?: string;
19
21
  };
20
22
 
@@ -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 ({ status, json }) => {
52
+ fetchFromCartApi("GET", pathDetails).then(async (res) => {
53
+ const { status } = res;
45
54
  if (status === 200) {
46
- const cart = await json();
47
- cartStore.set(
48
- JSON.stringify({
49
- authentication: { loggedIn: true, user_id: cart.user_id },
50
- items: JSON.parse((cart as any).items),
51
- order: cart.order ? JSON.parse((cart as any).order) : undefined,
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: JSON.parse((cart as any).items),
66
- order: cart.order ? JSON.parse((cart as any).order) : undefined,
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