@springmicro/cart 0.5.13 → 0.5.15

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.5.13",
4
+ "version": "0.5.15",
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.5.13",
27
+ "@springmicro/utils": "0.5.15",
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": "596e9fe58eab22df211e097c2058b5cc267fe30c"
52
+ "gitHead": "b830a46f0cb3e6081ed9a4f31861c0d0b66a88f2"
53
53
  }
@@ -140,7 +140,9 @@ function CartModal({
140
140
  );
141
141
  if (pricesToGet.length === 0) return;
142
142
 
143
- const url = `${apiBaseUrl}/api/ecommerce/price?filter={'ids':[${pricesToGet.join(",")}]}`;
143
+ const url = `${apiBaseUrl}/api/ecommerce/price?filter={'ids':[${pricesToGet.join(
144
+ ","
145
+ )}]}`;
144
146
  fetch(url, {
145
147
  method: "GET",
146
148
  headers: {
@@ -212,12 +214,18 @@ function CartModal({
212
214
  {prices === null
213
215
  ? "Price not found."
214
216
  : price
215
- ? `$${(price.unit_amount / 100).toFixed(2)}${
216
- price.recurring
217
- ? `/${price.recurring.interval_count > 1 ? `${price.recurring.interval_count} ` : ""}${price.recurring.interval}${price.recurring.interval_count > 1 ? "s" : ""}`
218
- : ""
219
- }`
220
- : "Loading 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
+ }`
228
+ : "Loading price..."}
221
229
  </h3>
222
230
  </Box>
223
231
  <Tooltip title="Remove from cart">
@@ -1,5 +1,5 @@
1
1
  import "./ReviewCartAndCalculateTaxes.css";
2
- import { Box, useTheme, useMediaQuery } from "@mui/material";
2
+ import { Box, useTheme, useMediaQuery, Button } from "@mui/material";
3
3
  import { setOrder as cartSetOrder } from "../utils/storage";
4
4
  import { AddCard, CCFocus, CreditCardValues } from "./components/AddCard";
5
5
  import { CartList } from "./components/CartList";
@@ -26,7 +26,8 @@ import { AddressValues } from "./components/Address";
26
26
  import { useFormik } from "formik";
27
27
  import { postCheckout } from "../utils/api";
28
28
 
29
- async function createOrder(cart, apiBaseUrl) {
29
+ async function createOrder(cart, apiBaseUrl, dev) {
30
+ dev && console.log("Creating order");
30
31
  // If an order has already been created and hasn't been changed, get the previous order.
31
32
  if (cart.order && cart.order.id && cart.order.reference) {
32
33
  const res = await fetch(
@@ -71,7 +72,8 @@ async function createOrder(cart, apiBaseUrl) {
71
72
  return order;
72
73
  }
73
74
 
74
- async function getTax(apiBaseUrl, taxProvider, items, v, products) {
75
+ async function getTax(apiBaseUrl, taxProvider, items, v, products, dev) {
76
+ dev && console.log("Getting tax");
75
77
  const taxable_items = items.filter(
76
78
  (i) => i.unit_amount > 0 && products[i.product_id].type !== "DONATION"
77
79
  );
@@ -118,6 +120,7 @@ export default function ReviewAndCalculateTaxes({
118
120
  onPlacement = undefined,
119
121
  CollectExtraInfo = undefined,
120
122
  defaultValues = {},
123
+ dev,
121
124
  }) {
122
125
  const [formDisabled, setFormDisabled] = useState(true);
123
126
  const [formError, setFormError] = useState(undefined);
@@ -130,10 +133,11 @@ export default function ReviewAndCalculateTaxes({
130
133
 
131
134
  async function createOrderAndGetTaxes(values) {
132
135
  setFormError(undefined);
136
+ setFormDisabled(true);
133
137
  // status === 0 when this is clicked.
134
138
  // Creates an order and calculates tax.
135
139
  Promise.all([
136
- createOrder(cart, apiBaseUrl),
140
+ createOrder(cart, apiBaseUrl, dev),
137
141
  getTax(
138
142
  apiBaseUrl,
139
143
  taxProvider,
@@ -141,20 +145,25 @@ export default function ReviewAndCalculateTaxes({
141
145
  amount: prices[i.price_id].unit_amount,
142
146
  reference: i.name,
143
147
  })),
144
- values
148
+ values,
149
+ products,
150
+ dev
145
151
  ),
146
152
  ])
147
153
  .then(([order, taxData]) => {
148
154
  setTax(taxData);
149
155
  setOrder(order);
150
156
  setStatus(1);
157
+ setFormDisabled(false);
151
158
  })
152
159
  .catch(() => {
153
160
  setFormError(true);
161
+ setFormDisabled(false);
154
162
  });
155
163
  }
156
164
 
157
165
  async function confirmOrder(values) {
166
+ dev && console.log("Confirm order");
158
167
  setFormError(undefined);
159
168
  setFormDisabled(true);
160
169
  postCheckout(
@@ -166,9 +175,10 @@ export default function ReviewAndCalculateTaxes({
166
175
  invoiceId
167
176
  )
168
177
  .then((data) => {
178
+ dev && console.log("Checkout submitted");
169
179
  if (data instanceof Response) {
170
180
  setFormError(true);
171
- alert(JSON.stringify(data));
181
+ dev && alert(data.statusText);
172
182
  } else {
173
183
  // success
174
184
  setSuccessData(data);
@@ -181,16 +191,20 @@ export default function ReviewAndCalculateTaxes({
181
191
  currentUrl.searchParams.set("orderRef", order.reference);
182
192
 
183
193
  // Update the browser's URL and history without refreshing the page
194
+ dev && console.log("Checkout success!");
184
195
  window.history.pushState({}, "", currentUrl);
185
196
  }
186
197
  setFormDisabled(false);
187
198
  })
188
- .catch(() => {
189
- // console.log("ERROR");
199
+ .catch((e) => {
200
+ dev && console.log("Checkout error", e);
190
201
  setFormError(true);
202
+ setFormDisabled(false);
191
203
  });
192
204
  }
193
205
 
206
+ const cardRequired = typeof subtotal === "string" || subtotal > 0;
207
+
194
208
  /**
195
209
  *
196
210
  * AddCard formik data
@@ -254,20 +268,18 @@ export default function ReviewAndCalculateTaxes({
254
268
  const formik = useFormik({
255
269
  initialValues,
256
270
  validationSchema: Yup.object(
257
- address
258
- ? {
259
- ...validationSchemaBase,
260
- ...validationSchemaAddress,
261
- }
262
- : validationSchemaBase
271
+ cardRequired
272
+ ? address
273
+ ? {
274
+ ...validationSchemaBase,
275
+ ...validationSchemaAddress,
276
+ }
277
+ : validationSchemaBase
278
+ : {}
263
279
  ),
264
280
  onSubmit: async (v, helpers) => {
265
- setFormDisabled(true);
281
+ dev && console.log("Formik submit");
266
282
  const values = { ...v };
267
- if (formDisabled) {
268
- setFormDisabled(false);
269
- throw new Error("Attempted to submit an invalid form.");
270
- }
271
283
 
272
284
  const submitFunc = status === 0 ? createOrderAndGetTaxes : confirmOrder;
273
285
 
@@ -294,8 +306,8 @@ export default function ReviewAndCalculateTaxes({
294
306
  // const res = await submitFunc(values);
295
307
  submitFunc(values)
296
308
  .then((v) => {})
297
- .finally(() => {
298
- setFormDisabled(false);
309
+ .catch((e) => {
310
+ dev && console.log("Submit errors", e);
299
311
  });
300
312
  } else {
301
313
  setFormDisabled(false);
@@ -325,6 +337,11 @@ export default function ReviewAndCalculateTaxes({
325
337
  };
326
338
 
327
339
  useEffect(() => {
340
+ dev &&
341
+ (Object.keys(formik.errors).length > 0
342
+ ? console.log("Formik errors", formik.errors)
343
+ : console.log("No formik errors"));
344
+
328
345
  setFormDisabled(Object.keys(formik.errors).length > 0);
329
346
  }, [formik.errors]);
330
347
 
@@ -342,7 +359,7 @@ export default function ReviewAndCalculateTaxes({
342
359
  >
343
360
  <CartList
344
361
  {...{
345
- status,
362
+ statusState: [status, setStatus],
346
363
  cart,
347
364
  subtotal,
348
365
  tax,
@@ -354,7 +371,7 @@ export default function ReviewAndCalculateTaxes({
354
371
  disableProductLink,
355
372
  formik,
356
373
  formDisabled,
357
- formError,
374
+ formErrorState: [formError, setFormError],
358
375
  }}
359
376
  />
360
377
  {status === 0 && (
@@ -363,6 +380,7 @@ export default function ReviewAndCalculateTaxes({
363
380
  >
364
381
  {CollectExtraInfo && <CollectExtraInfo formik={formik} />}
365
382
  <AddCard
383
+ cardRequired={cardRequired}
366
384
  onSubmit={createOrderAndGetTaxes}
367
385
  stacked={stacked}
368
386
  formData={{
@@ -55,6 +55,7 @@ export type AddCardProps = {
55
55
  };
56
56
  stacked?: boolean;
57
57
  error?: string;
58
+ cardRequired?: boolean;
58
59
  address?: boolean;
59
60
  onSubmit?: (
60
61
  values: CreditCardValues & Partial<AddressValues>
@@ -71,6 +72,7 @@ export type AddCardProps = {
71
72
 
72
73
  export const AddCard = ({
73
74
  contact,
75
+ cardRequired = true,
74
76
  error,
75
77
  stacked = false,
76
78
  address = true,
@@ -102,33 +104,37 @@ export const AddCard = ({
102
104
  }
103
105
  }
104
106
  >
105
- <Cards
106
- {...(formik.values as CreditCardValues)}
107
- focused={formik.values.focus || "number"}
108
- />
109
-
110
- <div>
111
- <TextField
112
- sx={stacked ? { mt: 2 } : {}}
113
- fullWidth
114
- type="tel"
115
- name="number"
116
- label="Card Number"
117
- required
118
- onBlur={formik.handleBlur}
119
- onChange={handleInputChange}
120
- onFocus={handleInputFocus}
121
- variant="outlined"
122
- value={formik.values.number}
123
- error={Boolean(formik.touched.number && formik.errors.number)}
124
- helperText={formik.touched.number && formik.errors.number}
107
+ {cardRequired && (
108
+ <Cards
109
+ {...(formik.values as CreditCardValues)}
110
+ focused={formik.values.focus || "number"}
125
111
  />
112
+ )}
113
+
114
+ <Box sx={{ flexGrow: cardRequired ? undefined : 1 }}>
115
+ {cardRequired && (
116
+ <TextField
117
+ sx={stacked ? { mt: 2 } : {}}
118
+ fullWidth
119
+ type="tel"
120
+ name="number"
121
+ label="Card Number"
122
+ required
123
+ onBlur={formik.handleBlur}
124
+ onChange={handleInputChange}
125
+ onFocus={handleInputFocus}
126
+ variant="outlined"
127
+ value={formik.values.number}
128
+ error={Boolean(formik.touched.number && formik.errors.number)}
129
+ helperText={formik.touched.number && formik.errors.number}
130
+ />
131
+ )}
126
132
  <TextField
127
133
  sx={{ mt: 2 }}
128
134
  fullWidth
129
135
  type="text"
130
136
  name="name"
131
- label="Name on Card"
137
+ label={cardRequired ? "Name on Card" : "Name"}
132
138
  required
133
139
  onBlur={formik.handleBlur}
134
140
  onChange={handleInputChange}
@@ -138,104 +144,119 @@ export const AddCard = ({
138
144
  error={Boolean(formik.touched.name && formik.errors.name)}
139
145
  helperText={formik.touched.name && formik.errors.name}
140
146
  />
141
- <TextField
142
- sx={{ mt: 2, width: "calc(50% - 8px)", mr: 1 }}
143
- type="tel"
144
- name="expiry"
145
- label="Valid Thru"
146
- placeholder="MM/YY"
147
- inputProps={{ pattern: "[0-9]{2}/[0-9]{2}" }}
148
- required
149
- onBlur={formik.handleBlur}
150
- onChange={handleInputChange}
151
- onFocus={handleInputFocus}
152
- variant="outlined"
153
- value={formik.values.expiry}
154
- error={Boolean(formik.touched.expiry && formik.errors.expiry)}
155
- helperText={
156
- (formik.touched.expiry && formik.errors.expiry) || "MM/YY"
157
- }
158
- />
159
- <TextField
160
- sx={{ mt: 2, width: "calc(50% - 8px)", ml: 1 }}
161
- type="tel"
162
- name="cvc"
163
- label="CVC"
164
- required
165
- onBlur={formik.handleBlur}
166
- onChange={handleInputChange}
167
- onFocus={handleInputFocus}
168
- variant="outlined"
169
- value={formik.values.cvc}
170
- error={Boolean(formik.touched.cvc && formik.errors.cvc)}
171
- helperText={formik.touched.cvc && formik.errors.cvc}
172
- />
173
- </div>
147
+ {cardRequired && (
148
+ <>
149
+ <TextField
150
+ sx={{ mt: 2, width: "calc(50% - 8px)", mr: 1 }}
151
+ type="tel"
152
+ name="expiry"
153
+ label="Valid Thru"
154
+ placeholder="MM/YY"
155
+ inputProps={{ pattern: "[0-9]{2}/[0-9]{2}" }}
156
+ required
157
+ onBlur={formik.handleBlur}
158
+ onChange={handleInputChange}
159
+ onFocus={handleInputFocus}
160
+ variant="outlined"
161
+ value={formik.values.expiry}
162
+ error={Boolean(formik.touched.expiry && formik.errors.expiry)}
163
+ helperText={
164
+ (formik.touched.expiry && formik.errors.expiry) || "MM/YY"
165
+ }
166
+ />
167
+ <TextField
168
+ sx={{ mt: 2, width: "calc(50% - 8px)", ml: 1 }}
169
+ type="tel"
170
+ name="cvc"
171
+ label="CVC"
172
+ required
173
+ onBlur={formik.handleBlur}
174
+ onChange={handleInputChange}
175
+ onFocus={handleInputFocus}
176
+ variant="outlined"
177
+ value={formik.values.cvc}
178
+ error={Boolean(formik.touched.cvc && formik.errors.cvc)}
179
+ helperText={formik.touched.cvc && formik.errors.cvc}
180
+ />
181
+ </>
182
+ )}
183
+ </Box>
174
184
  {/* {formik.errors.submit && (
175
185
  <Typography color="error" sx={{ mt: 2 }} variant="body2">
176
186
  {formik.errors.submit}
177
187
  </Typography>
178
188
  )} */}
179
189
  </Box>
180
- <Box
181
- sx={{ display: "flex", flexDirection: "row", gap: 2, width: "100%" }}
182
- >
183
- <Box sx={{ flexGrow: 1 }}>
184
- {!address ? null : (
185
- <Box>
186
- <Typography variant="h6" sx={{ mt: 2, mb: 3 }}>
187
- Billing Address
188
- </Typography>
189
- <AddressCountryField formik={formik} name="country" />
190
- <AddressEmailField
191
- formik={formik}
192
- name="email"
193
- sx={{ mt: 2 }}
194
- />
195
- <AddressOrganizationNameField
196
- sx={{ mt: 2 }}
197
- formik={formik}
198
- name="organization"
199
- />
200
- <AddressStreetField
201
- sx={{ mt: 2 }}
202
- formik={formik}
203
- name="line1"
204
- lineNo="1"
205
- />
206
- <AddressStreetField
207
- sx={{ mt: 2 }}
208
- formik={formik}
209
- name="line2"
210
- lineNo="2"
211
- required={false}
212
- />
213
- <AddressCityField sx={{ mt: 2 }} formik={formik} name="city" />
214
- <AddressRegionField
215
- sx={{ mt: 2 }}
216
- formik={formik}
217
- name="region"
218
- />
219
- <AddressPostalCodeField
220
- sx={{ mt: 2 }}
221
- formik={formik}
222
- name="postal_code"
223
- />
224
- </Box>
225
- )}
226
- </Box>
190
+ {cardRequired && (
227
191
  <Box
228
192
  sx={{
229
- mt: "16px",
230
193
  display: "flex",
231
- flexDirection: "column",
232
- alignItems: "flex-end",
233
- flexGrow: 1,
194
+ flexDirection: "row",
195
+ gap: 2,
196
+ width: "100%",
234
197
  }}
235
198
  >
236
- {PriceDetails}
199
+ <Box sx={{ flexGrow: 1 }}>
200
+ {!address ? null : (
201
+ <Box>
202
+ <Typography variant="h6" sx={{ mt: 2, mb: 3 }}>
203
+ Billing Address
204
+ </Typography>
205
+ <AddressCountryField formik={formik} name="country" />
206
+ <AddressEmailField
207
+ formik={formik}
208
+ name="email"
209
+ sx={{ mt: 2 }}
210
+ />
211
+ <AddressOrganizationNameField
212
+ sx={{ mt: 2 }}
213
+ formik={formik}
214
+ name="organization"
215
+ />
216
+ <AddressStreetField
217
+ sx={{ mt: 2 }}
218
+ formik={formik}
219
+ name="line1"
220
+ lineNo="1"
221
+ />
222
+ <AddressStreetField
223
+ sx={{ mt: 2 }}
224
+ formik={formik}
225
+ name="line2"
226
+ lineNo="2"
227
+ required={false}
228
+ />
229
+ <AddressCityField
230
+ sx={{ mt: 2 }}
231
+ formik={formik}
232
+ name="city"
233
+ />
234
+ <AddressRegionField
235
+ sx={{ mt: 2 }}
236
+ formik={formik}
237
+ name="region"
238
+ />
239
+ <AddressPostalCodeField
240
+ sx={{ mt: 2 }}
241
+ formik={formik}
242
+ name="postal_code"
243
+ />
244
+ </Box>
245
+ )}
246
+ </Box>
247
+ <Box
248
+ sx={{
249
+ mt: "16px",
250
+ display: "flex",
251
+ flexDirection: "column",
252
+ alignItems: "flex-end",
253
+ flexGrow: 1,
254
+ }}
255
+ >
256
+ {PriceDetails}
257
+ </Box>
237
258
  </Box>
238
- </Box>
259
+ )}
239
260
  {error && (
240
261
  <Typography color="error" sx={{ mt: 2 }} variant="body2">
241
262
  {error}
@@ -177,7 +177,7 @@ export function AddressEmailField({ formik, name, sx }: AddressFieldProps) {
177
177
  return (
178
178
  <>
179
179
  <TextField
180
- label={"Email"}
180
+ label="Billing email"
181
181
  sx={{ width: 400, display: "block", ...sx }}
182
182
  name={name as string}
183
183
  id={name as string}
@@ -7,7 +7,7 @@ function formatPrice(cents) {
7
7
  }
8
8
 
9
9
  export function CartList({
10
- status,
10
+ statusState,
11
11
  cart,
12
12
  subtotal,
13
13
  tax,
@@ -19,8 +19,11 @@ export function CartList({
19
19
  disableProductLink,
20
20
  formik,
21
21
  formDisabled,
22
- formError,
22
+ formErrorState,
23
23
  }) {
24
+ console.log("DISABLED", formDisabled);
25
+ const [status, setStatus] = statusState;
26
+ const [formError, setFormError] = formErrorState;
24
27
  return (
25
28
  <form
26
29
  style={{ flexGrow: 1 }}
@@ -116,12 +119,6 @@ export function CartList({
116
119
  : subtotal}
117
120
  </Typography>
118
121
  </Box>
119
- {formError && (
120
- <Typography color="red">
121
- Could not confirm payment. Your card info may have been entered
122
- incorrectly.
123
- </Typography>
124
- )}
125
122
  {status === 1 && (
126
123
  <Button
127
124
  variant="contained"
@@ -133,22 +130,41 @@ export function CartList({
133
130
  Confirm Order
134
131
  </Button>
135
132
  )}
133
+ {formError && (
134
+ <Box
135
+ sx={{
136
+ display: "flex",
137
+ flexDirection: "column",
138
+ gap: 1,
139
+ alignItems: "center",
140
+ }}
141
+ >
142
+ <Typography color="red">
143
+ Could not confirm payment. Your card info may have been entered
144
+ incorrectly.
145
+ </Typography>
146
+ <Button
147
+ onClick={() => {
148
+ setFormError(undefined);
149
+ setStatus((i) => i - 1);
150
+ }}
151
+ variant="contained"
152
+ >
153
+ Back
154
+ </Button>
155
+ </Box>
156
+ )}
136
157
  <Box className="checkout-list">
137
- {cart.items.map(
138
- (p, i) => (
139
- console.log(p),
140
- (
141
- <CartProductCard
142
- product={products[p.product_id] ?? p}
143
- i={i}
144
- price={prices[p.price_id]}
145
- disableProductLink={disableProductLink}
146
- disableMissingImage={disableMissingImage}
147
- disableModification={status != 0}
148
- />
149
- )
150
- )
151
- )}
158
+ {cart.items.map((p, i) => (
159
+ <CartProductCard
160
+ product={products[p.product_id] ?? p}
161
+ i={i}
162
+ price={prices[p.price_id]}
163
+ disableProductLink={disableProductLink}
164
+ disableMissingImage={disableMissingImage}
165
+ disableModification={status != 0}
166
+ />
167
+ ))}
152
168
  </Box>
153
169
  </Box>
154
170
  </Box>
@@ -1,4 +1,4 @@
1
- import { Box, IconButton, Typography } from "@mui/material";
1
+ import { Box, IconButton, Typography, Tooltip } from "@mui/material";
2
2
  import ArrowBackIcon from "@mui/icons-material/ArrowBack";
3
3
 
4
4
  export function StatusBar({ status, backlink = "" }) {
@@ -11,9 +11,11 @@ export function StatusBar({ status, backlink = "" }) {
11
11
  }}
12
12
  >
13
13
  <Box sx={{ flex: "1 1" }}>
14
- <IconButton href={`/${backlink}`}>
15
- <ArrowBackIcon />
16
- </IconButton>
14
+ <Tooltip title="Exit checkout">
15
+ <IconButton href={`/${backlink}`}>
16
+ <ArrowBackIcon />
17
+ </IconButton>
18
+ </Tooltip>
17
19
  </Box>
18
20
  <div id="status-bar">
19
21
  <Typography className="status-text active">PAYMENT DETAILS</Typography>