@betterstore/react 0.1.8 → 0.1.9

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.mjs ADDED
@@ -0,0 +1,393 @@
1
+ import { create } from 'zustand';
2
+ import React, { memo, useState, useEffect } from 'react';
3
+ import { useStripe, useElements, PaymentElement as PaymentElement$1, Elements } from '@stripe/react-stripe-js';
4
+ import { loadStripe } from '@stripe/stripe-js';
5
+
6
+ function createJSONStorage(getStorage, options) {
7
+ let storage;
8
+ try {
9
+ storage = getStorage();
10
+ } catch (e) {
11
+ return;
12
+ }
13
+ const persistStorage = {
14
+ getItem: (name) => {
15
+ var _a;
16
+ const parse = (str2) => {
17
+ if (str2 === null) {
18
+ return null;
19
+ }
20
+ return JSON.parse(str2, undefined );
21
+ };
22
+ const str = (_a = storage.getItem(name)) != null ? _a : null;
23
+ if (str instanceof Promise) {
24
+ return str.then(parse);
25
+ }
26
+ return parse(str);
27
+ },
28
+ setItem: (name, newValue) => storage.setItem(
29
+ name,
30
+ JSON.stringify(newValue, undefined )
31
+ ),
32
+ removeItem: (name) => storage.removeItem(name)
33
+ };
34
+ return persistStorage;
35
+ }
36
+ const toThenable = (fn) => (input) => {
37
+ try {
38
+ const result = fn(input);
39
+ if (result instanceof Promise) {
40
+ return result;
41
+ }
42
+ return {
43
+ then(onFulfilled) {
44
+ return toThenable(onFulfilled)(result);
45
+ },
46
+ catch(_onRejected) {
47
+ return this;
48
+ }
49
+ };
50
+ } catch (e) {
51
+ return {
52
+ then(_onFulfilled) {
53
+ return this;
54
+ },
55
+ catch(onRejected) {
56
+ return toThenable(onRejected)(e);
57
+ }
58
+ };
59
+ }
60
+ };
61
+ const persistImpl = (config, baseOptions) => (set, get, api) => {
62
+ let options = {
63
+ storage: createJSONStorage(() => localStorage),
64
+ partialize: (state) => state,
65
+ version: 0,
66
+ merge: (persistedState, currentState) => ({
67
+ ...currentState,
68
+ ...persistedState
69
+ }),
70
+ ...baseOptions
71
+ };
72
+ let hasHydrated = false;
73
+ const hydrationListeners = /* @__PURE__ */ new Set();
74
+ const finishHydrationListeners = /* @__PURE__ */ new Set();
75
+ let storage = options.storage;
76
+ if (!storage) {
77
+ return config(
78
+ (...args) => {
79
+ console.warn(
80
+ `[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
81
+ );
82
+ set(...args);
83
+ },
84
+ get,
85
+ api
86
+ );
87
+ }
88
+ const setItem = () => {
89
+ const state = options.partialize({ ...get() });
90
+ return storage.setItem(options.name, {
91
+ state,
92
+ version: options.version
93
+ });
94
+ };
95
+ const savedSetState = api.setState;
96
+ api.setState = (state, replace) => {
97
+ savedSetState(state, replace);
98
+ void setItem();
99
+ };
100
+ const configResult = config(
101
+ (...args) => {
102
+ set(...args);
103
+ void setItem();
104
+ },
105
+ get,
106
+ api
107
+ );
108
+ api.getInitialState = () => configResult;
109
+ let stateFromStorage;
110
+ const hydrate = () => {
111
+ var _a, _b;
112
+ if (!storage) return;
113
+ hasHydrated = false;
114
+ hydrationListeners.forEach((cb) => {
115
+ var _a2;
116
+ return cb((_a2 = get()) != null ? _a2 : configResult);
117
+ });
118
+ const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? undefined : _b.call(options, (_a = get()) != null ? _a : configResult)) || undefined;
119
+ return toThenable(storage.getItem.bind(storage))(options.name).then((deserializedStorageValue) => {
120
+ if (deserializedStorageValue) {
121
+ if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
122
+ if (options.migrate) {
123
+ const migration = options.migrate(
124
+ deserializedStorageValue.state,
125
+ deserializedStorageValue.version
126
+ );
127
+ if (migration instanceof Promise) {
128
+ return migration.then((result) => [true, result]);
129
+ }
130
+ return [true, migration];
131
+ }
132
+ console.error(
133
+ `State loaded from storage couldn't be migrated since no migrate function was provided`
134
+ );
135
+ } else {
136
+ return [false, deserializedStorageValue.state];
137
+ }
138
+ }
139
+ return [false, undefined];
140
+ }).then((migrationResult) => {
141
+ var _a2;
142
+ const [migrated, migratedState] = migrationResult;
143
+ stateFromStorage = options.merge(
144
+ migratedState,
145
+ (_a2 = get()) != null ? _a2 : configResult
146
+ );
147
+ set(stateFromStorage, true);
148
+ if (migrated) {
149
+ return setItem();
150
+ }
151
+ }).then(() => {
152
+ postRehydrationCallback == null ? undefined : postRehydrationCallback(stateFromStorage, undefined);
153
+ stateFromStorage = get();
154
+ hasHydrated = true;
155
+ finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
156
+ }).catch((e) => {
157
+ postRehydrationCallback == null ? undefined : postRehydrationCallback(undefined, e);
158
+ });
159
+ };
160
+ api.persist = {
161
+ setOptions: (newOptions) => {
162
+ options = {
163
+ ...options,
164
+ ...newOptions
165
+ };
166
+ if (newOptions.storage) {
167
+ storage = newOptions.storage;
168
+ }
169
+ },
170
+ clearStorage: () => {
171
+ storage == null ? undefined : storage.removeItem(options.name);
172
+ },
173
+ getOptions: () => options,
174
+ rehydrate: () => hydrate(),
175
+ hasHydrated: () => hasHydrated,
176
+ onHydrate: (cb) => {
177
+ hydrationListeners.add(cb);
178
+ return () => {
179
+ hydrationListeners.delete(cb);
180
+ };
181
+ },
182
+ onFinishHydration: (cb) => {
183
+ finishHydrationListeners.add(cb);
184
+ return () => {
185
+ finishHydrationListeners.delete(cb);
186
+ };
187
+ }
188
+ };
189
+ if (!options.skipHydration) {
190
+ hydrate();
191
+ }
192
+ return stateFromStorage || configResult;
193
+ };
194
+ const persist = persistImpl;
195
+
196
+ const generateLineItemId = (item) => {
197
+ return btoa(JSON.stringify({
198
+ productId: item.productId,
199
+ variantOptions: item.variantOptions,
200
+ metadata: item.metadata,
201
+ }));
202
+ };
203
+ const useCart = create()(persist((set, get) => ({
204
+ lineItems: [],
205
+ addItem: (product, additionalParams) => set((state) => {
206
+ var _a, _b;
207
+ const productId = typeof product === "string" ? product : product === null || product === void 0 ? void 0 : product.id;
208
+ const formattedNewItem = Object.assign(Object.assign({ productId: productId }, (typeof product !== "string" && Object.assign({}, product))), { quantity: (_a = additionalParams === null || additionalParams === void 0 ? void 0 : additionalParams.quantity) !== null && _a !== void 0 ? _a : 1, variantOptions: (_b = additionalParams === null || additionalParams === void 0 ? void 0 : additionalParams.variantOptions) !== null && _b !== void 0 ? _b : [], metadata: additionalParams === null || additionalParams === void 0 ? void 0 : additionalParams.metadata });
209
+ const id = generateLineItemId(formattedNewItem);
210
+ const existingItemIndex = state.lineItems.findIndex((item) => item.id === id);
211
+ if (existingItemIndex !== -1) {
212
+ const updatedItems = [...state.lineItems];
213
+ updatedItems[existingItemIndex] = Object.assign(Object.assign({}, updatedItems[existingItemIndex]), { quantity: updatedItems[existingItemIndex].quantity +
214
+ formattedNewItem.quantity });
215
+ return { lineItems: updatedItems };
216
+ }
217
+ return {
218
+ lineItems: [...state.lineItems, Object.assign(Object.assign({}, formattedNewItem), { id })],
219
+ };
220
+ }),
221
+ removeItem: (id) => set((state) => ({
222
+ lineItems: state.lineItems.filter((i) => i.id !== id),
223
+ })),
224
+ updateQuantity: (id, quantity) => set((state) => ({
225
+ lineItems: state.lineItems.map((i) => i.id === id ? Object.assign(Object.assign({}, i), { quantity }) : i),
226
+ })),
227
+ getProductQuantity: (productId) => {
228
+ const items = get().lineItems.filter((item) => item.productId === productId);
229
+ return items.reduce((acc, item) => acc + item.quantity, 0);
230
+ },
231
+ clearCart: () => set({ lineItems: [] }),
232
+ }), {
233
+ name: "cart",
234
+ }));
235
+
236
+ /******************************************************************************
237
+ Copyright (c) Microsoft Corporation.
238
+
239
+ Permission to use, copy, modify, and/or distribute this software for any
240
+ purpose with or without fee is hereby granted.
241
+
242
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
243
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
244
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
245
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
246
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
247
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
248
+ PERFORMANCE OF THIS SOFTWARE.
249
+ ***************************************************************************** */
250
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
251
+
252
+
253
+ function __awaiter(thisArg, _arguments, P, generator) {
254
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
255
+ return new (P || (P = Promise))(function (resolve, reject) {
256
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
257
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
258
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
259
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
260
+ });
261
+ }
262
+
263
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
264
+ var e = new Error(message);
265
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
266
+ };
267
+
268
+ function CheckoutSummary({ lineItems, shipping, tax, currency, }) {
269
+ const subtotal = lineItems.reduce((acc, item) => {
270
+ var _a, _b;
271
+ return acc + ((_b = (_a = item.product) === null || _a === void 0 ? void 0 : _a.priceInCents) !== null && _b !== void 0 ? _b : 0) * item.quantity;
272
+ }, 0);
273
+ const total = subtotal + (tax !== null && tax !== void 0 ? tax : 0) + (shipping !== null && shipping !== void 0 ? shipping : 0);
274
+ const formatPrice = (cents) => {
275
+ return `${(cents / 100).toFixed(2)} ${currency}`;
276
+ };
277
+ return (React.createElement("div", { className: "bg-black text-white p-6 rounded-lg" },
278
+ lineItems.map((item, index) => {
279
+ var _a, _b, _c, _d, _e;
280
+ return (React.createElement("div", { key: index, className: "flex items-center mb-6" },
281
+ React.createElement("div", { className: "relative" },
282
+ React.createElement("div", { className: "w-16 h-16 bg-zinc-900 rounded-lg overflow-hidden relative" }, ((_a = item.product) === null || _a === void 0 ? void 0 : _a.images[0]) ? (React.createElement("img", { src: item.product.images[0] || "/placeholder.svg", alt: ((_b = item.product) === null || _b === void 0 ? void 0 : _b.title) || "Product image", className: "object-cover" })) : (React.createElement("div", { className: "w-full h-full flex items-center justify-center bg-zinc-800" },
283
+ React.createElement("span", { className: "text-zinc-500" }, "No image")))),
284
+ React.createElement("div", { className: "absolute -top-2 -right-2 w-6 h-6 bg-zinc-700 rounded-full flex items-center justify-center text-sm" }, item.quantity)),
285
+ React.createElement("div", { className: "ml-4 flex-1" },
286
+ React.createElement("h3", { className: "text-lg font-medium" }, ((_c = item.product) === null || _c === void 0 ? void 0 : _c.title) || "Product"),
287
+ React.createElement("p", { className: "text-zinc-400 text-sm" }, item.variantOptions.map((option) => option.name).join(" / "))),
288
+ React.createElement("div", { className: "text-right" },
289
+ React.createElement("p", { className: "text-lg font-medium" }, formatPrice((_e = (_d = item.product) === null || _d === void 0 ? void 0 : _d.priceInCents) !== null && _e !== void 0 ? _e : 0)))));
290
+ }),
291
+ React.createElement("div", { className: "border-t border-zinc-800 pt-4 mt-2" },
292
+ React.createElement("div", { className: "flex justify-between py-2" },
293
+ React.createElement("span", { className: "text-lg" }, "Subtotal"),
294
+ React.createElement("span", { className: "text-lg" }, formatPrice(subtotal))),
295
+ React.createElement("div", { className: "flex justify-between py-2" },
296
+ React.createElement("span", { className: "text-lg" }, "Shipping"),
297
+ React.createElement("span", { className: "text-zinc-400" }, shipping !== undefined
298
+ ? formatPrice(shipping)
299
+ : "Calculated at next step")),
300
+ tax !== undefined && (React.createElement("div", { className: "flex justify-between py-2" },
301
+ React.createElement("span", { className: "text-lg" }, "Tax"),
302
+ React.createElement("span", { className: "text-lg" }, formatPrice(tax)))),
303
+ React.createElement("div", { className: "flex justify-between py-4 mt-2 border-t border-zinc-800 items-center" },
304
+ React.createElement("span", { className: "text-2xl font-bold" }, "Total"),
305
+ React.createElement("div", { className: "text-right" },
306
+ React.createElement("span", { className: "text-zinc-400 text-sm mr-2" }, currency),
307
+ React.createElement("span", { className: "text-2xl font-bold" }, formatPrice(total)))))));
308
+ }
309
+
310
+ function CheckoutEmbed({ betterStore, checkoutId, }) {
311
+ const [checkout, setCheckout] = useState(null);
312
+ const [loading, setLoading] = useState(true);
313
+ useEffect(() => {
314
+ function fetchCheckout() {
315
+ return __awaiter(this, void 0, void 0, function* () {
316
+ try {
317
+ const data = yield betterStore.checkout.retrieve(checkoutId);
318
+ setCheckout(data);
319
+ }
320
+ catch (error) {
321
+ console.error("Failed to fetch checkout:", error);
322
+ }
323
+ finally {
324
+ setLoading(false);
325
+ }
326
+ });
327
+ }
328
+ fetchCheckout();
329
+ }, [betterStore, checkoutId]);
330
+ if (loading) {
331
+ return React.createElement("div", null, "Loading...");
332
+ }
333
+ if (!checkout) {
334
+ return React.createElement("div", null, "Checkout not found");
335
+ }
336
+ return (React.createElement("div", { className: "grid md:grid-cols-2 gap-4" },
337
+ React.createElement("div", null, "forms here"),
338
+ React.createElement("div", null,
339
+ React.createElement(CheckoutSummary, { currency: checkout.currency, lineItems: checkout.lineItems, shipping: checkout === null || checkout === void 0 ? void 0 : checkout.shipping, tax: checkout === null || checkout === void 0 ? void 0 : checkout.tax }))));
340
+ }
341
+ var index$1 = memo(CheckoutEmbed);
342
+
343
+ const useCheckout = create((set) => ({
344
+ isSubmitting: false,
345
+ setIsSubmitting: (isSubmitting) => set({ isSubmitting }),
346
+ }));
347
+
348
+ const CheckoutForm = ({ onSuccess, onError, children, }) => {
349
+ const stripe = useStripe();
350
+ const elements = useElements();
351
+ const { setIsSubmitting } = useCheckout();
352
+ const [errorMessage, setErrorMessage] = useState(undefined);
353
+ const handleSubmit = (event) => __awaiter(void 0, void 0, void 0, function* () {
354
+ event.preventDefault();
355
+ if (!stripe || !elements) {
356
+ return;
357
+ }
358
+ setIsSubmitting(true);
359
+ const response = yield stripe.confirmPayment({
360
+ elements,
361
+ redirect: "if_required",
362
+ });
363
+ if (response.error) {
364
+ setErrorMessage(response.error.message || "Something went wrong.");
365
+ setIsSubmitting(false);
366
+ onError === null || onError === void 0 ? void 0 : onError();
367
+ }
368
+ else {
369
+ setErrorMessage(undefined);
370
+ setIsSubmitting(false);
371
+ onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess();
372
+ }
373
+ setErrorMessage(undefined);
374
+ setIsSubmitting(false);
375
+ });
376
+ return (React.createElement("form", { onSubmit: handleSubmit },
377
+ React.createElement("div", null,
378
+ React.createElement(PaymentElement$1, null),
379
+ React.createElement("p", { className: "text-red-500" }, errorMessage)),
380
+ children));
381
+ };
382
+ var CheckoutForm$1 = memo(CheckoutForm);
383
+
384
+ const publicStripeKey = "pk_test_51OjSZ2JoDmiuDQz4Vub296KIgTCy4y8NJos59h93bq3sLe3veuXnV9XVmvvWDFlt3aEWHY4pOuIXyahEjjKZwezn00qo4U5fQS";
385
+ const stripePromise = loadStripe(publicStripeKey);
386
+ function PaymentElement({ paymentSecret, checkoutAppearance, onSuccess, onError, children, }) {
387
+ const options = Object.assign(Object.assign({ locale: "en" }, (checkoutAppearance ? checkoutAppearance : {})), { clientSecret: paymentSecret });
388
+ return (React.createElement(Elements, { stripe: stripePromise, options: options },
389
+ React.createElement(CheckoutForm$1, { onSuccess: onSuccess, onError: onError, children: children })));
390
+ }
391
+ var index = memo(PaymentElement);
392
+
393
+ export { index$1 as CheckoutEmbed, index as PaymentElement, useCart, useCheckout };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betterstore/react",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "E-commerce for Developers",
5
5
  "private": false,
6
6
  "publishConfig": {
package/rollup.config.mjs CHANGED
@@ -13,8 +13,8 @@ export default {
13
13
  format: "cjs",
14
14
  },
15
15
  {
16
- file: "dist/index.esm.js",
17
- format: "esm",
16
+ file: "dist/index.mjs",
17
+ format: "es",
18
18
  },
19
19
  ],
20
20
  plugins: [
@@ -26,5 +26,12 @@ export default {
26
26
  plugins: [tailwindcss("./tailwind.config.js"), autoprefixer()],
27
27
  }),
28
28
  ],
29
- external: ["react", "react-dom"],
29
+ external: [
30
+ "react",
31
+ "react-dom",
32
+ "@betterstore/sdk",
33
+ "@stripe/react-stripe-js",
34
+ "@stripe/stripe-js",
35
+ "zustand",
36
+ ],
30
37
  };