@revova/hydrogen 1.0.1 → 1.1.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 +87 -124
- package/dist/index.cjs +1139 -853
- package/dist/index.d.cts +52 -61
- package/dist/index.d.ts +52 -61
- package/dist/index.js +1112 -826
- package/dist/styles.css +629 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -17,55 +17,53 @@ npm install @revova/hydrogen
|
|
|
17
17
|
|
|
18
18
|
## How it works
|
|
19
19
|
|
|
20
|
-
|
|
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
|
|
23
|
+
GET https://yourapp.trycloudflare.com/api/reviews?shop=yourstore.myshopify.com&...
|
|
24
|
+
Authorization: Bearer <apiToken>
|
|
26
25
|
```
|
|
27
26
|
|
|
28
|
-
No
|
|
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 —
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
55
|
+
### 2 — Spread credentials into any component
|
|
57
56
|
|
|
58
|
-
Every component accepts
|
|
57
|
+
Every component accepts `apiUrl`, `shop`, and `apiToken`. The easiest pattern is to spread a single `creds` object:
|
|
59
58
|
|
|
60
59
|
```tsx
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
---
|
|
@@ -74,32 +72,28 @@ Every component accepts either prop — use whichever is more convenient:
|
|
|
74
72
|
|
|
75
73
|
```tsx
|
|
76
74
|
// app/routes/products.$handle.tsx
|
|
75
|
+
import '@revova/hydrogen/styles.css'; // import once in app/root.tsx instead
|
|
77
76
|
import { ReviewWidget, FloatingReviewButton } from '@revova/hydrogen';
|
|
78
77
|
|
|
79
78
|
export async function loader({ context, params }: LoaderArgs) {
|
|
80
79
|
const product = await getProduct(params.handle);
|
|
81
80
|
return {
|
|
82
81
|
product,
|
|
83
|
-
|
|
82
|
+
apiUrl: context.env.PUBLIC_REVOVA_API_URL,
|
|
83
|
+
shop: context.env.PUBLIC_STORE_DOMAIN,
|
|
84
|
+
apiToken: context.env.PUBLIC_REVOVA_API_TOKEN,
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
export default function ProductPage() {
|
|
88
|
-
const { product,
|
|
89
|
+
const { product, apiUrl, shop, apiToken } = useLoaderData<typeof loader>();
|
|
90
|
+
const creds = { apiUrl, shop, apiToken };
|
|
89
91
|
|
|
90
92
|
return (
|
|
91
93
|
<>
|
|
92
94
|
<h1>{product.title}</h1>
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
storeDomain={storeDomain}
|
|
96
|
-
productId={product.id}
|
|
97
|
-
/>
|
|
98
|
-
|
|
99
|
-
<FloatingReviewButton
|
|
100
|
-
storeDomain={storeDomain}
|
|
101
|
-
productId={product.id}
|
|
102
|
-
/>
|
|
95
|
+
<ReviewWidget {...creds} productId={product.id} />
|
|
96
|
+
<FloatingReviewButton {...creds} productId={product.id} />
|
|
103
97
|
</>
|
|
104
98
|
);
|
|
105
99
|
}
|
|
@@ -107,9 +101,19 @@ export default function ProductPage() {
|
|
|
107
101
|
|
|
108
102
|
---
|
|
109
103
|
|
|
110
|
-
##
|
|
104
|
+
## API Credentials (`ApiProps`)
|
|
105
|
+
|
|
106
|
+
All components and hooks require these three props. Spread them as a group for convenience.
|
|
107
|
+
|
|
108
|
+
| Prop | Type | Description |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `apiUrl` | `string` | Revova backend base URL (e.g. `https://yourapp.trycloudflare.com`) |
|
|
111
|
+
| `shop` | `string` | Store myshopify.com domain (e.g. `yourstore.myshopify.com`) |
|
|
112
|
+
| `apiToken` | `string` | Bearer token for authorization |
|
|
113
|
+
|
|
114
|
+
---
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
## Components
|
|
113
117
|
|
|
114
118
|
### `<ReviewWidget>`
|
|
115
119
|
|
|
@@ -119,7 +123,7 @@ Full review list with pagination, sorting, and an inline submission form.
|
|
|
119
123
|
import { ReviewWidget } from '@revova/hydrogen';
|
|
120
124
|
|
|
121
125
|
<ReviewWidget
|
|
122
|
-
|
|
126
|
+
{...creds}
|
|
123
127
|
productId={product.id}
|
|
124
128
|
pageSize={10}
|
|
125
129
|
showForm
|
|
@@ -129,8 +133,7 @@ import { ReviewWidget } from '@revova/hydrogen';
|
|
|
129
133
|
|
|
130
134
|
| Prop | Type | Default | Description |
|
|
131
135
|
|---|---|---|---|
|
|
132
|
-
| `
|
|
133
|
-
| `proxyUrl` | `string` | — | Explicit proxy URL — use instead of `storeDomain` if preferred |
|
|
136
|
+
| `...creds` | `ApiProps` | — | `apiUrl`, `shop`, `apiToken` |
|
|
134
137
|
| `productId` | `string` | — | Shopify product GID |
|
|
135
138
|
| `locale` | `string` | — | Locale code for translated reviews (e.g. `fr`) |
|
|
136
139
|
| `pageSize` | `number` | `10` | Reviews per page |
|
|
@@ -149,14 +152,14 @@ import { ReviewForm } from '@revova/hydrogen';
|
|
|
149
152
|
|
|
150
153
|
// `form` comes from the ReviewsResponse returned by useReviews
|
|
151
154
|
<ReviewForm
|
|
152
|
-
|
|
155
|
+
{...creds}
|
|
153
156
|
productId={product.id}
|
|
154
157
|
form={data.form}
|
|
155
158
|
onSuccess={() => console.log('submitted')}
|
|
156
159
|
/>
|
|
157
160
|
```
|
|
158
161
|
|
|
159
|
-
**Props:**
|
|
162
|
+
**Props:** `...creds`, `productId`, `form` (required — `ResolvedForm`), `onSuccess?`, `className?`
|
|
160
163
|
|
|
161
164
|
---
|
|
162
165
|
|
|
@@ -181,10 +184,10 @@ Aggregate rating + review count badge. Great for product cards and PDP headers.
|
|
|
181
184
|
```tsx
|
|
182
185
|
import { ReviewCount } from '@revova/hydrogen';
|
|
183
186
|
|
|
184
|
-
<ReviewCount
|
|
187
|
+
<ReviewCount {...creds} starSize={14} />
|
|
185
188
|
```
|
|
186
189
|
|
|
187
|
-
**Props:**
|
|
190
|
+
**Props:** `...creds`, `starColor?`, `starSize?`, `className?`
|
|
188
191
|
|
|
189
192
|
---
|
|
190
193
|
|
|
@@ -195,15 +198,10 @@ Auto-advancing carousel of recent reviews. Includes previous/next arrows and dot
|
|
|
195
198
|
```tsx
|
|
196
199
|
import { ReviewCarousel } from '@revova/hydrogen';
|
|
197
200
|
|
|
198
|
-
<ReviewCarousel
|
|
199
|
-
storeDomain={storeDomain}
|
|
200
|
-
limit={10}
|
|
201
|
-
autoPlay
|
|
202
|
-
intervalMs={4000}
|
|
203
|
-
/>
|
|
201
|
+
<ReviewCarousel {...creds} limit={10} autoPlay intervalMs={4000} />
|
|
204
202
|
```
|
|
205
203
|
|
|
206
|
-
**Props:**
|
|
204
|
+
**Props:** `...creds`, `limit?` (default 10), `autoPlay?` (default `true`), `intervalMs?` (default 4000), `starColor?`, `className?`
|
|
207
205
|
|
|
208
206
|
---
|
|
209
207
|
|
|
@@ -214,14 +212,10 @@ Masonry photo grid of reviews that have images. Clicking any photo opens a light
|
|
|
214
212
|
```tsx
|
|
215
213
|
import { ReviewGallery } from '@revova/hydrogen';
|
|
216
214
|
|
|
217
|
-
<ReviewGallery
|
|
218
|
-
storeDomain={storeDomain}
|
|
219
|
-
columns={3}
|
|
220
|
-
limit={20}
|
|
221
|
-
/>
|
|
215
|
+
<ReviewGallery {...creds} columns={3} limit={20} />
|
|
222
216
|
```
|
|
223
217
|
|
|
224
|
-
**Props:**
|
|
218
|
+
**Props:** `...creds`, `limit?` (default 20), `columns?` (default 3), `starColor?`, `className?`
|
|
225
219
|
|
|
226
220
|
---
|
|
227
221
|
|
|
@@ -232,13 +226,10 @@ Product Q&A — lists questions with answers and includes an ask-a-question form
|
|
|
232
226
|
```tsx
|
|
233
227
|
import { QnAWidget } from '@revova/hydrogen';
|
|
234
228
|
|
|
235
|
-
<QnAWidget
|
|
236
|
-
storeDomain={storeDomain}
|
|
237
|
-
productId={product.id}
|
|
238
|
-
/>
|
|
229
|
+
<QnAWidget {...creds} productId={product.id} />
|
|
239
230
|
```
|
|
240
231
|
|
|
241
|
-
**Props:**
|
|
232
|
+
**Props:** `...creds`, `productId`, `className?`
|
|
242
233
|
|
|
243
234
|
---
|
|
244
235
|
|
|
@@ -249,17 +240,12 @@ Aggregate rating badge in three styles: `pill` (default), `inline`, or `card`.
|
|
|
249
240
|
```tsx
|
|
250
241
|
import { TrustBadge } from '@revova/hydrogen';
|
|
251
242
|
|
|
252
|
-
|
|
253
|
-
<TrustBadge
|
|
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" />
|
|
243
|
+
<TrustBadge {...creds} style="pill" />
|
|
244
|
+
<TrustBadge {...creds} style="card" />
|
|
245
|
+
<TrustBadge {...creds} style="inline" />
|
|
260
246
|
```
|
|
261
247
|
|
|
262
|
-
**Props:**
|
|
248
|
+
**Props:** `...creds`, `style?` (`'pill' | 'inline' | 'card'`, default `'pill'`), `starColor?`, `className?`
|
|
263
249
|
|
|
264
250
|
---
|
|
265
251
|
|
|
@@ -270,14 +256,10 @@ A continuously scrolling marquee of recent review snippets — ideal for homepag
|
|
|
270
256
|
```tsx
|
|
271
257
|
import { ReviewTicker } from '@revova/hydrogen';
|
|
272
258
|
|
|
273
|
-
<ReviewTicker
|
|
274
|
-
storeDomain={storeDomain}
|
|
275
|
-
limit={20}
|
|
276
|
-
speedSeconds={30}
|
|
277
|
-
/>
|
|
259
|
+
<ReviewTicker {...creds} limit={20} speedSeconds={30} />
|
|
278
260
|
```
|
|
279
261
|
|
|
280
|
-
**Props:**
|
|
262
|
+
**Props:** `...creds`, `limit?` (default 20), `speedSeconds?` (default 30), `starColor?`, `className?`
|
|
281
263
|
|
|
282
264
|
---
|
|
283
265
|
|
|
@@ -289,14 +271,14 @@ A timed floating popup showing recent reviews one at a time. Add once to your ro
|
|
|
289
271
|
import { SocialProofPopup } from '@revova/hydrogen';
|
|
290
272
|
|
|
291
273
|
<SocialProofPopup
|
|
292
|
-
|
|
274
|
+
{...creds}
|
|
293
275
|
position="bottom-left"
|
|
294
276
|
intervalMs={8000}
|
|
295
277
|
displayMs={5000}
|
|
296
278
|
/>
|
|
297
279
|
```
|
|
298
280
|
|
|
299
|
-
**Props:**
|
|
281
|
+
**Props:** `...creds`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-left'`), `intervalMs?` (default 8000), `displayMs?` (default 5000), `starColor?`, `className?`
|
|
300
282
|
|
|
301
283
|
---
|
|
302
284
|
|
|
@@ -307,15 +289,10 @@ A sticky tab fixed to the left or right edge of the viewport. Clicking it slides
|
|
|
307
289
|
```tsx
|
|
308
290
|
import { FloatingReviewsTab } from '@revova/hydrogen';
|
|
309
291
|
|
|
310
|
-
<FloatingReviewsTab
|
|
311
|
-
storeDomain={storeDomain}
|
|
312
|
-
label="Reviews"
|
|
313
|
-
position="right"
|
|
314
|
-
color="#111827"
|
|
315
|
-
/>
|
|
292
|
+
<FloatingReviewsTab {...creds} label="Reviews" position="right" />
|
|
316
293
|
```
|
|
317
294
|
|
|
318
|
-
**Props:**
|
|
295
|
+
**Props:** `...creds`, `label?` (default `'Reviews'`), `position?` (`'left' | 'right'`, default `'right'`), `color?`, `limit?` (default 5), `starColor?`, `className?`
|
|
319
296
|
|
|
320
297
|
---
|
|
321
298
|
|
|
@@ -327,37 +304,30 @@ A fixed "Write a Review" button that opens a modal with the submission form.
|
|
|
327
304
|
import { FloatingReviewButton } from '@revova/hydrogen';
|
|
328
305
|
|
|
329
306
|
<FloatingReviewButton
|
|
330
|
-
|
|
307
|
+
{...creds}
|
|
331
308
|
productId={product.id}
|
|
332
309
|
text="Write a Review"
|
|
333
310
|
position="bottom-right"
|
|
334
|
-
color="#111827"
|
|
335
311
|
/>
|
|
336
312
|
```
|
|
337
313
|
|
|
338
|
-
**Props:**
|
|
314
|
+
**Props:** `...creds`, `productId`, `text?` (default `'Write a Review'`), `color?`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-right'`), `className?`
|
|
339
315
|
|
|
340
316
|
---
|
|
341
317
|
|
|
342
318
|
## Hooks
|
|
343
319
|
|
|
344
|
-
Use hooks to build fully custom UIs while Revova handles data fetching.
|
|
345
|
-
|
|
346
|
-
```ts
|
|
347
|
-
import { resolveProxyUrl } from '@revova/hydrogen';
|
|
348
|
-
|
|
349
|
-
const proxyUrl = resolveProxyUrl({ storeDomain }); // or { proxyUrl }
|
|
350
|
-
```
|
|
320
|
+
Use hooks to build fully custom UIs while Revova handles data fetching.
|
|
351
321
|
|
|
352
322
|
### `useReviews(options)`
|
|
353
323
|
|
|
354
324
|
Paginated reviews for a product with sorting controls.
|
|
355
325
|
|
|
356
326
|
```tsx
|
|
357
|
-
import { useReviews
|
|
327
|
+
import { useReviews } from '@revova/hydrogen';
|
|
358
328
|
|
|
359
329
|
const { data, loading, error, setPage, setSort, currentPage, currentSort, refetch } = useReviews({
|
|
360
|
-
|
|
330
|
+
...creds,
|
|
361
331
|
productId: product.id,
|
|
362
332
|
limit: 5,
|
|
363
333
|
sort: 'helpful', // 'recent' | 'helpful' | 'rating_high' | 'rating_low'
|
|
@@ -367,15 +337,12 @@ const { data, loading, error, setPage, setSort, currentPage, currentSort, refetc
|
|
|
367
337
|
|
|
368
338
|
### `useWidgetGlobals(options)`
|
|
369
339
|
|
|
370
|
-
Shop-level stats and recent reviews. Used internally by
|
|
340
|
+
Shop-level stats and recent reviews. Used internally by most global widgets.
|
|
371
341
|
|
|
372
342
|
```tsx
|
|
373
|
-
import { useWidgetGlobals
|
|
343
|
+
import { useWidgetGlobals } from '@revova/hydrogen';
|
|
374
344
|
|
|
375
|
-
const { data, loading, error } = useWidgetGlobals({
|
|
376
|
-
proxyUrl: resolveProxyUrl({ storeDomain }),
|
|
377
|
-
limit: 20,
|
|
378
|
-
});
|
|
345
|
+
const { data, loading, error } = useWidgetGlobals({ ...creds, limit: 20 });
|
|
379
346
|
|
|
380
347
|
// data.stats.averageRating — "4.8"
|
|
381
348
|
// data.stats.totalReviews — 142
|
|
@@ -383,16 +350,14 @@ const { data, loading, error } = useWidgetGlobals({
|
|
|
383
350
|
// data.config — merchant widget settings
|
|
384
351
|
```
|
|
385
352
|
|
|
386
|
-
### `useSubmitReview(
|
|
353
|
+
### `useSubmitReview(creds)`
|
|
387
354
|
|
|
388
355
|
Submit a new review programmatically.
|
|
389
356
|
|
|
390
357
|
```tsx
|
|
391
|
-
import { useSubmitReview
|
|
358
|
+
import { useSubmitReview } from '@revova/hydrogen';
|
|
392
359
|
|
|
393
|
-
const { submit, submitting, success, error, result, reset } = useSubmitReview(
|
|
394
|
-
resolveProxyUrl({ storeDomain })
|
|
395
|
-
);
|
|
360
|
+
const { submit, submitting, success, error, result, reset } = useSubmitReview(creds);
|
|
396
361
|
|
|
397
362
|
await submit({
|
|
398
363
|
productId: 'gid://shopify/Product/123',
|
|
@@ -411,18 +376,10 @@ await submit({
|
|
|
411
376
|
Fetch and submit Q&A for a product.
|
|
412
377
|
|
|
413
378
|
```tsx
|
|
414
|
-
import { useQnA
|
|
415
|
-
|
|
416
|
-
const {
|
|
417
|
-
|
|
418
|
-
loading,
|
|
419
|
-
setPage,
|
|
420
|
-
submitQuestion,
|
|
421
|
-
submitAnswer,
|
|
422
|
-
submitState,
|
|
423
|
-
resetSubmit,
|
|
424
|
-
} = useQnA({
|
|
425
|
-
proxyUrl: resolveProxyUrl({ storeDomain }),
|
|
379
|
+
import { useQnA } from '@revova/hydrogen';
|
|
380
|
+
|
|
381
|
+
const { data, loading, setPage, submitQuestion, submitAnswer, submitState, resetSubmit } = useQnA({
|
|
382
|
+
...creds,
|
|
426
383
|
productId: product.id,
|
|
427
384
|
});
|
|
428
385
|
|
|
@@ -434,14 +391,14 @@ await submitQuestion({
|
|
|
434
391
|
});
|
|
435
392
|
```
|
|
436
393
|
|
|
437
|
-
### `useHelpfulVote(
|
|
394
|
+
### `useHelpfulVote(creds)`
|
|
438
395
|
|
|
439
396
|
Cast a helpful / not-helpful vote on a review.
|
|
440
397
|
|
|
441
398
|
```tsx
|
|
442
|
-
import { useHelpfulVote
|
|
399
|
+
import { useHelpfulVote } from '@revova/hydrogen';
|
|
443
400
|
|
|
444
|
-
const { vote, loading, voted } = useHelpfulVote(
|
|
401
|
+
const { vote, loading, voted } = useHelpfulVote(creds);
|
|
445
402
|
|
|
446
403
|
<button onClick={() => vote({ reviewId: review.id, vote: 'helpful' })} disabled={voted}>
|
|
447
404
|
Helpful ({review.helpfulCount})
|
|
@@ -452,9 +409,15 @@ const { vote, loading, voted } = useHelpfulVote(resolveProxyUrl({ storeDomain })
|
|
|
452
409
|
|
|
453
410
|
## Styling
|
|
454
411
|
|
|
455
|
-
|
|
412
|
+
The package ships a bundled CSS file. Import it **once** in your app root (e.g. `app/root.tsx`):
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import '@revova/hydrogen/styles.css';
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
This applies the full `rv-*` design system — cards, stars, modal, form, ticker animation, popup, gallery lightbox, and all widget layouts. All class names are prefixed `rv-` so they won't conflict with your theme styles.
|
|
456
419
|
|
|
457
|
-
The `starColor` prop is available on every component that
|
|
420
|
+
To customise, pass a `className` prop on any component and override the relevant `rv-*` classes in your own CSS. The `starColor` prop (default `#f59e0b`) is available on every component that renders stars.
|
|
458
421
|
|
|
459
422
|
---
|
|
460
423
|
|