@revova/hydrogen 1.0.1 → 1.0.2

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 CHANGED
@@ -17,55 +17,53 @@ npm install @revova/hydrogen
17
17
 
18
18
  ## How it works
19
19
 
20
- Revova uses Shopify's built-in **App Proxy**. When the Revova app is installed on a store, Shopify automatically registers the route `/apps/revova` on the `myshopify.com` domain.
21
-
22
- In Hydrogen, your storefront runs on its own domain (Oxygen, Vercel, etc.), so you must pass the **absolute** proxy URL — not a relative path.
20
+ Components and hooks call your Revova backend directly over HTTPS with a `shop` query parameter and a `Bearer` token:
23
21
 
24
22
  ```
25
- https://yourstore.myshopify.com/apps/revova
23
+ GET https://yourapp.trycloudflare.com/api/reviews?shop=yourstore.myshopify.com&...
24
+ Authorization: Bearer <apiToken>
26
25
  ```
27
26
 
28
- No API keys. No CORS config. Shopify handles all of it.
27
+ No Shopify App Proxy. No CORS issues. No API keys exposed beyond what you pass in.
29
28
 
30
29
  ---
31
30
 
32
31
  ## Setup (Hydrogen)
33
32
 
34
- ### 1 — Install the Revova app on your Shopify store
35
-
36
- Same as any merchant. The App Proxy is registered automatically on install.
37
-
38
- ### 2 — Expose your store domain in the Hydrogen environment
33
+ ### 1 — Add your credentials to the environment
39
34
 
40
35
  ```env
41
36
  # .env
37
+ PUBLIC_REVOVA_API_URL=https://yourapp.trycloudflare.com
42
38
  PUBLIC_STORE_DOMAIN=yourstore.myshopify.com
39
+ PUBLIC_REVOVA_API_TOKEN=your_token_here
43
40
  ```
44
41
 
45
- Hydrogen exposes this through `context.env` in loaders:
42
+ Expose them through your Hydrogen loader:
46
43
 
47
44
  ```ts
48
45
  // app/root.tsx or any route loader
49
46
  export async function loader({ context }: LoaderArgs) {
50
47
  return {
51
- storeDomain: context.env.PUBLIC_STORE_DOMAIN,
48
+ apiUrl: context.env.PUBLIC_REVOVA_API_URL,
49
+ shop: context.env.PUBLIC_STORE_DOMAIN,
50
+ apiToken: context.env.PUBLIC_REVOVA_API_TOKEN,
52
51
  };
53
52
  }
54
53
  ```
55
54
 
56
- ### 3Pass `storeDomain` (or `proxyUrl`) to any component
55
+ ### 2Spread credentials into any component
57
56
 
58
- Every component accepts either prop use whichever is more convenient:
57
+ Every component accepts `apiUrl`, `shop`, and `apiToken`. The easiest pattern is to spread a single `creds` object:
59
58
 
60
59
  ```tsx
61
- // Recommended convenience shorthand
62
- <ReviewWidget storeDomain={storeDomain} productId={product.id} />
60
+ const creds = {
61
+ apiUrl: env.PUBLIC_REVOVA_API_URL,
62
+ shop: env.PUBLIC_STORE_DOMAIN,
63
+ apiToken: env.PUBLIC_REVOVA_API_TOKEN,
64
+ };
63
65
 
64
- // Explicit same result
65
- <ReviewWidget
66
- proxyUrl={`https://${storeDomain}/apps/revova`}
67
- productId={product.id}
68
- />
66
+ <ReviewWidget {...creds} productId={product.id} />
69
67
  ```
70
68
 
71
69
  ---
@@ -80,26 +78,21 @@ export async function loader({ context, params }: LoaderArgs) {
80
78
  const product = await getProduct(params.handle);
81
79
  return {
82
80
  product,
83
- storeDomain: context.env.PUBLIC_STORE_DOMAIN,
81
+ apiUrl: context.env.PUBLIC_REVOVA_API_URL,
82
+ shop: context.env.PUBLIC_STORE_DOMAIN,
83
+ apiToken: context.env.PUBLIC_REVOVA_API_TOKEN,
84
84
  };
85
85
  }
86
86
 
87
87
  export default function ProductPage() {
88
- const { product, storeDomain } = useLoaderData<typeof loader>();
88
+ const { product, apiUrl, shop, apiToken } = useLoaderData<typeof loader>();
89
+ const creds = { apiUrl, shop, apiToken };
89
90
 
90
91
  return (
91
92
  <>
92
93
  <h1>{product.title}</h1>
93
-
94
- <ReviewWidget
95
- storeDomain={storeDomain}
96
- productId={product.id}
97
- />
98
-
99
- <FloatingReviewButton
100
- storeDomain={storeDomain}
101
- productId={product.id}
102
- />
94
+ <ReviewWidget {...creds} productId={product.id} />
95
+ <FloatingReviewButton {...creds} productId={product.id} />
103
96
  </>
104
97
  );
105
98
  }
@@ -107,9 +100,19 @@ export default function ProductPage() {
107
100
 
108
101
  ---
109
102
 
110
- ## Components
103
+ ## API Credentials (`ApiProps`)
104
+
105
+ All components and hooks require these three props. Spread them as a group for convenience.
106
+
107
+ | Prop | Type | Description |
108
+ |---|---|---|
109
+ | `apiUrl` | `string` | Revova backend base URL (e.g. `https://yourapp.trycloudflare.com`) |
110
+ | `shop` | `string` | Store myshopify.com domain (e.g. `yourstore.myshopify.com`) |
111
+ | `apiToken` | `string` | Bearer token for authorization |
111
112
 
112
- All components accept either `storeDomain` **or** `proxyUrl` — never both.
113
+ ---
114
+
115
+ ## Components
113
116
 
114
117
  ### `<ReviewWidget>`
115
118
 
@@ -119,7 +122,7 @@ Full review list with pagination, sorting, and an inline submission form.
119
122
  import { ReviewWidget } from '@revova/hydrogen';
120
123
 
121
124
  <ReviewWidget
122
- storeDomain={storeDomain}
125
+ {...creds}
123
126
  productId={product.id}
124
127
  pageSize={10}
125
128
  showForm
@@ -129,8 +132,7 @@ import { ReviewWidget } from '@revova/hydrogen';
129
132
 
130
133
  | Prop | Type | Default | Description |
131
134
  |---|---|---|---|
132
- | `storeDomain` | `string` | — | Your `myshopify.com` domain (e.g. `yourstore.myshopify.com`) |
133
- | `proxyUrl` | `string` | — | Explicit proxy URL — use instead of `storeDomain` if preferred |
135
+ | `...creds` | `ApiProps` | — | `apiUrl`, `shop`, `apiToken` |
134
136
  | `productId` | `string` | — | Shopify product GID |
135
137
  | `locale` | `string` | — | Locale code for translated reviews (e.g. `fr`) |
136
138
  | `pageSize` | `number` | `10` | Reviews per page |
@@ -149,14 +151,14 @@ import { ReviewForm } from '@revova/hydrogen';
149
151
 
150
152
  // `form` comes from the ReviewsResponse returned by useReviews
151
153
  <ReviewForm
152
- storeDomain={storeDomain}
154
+ {...creds}
153
155
  productId={product.id}
154
156
  form={data.form}
155
157
  onSuccess={() => console.log('submitted')}
156
158
  />
157
159
  ```
158
160
 
159
- **Props:** `storeDomain` or `proxyUrl`, `productId`, `form` (required — `ResolvedForm` from loader), `onSuccess?`, `className?`
161
+ **Props:** `...creds`, `productId`, `form` (required — `ResolvedForm`), `onSuccess?`, `className?`
160
162
 
161
163
  ---
162
164
 
@@ -181,10 +183,10 @@ Aggregate rating + review count badge. Great for product cards and PDP headers.
181
183
  ```tsx
182
184
  import { ReviewCount } from '@revova/hydrogen';
183
185
 
184
- <ReviewCount storeDomain={storeDomain} starSize={14} />
186
+ <ReviewCount {...creds} starSize={14} />
185
187
  ```
186
188
 
187
- **Props:** `storeDomain` or `proxyUrl`, `starColor?`, `starSize?`, `className?`
189
+ **Props:** `...creds`, `starColor?`, `starSize?`, `className?`
188
190
 
189
191
  ---
190
192
 
@@ -195,15 +197,10 @@ Auto-advancing carousel of recent reviews. Includes previous/next arrows and dot
195
197
  ```tsx
196
198
  import { ReviewCarousel } from '@revova/hydrogen';
197
199
 
198
- <ReviewCarousel
199
- storeDomain={storeDomain}
200
- limit={10}
201
- autoPlay
202
- intervalMs={4000}
203
- />
200
+ <ReviewCarousel {...creds} limit={10} autoPlay intervalMs={4000} />
204
201
  ```
205
202
 
206
- **Props:** `storeDomain` or `proxyUrl`, `limit?` (default 10), `autoPlay?` (default `true`), `intervalMs?` (default 4000), `starColor?`, `className?`
203
+ **Props:** `...creds`, `limit?` (default 10), `autoPlay?` (default `true`), `intervalMs?` (default 4000), `starColor?`, `className?`
207
204
 
208
205
  ---
209
206
 
@@ -214,14 +211,10 @@ Masonry photo grid of reviews that have images. Clicking any photo opens a light
214
211
  ```tsx
215
212
  import { ReviewGallery } from '@revova/hydrogen';
216
213
 
217
- <ReviewGallery
218
- storeDomain={storeDomain}
219
- columns={3}
220
- limit={20}
221
- />
214
+ <ReviewGallery {...creds} columns={3} limit={20} />
222
215
  ```
223
216
 
224
- **Props:** `storeDomain` or `proxyUrl`, `limit?` (default 20), `columns?` (default 3), `starColor?`, `className?`
217
+ **Props:** `...creds`, `limit?` (default 20), `columns?` (default 3), `starColor?`, `className?`
225
218
 
226
219
  ---
227
220
 
@@ -232,13 +225,10 @@ Product Q&A — lists questions with answers and includes an ask-a-question form
232
225
  ```tsx
233
226
  import { QnAWidget } from '@revova/hydrogen';
234
227
 
235
- <QnAWidget
236
- storeDomain={storeDomain}
237
- productId={product.id}
238
- />
228
+ <QnAWidget {...creds} productId={product.id} />
239
229
  ```
240
230
 
241
- **Props:** `storeDomain` or `proxyUrl`, `productId`, `className?`
231
+ **Props:** `...creds`, `productId`, `className?`
242
232
 
243
233
  ---
244
234
 
@@ -249,17 +239,12 @@ Aggregate rating badge in three styles: `pill` (default), `inline`, or `card`.
249
239
  ```tsx
250
240
  import { TrustBadge } from '@revova/hydrogen';
251
241
 
252
- // Pill great for footers, headers
253
- <TrustBadge storeDomain={storeDomain} style="pill" />
254
-
255
- // Card — great for landing pages
256
- <TrustBadge storeDomain={storeDomain} style="card" />
257
-
258
- // Inline — great inside sentences or product specs
259
- <TrustBadge storeDomain={storeDomain} style="inline" />
242
+ <TrustBadge {...creds} style="pill" />
243
+ <TrustBadge {...creds} style="card" />
244
+ <TrustBadge {...creds} style="inline" />
260
245
  ```
261
246
 
262
- **Props:** `storeDomain` or `proxyUrl`, `style?` (`'pill' | 'inline' | 'card'`, default `'pill'`), `starColor?`, `className?`
247
+ **Props:** `...creds`, `style?` (`'pill' | 'inline' | 'card'`, default `'pill'`), `starColor?`, `className?`
263
248
 
264
249
  ---
265
250
 
@@ -270,14 +255,10 @@ A continuously scrolling marquee of recent review snippets — ideal for homepag
270
255
  ```tsx
271
256
  import { ReviewTicker } from '@revova/hydrogen';
272
257
 
273
- <ReviewTicker
274
- storeDomain={storeDomain}
275
- limit={20}
276
- speedSeconds={30}
277
- />
258
+ <ReviewTicker {...creds} limit={20} speedSeconds={30} />
278
259
  ```
279
260
 
280
- **Props:** `storeDomain` or `proxyUrl`, `limit?` (default 20), `speedSeconds?` (default 30), `starColor?`, `className?`
261
+ **Props:** `...creds`, `limit?` (default 20), `speedSeconds?` (default 30), `starColor?`, `className?`
281
262
 
282
263
  ---
283
264
 
@@ -289,14 +270,14 @@ A timed floating popup showing recent reviews one at a time. Add once to your ro
289
270
  import { SocialProofPopup } from '@revova/hydrogen';
290
271
 
291
272
  <SocialProofPopup
292
- storeDomain={storeDomain}
273
+ {...creds}
293
274
  position="bottom-left"
294
275
  intervalMs={8000}
295
276
  displayMs={5000}
296
277
  />
297
278
  ```
298
279
 
299
- **Props:** `storeDomain` or `proxyUrl`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-left'`), `intervalMs?` (default 8000), `displayMs?` (default 5000), `starColor?`, `className?`
280
+ **Props:** `...creds`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-left'`), `intervalMs?` (default 8000), `displayMs?` (default 5000), `starColor?`, `className?`
300
281
 
301
282
  ---
302
283
 
@@ -307,15 +288,10 @@ A sticky tab fixed to the left or right edge of the viewport. Clicking it slides
307
288
  ```tsx
308
289
  import { FloatingReviewsTab } from '@revova/hydrogen';
309
290
 
310
- <FloatingReviewsTab
311
- storeDomain={storeDomain}
312
- label="Reviews"
313
- position="right"
314
- color="#111827"
315
- />
291
+ <FloatingReviewsTab {...creds} label="Reviews" position="right" />
316
292
  ```
317
293
 
318
- **Props:** `storeDomain` or `proxyUrl`, `label?` (default `'Reviews'`), `position?` (`'left' | 'right'`, default `'right'`), `color?`, `limit?` (default 5), `starColor?`, `className?`
294
+ **Props:** `...creds`, `label?` (default `'Reviews'`), `position?` (`'left' | 'right'`, default `'right'`), `color?`, `limit?` (default 5), `starColor?`, `className?`
319
295
 
320
296
  ---
321
297
 
@@ -327,37 +303,30 @@ A fixed "Write a Review" button that opens a modal with the submission form.
327
303
  import { FloatingReviewButton } from '@revova/hydrogen';
328
304
 
329
305
  <FloatingReviewButton
330
- storeDomain={storeDomain}
306
+ {...creds}
331
307
  productId={product.id}
332
308
  text="Write a Review"
333
309
  position="bottom-right"
334
- color="#111827"
335
310
  />
336
311
  ```
337
312
 
338
- **Props:** `storeDomain` or `proxyUrl`, `productId`, `text?` (default `'Write a Review'`), `color?`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-right'`), `className?`
313
+ **Props:** `...creds`, `productId`, `text?` (default `'Write a Review'`), `color?`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-right'`), `className?`
339
314
 
340
315
  ---
341
316
 
342
317
  ## Hooks
343
318
 
344
- Use hooks to build fully custom UIs while Revova handles data fetching. Hooks take `proxyUrl` as a plain string — use the exported `resolveProxyUrl` helper to build it from `storeDomain`.
345
-
346
- ```ts
347
- import { resolveProxyUrl } from '@revova/hydrogen';
348
-
349
- const proxyUrl = resolveProxyUrl({ storeDomain }); // or { proxyUrl }
350
- ```
319
+ Use hooks to build fully custom UIs while Revova handles data fetching.
351
320
 
352
321
  ### `useReviews(options)`
353
322
 
354
323
  Paginated reviews for a product with sorting controls.
355
324
 
356
325
  ```tsx
357
- import { useReviews, resolveProxyUrl } from '@revova/hydrogen';
326
+ import { useReviews } from '@revova/hydrogen';
358
327
 
359
328
  const { data, loading, error, setPage, setSort, currentPage, currentSort, refetch } = useReviews({
360
- proxyUrl: resolveProxyUrl({ storeDomain }),
329
+ ...creds,
361
330
  productId: product.id,
362
331
  limit: 5,
363
332
  sort: 'helpful', // 'recent' | 'helpful' | 'rating_high' | 'rating_low'
@@ -367,15 +336,12 @@ const { data, loading, error, setPage, setSort, currentPage, currentSort, refetc
367
336
 
368
337
  ### `useWidgetGlobals(options)`
369
338
 
370
- Shop-level stats and recent reviews. Used internally by `<ReviewCount>`, `<TrustBadge>`, `<ReviewCarousel>`, `<ReviewGallery>`, `<ReviewTicker>`, `<SocialProofPopup>`, and `<FloatingReviewsTab>`.
339
+ Shop-level stats and recent reviews. Used internally by most global widgets.
371
340
 
372
341
  ```tsx
373
- import { useWidgetGlobals, resolveProxyUrl } from '@revova/hydrogen';
342
+ import { useWidgetGlobals } from '@revova/hydrogen';
374
343
 
375
- const { data, loading, error } = useWidgetGlobals({
376
- proxyUrl: resolveProxyUrl({ storeDomain }),
377
- limit: 20,
378
- });
344
+ const { data, loading, error } = useWidgetGlobals({ ...creds, limit: 20 });
379
345
 
380
346
  // data.stats.averageRating — "4.8"
381
347
  // data.stats.totalReviews — 142
@@ -383,16 +349,14 @@ const { data, loading, error } = useWidgetGlobals({
383
349
  // data.config — merchant widget settings
384
350
  ```
385
351
 
386
- ### `useSubmitReview(proxyUrl)`
352
+ ### `useSubmitReview(creds)`
387
353
 
388
354
  Submit a new review programmatically.
389
355
 
390
356
  ```tsx
391
- import { useSubmitReview, resolveProxyUrl } from '@revova/hydrogen';
357
+ import { useSubmitReview } from '@revova/hydrogen';
392
358
 
393
- const { submit, submitting, success, error, result, reset } = useSubmitReview(
394
- resolveProxyUrl({ storeDomain })
395
- );
359
+ const { submit, submitting, success, error, result, reset } = useSubmitReview(creds);
396
360
 
397
361
  await submit({
398
362
  productId: 'gid://shopify/Product/123',
@@ -411,18 +375,10 @@ await submit({
411
375
  Fetch and submit Q&A for a product.
412
376
 
413
377
  ```tsx
414
- import { useQnA, resolveProxyUrl } from '@revova/hydrogen';
415
-
416
- const {
417
- data,
418
- loading,
419
- setPage,
420
- submitQuestion,
421
- submitAnswer,
422
- submitState,
423
- resetSubmit,
424
- } = useQnA({
425
- proxyUrl: resolveProxyUrl({ storeDomain }),
378
+ import { useQnA } from '@revova/hydrogen';
379
+
380
+ const { data, loading, setPage, submitQuestion, submitAnswer, submitState, resetSubmit } = useQnA({
381
+ ...creds,
426
382
  productId: product.id,
427
383
  });
428
384
 
@@ -434,14 +390,14 @@ await submitQuestion({
434
390
  });
435
391
  ```
436
392
 
437
- ### `useHelpfulVote(proxyUrl)`
393
+ ### `useHelpfulVote(creds)`
438
394
 
439
395
  Cast a helpful / not-helpful vote on a review.
440
396
 
441
397
  ```tsx
442
- import { useHelpfulVote, resolveProxyUrl } from '@revova/hydrogen';
398
+ import { useHelpfulVote } from '@revova/hydrogen';
443
399
 
444
- const { vote, loading, voted } = useHelpfulVote(resolveProxyUrl({ storeDomain }));
400
+ const { vote, loading, voted } = useHelpfulVote(creds);
445
401
 
446
402
  <button onClick={() => vote({ reviewId: review.id, vote: 'helpful' })} disabled={voted}>
447
403
  Helpful ({review.helpfulCount})