@springmicro/cart 0.3.0 → 0.3.4

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.3.0",
4
+ "version": "0.3.4",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "dev": "vite build --watch",
14
+ "autopack": "chokidar \"dist/*\" -c \"pnpm pack\"",
14
15
  "build": "rm -rf dist && vite build",
15
16
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
16
17
  "preview": "vite preview"
@@ -23,7 +24,7 @@
23
24
  "@nanostores/persistent": "^0.10.1",
24
25
  "@nanostores/query": "^0.3.3",
25
26
  "@nanostores/react": "^0.7.2",
26
- "@springmicro/utils": "0.3.0",
27
+ "@springmicro/utils": "0.3.4",
27
28
  "dotenv": "^16.4.5",
28
29
  "nanostores": "^0.10.3",
29
30
  "react": "^18.2.0",
@@ -38,6 +39,7 @@
38
39
  "@typescript-eslint/eslint-plugin": "^7.2.0",
39
40
  "@typescript-eslint/parser": "^7.2.0",
40
41
  "@vitejs/plugin-react": "^4.2.1",
42
+ "chokidar-cli": "^3.0.0",
41
43
  "eslint": "^8.57.0",
42
44
  "eslint-plugin-react-hooks": "^4.6.0",
43
45
  "eslint-plugin-react-refresh": "^0.4.6",
@@ -47,5 +49,5 @@
47
49
  "vite-plugin-css-injected-by-js": "^3.5.1",
48
50
  "yup": "^1.4.0"
49
51
  },
50
- "gitHead": "a50179e6b459af707d7ba19db175073207ce28c0"
52
+ "gitHead": "291a6da39f3801c2d7993958d252a2ffa3f4ef1e"
51
53
  }
@@ -1,16 +1,16 @@
1
- import { CartProduct } from "./types";
2
- import { addToCart } from "./utils/storage";
3
-
4
- type Props = {
5
- item: CartProduct;
6
- children: any;
7
- };
8
-
9
- export default function AddToCartForm({ item, children }: Props) {
10
- function addItemToCart(e: any) {
11
- e.preventDefault();
12
- addToCart(item);
13
- }
14
-
15
- return <form onSubmit={addItemToCart}>{children}</form>;
16
- }
1
+ import { CartProduct } from "./types";
2
+ import { addToCart } from "./utils/storage";
3
+
4
+ type Props = {
5
+ item: CartProduct;
6
+ children: any;
7
+ };
8
+
9
+ export default function AddToCartForm({ item, children }: Props) {
10
+ function addItemToCart(e: any) {
11
+ e.preventDefault();
12
+ addToCart(item);
13
+ }
14
+
15
+ return <form onSubmit={addItemToCart}>{children}</form>;
16
+ }
@@ -1,249 +1,249 @@
1
- import {
2
- Box,
3
- Button,
4
- ClickAwayListener,
5
- IconButton,
6
- Tooltip,
7
- } from "@mui/material";
8
- import ShoppingCartOutlinedIcon from "@mui/icons-material/ShoppingCartOutlined";
9
- import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
10
- import React from "react";
11
- import CloseIcon from "@mui/icons-material/Close";
12
- import type { Cart } from "./types";
13
- import { useStore } from "@nanostores/react";
14
- import {
15
- clearCart,
16
- removeFromCart,
17
- cartStore,
18
- apiPathDetails,
19
- } from "./utils/storage";
20
-
21
- export default function CartButton({
22
- user,
23
- apiBaseUrl,
24
- color,
25
- modalYOffset,
26
- checkout,
27
- float,
28
- }: {
29
- user?: { id: string | number } & any;
30
- apiBaseUrl: string;
31
- color?: string;
32
- modalYOffset?: string | number;
33
- checkout: () => void;
34
- float?: boolean;
35
- }) {
36
- const [modalOpen, setModalOpen] = React.useState(false);
37
-
38
- React.useEffect(() => {
39
- apiPathDetails.set({
40
- baseUrl: apiBaseUrl,
41
- userId: user ? user.id : undefined,
42
- });
43
- }, [user]);
44
-
45
- const cartStorage = useStore(cartStore);
46
- const cartData = JSON.parse(cartStorage);
47
-
48
- return (
49
- <Cart
50
- cart={cartData}
51
- modalState={[modalOpen, setModalOpen]}
52
- color={color}
53
- modalYOffset={modalYOffset}
54
- checkout={checkout}
55
- apiBaseUrl={apiBaseUrl}
56
- float={float}
57
- />
58
- );
59
- }
60
-
61
- function Cart({
62
- modalState,
63
- cart,
64
- color,
65
- modalYOffset,
66
- checkout,
67
- apiBaseUrl,
68
- float,
69
- }: {
70
- modalState: [boolean, React.Dispatch<React.SetStateAction<boolean>>];
71
- cart: Cart;
72
- color?: string;
73
- modalYOffset?: string | number;
74
- checkout: () => void;
75
- apiBaseUrl: string;
76
- float?: boolean;
77
- }) {
78
- const [modalOpen, setModalOpen] = modalState;
79
-
80
- return (
81
- <Box>
82
- <ClickAwayListener
83
- onClickAway={() => {
84
- setModalOpen(false);
85
- }}
86
- >
87
- <Box>
88
- <IconButton
89
- sx={{ color: color ?? "white" }}
90
- onClick={() => {
91
- setModalOpen((t) => !t);
92
- }}
93
- >
94
- {cart.items.length > 0 ? (
95
- <ShoppingCartIcon />
96
- ) : (
97
- <ShoppingCartOutlinedIcon />
98
- )}
99
- </IconButton>
100
- <CartModal
101
- open={modalOpen}
102
- cart={cart}
103
- clearCart={clearCart}
104
- modalYOffset={modalYOffset}
105
- checkout={checkout}
106
- apiBaseUrl={apiBaseUrl}
107
- float={float}
108
- />
109
- </Box>
110
- </ClickAwayListener>
111
- </Box>
112
- );
113
- }
114
-
115
- function CartModal({
116
- open,
117
- cart,
118
- clearCart,
119
- modalYOffset,
120
- checkout,
121
- apiBaseUrl,
122
- float,
123
- }: {
124
- open?: boolean;
125
- cart: Cart;
126
- clearCart: () => void;
127
- modalYOffset?: string | number;
128
- checkout: () => void;
129
- apiBaseUrl: string;
130
- float?: boolean;
131
- }) {
132
- const [prices, setPrices] = React.useState({});
133
- // build pricing list
134
- React.useEffect(() => {
135
- // filter out prices that have already been queried
136
- const pricesToGet = cart.items
137
- .map((c) => c.price_id)
138
- .filter(
139
- (pId) => Object.keys(prices).findIndex((pKey) => pKey == pId) === -1
140
- );
141
- if (pricesToGet.length === 0) return;
142
-
143
- const url = `${apiBaseUrl}/api/ecommerce/price?filter={'ids':[${pricesToGet.join(",")}]}`;
144
- fetch(url, {
145
- method: "GET",
146
- headers: {
147
- "Content-Type": "application/json",
148
- },
149
- })
150
- .then((res) =>
151
- res.json().then((data) => {
152
- const pricingData = { ...prices };
153
-
154
- data.forEach((p) => {
155
- pricingData[p.id] = p;
156
- });
157
- setPrices(pricingData);
158
- })
159
- )
160
- .catch(() => {
161
- setPrices(null);
162
- });
163
- }, [cart]);
164
-
165
- if (!open) return null;
166
- return (
167
- <Box
168
- sx={{
169
- marginTop: modalYOffset,
170
- position: float ? "fixed" : "absolute",
171
- width: 325 + 16,
172
- minHeight: 450,
173
- maxHeight: "calc(90vh - 8rem)",
174
- boxShadow: "-4px 8px 5px -7px rgba(0,0,0,0.75)",
175
- py: 1,
176
- right: -16,
177
- pr: 2,
178
- display: "grid",
179
- gridTemplateRows: "1fr 40px",
180
- color: "black",
181
- bgcolor: "white",
182
- borderBottomLeftRadius: 12,
183
- gap: 2,
184
- zIndex: 1,
185
- }}
186
- >
187
- <Box sx={{ overflow: "auto", pl: 2 }}>
188
- {cart.items.length === 0 ? <Box>Your cart is empty.</Box> : null}
189
- {cart.items.map((product, i) => {
190
- const price = prices[product.price_id];
191
- return (
192
- <Box
193
- key={product.product_id}
194
- sx={{
195
- display: "flex",
196
- width: "100%",
197
- borderBottom: "2px solid #ddd",
198
- boxSizing: "border-box",
199
- pr: 1,
200
- py: 1.5,
201
- ">*": {
202
- m: 0,
203
- },
204
- justifyContent: "space-between",
205
- alignItems: "center",
206
- }}
207
- >
208
- <Box sx={{ display: "flex", flexDirection: "column" }}>
209
- <h2>{product.name}</h2>
210
-
211
- <h3 style={{ fontSize: 13 }}>
212
- {prices === null
213
- ? "Price not found."
214
- : 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..."}
221
- </h3>
222
- </Box>
223
- <Tooltip title="Remove from cart">
224
- <IconButton
225
- sx={{ color: "red" }}
226
- onClick={() => {
227
- removeFromCart(i);
228
- }}
229
- >
230
- <CloseIcon />
231
- </IconButton>
232
- </Tooltip>
233
- </Box>
234
- );
235
- })}
236
- </Box>
237
- <Box sx={{ display: "flex", justifyContent: "space-between", mx: 2 }}>
238
- <Button
239
- sx={{ color: "red" }}
240
- onClick={clearCart}
241
- disabled={cart.items.length === 0}
242
- >
243
- Clear cart
244
- </Button>
245
- <Button onClick={checkout}>Checkout →</Button>
246
- </Box>
247
- </Box>
248
- );
249
- }
1
+ import {
2
+ Box,
3
+ Button,
4
+ ClickAwayListener,
5
+ IconButton,
6
+ Tooltip,
7
+ } from "@mui/material";
8
+ import ShoppingCartOutlinedIcon from "@mui/icons-material/ShoppingCartOutlined";
9
+ import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
10
+ import React from "react";
11
+ import CloseIcon from "@mui/icons-material/Close";
12
+ import type { Cart } from "./types";
13
+ import { useStore } from "@nanostores/react";
14
+ import {
15
+ clearCart,
16
+ removeFromCart,
17
+ cartStore,
18
+ apiPathDetails,
19
+ } from "./utils/storage";
20
+
21
+ export default function CartButton({
22
+ user,
23
+ apiBaseUrl,
24
+ color,
25
+ modalYOffset,
26
+ checkout,
27
+ float,
28
+ }: {
29
+ user?: { id: string | number } & any;
30
+ apiBaseUrl: string;
31
+ color?: string;
32
+ modalYOffset?: string | number;
33
+ checkout: () => void;
34
+ float?: boolean;
35
+ }) {
36
+ const [modalOpen, setModalOpen] = React.useState(false);
37
+
38
+ React.useEffect(() => {
39
+ apiPathDetails.set({
40
+ baseUrl: apiBaseUrl,
41
+ userId: user ? user.id : undefined,
42
+ });
43
+ }, [user]);
44
+
45
+ const cartStorage = useStore(cartStore);
46
+ const cartData = JSON.parse(cartStorage);
47
+
48
+ return (
49
+ <Cart
50
+ cart={cartData}
51
+ modalState={[modalOpen, setModalOpen]}
52
+ color={color}
53
+ modalYOffset={modalYOffset}
54
+ checkout={checkout}
55
+ apiBaseUrl={apiBaseUrl}
56
+ float={float}
57
+ />
58
+ );
59
+ }
60
+
61
+ function Cart({
62
+ modalState,
63
+ cart,
64
+ color,
65
+ modalYOffset,
66
+ checkout,
67
+ apiBaseUrl,
68
+ float,
69
+ }: {
70
+ modalState: [boolean, React.Dispatch<React.SetStateAction<boolean>>];
71
+ cart: Cart;
72
+ color?: string;
73
+ modalYOffset?: string | number;
74
+ checkout: () => void;
75
+ apiBaseUrl: string;
76
+ float?: boolean;
77
+ }) {
78
+ const [modalOpen, setModalOpen] = modalState;
79
+
80
+ return (
81
+ <Box>
82
+ <ClickAwayListener
83
+ onClickAway={() => {
84
+ setModalOpen(false);
85
+ }}
86
+ >
87
+ <Box>
88
+ <IconButton
89
+ sx={{ color: color ?? "white" }}
90
+ onClick={() => {
91
+ setModalOpen((t) => !t);
92
+ }}
93
+ >
94
+ {cart.items.length > 0 ? (
95
+ <ShoppingCartIcon />
96
+ ) : (
97
+ <ShoppingCartOutlinedIcon />
98
+ )}
99
+ </IconButton>
100
+ <CartModal
101
+ open={modalOpen}
102
+ cart={cart}
103
+ clearCart={clearCart}
104
+ modalYOffset={modalYOffset}
105
+ checkout={checkout}
106
+ apiBaseUrl={apiBaseUrl}
107
+ float={float}
108
+ />
109
+ </Box>
110
+ </ClickAwayListener>
111
+ </Box>
112
+ );
113
+ }
114
+
115
+ function CartModal({
116
+ open,
117
+ cart,
118
+ clearCart,
119
+ modalYOffset,
120
+ checkout,
121
+ apiBaseUrl,
122
+ float,
123
+ }: {
124
+ open?: boolean;
125
+ cart: Cart;
126
+ clearCart: () => void;
127
+ modalYOffset?: string | number;
128
+ checkout: () => void;
129
+ apiBaseUrl: string;
130
+ float?: boolean;
131
+ }) {
132
+ const [prices, setPrices] = React.useState({});
133
+ // build pricing list
134
+ React.useEffect(() => {
135
+ // filter out prices that have already been queried
136
+ const pricesToGet = cart.items
137
+ .map((c) => c.price_id)
138
+ .filter(
139
+ (pId) => Object.keys(prices).findIndex((pKey) => pKey == pId) === -1
140
+ );
141
+ if (pricesToGet.length === 0) return;
142
+
143
+ const url = `${apiBaseUrl}/api/ecommerce/price?filter={'ids':[${pricesToGet.join(",")}]}`;
144
+ fetch(url, {
145
+ method: "GET",
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ },
149
+ })
150
+ .then((res) =>
151
+ res.json().then((data) => {
152
+ const pricingData = { ...prices };
153
+
154
+ data.forEach((p) => {
155
+ pricingData[p.id] = p;
156
+ });
157
+ setPrices(pricingData);
158
+ })
159
+ )
160
+ .catch(() => {
161
+ setPrices(null);
162
+ });
163
+ }, [cart]);
164
+
165
+ if (!open) return null;
166
+ return (
167
+ <Box
168
+ sx={{
169
+ marginTop: modalYOffset,
170
+ position: float ? "fixed" : "absolute",
171
+ width: 325 + 16,
172
+ minHeight: 450,
173
+ maxHeight: "calc(90vh - 8rem)",
174
+ boxShadow: "-4px 8px 5px -7px rgba(0,0,0,0.75)",
175
+ py: 1,
176
+ right: -16,
177
+ pr: 2,
178
+ display: "grid",
179
+ gridTemplateRows: "1fr 40px",
180
+ color: "black",
181
+ bgcolor: "white",
182
+ borderBottomLeftRadius: 12,
183
+ gap: 2,
184
+ zIndex: 1,
185
+ }}
186
+ >
187
+ <Box sx={{ overflow: "auto", pl: 2 }}>
188
+ {cart.items.length === 0 ? <Box>Your cart is empty.</Box> : null}
189
+ {cart.items.map((product, i) => {
190
+ const price = prices[product.price_id];
191
+ return (
192
+ <Box
193
+ key={product.product_id}
194
+ sx={{
195
+ display: "flex",
196
+ width: "100%",
197
+ borderBottom: "2px solid #ddd",
198
+ boxSizing: "border-box",
199
+ pr: 1,
200
+ py: 1.5,
201
+ ">*": {
202
+ m: 0,
203
+ },
204
+ justifyContent: "space-between",
205
+ alignItems: "center",
206
+ }}
207
+ >
208
+ <Box sx={{ display: "flex", flexDirection: "column" }}>
209
+ <h2>{product.name}</h2>
210
+
211
+ <h3 style={{ fontSize: 13 }}>
212
+ {prices === null
213
+ ? "Price not found."
214
+ : 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..."}
221
+ </h3>
222
+ </Box>
223
+ <Tooltip title="Remove from cart">
224
+ <IconButton
225
+ sx={{ color: "red" }}
226
+ onClick={() => {
227
+ removeFromCart(i);
228
+ }}
229
+ >
230
+ <CloseIcon />
231
+ </IconButton>
232
+ </Tooltip>
233
+ </Box>
234
+ );
235
+ })}
236
+ </Box>
237
+ <Box sx={{ display: "flex", justifyContent: "space-between", mx: 2 }}>
238
+ <Button
239
+ sx={{ color: "red" }}
240
+ onClick={clearCart}
241
+ disabled={cart.items.length === 0}
242
+ >
243
+ Clear cart
244
+ </Button>
245
+ <Button onClick={checkout}>Checkout →</Button>
246
+ </Box>
247
+ </Box>
248
+ );
249
+ }