@personizely/shopify-hydrogen 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 (66) hide show
  1. package/README.md +533 -0
  2. package/dist/classes/Adapter.d.ts +33 -0
  3. package/dist/classes/Adapter.d.ts.map +1 -0
  4. package/dist/classes/Adapter.js +126 -0
  5. package/dist/classes/Adapter.js.map +1 -0
  6. package/dist/classes/Cart.d.ts +29 -0
  7. package/dist/classes/Cart.d.ts.map +1 -0
  8. package/dist/classes/Cart.js +272 -0
  9. package/dist/classes/Cart.js.map +1 -0
  10. package/dist/components/PersonizelyProvider.d.ts +36 -0
  11. package/dist/components/PersonizelyProvider.d.ts.map +1 -0
  12. package/dist/components/PersonizelyProvider.js +118 -0
  13. package/dist/components/PersonizelyProvider.js.map +1 -0
  14. package/dist/graphql/queries/cart.d.ts +7 -0
  15. package/dist/graphql/queries/cart.d.ts.map +1 -0
  16. package/dist/graphql/queries/cart.js +142 -0
  17. package/dist/graphql/queries/cart.js.map +1 -0
  18. package/dist/graphql/queries/customer.d.ts +5 -0
  19. package/dist/graphql/queries/customer.d.ts.map +1 -0
  20. package/dist/graphql/queries/customer.js +72 -0
  21. package/dist/graphql/queries/customer.js.map +1 -0
  22. package/dist/graphql/queries/product.d.ts +5 -0
  23. package/dist/graphql/queries/product.d.ts.map +1 -0
  24. package/dist/graphql/queries/product.js +114 -0
  25. package/dist/graphql/queries/product.js.map +1 -0
  26. package/dist/hooks/useCartAdd.d.ts +3 -0
  27. package/dist/hooks/useCartAdd.d.ts.map +1 -0
  28. package/dist/hooks/useCartAdd.js +18 -0
  29. package/dist/hooks/useCartAdd.js.map +1 -0
  30. package/dist/hooks/useCheckout.d.ts +2 -0
  31. package/dist/hooks/useCheckout.d.ts.map +1 -0
  32. package/dist/hooks/useCheckout.js +12 -0
  33. package/dist/hooks/useCheckout.js.map +1 -0
  34. package/dist/hooks/usePageContext.d.ts +3 -0
  35. package/dist/hooks/usePageContext.d.ts.map +1 -0
  36. package/dist/hooks/usePageContext.js +6 -0
  37. package/dist/hooks/usePageContext.js.map +1 -0
  38. package/dist/hooks/usePersonizely.d.ts +3 -0
  39. package/dist/hooks/usePersonizely.d.ts.map +1 -0
  40. package/dist/hooks/usePersonizely.js +11 -0
  41. package/dist/hooks/usePersonizely.js.map +1 -0
  42. package/dist/index.d.ts +10 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +8 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/types/index.d.ts +170 -0
  47. package/dist/types/index.d.ts.map +1 -0
  48. package/dist/types/index.js +2 -0
  49. package/dist/types/index.js.map +1 -0
  50. package/dist/types/interfaces.d.ts +35 -0
  51. package/dist/types/interfaces.d.ts.map +1 -0
  52. package/dist/types/interfaces.js +2 -0
  53. package/dist/types/interfaces.js.map +1 -0
  54. package/dist/utils/cookies.d.ts +4 -0
  55. package/dist/utils/cookies.d.ts.map +1 -0
  56. package/dist/utils/cookies.js +33 -0
  57. package/dist/utils/cookies.js.map +1 -0
  58. package/dist/utils/id.d.ts +3 -0
  59. package/dist/utils/id.d.ts.map +1 -0
  60. package/dist/utils/id.js +7 -0
  61. package/dist/utils/id.js.map +1 -0
  62. package/dist/utils/storefront-client.d.ts +15 -0
  63. package/dist/utils/storefront-client.d.ts.map +1 -0
  64. package/dist/utils/storefront-client.js +39 -0
  65. package/dist/utils/storefront-client.js.map +1 -0
  66. package/package.json +56 -0
package/README.md ADDED
@@ -0,0 +1,533 @@
1
+ # @personizely/shopify-hydrogen
2
+
3
+ Personizely adapter for Shopify Hydrogen headless storefronts. Simple React Provider integration for personalization, popups, and A/B testing.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Plug & Play** - Minimal configuration required
8
+ - ✅ **Server-Side Script Loading** - Prevents A/B test flicker
9
+ - ✅ **Fully Reactive** - Integrates seamlessly with Hydrogen's cart
10
+ - ✅ **TypeScript** - Fully typed for great DX
11
+ - ✅ **Auto Page Detection** - Automatically detects product/collection pages
12
+ - ✅ **Cart & Checkout Triggers** - Easy widget triggering
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @personizely/shopify-hydrogen
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Set Up Your Root Loader
23
+
24
+ In your `app/root.tsx`, add Personizely configuration to the loader:
25
+
26
+ ```tsx
27
+ export async function loader(args: Route.LoaderArgs) {
28
+ const { env } = args.context
29
+
30
+ return {
31
+ // ... other data
32
+ personizely: {
33
+ storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
34
+ shopDomain: env.PUBLIC_STORE_DOMAIN,
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### 2. Wrap Your App with PersonizelyProvider
41
+
42
+ In your `app/root.tsx` default export:
43
+
44
+ ```tsx
45
+ import { PersonizelyProvider } from '@personizely/shopify-hydrogen'
46
+
47
+ export default function App() {
48
+ const data = useRouteLoaderData<RootLoader>('root')
49
+
50
+ if (!data) {
51
+ return <Outlet />
52
+ }
53
+
54
+ return (
55
+ <PersonizelyProvider
56
+ websiteApiKey="your-api-key"
57
+ storefrontAccessToken={data.personizely.storefrontAccessToken}
58
+ shopDomain={data.personizely.shopDomain}
59
+ locale="en-US"
60
+ >
61
+ <Outlet />
62
+ </PersonizelyProvider>
63
+ )
64
+ }
65
+ ```
66
+
67
+ **That's it!** The Provider:
68
+ - ✅ Automatically integrates with Hydrogen's cart from the root loader
69
+ - ✅ Sets up the config before loading the script
70
+ - ✅ Loads the Personizely snippet dynamically
71
+ - ✅ Provides hooks for triggering widgets
72
+
73
+ ### 3. Trigger Cart Add Events
74
+
75
+ In your product form component:
76
+
77
+ ```tsx
78
+ import { useCartAdd } from '@personizely/shopify-hydrogen'
79
+
80
+ export function ProductForm({ product, selectedVariant }) {
81
+ const triggerCartAdd = useCartAdd()
82
+
83
+ const handleAddToCart = async () => {
84
+ // Trigger Personizely widget (e.g., upsell popup)
85
+ const widgetShown = await triggerCartAdd({
86
+ id: product.id,
87
+ variantId: selectedVariant.id,
88
+ price: parseFloat(selectedVariant.price.amount),
89
+ quantity: 1,
90
+ handle: product.handle,
91
+ })
92
+
93
+ if (!widgetShown) {
94
+ // Add to cart normally if no widget was shown
95
+ // Your cart add logic here
96
+ }
97
+ }
98
+
99
+ return (
100
+ <button onClick={handleAddToCart}>
101
+ Add to Cart
102
+ </button>
103
+ )
104
+ }
105
+ ```
106
+
107
+ ### 4. Trigger Checkout Events
108
+
109
+ In your cart or checkout button component:
110
+
111
+ ```tsx
112
+ import { useCheckout } from '@personizely/shopify-hydrogen'
113
+
114
+ export function CheckoutButton({ checkoutUrl }) {
115
+ const triggerCheckout = useCheckout()
116
+
117
+ const handleCheckout = async () => {
118
+ // Trigger Personizely widget (e.g., exit intent offer)
119
+ const widgetShown = await triggerCheckout()
120
+
121
+ if (!widgetShown) {
122
+ // Proceed to checkout if no widget was shown
123
+ window.location.href = checkoutUrl
124
+ }
125
+ }
126
+
127
+ return (
128
+ <button onClick={handleCheckout}>
129
+ Proceed to Checkout
130
+ </button>
131
+ )
132
+ }
133
+ ```
134
+
135
+ ### 5. Set Page Context
136
+
137
+ Use `usePageContext` to inform Personizely about the current page for better targeting:
138
+
139
+ ```tsx
140
+ import { usePageContext } from '@personizely/shopify-hydrogen'
141
+
142
+ export default function ProductRoute() {
143
+ const { product } = useLoaderData<typeof loader>()
144
+
145
+ // Set page context on product pages
146
+ usePageContext({
147
+ pageType: 'product',
148
+ product: {
149
+ id: extractId(product.id), // Numeric ID
150
+ handle: product.handle,
151
+ tags: product.tags
152
+ }
153
+ })
154
+
155
+ return <ProductPage product={product} />
156
+ }
157
+ ```
158
+
159
+ **When to use `usePageContext`:**
160
+ - **Product pages** - Set `pageType: 'product'` with product data
161
+ - **Collection pages** - Set `pageType: 'collection'` with collection data
162
+ - **Home page** - Set `pageType: 'home'`
163
+ - **Cart page** - Set `pageType: 'cart'`
164
+
165
+ **Example for collection page:**
166
+ ```tsx
167
+ usePageContext({
168
+ pageType: 'collection',
169
+ collection: {
170
+ id: extractId(collection.id)
171
+ }
172
+ })
173
+ ```
174
+
175
+ ## API Reference
176
+
177
+ ### Components
178
+
179
+ #### `<PersonizelyProvider>`
180
+
181
+ React context provider for Personizely integration.
182
+
183
+ **Props:**
184
+
185
+ | Prop | Type | Required | Description |
186
+ |------|------|----------|-------------|
187
+ | `websiteApiKey` | `string` | ✅ | Your Personizely website API key |
188
+ | `storefrontAccessToken` | `string` | ✅ | Shopify Storefront API access token |
189
+ | `shopDomain` | `string` | ✅ | Your shop domain (e.g., `'myshop.myshopify.com'`) |
190
+ | `locale` | `string` | ❌ | Shop locale (default: `'en-US'`) |
191
+ | `currency` | `object` | ❌ | Currency configuration (see below) |
192
+ | `market` | `string` | ❌ | Market handle for international stores |
193
+ | `apiVersion` | `string` | ❌ | Storefront API version (default: `'2024-01'`) |
194
+ | `methods` | `object` | ❌ | Custom method overrides (see below) |
195
+ | `customerAccessToken` | `string \| null` | ❌ | Customer access token for authenticated requests |
196
+
197
+ **Currency Configuration:**
198
+ ```tsx
199
+ currency={{
200
+ rate: 1.2, // Conversion rate from base currency
201
+ active: 'EUR', // Current currency code
202
+ base: 'USD' // Base currency code
203
+ }}
204
+ ```
205
+
206
+ **Methods Configuration:**
207
+ ```tsx
208
+ methods={{
209
+ // Custom money formatting
210
+ formatMoney: (amount, includeDecimals) => {
211
+ // amount is in cents
212
+ // Return formatted string (e.g., "$19.99" or "$20")
213
+ return includeDecimals
214
+ ? `$${(amount / 100).toFixed(2)}`
215
+ : `$${Math.round(amount / 100)}`
216
+ },
217
+
218
+ // Custom product URL builder
219
+ builtProductPath: ({ handle }) => {
220
+ // Return product URL path
221
+ return `/products/${handle}`
222
+ }
223
+ }}
224
+ ```
225
+
226
+ **Complete Example:**
227
+ ```tsx
228
+ <PersonizelyProvider
229
+ websiteApiKey="your-api-key"
230
+ storefrontAccessToken={data.personizely.storefrontAccessToken}
231
+ shopDomain={data.personizely.shopDomain}
232
+ locale="en-US"
233
+ currency={{
234
+ rate: 0.85,
235
+ active: 'EUR',
236
+ base: 'USD'
237
+ }}
238
+ market="europe"
239
+ methods={{
240
+ formatMoney: (amount, includeDecimals) => {
241
+ const value = amount / 100
242
+ return includeDecimals
243
+ ? `€${value.toFixed(2).replace('.', ',')}`
244
+ : `€${Math.round(value)}`
245
+ },
246
+ builtProductPath: ({ handle }) => `/shop/${handle}`
247
+ }}
248
+ customerAccessToken={customerAccessToken}
249
+ >
250
+ {children}
251
+ </PersonizelyProvider>
252
+ ```
253
+
254
+ ### Hooks
255
+
256
+ #### `useCartAdd()`
257
+
258
+ Returns a function to trigger cart add widgets (e.g., upsells, cross-sells).
259
+
260
+ **Returns:** `(product: CartAddProduct) => Promise<boolean>`
261
+ - Returns `true` if a widget was shown
262
+ - Returns `false` if no widget was shown
263
+
264
+ **CartAddProduct Type:**
265
+ ```tsx
266
+ {
267
+ id: string | number // Product ID
268
+ variantId: string | number // Variant ID
269
+ price?: string | number // Price in dollars (will be converted to cents)
270
+ quantity?: number // Quantity (default: 1)
271
+ handle?: string // Product handle
272
+ properties?: Record<string, string> // Custom properties
273
+ }
274
+ ```
275
+
276
+ **Example:**
277
+ ```tsx
278
+ const triggerCartAdd = useCartAdd()
279
+
280
+ const widgetShown = await triggerCartAdd({
281
+ id: 'gid://shopify/Product/123',
282
+ variantId: 'gid://shopify/ProductVariant/456',
283
+ price: 19.99, // In dollars
284
+ quantity: 1,
285
+ handle: 'awesome-product',
286
+ properties: {
287
+ gift_wrap: 'yes'
288
+ }
289
+ })
290
+ ```
291
+
292
+ #### `useCheckout()`
293
+
294
+ Returns a function to trigger checkout widgets (e.g., exit intent offers, discount popups).
295
+
296
+ **Returns:** `() => Promise<boolean>`
297
+ - Returns `true` if a widget was shown
298
+ - Returns `false` if no widget was shown
299
+
300
+ **Example:**
301
+ ```tsx
302
+ const triggerCheckout = useCheckout()
303
+
304
+ const widgetShown = await triggerCheckout()
305
+ if (!widgetShown) {
306
+ // Proceed with checkout
307
+ window.location.href = checkoutUrl
308
+ }
309
+ ```
310
+
311
+ #### `usePageContext(context)`
312
+
313
+ Sets the current page context for better widget targeting. Call this hook in your route components.
314
+
315
+ **Parameters:**
316
+ - `context: Partial<PageContext>` - Page context object
317
+
318
+ **PageContext Type:**
319
+ ```tsx
320
+ {
321
+ pageType?: string | number // Page type: 'product' | 'collection' | 'home' | 'cart' | custom
322
+ product?: { // Product data (for product pages)
323
+ id: number // Numeric product ID
324
+ handle: string // Product handle
325
+ tags?: string[] // Product tags
326
+ } | null
327
+ collection?: { // Collection data (for collection pages)
328
+ id: number // Numeric collection ID
329
+ } | null
330
+ }
331
+ ```
332
+
333
+ **Examples:**
334
+
335
+ Product page:
336
+ ```tsx
337
+ import { usePageContext } from '@personizely/shopify-hydrogen'
338
+
339
+ export default function ProductRoute() {
340
+ const { product } = useLoaderData<typeof loader>()
341
+
342
+ usePageContext({
343
+ pageType: 'product',
344
+ product: {
345
+ id: extractNumericId(product.id), // Extract numeric ID from GID
346
+ handle: product.handle,
347
+ tags: product.tags
348
+ }
349
+ })
350
+
351
+ return <ProductPage product={product} />
352
+ }
353
+ ```
354
+
355
+ Collection page:
356
+ ```tsx
357
+ usePageContext({
358
+ pageType: 'collection',
359
+ collection: {
360
+ id: extractNumericId(collection.id)
361
+ }
362
+ })
363
+ ```
364
+
365
+ Home page:
366
+ ```tsx
367
+ usePageContext({
368
+ pageType: 'home'
369
+ })
370
+ ```
371
+
372
+ Cart page:
373
+ ```tsx
374
+ usePageContext({
375
+ pageType: 'cart'
376
+ })
377
+ ```
378
+
379
+ #### `usePersonizely()`
380
+
381
+ Returns the Personizely context with cart and adapter instances.
382
+
383
+ **Returns:** `{ cart: Cart, adapter: Adapter, websiteApiKey: string }`
384
+
385
+ **Example:**
386
+ ```tsx
387
+ const { cart, adapter, websiteApiKey } = usePersonizely()
388
+
389
+ // Access cart methods
390
+ const cartTotal = cart.getValue()
391
+ const itemCount = cart.getSize()
392
+
393
+ // Access adapter methods
394
+ const formattedPrice = adapter.formatMoney(1999, true) // "$19.99"
395
+ const productUrl = adapter.buildProductPath({ handle: 'my-product' })
396
+ ```
397
+
398
+ ## Advanced Usage
399
+
400
+ ### Multi-Currency Support
401
+
402
+ ```tsx
403
+ // Define currency outside component for stable reference
404
+ const CURRENCY = {
405
+ rate: 0.85, // EUR/USD rate
406
+ active: 'EUR', // Display currency
407
+ base: 'USD' // Base currency
408
+ }
409
+
410
+ export default function App() {
411
+ return (
412
+ <PersonizelyProvider
413
+ websiteApiKey="your-api-key"
414
+ storefrontAccessToken={token}
415
+ shopDomain={domain}
416
+ currency={CURRENCY}
417
+ methods={{
418
+ formatMoney: (amount, includeDecimals) => {
419
+ const value = amount / 100
420
+ return includeDecimals
421
+ ? `€${value.toFixed(2).replace('.', ',')}`
422
+ : `€${Math.round(value)}`
423
+ }
424
+ }}
425
+ >
426
+ {children}
427
+ </PersonizelyProvider>
428
+ )
429
+ }
430
+ ```
431
+
432
+ ### International Markets
433
+
434
+ ```tsx
435
+ // Define market outside component for stable reference
436
+ const MARKET = 'europe'
437
+
438
+ export default function App() {
439
+ return (
440
+ <PersonizelyProvider
441
+ websiteApiKey="your-api-key"
442
+ storefrontAccessToken={token}
443
+ shopDomain={domain}
444
+ market={MARKET}
445
+ locale="de-DE"
446
+ >
447
+ {children}
448
+ </PersonizelyProvider>
449
+ )
450
+ }
451
+ ```
452
+
453
+ ### Authenticated Customers
454
+
455
+ ```tsx
456
+ export default function App() {
457
+ const data = useRouteLoaderData<RootLoader>('root')
458
+ const customerAccessToken = data.customerAccessToken // From your auth logic
459
+
460
+ return (
461
+ <PersonizelyProvider
462
+ websiteApiKey="your-api-key"
463
+ storefrontAccessToken={data.personizely.storefrontAccessToken}
464
+ shopDomain={data.personizely.shopDomain}
465
+ customerAccessToken={customerAccessToken}
466
+ >
467
+ {children}
468
+ </PersonizelyProvider>
469
+ )
470
+ }
471
+ ```
472
+
473
+ ### Custom Product URLs
474
+
475
+ ```tsx
476
+ <PersonizelyProvider
477
+ websiteApiKey="your-api-key"
478
+ storefrontAccessToken={token}
479
+ shopDomain={domain}
480
+ methods={{
481
+ builtProductPath: ({ handle }) => {
482
+ // Custom URL structure
483
+ return `/shop/products/${handle}`
484
+ }
485
+ }}
486
+ >
487
+ {children}
488
+ </PersonizelyProvider>
489
+ ```
490
+
491
+ ## TypeScript
492
+
493
+ This package is fully typed. Import types as needed:
494
+
495
+ ```tsx
496
+ import type {
497
+ CartAddProduct,
498
+ PageContext,
499
+ PersonizelyConfigMethods,
500
+ Product,
501
+ Customer,
502
+ } from '@personizely/shopify-hydrogen'
503
+ ```
504
+
505
+ ## How It Works
506
+
507
+ 1. **Provider Setup**: `PersonizelyProvider` sets up `window.__PLY_HYDROGEN_CONFIG__` with cart and adapter instances
508
+ 2. **Script Loading**: Provider dynamically loads the Personizely snippet after config is ready
509
+ 3. **Cart Integration**: Automatically integrates with Hydrogen's cart from the root loader
510
+ 4. **Page Context**: Use `usePageContext()` in route components to set page type and data
511
+ 5. **Event Triggers**: `useCartAdd` and `useCheckout` hooks trigger widgets at key moments
512
+
513
+ ## Compatibility
514
+
515
+ - **Shopify Hydrogen**: v2.x
516
+ - **React Router**: v7.x
517
+ - **React**: v18.x
518
+ - **TypeScript**: v5.x
519
+
520
+ ## Getting Your API Keys
521
+
522
+ 1. **Personizely Website API Key**: Get this from your [Personizely dashboard](https://app.personizely.net/settings)
523
+ 2. **Shopify Storefront Access Token**: Create a [custom app in Shopify Admin](https://shopify.dev/docs/api/usage/authentication#getting-started-with-authenticated-access) with Storefront API access
524
+
525
+ ## Support
526
+
527
+ - 📧 Email: support@personizely.net
528
+ - 📚 Docs: https://help.personizely.net
529
+ - 💬 Chat: Available in your Personizely dashboard
530
+
531
+ ## License
532
+
533
+ MIT
@@ -0,0 +1,33 @@
1
+ import type { IShopifyAdapter, Product, Customer, PageContext, PersonizelyConfig, ProductData } from '../types';
2
+ import { StorefrontClient } from '../utils/storefront-client';
3
+ export declare class Adapter implements IShopifyAdapter {
4
+ private client;
5
+ private currency;
6
+ private market;
7
+ private locale;
8
+ private pageContext;
9
+ private methods?;
10
+ private customerAccessToken;
11
+ constructor(config: PersonizelyConfig, storefrontClient: StorefrontClient);
12
+ setCustomerAccessToken(token: string | null): void;
13
+ fetchProduct(handle: string): Promise<ProductData>;
14
+ fetchProductCollections(handle: string, productId: number): Promise<number[]>;
15
+ fetchRecommendations(productId: number, intent: string): Promise<{
16
+ products: ProductData[];
17
+ intent: string;
18
+ }>;
19
+ buildProductPath(product: {
20
+ handle: string;
21
+ }): string;
22
+ convertToBaseCurrency(amount: number): number;
23
+ convertFromBaseCurrency(amount: number): number;
24
+ formatMoney(amount: number, includeDecimals?: boolean): string;
25
+ getCustomer(): Promise<Customer | null>;
26
+ getPageContext(): PageContext;
27
+ setPageContext(context: Partial<PageContext>): void;
28
+ getVisitorId(): string | null;
29
+ getMarket(): string | null;
30
+ getCurrencyRate(): number;
31
+ toProductData(product: Product): ProductData;
32
+ }
33
+ //# sourceMappingURL=Adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Adapter.d.ts","sourceRoot":"","sources":["../../src/classes/Adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,OAAO,EACP,QAAQ,EACR,WAAW,EACX,iBAAiB,EAEjB,WAAW,EACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAQ7D,qBAAa,OAAQ,YAAW,eAAe;IAC7C,OAAO,CAAC,MAAM,CAAkB;IAEhC,OAAO,CAAC,QAAQ,CAIf;IAED,OAAO,CAAC,MAAM,CAAe;IAE7B,OAAO,CAAC,MAAM,CAAQ;IAEtB,OAAO,CAAC,WAAW,CAAa;IAEhC,OAAO,CAAC,OAAO,CAAC,CAA0B;IAE1C,OAAO,CAAC,mBAAmB,CAAsB;gBAEpC,MAAM,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB;IAkB1E,sBAAsB,CAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAO7C,YAAY,CAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAanD,uBAAuB,CAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAkB9E,oBAAoB,CAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAYpH,gBAAgB,CAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAStD,qBAAqB,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAO9C,uBAAuB,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAOhD,WAAW,CAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAE,OAAc,GAAG,MAAM;IAc/D,WAAW,IAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAmB9C,cAAc,IAAK,WAAW;IAI9B,cAAc,CAAE,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC;IAU7C,YAAY,IAAK,MAAM,GAAG,IAAI;IAO9B,SAAS,IAAK,MAAM,GAAG,IAAI;IAQ3B,eAAe,IAAK,MAAM;IAO1B,aAAa,CAAE,OAAO,EAAE,OAAO,GAAG,WAAW;CAuB9C"}
@@ -0,0 +1,126 @@
1
+ import { GET_PRODUCT_BY_HANDLE, GET_PRODUCT_BY_ID, GET_PRODUCT_RECOMMENDATIONS } from '../graphql/queries/product';
2
+ import { GET_CUSTOMER } from '../graphql/queries/customer';
3
+ import { toId } from '../utils/id';
4
+ export class Adapter {
5
+ constructor(config, storefrontClient) {
6
+ this.customerAccessToken = null;
7
+ this.client = storefrontClient;
8
+ this.methods = config.methods;
9
+ this.currency = { active: 'USD', base: 'USD', rate: 1, ...config.currency };
10
+ this.locale = config.locale || 'en-US';
11
+ this.market = config.market || null;
12
+ this.pageContext = {
13
+ pageType: undefined,
14
+ product: null,
15
+ collection: null
16
+ };
17
+ }
18
+ setCustomerAccessToken(token) {
19
+ this.customerAccessToken = token;
20
+ }
21
+ async fetchProduct(handle) {
22
+ const data = await this.client.query(GET_PRODUCT_BY_HANDLE, { handle });
23
+ if (!data.product) {
24
+ throw new Error(`Product not found: ${handle}`);
25
+ }
26
+ return this.toProductData(data.product);
27
+ }
28
+ async fetchProductCollections(handle, productId) {
29
+ let data;
30
+ if (productId) {
31
+ data = await this.client.query(GET_PRODUCT_BY_ID, { id: `gid://shopify/Product/${productId}` });
32
+ }
33
+ else if (handle) {
34
+ data = await this.client.query(GET_PRODUCT_BY_HANDLE, { handle });
35
+ }
36
+ if (!data || !data.product) {
37
+ throw new Error(`Product not found: ${productId}`);
38
+ }
39
+ return data.product.collections.nodes.map(c => toId(c.id));
40
+ }
41
+ async fetchRecommendations(productId, intent) {
42
+ const data = await this.client.query(GET_PRODUCT_RECOMMENDATIONS, { productId: `gid://shopify/Product/${productId}`, intent });
43
+ return {
44
+ intent,
45
+ products: data.productRecommendations.map(p => this.toProductData(p)) || []
46
+ };
47
+ }
48
+ buildProductPath(product) {
49
+ if (typeof this.methods?.builtProductPath !== 'function')
50
+ return `/products/${product.handle}`;
51
+ return this.methods.builtProductPath(product);
52
+ }
53
+ convertToBaseCurrency(amount) {
54
+ return this.currency.rate / amount;
55
+ }
56
+ convertFromBaseCurrency(amount) {
57
+ return this.currency.rate * amount;
58
+ }
59
+ formatMoney(amount, includeDecimals = true) {
60
+ if (typeof this.methods?.formatMoney !== 'function')
61
+ return new Intl.NumberFormat(this.locale, {
62
+ style: 'currency',
63
+ currency: this.currency.active,
64
+ maximumFractionDigits: includeDecimals ? undefined : 0
65
+ }).format(amount / 100);
66
+ return this.methods.formatMoney(amount, includeDecimals);
67
+ }
68
+ async getCustomer() {
69
+ if (!this.customerAccessToken) {
70
+ return null;
71
+ }
72
+ try {
73
+ const data = await this.client.query(GET_CUSTOMER, { customerAccessToken: this.customerAccessToken });
74
+ return data.customer || null;
75
+ }
76
+ catch (error) {
77
+ console.error('Error fetching customer:', error);
78
+ return null;
79
+ }
80
+ }
81
+ getPageContext() {
82
+ return this.pageContext;
83
+ }
84
+ setPageContext(context) {
85
+ if (context.product) {
86
+ context.product.id = toId(String(context.product.id));
87
+ }
88
+ if (context.collection) {
89
+ context.collection.id = toId(String(context.collection.id));
90
+ }
91
+ this.pageContext = context;
92
+ }
93
+ getVisitorId() {
94
+ return null;
95
+ }
96
+ getMarket() {
97
+ return this.market;
98
+ }
99
+ getCurrencyRate() {
100
+ return this.currency.rate;
101
+ }
102
+ toProductData(product) {
103
+ return {
104
+ id: toId(product.id),
105
+ handle: product.handle,
106
+ title: product.title,
107
+ available: product.availableForSale,
108
+ requires_selling_plan: product.requiresSellingPlan,
109
+ images: product.images.nodes.map(img => img.url),
110
+ variants: product.variants.nodes.map(variant => ({
111
+ id: toId(variant.id),
112
+ title: variant.title,
113
+ available: variant.availableForSale,
114
+ price: Math.round(parseFloat(variant.price.amount) * 100),
115
+ compare_at_price: variant.compareAtPrice
116
+ ? Math.round(parseFloat(variant.compareAtPrice.amount) * 100)
117
+ : null,
118
+ featured_image: variant.image ? { src: variant.image.url } : null,
119
+ selling_plan_allocations: variant.sellingPlanAllocations?.edges?.map(edge => ({
120
+ selling_plan_id: edge.node.sellingPlan.id
121
+ })) || []
122
+ }))
123
+ };
124
+ }
125
+ }
126
+ //# sourceMappingURL=Adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Adapter.js","sourceRoot":"","sources":["../../src/classes/Adapter.ts"],"names":[],"mappings":"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"}