@returnflows/shop-overlay 1.0.0

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.
Files changed (149) hide show
  1. package/README.md +976 -0
  2. package/dist/index.esm.css +3 -0
  3. package/dist/index.esm.js +12494 -0
  4. package/dist/index.esm.js.map +1 -0
  5. package/dist/messages/da.json +1 -0
  6. package/dist/messages/de.json +1 -0
  7. package/dist/messages/en.json +1 -0
  8. package/dist/messages/es.json +1 -0
  9. package/dist/messages/fi.json +1 -0
  10. package/dist/messages/fr.json +1 -0
  11. package/dist/messages/it.json +1 -0
  12. package/dist/messages/nb.json +1 -0
  13. package/dist/messages/nl.json +1 -0
  14. package/dist/messages/pl.json +1 -0
  15. package/dist/messages/sv.json +1 -0
  16. package/dist/types/api/index.d.ts +6 -0
  17. package/dist/types/api/state-bridge.d.ts +37 -0
  18. package/dist/types/api/storefront-cart.d.ts +3 -0
  19. package/dist/types/api/types.d.ts +402 -0
  20. package/dist/types/api/widget-api.d.ts +20 -0
  21. package/dist/types/index.d.ts +2 -0
  22. package/dist/types/widget/components/dialog/common/dialog-backdrop.d.ts +6 -0
  23. package/dist/types/widget/components/dialog/common/dialog-button.d.ts +6 -0
  24. package/dist/types/widget/components/dialog/common/dialog-modal.d.ts +9 -0
  25. package/dist/types/widget/components/dialog/common/dialog-subtitle.d.ts +2 -0
  26. package/dist/types/widget/components/dialog/common/dialog-title.d.ts +2 -0
  27. package/dist/types/widget/components/dialog/common/index.d.ts +10 -0
  28. package/dist/types/widget/components/dialog/return-decision-dialog.d.ts +2 -0
  29. package/dist/types/widget/components/drawer/adjustments/exchange-adjustments.d.ts +2 -0
  30. package/dist/types/widget/components/drawer/adjustments/exchange-banner.d.ts +2 -0
  31. package/dist/types/widget/components/drawer/adjustments/table/adjustments-table-skeleton.d.ts +2 -0
  32. package/dist/types/widget/components/drawer/adjustments/table/adjustments-table.d.ts +2 -0
  33. package/dist/types/widget/components/drawer/annoucement-bar.d.ts +2 -0
  34. package/dist/types/widget/components/drawer/error-screen.d.ts +6 -0
  35. package/dist/types/widget/components/drawer/exchange-confirmation.d.ts +2 -0
  36. package/dist/types/widget/components/drawer/exchange-drawer.d.ts +5 -0
  37. package/dist/types/widget/components/drawer/exchange-header.d.ts +2 -0
  38. package/dist/types/widget/components/drawer/exchange-placeholder.d.ts +2 -0
  39. package/dist/types/widget/components/drawer/product/product-actions.d.ts +8 -0
  40. package/dist/types/widget/components/drawer/product/product-attributes.d.ts +14 -0
  41. package/dist/types/widget/components/drawer/product/product-errors.d.ts +2 -0
  42. package/dist/types/widget/components/drawer/product/product-image.d.ts +7 -0
  43. package/dist/types/widget/components/drawer/product/product-list.d.ts +2 -0
  44. package/dist/types/widget/components/drawer/product/product.d.ts +9 -0
  45. package/dist/types/widget/components/drawer/product/quantity-picker.d.ts +20 -0
  46. package/dist/types/widget/components/drawer/return/return-product-actions.d.ts +10 -0
  47. package/dist/types/widget/components/drawer/return/return-product-badge.d.ts +7 -0
  48. package/dist/types/widget/components/drawer/return/return-product-list.d.ts +2 -0
  49. package/dist/types/widget/components/drawer/return/return-product-skeleton.d.ts +2 -0
  50. package/dist/types/widget/components/drawer/return/return-product.d.ts +14 -0
  51. package/dist/types/widget/components/drawer/scroll-indicator.d.ts +5 -0
  52. package/dist/types/widget/components/handlers/dialog-handler.d.ts +2 -0
  53. package/dist/types/widget/components/handlers/initialization-handler.d.ts +2 -0
  54. package/dist/types/widget/components/icons/arrow-down.d.ts +2 -0
  55. package/dist/types/widget/components/icons/arrow-left.d.ts +2 -0
  56. package/dist/types/widget/components/icons/arrow-right.d.ts +2 -0
  57. package/dist/types/widget/components/icons/bag.d.ts +2 -0
  58. package/dist/types/widget/components/icons/banner/action.d.ts +2 -0
  59. package/dist/types/widget/components/icons/banner/info.d.ts +2 -0
  60. package/dist/types/widget/components/icons/cart.d.ts +2 -0
  61. package/dist/types/widget/components/icons/chevron.d.ts +2 -0
  62. package/dist/types/widget/components/icons/giftcard.d.ts +2 -0
  63. package/dist/types/widget/components/icons/icon.d.ts +17 -0
  64. package/dist/types/widget/components/icons/spinner.d.ts +2 -0
  65. package/dist/types/widget/components/icons/star.d.ts +2 -0
  66. package/dist/types/widget/components/icons/warning.d.ts +2 -0
  67. package/dist/types/widget/components/icons/x.d.ts +2 -0
  68. package/dist/types/widget/components/is-mounted.d.ts +4 -0
  69. package/dist/types/widget/components/providers/error-provider.d.ts +4 -0
  70. package/dist/types/widget/components/providers/return-context-provider.d.ts +17 -0
  71. package/dist/types/widget/components/providers/root-provider.d.ts +2 -0
  72. package/dist/types/widget/components/providers/widget-context-provider.d.ts +6 -0
  73. package/dist/types/widget/components/return-avatar-stack.d.ts +2 -0
  74. package/dist/types/widget/components/summary/back-button.d.ts +2 -0
  75. package/dist/types/widget/components/summary/exchange-summary.d.ts +2 -0
  76. package/dist/types/widget/components/summary/exchange-total-skeleton.d.ts +2 -0
  77. package/dist/types/widget/components/summary/exchange-total.d.ts +2 -0
  78. package/dist/types/widget/components/toast/toast-container.d.ts +2 -0
  79. package/dist/types/widget/components/toast/toast-provider.d.ts +4 -0
  80. package/dist/types/widget/components/toast/toast.d.ts +16 -0
  81. package/dist/types/widget/components/ui/avatar/avatar-stack-skeleton.d.ts +2 -0
  82. package/dist/types/widget/components/ui/avatar/avatar-stack.d.ts +12 -0
  83. package/dist/types/widget/components/ui/avatar/avatar.d.ts +10 -0
  84. package/dist/types/widget/components/ui/banner/banner-skeleton.d.ts +2 -0
  85. package/dist/types/widget/components/ui/banner/banner.d.ts +13 -0
  86. package/dist/types/widget/components/ui/button.d.ts +11 -0
  87. package/dist/types/widget/components/ui/expander.d.ts +8 -0
  88. package/dist/types/widget/components/ui/image.d.ts +5 -0
  89. package/dist/types/widget/components/ui/link.d.ts +7 -0
  90. package/dist/types/widget/components/ui/price.d.ts +7 -0
  91. package/dist/types/widget/components/ui/skeleton/skeleton-container.d.ts +6 -0
  92. package/dist/types/widget/components/ui/skeleton/skeleton.d.ts +2 -0
  93. package/dist/types/widget/components/widget-container.d.ts +3 -0
  94. package/dist/types/widget/components/widget.d.ts +2 -0
  95. package/dist/types/widget/lib/constants.d.ts +13 -0
  96. package/dist/types/widget/lib/context/dialog-context.d.ts +3 -0
  97. package/dist/types/widget/lib/context/error-context.d.ts +10 -0
  98. package/dist/types/widget/lib/context/return-context.d.ts +12 -0
  99. package/dist/types/widget/lib/context/root-context.d.ts +2 -0
  100. package/dist/types/widget/lib/context/toast-context.d.ts +13 -0
  101. package/dist/types/widget/lib/context/widget-context.d.ts +9 -0
  102. package/dist/types/widget/lib/hooks/common/use-array.d.ts +14 -0
  103. package/dist/types/widget/lib/hooks/common/use-async-state.d.ts +40 -0
  104. package/dist/types/widget/lib/hooks/common/use-dialog.d.ts +11 -0
  105. package/dist/types/widget/lib/hooks/common/use-is-mounted.d.ts +2 -0
  106. package/dist/types/widget/lib/hooks/common/use-once.d.ts +3 -0
  107. package/dist/types/widget/lib/hooks/common/use-value-state.d.ts +7 -0
  108. package/dist/types/widget/lib/hooks/common/use-y-scrollable.d.ts +10 -0
  109. package/dist/types/widget/lib/hooks/context/use-calculation.d.ts +12 -0
  110. package/dist/types/widget/lib/hooks/context/use-cart-items.d.ts +9 -0
  111. package/dist/types/widget/lib/hooks/context/use-currency.d.ts +12 -0
  112. package/dist/types/widget/lib/hooks/context/use-errors.d.ts +2 -0
  113. package/dist/types/widget/lib/hooks/context/use-navigation.d.ts +6 -0
  114. package/dist/types/widget/lib/hooks/context/use-return-context.d.ts +2 -0
  115. package/dist/types/widget/lib/hooks/context/use-root.d.ts +2 -0
  116. package/dist/types/widget/lib/hooks/context/use-theme.d.ts +5 -0
  117. package/dist/types/widget/lib/hooks/context/use-toast.d.ts +2 -0
  118. package/dist/types/widget/lib/hooks/context/use-widget-context.d.ts +2 -0
  119. package/dist/types/widget/lib/hooks/feature/use-api-bridge.d.ts +6 -0
  120. package/dist/types/widget/lib/hooks/feature/use-cart-activator.d.ts +2 -0
  121. package/dist/types/widget/lib/hooks/feature/use-external-events.d.ts +2 -0
  122. package/dist/types/widget/lib/hooks/feature/use-fetch-interceptor.d.ts +4 -0
  123. package/dist/types/widget/lib/hooks/feature/use-override-scroll-blocking.d.ts +2 -0
  124. package/dist/types/widget/lib/types.d.ts +382 -0
  125. package/dist/types/widget/lib/utils/api.d.ts +26 -0
  126. package/dist/types/widget/lib/utils/cart-provider-store.d.ts +3 -0
  127. package/dist/types/widget/lib/utils/cart.d.ts +17 -0
  128. package/dist/types/widget/lib/utils/custom-events.d.ts +48 -0
  129. package/dist/types/widget/lib/utils/dom.d.ts +2 -0
  130. package/dist/types/widget/lib/utils/errors.d.ts +6 -0
  131. package/dist/types/widget/lib/utils/helpers.d.ts +24 -0
  132. package/dist/types/widget/lib/utils/i18n.d.ts +8 -0
  133. package/dist/types/widget/lib/utils/initialization/dataset.d.ts +5 -0
  134. package/dist/types/widget/lib/utils/initialization/font.d.ts +7 -0
  135. package/dist/types/widget/lib/utils/initialization/index.d.ts +6 -0
  136. package/dist/types/widget/lib/utils/initialization/styles.d.ts +4 -0
  137. package/dist/types/widget/lib/utils/initialization/types.d.ts +51 -0
  138. package/dist/types/widget/lib/utils/interceptor/fetch.d.ts +5 -0
  139. package/dist/types/widget/lib/utils/interceptor/index.d.ts +26 -0
  140. package/dist/types/widget/lib/utils/interceptor/xhr.d.ts +5 -0
  141. package/dist/types/widget/lib/utils/logger.d.ts +13 -0
  142. package/dist/types/widget/lib/utils/navigation.d.ts +7 -0
  143. package/dist/types/widget/lib/utils/session.d.ts +54 -0
  144. package/dist/types/widget/lib/utils/shopify.d.ts +6 -0
  145. package/dist/types/widget/lib/utils/storage.d.ts +33 -0
  146. package/dist/variables.css +1 -0
  147. package/dist/widget.css +3 -0
  148. package/dist/widget.js +12006 -0
  149. package/package.json +112 -0
package/README.md ADDED
@@ -0,0 +1,976 @@
1
+ # @returnflows/shop-overlay
2
+
3
+ A modular exchange/return widget with a programmatic API for headless Shopify storefronts and custom e-commerce implementations.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Configuration](#configuration)
10
+ - [API Reference](#api-reference)
11
+ - [Initialization](#initialization)
12
+ - [Drawer API](#drawer-api)
13
+ - [Cart API](#cart-api)
14
+ - [Data API](#data-api)
15
+ - [Toast API](#toast-api)
16
+ - [Events](#events)
17
+ - [Cart Providers](#cart-providers)
18
+ - [Storefront API Support](#storefront-api-support)
19
+ - [Examples](#examples)
20
+ - [Vanilla JavaScript](#vanilla-javascript)
21
+ - [React](#react)
22
+ - [TypeScript Support](#typescript-support)
23
+ - [Troubleshooting](#troubleshooting)
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @returnflows/shop-overlay
29
+ # or
30
+ pnpm add @returnflows/shop-overlay
31
+ # or
32
+ yarn add @returnflows/shop-overlay
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### Vanilla JavaScript
38
+
39
+ ```javascript
40
+ import { Returnflows } from "@returnflows/shop-overlay";
41
+
42
+ // Initialize the widget
43
+ await Returnflows.init({
44
+ onReady: () => {
45
+ console.log("Widget ready!");
46
+ },
47
+ onError: error => {
48
+ console.error("Widget error:", error);
49
+ }
50
+ });
51
+
52
+ // Open the drawer
53
+ Returnflows.drawer.open();
54
+ ```
55
+
56
+ ### React
57
+
58
+ ```tsx
59
+ import { useEffect } from "react";
60
+ import { Returnflows } from "@returnflows/shop-overlay";
61
+
62
+ function App() {
63
+ useEffect(() => {
64
+ Returnflows.init({
65
+ onReady: () => console.log("Ready"),
66
+ onError: err => console.error(err)
67
+ });
68
+ }, []);
69
+
70
+ return (
71
+ <button onClick={() => Returnflows.drawer.toggle()}>Open Returns</button>
72
+ );
73
+ }
74
+ ```
75
+
76
+ ## Configuration
77
+
78
+ ### `ReturnflowsConfig`
79
+
80
+ Configuration options passed to `Returnflows.init()`:
81
+
82
+ | Property | Type | Default | Description |
83
+ | --------------------------- | ----------------------------------------- | -------------- | ----------------------------------------- |
84
+ | `env` | `'development' \| 'test' \| 'production'` | `'production'` | Environment for API endpoints |
85
+ | `debug` | `boolean` | `false` | Enable debug logging |
86
+ | `autoOpen` | `boolean` | `false` | Automatically open drawer on cart changes |
87
+ | `autoOpenTriggerMethod` | `'endpoint' \| 'change' \| 'combination'` | `'change'` | How to detect cart changes for auto-open |
88
+ | `autoOpenTriggerEndpoint` | `'add' \| 'update' \| 'change'` | `'change'` | Which cart endpoint triggers auto-open |
89
+ | `networkInterceptionMethod` | `'none' \| 'fetch' \| 'xhr' \| 'both'` | `'none'` | Network request interception method |
90
+ | `defaultLocale` | `string` | `'en'` | Default locale for translations |
91
+ | `defaultCurrency` | `string` | `'USD'` | Default currency code |
92
+ | `clearCartOnStart` | `boolean` | `false` | Clear cart state on initialization |
93
+ | `cartProvider` | `CartProvider` | `undefined` | Custom cart provider for headless setups |
94
+ | `onReady` | `() => void` | `undefined` | Callback when widget is ready |
95
+ | `onError` | `(error: Error) => void` | `undefined` | Callback on initialization error |
96
+
97
+ ### Supported Locales
98
+
99
+ The widget includes translations for:
100
+
101
+ - `da` - Danish
102
+ - `de` - German
103
+ - `en` - English (default)
104
+ - `es` - Spanish
105
+ - `fi` - Finnish
106
+ - `fr` - French
107
+ - `it` - Italian
108
+ - `nb` - Norwegian Bokmål
109
+ - `nl` - Dutch
110
+ - `pl` - Polish
111
+ - `sv` - Swedish
112
+
113
+ ## API Reference
114
+
115
+ ### Initialization
116
+
117
+ #### `Returnflows.init(config?)`
118
+
119
+ Initialize the widget. Returns a Promise that resolves when the widget is ready.
120
+
121
+ ```typescript
122
+ await Returnflows.init({
123
+ defaultLocale: "en",
124
+ defaultCurrency: "USD",
125
+ onReady: () => console.log("Widget initialized"),
126
+ onError: error => console.error("Init failed:", error)
127
+ });
128
+ ```
129
+
130
+ #### `Returnflows.destroy()`
131
+
132
+ Destroy the widget instance and clean up resources.
133
+
134
+ ```typescript
135
+ Returnflows.destroy();
136
+ ```
137
+
138
+ #### `Returnflows.isInitialized()`
139
+
140
+ Check if the widget is currently initialized.
141
+
142
+ ```typescript
143
+ if (Returnflows.isInitialized()) {
144
+ Returnflows.drawer.open();
145
+ }
146
+ ```
147
+
148
+ #### `Returnflows.version`
149
+
150
+ Get the current widget version.
151
+
152
+ ```typescript
153
+ console.log(Returnflows.version); // "1.0.0"
154
+ ```
155
+
156
+ ### Drawer API
157
+
158
+ Control the widget drawer visibility.
159
+
160
+ #### `Returnflows.drawer.open()`
161
+
162
+ Open the drawer.
163
+
164
+ ```typescript
165
+ Returnflows.drawer.open();
166
+ ```
167
+
168
+ #### `Returnflows.drawer.close()`
169
+
170
+ Close the drawer.
171
+
172
+ ```typescript
173
+ Returnflows.drawer.close();
174
+ ```
175
+
176
+ #### `Returnflows.drawer.toggle()`
177
+
178
+ Toggle the drawer open/closed state.
179
+
180
+ ```typescript
181
+ Returnflows.drawer.toggle();
182
+ ```
183
+
184
+ #### `Returnflows.drawer.isOpen()`
185
+
186
+ Check if the drawer is currently open.
187
+
188
+ ```typescript
189
+ if (Returnflows.drawer.isOpen()) {
190
+ console.log("Drawer is open");
191
+ }
192
+ ```
193
+
194
+ ### Cart API
195
+
196
+ Manage the cart state programmatically.
197
+
198
+ #### `Returnflows.cart.setCart(cart)`
199
+
200
+ Set the cart data. Accepts both Shopify AJAX API format and Storefront API format.
201
+
202
+ ```typescript
203
+ // Shopify AJAX API format
204
+ const result = await Returnflows.cart.setCart({
205
+ items: [
206
+ {
207
+ variant_id: 12345,
208
+ quantity: 1,
209
+ title: "Product Name",
210
+ price: 2999,
211
+ product_id: 67890,
212
+ product_title: "Product Name",
213
+ handle: "product-handle",
214
+ image: "https://...",
215
+ grams: 500,
216
+ line_price: 2999,
217
+ final_price: 2999,
218
+ final_line_price: 2999
219
+ }
220
+ ],
221
+ item_count: 1
222
+ });
223
+
224
+ console.log(result.hasChanged); // true if cart was updated
225
+ console.log(result.cart); // normalized cart data
226
+ console.log(result.calculation); // return calculation
227
+ ```
228
+
229
+ Returns: `Promise<{ cart: ShopifyCart | null; calculation: CalculationResponse | null; hasChanged: boolean }>`
230
+
231
+ #### `Returnflows.cart.refetch()`
232
+
233
+ Refetch cart data from the cart provider or default source.
234
+
235
+ ```typescript
236
+ await Returnflows.cart.refetch();
237
+ ```
238
+
239
+ #### `Returnflows.cart.clear()`
240
+
241
+ Clear the current cart.
242
+
243
+ ```typescript
244
+ await Returnflows.cart.clear();
245
+ ```
246
+
247
+ ### Data API
248
+
249
+ Access widget state data.
250
+
251
+ #### `Returnflows.data.getCart()`
252
+
253
+ Get the current cart data.
254
+
255
+ ```typescript
256
+ const cart = Returnflows.data.getCart();
257
+ // Returns ShopifyCart | null
258
+ ```
259
+
260
+ #### `Returnflows.data.getCalculation()`
261
+
262
+ Get the current return calculation.
263
+
264
+ ```typescript
265
+ const calculation = Returnflows.data.getCalculation();
266
+ // Returns CalculationResponse | null
267
+ ```
268
+
269
+ #### `Returnflows.data.getOrder()`
270
+
271
+ Get the current order data.
272
+
273
+ ```typescript
274
+ const order = Returnflows.data.getOrder();
275
+ // Returns OrderData | null
276
+ ```
277
+
278
+ #### `Returnflows.data.isLoading()`
279
+
280
+ Check if data is currently loading.
281
+
282
+ ```typescript
283
+ if (Returnflows.data.isLoading()) {
284
+ console.log("Loading...");
285
+ }
286
+ ```
287
+
288
+ ### Toast API
289
+
290
+ Display toast notifications within the widget.
291
+
292
+ #### `Returnflows.toast.show(options)`
293
+
294
+ Show a toast notification. Returns the toast ID.
295
+
296
+ ```typescript
297
+ const toastId = Returnflows.toast.show({
298
+ message: "Item added successfully",
299
+ tone: "info", // 'info' | 'tip' | 'action' | 'error' | 'giftcard'
300
+ duration: 5000, // auto-dismiss after 5 seconds
301
+ onClose: () => console.log("Toast closed")
302
+ });
303
+ ```
304
+
305
+ #### `Returnflows.toast.dismiss(id)`
306
+
307
+ Dismiss a specific toast by ID.
308
+
309
+ ```typescript
310
+ Returnflows.toast.dismiss(toastId);
311
+ ```
312
+
313
+ #### `Returnflows.toast.clear()`
314
+
315
+ Clear all toasts.
316
+
317
+ ```typescript
318
+ Returnflows.toast.clear();
319
+ ```
320
+
321
+ ### Events
322
+
323
+ Subscribe to widget events.
324
+
325
+ #### `Returnflows.on(event, callback)`
326
+
327
+ Subscribe to an event. Returns an unsubscribe function.
328
+
329
+ ```typescript
330
+ const unsubscribe = Returnflows.on("drawer:open", () => {
331
+ console.log("Drawer opened");
332
+ });
333
+
334
+ // Later: unsubscribe
335
+ unsubscribe();
336
+ ```
337
+
338
+ #### `Returnflows.off(event, callback)`
339
+
340
+ Unsubscribe from an event.
341
+
342
+ ```typescript
343
+ const handler = () => console.log("Cart updated");
344
+ Returnflows.on("cart:updated", handler);
345
+
346
+ // Later: unsubscribe
347
+ Returnflows.off("cart:updated", handler);
348
+ ```
349
+
350
+ #### Available Events
351
+
352
+ | Event | Callback Type | Description |
353
+ | --------------------- | -------------------------------------------- | ------------------------------- |
354
+ | `ready` | `() => void` | Widget is initialized and ready |
355
+ | `drawer:open` | `() => void` | Drawer was opened |
356
+ | `drawer:close` | `() => void` | Drawer was closed |
357
+ | `cart:updated` | `(cart: ShopifyCart) => void` | Cart data was updated |
358
+ | `calculation:updated` | `(calculation: CalculationResponse) => void` | Return calculation was updated |
359
+ | `error` | `(error: Error) => void` | An error occurred |
360
+
361
+ ## Cart Providers
362
+
363
+ For headless implementations, you can provide a custom cart provider to manage cart operations.
364
+
365
+ ### `CartProvider` Interface
366
+
367
+ ```typescript
368
+ interface CartProvider<T extends CartInput = CartInput> {
369
+ getCart(): Promise<T | undefined>;
370
+ clearCart(): Promise<T>;
371
+ addItems(items: CartAddItem[]): Promise<T>;
372
+ updateItems(items: CartUpdateItem[]): Promise<T>;
373
+ removeItems(lineIds: string[]): Promise<T>;
374
+ }
375
+
376
+ interface CartAddItem {
377
+ merchandiseId: string;
378
+ quantity: number;
379
+ }
380
+
381
+ interface CartUpdateItem {
382
+ id: string;
383
+ merchandiseId: string;
384
+ quantity: number;
385
+ }
386
+ ```
387
+
388
+ ### Example: Custom Cart Provider
389
+
390
+ ```typescript
391
+ import {
392
+ Returnflows,
393
+ CartProvider,
394
+ StorefrontCart
395
+ } from "@returnflows/shop-overlay";
396
+
397
+ const cartProvider: CartProvider<StorefrontCart> = {
398
+ async getCart() {
399
+ const response = await fetch("/api/cart");
400
+ return response.json();
401
+ },
402
+
403
+ async clearCart() {
404
+ const response = await fetch("/api/cart", { method: "DELETE" });
405
+ return response.json();
406
+ },
407
+
408
+ async addItems(items) {
409
+ const response = await fetch("/api/cart/add", {
410
+ method: "POST",
411
+ body: JSON.stringify({ items })
412
+ });
413
+ return response.json();
414
+ },
415
+
416
+ async updateItems(items) {
417
+ const response = await fetch("/api/cart/update", {
418
+ method: "POST",
419
+ body: JSON.stringify({ items })
420
+ });
421
+ return response.json();
422
+ },
423
+
424
+ async removeItems(lineIds) {
425
+ const response = await fetch("/api/cart/remove", {
426
+ method: "POST",
427
+ body: JSON.stringify({ lineIds })
428
+ });
429
+ return response.json();
430
+ }
431
+ };
432
+
433
+ await Returnflows.init({ cartProvider });
434
+ ```
435
+
436
+ ## Storefront API Support
437
+
438
+ The widget automatically normalizes Shopify Storefront API cart format to the internal format. You can pass either format to `setCart()` or return it from your `CartProvider`.
439
+
440
+ ### Storefront API Cart Format
441
+
442
+ ```typescript
443
+ interface StorefrontCart {
444
+ id?: string;
445
+ totalQuantity?: number;
446
+ lines: StorefrontCartLine[] | { edges: Array<{ node: StorefrontCartLine }> };
447
+ checkoutUrl?: string;
448
+ note?: string;
449
+ }
450
+
451
+ interface StorefrontCartLine {
452
+ id?: string;
453
+ quantity: number;
454
+ cost?: {
455
+ totalAmount: { amount: string; currencyCode: string };
456
+ amountPerQuantity?: { amount: string; currencyCode: string };
457
+ };
458
+ merchandise: {
459
+ id: string; // GID format: "gid://shopify/ProductVariant/12345"
460
+ title?: string;
461
+ price?: { amount: string; currencyCode: string };
462
+ image?: { url: string; altText?: string } | null;
463
+ product: {
464
+ id: string;
465
+ handle: string;
466
+ title: string;
467
+ vendor?: string;
468
+ };
469
+ };
470
+ }
471
+ ```
472
+
473
+ ### Using Storefront API Cart
474
+
475
+ ```typescript
476
+ // Storefront API cart (GraphQL format)
477
+ const storefrontCart = {
478
+ id: "gid://shopify/Cart/abc123",
479
+ totalQuantity: 2,
480
+ lines: {
481
+ edges: [
482
+ {
483
+ node: {
484
+ id: "gid://shopify/CartLine/xyz",
485
+ quantity: 2,
486
+ cost: {
487
+ totalAmount: { amount: "59.98", currencyCode: "USD" },
488
+ amountPerQuantity: { amount: "29.99", currencyCode: "USD" }
489
+ },
490
+ merchandise: {
491
+ id: "gid://shopify/ProductVariant/12345",
492
+ title: "Medium / Blue",
493
+ product: {
494
+ id: "gid://shopify/Product/67890",
495
+ handle: "t-shirt",
496
+ title: "Classic T-Shirt"
497
+ }
498
+ }
499
+ }
500
+ }
501
+ ]
502
+ }
503
+ };
504
+
505
+ // Pass directly - widget handles normalization
506
+ await Returnflows.cart.setCart(storefrontCart);
507
+ ```
508
+
509
+ ## Examples
510
+
511
+ ### Vanilla JavaScript
512
+
513
+ #### Basic Integration
514
+
515
+ ```html
516
+ <!DOCTYPE html>
517
+ <html>
518
+ <head>
519
+ <title>My Store</title>
520
+ </head>
521
+ <body>
522
+ <button id="returns-btn">Start Return</button>
523
+
524
+ <script type="module">
525
+ import { Returnflows } from "@returnflows/shop-overlay";
526
+
527
+ // Initialize when DOM is ready
528
+ document.addEventListener("DOMContentLoaded", async () => {
529
+ try {
530
+ await Returnflows.init({
531
+ defaultLocale: "en",
532
+ defaultCurrency: "USD",
533
+ onReady: () => {
534
+ document.getElementById("returns-btn").disabled = false;
535
+ }
536
+ });
537
+
538
+ // Listen for events
539
+ Returnflows.on("drawer:open", () => {
540
+ document.body.style.overflow = "hidden";
541
+ });
542
+
543
+ Returnflows.on("drawer:close", () => {
544
+ document.body.style.overflow = "";
545
+ });
546
+
547
+ Returnflows.on("calculation:updated", calc => {
548
+ console.log("Return value:", calc.total);
549
+ });
550
+ } catch (error) {
551
+ console.error("Failed to initialize:", error);
552
+ }
553
+ });
554
+
555
+ // Button handler
556
+ document.getElementById("returns-btn").addEventListener("click", () => {
557
+ Returnflows.drawer.toggle();
558
+ });
559
+ </script>
560
+ </body>
561
+ </html>
562
+ ```
563
+
564
+ #### With Cart Provider (Headless)
565
+
566
+ ```javascript
567
+ import { Returnflows } from "@returnflows/shop-overlay";
568
+
569
+ // Shopify Storefront API client
570
+ const storefrontClient = {
571
+ async query(query, variables) {
572
+ const response = await fetch("/api/storefront", {
573
+ method: "POST",
574
+ headers: { "Content-Type": "application/json" },
575
+ body: JSON.stringify({ query, variables })
576
+ });
577
+ return response.json();
578
+ }
579
+ };
580
+
581
+ // Cart provider using Storefront API
582
+ const cartProvider = {
583
+ async getCart() {
584
+ const { data } = await storefrontClient.query(
585
+ `
586
+ query GetCart($cartId: ID!) {
587
+ cart(id: $cartId) {
588
+ id
589
+ totalQuantity
590
+ lines(first: 100) {
591
+ edges {
592
+ node {
593
+ id
594
+ quantity
595
+ cost {
596
+ totalAmount { amount currencyCode }
597
+ amountPerQuantity { amount currencyCode }
598
+ }
599
+ merchandise {
600
+ ... on ProductVariant {
601
+ id
602
+ title
603
+ image { url altText }
604
+ product {
605
+ id
606
+ handle
607
+ title
608
+ vendor
609
+ }
610
+ }
611
+ }
612
+ }
613
+ }
614
+ }
615
+ }
616
+ }
617
+ `,
618
+ { cartId: localStorage.getItem("cartId") }
619
+ );
620
+
621
+ return data?.cart;
622
+ },
623
+
624
+ async addItems(items) {
625
+ const lines = items.map(item => ({
626
+ merchandiseId: item.merchandiseId,
627
+ quantity: item.quantity
628
+ }));
629
+
630
+ const { data } = await storefrontClient.query(
631
+ `
632
+ mutation AddToCart($cartId: ID!, $lines: [CartLineInput!]!) {
633
+ cartLinesAdd(cartId: $cartId, lines: $lines) {
634
+ cart { id totalQuantity lines(first: 100) { edges { node { id quantity } } } }
635
+ }
636
+ }
637
+ `,
638
+ { cartId: localStorage.getItem("cartId"), lines }
639
+ );
640
+
641
+ return data.cartLinesAdd.cart;
642
+ },
643
+
644
+ async updateItems(items) {
645
+ const lines = items.map(item => ({
646
+ id: item.id,
647
+ merchandiseId: item.merchandiseId,
648
+ quantity: item.quantity
649
+ }));
650
+
651
+ const { data } = await storefrontClient.query(
652
+ `
653
+ mutation UpdateCart($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
654
+ cartLinesUpdate(cartId: $cartId, lines: $lines) {
655
+ cart { id totalQuantity lines(first: 100) { edges { node { id quantity } } } }
656
+ }
657
+ }
658
+ `,
659
+ { cartId: localStorage.getItem("cartId"), lines }
660
+ );
661
+
662
+ return data.cartLinesUpdate.cart;
663
+ },
664
+
665
+ async removeItems(lineIds) {
666
+ const { data } = await storefrontClient.query(
667
+ `
668
+ mutation RemoveFromCart($cartId: ID!, $lineIds: [ID!]!) {
669
+ cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
670
+ cart { id totalQuantity lines(first: 100) { edges { node { id quantity } } } }
671
+ }
672
+ }
673
+ `,
674
+ { cartId: localStorage.getItem("cartId"), lineIds }
675
+ );
676
+
677
+ return data.cartLinesRemove.cart;
678
+ },
679
+
680
+ async clearCart() {
681
+ // Create a new cart
682
+ const { data } = await storefrontClient.query(`
683
+ mutation CreateCart {
684
+ cartCreate { cart { id } }
685
+ }
686
+ `);
687
+ localStorage.setItem("cartId", data.cartCreate.cart.id);
688
+ return data.cartCreate.cart;
689
+ }
690
+ };
691
+
692
+ // Initialize with cart provider
693
+ await Returnflows.init({
694
+ cartProvider,
695
+ onReady: () => console.log("Widget ready with headless cart")
696
+ });
697
+ ```
698
+
699
+ ### React
700
+
701
+ #### Basic Hook
702
+
703
+ ```tsx
704
+ import { useEffect, useCallback, useState } from "react";
705
+ import {
706
+ Returnflows,
707
+ type ShopifyCart,
708
+ type CalculationResponse
709
+ } from "@returnflows/shop-overlay";
710
+
711
+ function useReturnflows() {
712
+ const [isReady, setIsReady] = useState(false);
713
+ const [isOpen, setIsOpen] = useState(false);
714
+ const [cart, setCart] = useState<ShopifyCart | null>(null);
715
+ const [calculation, setCalculation] = useState<CalculationResponse | null>(
716
+ null
717
+ );
718
+
719
+ useEffect(() => {
720
+ let mounted = true;
721
+
722
+ const init = async () => {
723
+ try {
724
+ await Returnflows.init({
725
+ onReady: () => {
726
+ if (mounted) setIsReady(true);
727
+ }
728
+ });
729
+ } catch (error) {
730
+ console.error("Returnflows init failed:", error);
731
+ }
732
+ };
733
+
734
+ init();
735
+
736
+ // Subscribe to events
737
+ const unsubOpen = Returnflows.on("drawer:open", () => setIsOpen(true));
738
+ const unsubClose = Returnflows.on("drawer:close", () => setIsOpen(false));
739
+ const unsubCart = Returnflows.on("cart:updated", setCart);
740
+ const unsubCalc = Returnflows.on("calculation:updated", setCalculation);
741
+
742
+ return () => {
743
+ mounted = false;
744
+ unsubOpen();
745
+ unsubClose();
746
+ unsubCart();
747
+ unsubCalc();
748
+ };
749
+ }, []);
750
+
751
+ const openDrawer = useCallback(() => Returnflows.drawer.open(), []);
752
+ const closeDrawer = useCallback(() => Returnflows.drawer.close(), []);
753
+ const toggleDrawer = useCallback(() => Returnflows.drawer.toggle(), []);
754
+
755
+ return {
756
+ isReady,
757
+ isOpen,
758
+ cart,
759
+ calculation,
760
+ openDrawer,
761
+ closeDrawer,
762
+ toggleDrawer
763
+ };
764
+ }
765
+
766
+ export default useReturnflows;
767
+ ```
768
+
769
+ #### React Component Example
770
+
771
+ ```tsx
772
+ import { useEffect, useState } from "react";
773
+ import { Returnflows } from "@returnflows/shop-overlay";
774
+ import type {
775
+ ShopifyCart,
776
+ CalculationResponse
777
+ } from "@returnflows/shop-overlay";
778
+
779
+ function ReturnflowsProvider({ children }: { children: React.ReactNode }) {
780
+ const [initialized, setInitialized] = useState(false);
781
+
782
+ useEffect(() => {
783
+ Returnflows.init({
784
+ defaultLocale: "en",
785
+ defaultCurrency: "USD",
786
+ onReady: () => setInitialized(true),
787
+ onError: error => console.error("Widget error:", error)
788
+ });
789
+ }, []);
790
+
791
+ if (!initialized) {
792
+ return <div>Loading returns widget...</div>;
793
+ }
794
+
795
+ return <>{children}</>;
796
+ }
797
+
798
+ function ReturnsButton() {
799
+ const [isOpen, setIsOpen] = useState(false);
800
+
801
+ useEffect(() => {
802
+ const unsubOpen = Returnflows.on("drawer:open", () => setIsOpen(true));
803
+ const unsubClose = Returnflows.on("drawer:close", () => setIsOpen(false));
804
+
805
+ return () => {
806
+ unsubOpen();
807
+ unsubClose();
808
+ };
809
+ }, []);
810
+
811
+ return (
812
+ <button onClick={() => Returnflows.drawer.toggle()} aria-expanded={isOpen}>
813
+ {isOpen ? "Close Returns" : "Start Return"}
814
+ </button>
815
+ );
816
+ }
817
+
818
+ function ReturnsSummary() {
819
+ const [calculation, setCalculation] = useState<CalculationResponse | null>(
820
+ null
821
+ );
822
+
823
+ useEffect(() => {
824
+ // Get initial calculation
825
+ setCalculation(Returnflows.data.getCalculation());
826
+
827
+ // Subscribe to updates
828
+ const unsub = Returnflows.on("calculation:updated", setCalculation);
829
+ return unsub;
830
+ }, []);
831
+
832
+ if (!calculation) return null;
833
+
834
+ return (
835
+ <div>
836
+ <p>
837
+ Return Value: {calculation.currency.localCurrency}{" "}
838
+ {(calculation.total / 100).toFixed(2)}
839
+ </p>
840
+ <p>Items: {calculation.lineItems.length}</p>
841
+ </div>
842
+ );
843
+ }
844
+
845
+ // Usage
846
+ function App() {
847
+ return (
848
+ <ReturnflowsProvider>
849
+ <header>
850
+ <ReturnsButton />
851
+ </header>
852
+ <main>
853
+ <ReturnsSummary />
854
+ </main>
855
+ </ReturnflowsProvider>
856
+ );
857
+ }
858
+ ```
859
+
860
+ ## TypeScript Support
861
+
862
+ The package includes full TypeScript definitions. All types are exported from the main entry point:
863
+
864
+ ```typescript
865
+ import {
866
+ // API Types
867
+ Returnflows,
868
+ ReturnflowsAPI,
869
+ ReturnflowsConfig,
870
+
871
+ // Cart Types
872
+ CartInput,
873
+ CartProvider,
874
+ CartAddItem,
875
+ CartUpdateItem,
876
+ ShopifyCart,
877
+ ShopifyCartItem,
878
+ StorefrontCart,
879
+ StorefrontCartLine,
880
+
881
+ // Data Types
882
+ CalculationResponse,
883
+ OrderData,
884
+
885
+ // API Interfaces
886
+ DrawerAPI,
887
+ CartAPI,
888
+ DataAPI,
889
+ ToastAPI,
890
+ ToastOptions,
891
+ ToastTone,
892
+
893
+ // Events
894
+ EventName,
895
+ EventCallback,
896
+
897
+ // Other
898
+ Environment,
899
+ ThemeVariables,
900
+ WidgetViewState
901
+ } from "@returnflows/shop-overlay";
902
+ ```
903
+
904
+ ## Troubleshooting
905
+
906
+ ### Widget not initializing
907
+
908
+ Ensure you're calling `init()` after the DOM is ready:
909
+
910
+ ```javascript
911
+ // Vanilla JS
912
+ document.addEventListener("DOMContentLoaded", () => {
913
+ Returnflows.init();
914
+ });
915
+
916
+ // React - use useEffect
917
+ useEffect(() => {
918
+ Returnflows.init();
919
+ }, []);
920
+ ```
921
+
922
+ ### "Widget not initialized" errors
923
+
924
+ Always check `isInitialized()` before calling API methods:
925
+
926
+ ```javascript
927
+ if (Returnflows.isInitialized()) {
928
+ Returnflows.drawer.open();
929
+ } else {
930
+ console.warn("Widget not ready yet");
931
+ }
932
+ ```
933
+
934
+ ### Cart not updating
935
+
936
+ If using a custom cart provider, ensure all methods return the updated cart:
937
+
938
+ ```javascript
939
+ const cartProvider = {
940
+ async addItems(items) {
941
+ await fetch('/api/cart/add', { ... });
942
+ // Must return the updated cart!
943
+ return this.getCart();
944
+ }
945
+ };
946
+ ```
947
+
948
+ ### Storefront API cart not recognized
949
+
950
+ Ensure your cart has the `lines` property with the correct structure:
951
+
952
+ ```javascript
953
+ // Correct - has lines array or edges
954
+ { lines: [...] }
955
+ { lines: { edges: [...] } }
956
+
957
+ // Incorrect - using items instead of lines
958
+ { items: [...] } // This is AJAX API format
959
+ ```
960
+
961
+ ### Multiple instances
962
+
963
+ Only one widget instance can exist. Call `destroy()` before re-initializing:
964
+
965
+ ```javascript
966
+ Returnflows.destroy();
967
+ await Returnflows.init({
968
+ /* new config */
969
+ });
970
+ ```
971
+
972
+ ---
973
+
974
+ ## License
975
+
976
+ Proprietary - Returnflows ApS