@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.
- package/README.md +533 -0
- package/dist/classes/Adapter.d.ts +33 -0
- package/dist/classes/Adapter.d.ts.map +1 -0
- package/dist/classes/Adapter.js +126 -0
- package/dist/classes/Adapter.js.map +1 -0
- package/dist/classes/Cart.d.ts +29 -0
- package/dist/classes/Cart.d.ts.map +1 -0
- package/dist/classes/Cart.js +272 -0
- package/dist/classes/Cart.js.map +1 -0
- package/dist/components/PersonizelyProvider.d.ts +36 -0
- package/dist/components/PersonizelyProvider.d.ts.map +1 -0
- package/dist/components/PersonizelyProvider.js +118 -0
- package/dist/components/PersonizelyProvider.js.map +1 -0
- package/dist/graphql/queries/cart.d.ts +7 -0
- package/dist/graphql/queries/cart.d.ts.map +1 -0
- package/dist/graphql/queries/cart.js +142 -0
- package/dist/graphql/queries/cart.js.map +1 -0
- package/dist/graphql/queries/customer.d.ts +5 -0
- package/dist/graphql/queries/customer.d.ts.map +1 -0
- package/dist/graphql/queries/customer.js +72 -0
- package/dist/graphql/queries/customer.js.map +1 -0
- package/dist/graphql/queries/product.d.ts +5 -0
- package/dist/graphql/queries/product.d.ts.map +1 -0
- package/dist/graphql/queries/product.js +114 -0
- package/dist/graphql/queries/product.js.map +1 -0
- package/dist/hooks/useCartAdd.d.ts +3 -0
- package/dist/hooks/useCartAdd.d.ts.map +1 -0
- package/dist/hooks/useCartAdd.js +18 -0
- package/dist/hooks/useCartAdd.js.map +1 -0
- package/dist/hooks/useCheckout.d.ts +2 -0
- package/dist/hooks/useCheckout.d.ts.map +1 -0
- package/dist/hooks/useCheckout.js +12 -0
- package/dist/hooks/useCheckout.js.map +1 -0
- package/dist/hooks/usePageContext.d.ts +3 -0
- package/dist/hooks/usePageContext.d.ts.map +1 -0
- package/dist/hooks/usePageContext.js +6 -0
- package/dist/hooks/usePageContext.js.map +1 -0
- package/dist/hooks/usePersonizely.d.ts +3 -0
- package/dist/hooks/usePersonizely.d.ts.map +1 -0
- package/dist/hooks/usePersonizely.js +11 -0
- package/dist/hooks/usePersonizely.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +170 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interfaces.d.ts +35 -0
- package/dist/types/interfaces.d.ts.map +1 -0
- package/dist/types/interfaces.js +2 -0
- package/dist/types/interfaces.js.map +1 -0
- package/dist/utils/cookies.d.ts +4 -0
- package/dist/utils/cookies.d.ts.map +1 -0
- package/dist/utils/cookies.js +33 -0
- package/dist/utils/cookies.js.map +1 -0
- package/dist/utils/id.d.ts +3 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +7 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/storefront-client.d.ts +15 -0
- package/dist/utils/storefront-client.d.ts.map +1 -0
- package/dist/utils/storefront-client.js +39 -0
- package/dist/utils/storefront-client.js.map +1 -0
- 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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"}
|