@revova/hydrogen 1.0.0 → 1.0.1

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
@@ -11,42 +11,126 @@ npm install @revova/hydrogen
11
11
  ## Requirements
12
12
 
13
13
  - React 18+
14
- - Shopify Hydrogen storefront with the Revova app installed
14
+ - Shopify Hydrogen storefront with the Revova app installed on the store
15
15
 
16
16
  ---
17
17
 
18
- ## Quick Start
18
+ ## How it works
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.
19
21
 
20
- The `proxyUrl` is always `/apps/revova` Shopify's App Proxy routes requests to your Revova backend automatically. No API keys needed in the storefront.
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.
23
+
24
+ ```
25
+ https://yourstore.myshopify.com/apps/revova
26
+ ```
27
+
28
+ No API keys. No CORS config. Shopify handles all of it.
21
29
 
22
30
  ---
23
31
 
24
- ## Components
32
+ ## Setup (Hydrogen)
25
33
 
26
- ### `<ReviewWidget>`
34
+ ### 1 — Install the Revova app on your Shopify store
27
35
 
28
- Full review list with pagination, sorting, and an inline submission form.
36
+ Same as any merchant. The App Proxy is registered automatically on install.
37
+
38
+ ### 2 — Expose your store domain in the Hydrogen environment
39
+
40
+ ```env
41
+ # .env
42
+ PUBLIC_STORE_DOMAIN=yourstore.myshopify.com
43
+ ```
44
+
45
+ Hydrogen exposes this through `context.env` in loaders:
46
+
47
+ ```ts
48
+ // app/root.tsx or any route loader
49
+ export async function loader({ context }: LoaderArgs) {
50
+ return {
51
+ storeDomain: context.env.PUBLIC_STORE_DOMAIN,
52
+ };
53
+ }
54
+ ```
55
+
56
+ ### 3 — Pass `storeDomain` (or `proxyUrl`) to any component
57
+
58
+ Every component accepts either prop — use whichever is more convenient:
29
59
 
30
60
  ```tsx
31
- import { ReviewWidget } from '@revova/hydrogen';
61
+ // Recommended convenience shorthand
62
+ <ReviewWidget storeDomain={storeDomain} productId={product.id} />
63
+
64
+ // Explicit — same result
65
+ <ReviewWidget
66
+ proxyUrl={`https://${storeDomain}/apps/revova`}
67
+ productId={product.id}
68
+ />
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Quick Start
74
+
75
+ ```tsx
76
+ // app/routes/products.$handle.tsx
77
+ import { ReviewWidget, FloatingReviewButton } from '@revova/hydrogen';
78
+
79
+ export async function loader({ context, params }: LoaderArgs) {
80
+ const product = await getProduct(params.handle);
81
+ return {
82
+ product,
83
+ storeDomain: context.env.PUBLIC_STORE_DOMAIN,
84
+ };
85
+ }
32
86
 
33
87
  export default function ProductPage() {
34
- const { product } = useLoaderData<typeof loader>();
88
+ const { product, storeDomain } = useLoaderData<typeof loader>();
35
89
 
36
90
  return (
37
- <ReviewWidget
38
- proxyUrl="/apps/revova"
39
- productId={product.id}
40
- />
91
+ <>
92
+ <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
+ />
103
+ </>
41
104
  );
42
105
  }
43
106
  ```
44
107
 
45
- **Props**
108
+ ---
109
+
110
+ ## Components
111
+
112
+ All components accept either `storeDomain` **or** `proxyUrl` — never both.
113
+
114
+ ### `<ReviewWidget>`
115
+
116
+ Full review list with pagination, sorting, and an inline submission form.
117
+
118
+ ```tsx
119
+ import { ReviewWidget } from '@revova/hydrogen';
120
+
121
+ <ReviewWidget
122
+ storeDomain={storeDomain}
123
+ productId={product.id}
124
+ pageSize={10}
125
+ showForm
126
+ locale="fr"
127
+ />
128
+ ```
46
129
 
47
130
  | Prop | Type | Default | Description |
48
131
  |---|---|---|---|
49
- | `proxyUrl` | `string` | — | App proxy base URL (`/apps/revova`) |
132
+ | `storeDomain` | `string` | — | Your `myshopify.com` domain (e.g. `yourstore.myshopify.com`) |
133
+ | `proxyUrl` | `string` | — | Explicit proxy URL — use instead of `storeDomain` if preferred |
50
134
  | `productId` | `string` | — | Shopify product GID |
51
135
  | `locale` | `string` | — | Locale code for translated reviews (e.g. `fr`) |
52
136
  | `pageSize` | `number` | `10` | Reviews per page |
@@ -65,14 +149,14 @@ import { ReviewForm } from '@revova/hydrogen';
65
149
 
66
150
  // `form` comes from the ReviewsResponse returned by useReviews
67
151
  <ReviewForm
68
- proxyUrl="/apps/revova"
152
+ storeDomain={storeDomain}
69
153
  productId={product.id}
70
154
  form={data.form}
71
155
  onSuccess={() => console.log('submitted')}
72
156
  />
73
157
  ```
74
158
 
75
- **Props:** `proxyUrl`, `productId`, `form` (required — `ResolvedForm` from loader), `onSuccess?`, `className?`
159
+ **Props:** `storeDomain` or `proxyUrl`, `productId`, `form` (required — `ResolvedForm` from loader), `onSuccess?`, `className?`
76
160
 
77
161
  ---
78
162
 
@@ -92,16 +176,15 @@ import { StarRating } from '@revova/hydrogen';
92
176
 
93
177
  ### `<ReviewCount>`
94
178
 
95
- Aggregate rating + review count badge. Fetches from `widget-globals`.
179
+ Aggregate rating + review count badge. Great for product cards and PDP headers.
96
180
 
97
181
  ```tsx
98
182
  import { ReviewCount } from '@revova/hydrogen';
99
183
 
100
- // On a product card or PDP header
101
- <ReviewCount proxyUrl="/apps/revova" starSize={14} />
184
+ <ReviewCount storeDomain={storeDomain} starSize={14} />
102
185
  ```
103
186
 
104
- **Props:** `proxyUrl`, `starColor?`, `starSize?`, `className?`
187
+ **Props:** `storeDomain` or `proxyUrl`, `starColor?`, `starSize?`, `className?`
105
188
 
106
189
  ---
107
190
 
@@ -113,14 +196,14 @@ Auto-advancing carousel of recent reviews. Includes previous/next arrows and dot
113
196
  import { ReviewCarousel } from '@revova/hydrogen';
114
197
 
115
198
  <ReviewCarousel
116
- proxyUrl="/apps/revova"
199
+ storeDomain={storeDomain}
117
200
  limit={10}
118
201
  autoPlay
119
202
  intervalMs={4000}
120
203
  />
121
204
  ```
122
205
 
123
- **Props:** `proxyUrl`, `limit?` (default 10), `autoPlay?` (default `true`), `intervalMs?` (default 4000), `starColor?`, `className?`
206
+ **Props:** `storeDomain` or `proxyUrl`, `limit?` (default 10), `autoPlay?` (default `true`), `intervalMs?` (default 4000), `starColor?`, `className?`
124
207
 
125
208
  ---
126
209
 
@@ -132,13 +215,13 @@ Masonry photo grid of reviews that have images. Clicking any photo opens a light
132
215
  import { ReviewGallery } from '@revova/hydrogen';
133
216
 
134
217
  <ReviewGallery
135
- proxyUrl="/apps/revova"
218
+ storeDomain={storeDomain}
136
219
  columns={3}
137
220
  limit={20}
138
221
  />
139
222
  ```
140
223
 
141
- **Props:** `proxyUrl`, `limit?` (default 20), `columns?` (default 3), `starColor?`, `className?`
224
+ **Props:** `storeDomain` or `proxyUrl`, `limit?` (default 20), `columns?` (default 3), `starColor?`, `className?`
142
225
 
143
226
  ---
144
227
 
@@ -150,12 +233,12 @@ Product Q&A — lists questions with answers and includes an ask-a-question form
150
233
  import { QnAWidget } from '@revova/hydrogen';
151
234
 
152
235
  <QnAWidget
153
- proxyUrl="/apps/revova"
236
+ storeDomain={storeDomain}
154
237
  productId={product.id}
155
238
  />
156
239
  ```
157
240
 
158
- **Props:** `proxyUrl`, `productId`, `className?`
241
+ **Props:** `storeDomain` or `proxyUrl`, `productId`, `className?`
159
242
 
160
243
  ---
161
244
 
@@ -167,16 +250,16 @@ Aggregate rating badge in three styles: `pill` (default), `inline`, or `card`.
167
250
  import { TrustBadge } from '@revova/hydrogen';
168
251
 
169
252
  // Pill — great for footers, headers
170
- <TrustBadge proxyUrl="/apps/revova" style="pill" />
253
+ <TrustBadge storeDomain={storeDomain} style="pill" />
171
254
 
172
255
  // Card — great for landing pages
173
- <TrustBadge proxyUrl="/apps/revova" style="card" />
256
+ <TrustBadge storeDomain={storeDomain} style="card" />
174
257
 
175
258
  // Inline — great inside sentences or product specs
176
- <TrustBadge proxyUrl="/apps/revova" style="inline" />
259
+ <TrustBadge storeDomain={storeDomain} style="inline" />
177
260
  ```
178
261
 
179
- **Props:** `proxyUrl`, `style?` (`'pill' | 'inline' | 'card'`, default `'pill'`), `starColor?`, `className?`
262
+ **Props:** `storeDomain` or `proxyUrl`, `style?` (`'pill' | 'inline' | 'card'`, default `'pill'`), `starColor?`, `className?`
180
263
 
181
264
  ---
182
265
 
@@ -188,33 +271,32 @@ A continuously scrolling marquee of recent review snippets — ideal for homepag
188
271
  import { ReviewTicker } from '@revova/hydrogen';
189
272
 
190
273
  <ReviewTicker
191
- proxyUrl="/apps/revova"
274
+ storeDomain={storeDomain}
192
275
  limit={20}
193
276
  speedSeconds={30}
194
277
  />
195
278
  ```
196
279
 
197
- **Props:** `proxyUrl`, `limit?` (default 20), `speedSeconds?` (default 30), `starColor?`, `className?`
280
+ **Props:** `storeDomain` or `proxyUrl`, `limit?` (default 20), `speedSeconds?` (default 30), `starColor?`, `className?`
198
281
 
199
282
  ---
200
283
 
201
284
  ### `<SocialProofPopup>`
202
285
 
203
- A timed floating popup showing recent reviews one at a time. Appears after a short delay and cycles automatically.
286
+ A timed floating popup showing recent reviews one at a time. Add once to your root layout.
204
287
 
205
288
  ```tsx
206
289
  import { SocialProofPopup } from '@revova/hydrogen';
207
290
 
208
- // Add once to your root layout
209
291
  <SocialProofPopup
210
- proxyUrl="/apps/revova"
292
+ storeDomain={storeDomain}
211
293
  position="bottom-left"
212
294
  intervalMs={8000}
213
295
  displayMs={5000}
214
296
  />
215
297
  ```
216
298
 
217
- **Props:** `proxyUrl`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-left'`), `intervalMs?` (default 8000), `displayMs?` (default 5000), `starColor?`, `className?`
299
+ **Props:** `storeDomain` or `proxyUrl`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-left'`), `intervalMs?` (default 8000), `displayMs?` (default 5000), `starColor?`, `className?`
218
300
 
219
301
  ---
220
302
 
@@ -226,14 +308,14 @@ A sticky tab fixed to the left or right edge of the viewport. Clicking it slides
226
308
  import { FloatingReviewsTab } from '@revova/hydrogen';
227
309
 
228
310
  <FloatingReviewsTab
229
- proxyUrl="/apps/revova"
311
+ storeDomain={storeDomain}
230
312
  label="Reviews"
231
313
  position="right"
232
314
  color="#111827"
233
315
  />
234
316
  ```
235
317
 
236
- **Props:** `proxyUrl`, `label?` (default `'Reviews'`), `position?` (`'left' | 'right'`, default `'right'`), `color?`, `limit?` (default 5), `starColor?`, `className?`
318
+ **Props:** `storeDomain` or `proxyUrl`, `label?` (default `'Reviews'`), `position?` (`'left' | 'right'`, default `'right'`), `color?`, `limit?` (default 5), `starColor?`, `className?`
237
319
 
238
320
  ---
239
321
 
@@ -245,7 +327,7 @@ A fixed "Write a Review" button that opens a modal with the submission form.
245
327
  import { FloatingReviewButton } from '@revova/hydrogen';
246
328
 
247
329
  <FloatingReviewButton
248
- proxyUrl="/apps/revova"
330
+ storeDomain={storeDomain}
249
331
  productId={product.id}
250
332
  text="Write a Review"
251
333
  position="bottom-right"
@@ -253,23 +335,29 @@ import { FloatingReviewButton } from '@revova/hydrogen';
253
335
  />
254
336
  ```
255
337
 
256
- **Props:** `proxyUrl`, `productId`, `text?` (default `'Write a Review'`), `color?`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-right'`), `className?`
338
+ **Props:** `storeDomain` or `proxyUrl`, `productId`, `text?` (default `'Write a Review'`), `color?`, `position?` (`'bottom-left' | 'bottom-right'`, default `'bottom-right'`), `className?`
257
339
 
258
340
  ---
259
341
 
260
342
  ## Hooks
261
343
 
262
- Use hooks to build fully custom UIs while Revova handles data fetching.
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
+ ```
263
351
 
264
352
  ### `useReviews(options)`
265
353
 
266
354
  Paginated reviews for a product with sorting controls.
267
355
 
268
356
  ```tsx
269
- import { useReviews } from '@revova/hydrogen';
357
+ import { useReviews, resolveProxyUrl } from '@revova/hydrogen';
270
358
 
271
359
  const { data, loading, error, setPage, setSort, currentPage, currentSort, refetch } = useReviews({
272
- proxyUrl: '/apps/revova',
360
+ proxyUrl: resolveProxyUrl({ storeDomain }),
273
361
  productId: product.id,
274
362
  limit: 5,
275
363
  sort: 'helpful', // 'recent' | 'helpful' | 'rating_high' | 'rating_low'
@@ -282,10 +370,10 @@ const { data, loading, error, setPage, setSort, currentPage, currentSort, refetc
282
370
  Shop-level stats and recent reviews. Used internally by `<ReviewCount>`, `<TrustBadge>`, `<ReviewCarousel>`, `<ReviewGallery>`, `<ReviewTicker>`, `<SocialProofPopup>`, and `<FloatingReviewsTab>`.
283
371
 
284
372
  ```tsx
285
- import { useWidgetGlobals } from '@revova/hydrogen';
373
+ import { useWidgetGlobals, resolveProxyUrl } from '@revova/hydrogen';
286
374
 
287
375
  const { data, loading, error } = useWidgetGlobals({
288
- proxyUrl: '/apps/revova',
376
+ proxyUrl: resolveProxyUrl({ storeDomain }),
289
377
  limit: 20,
290
378
  });
291
379
 
@@ -300,9 +388,11 @@ const { data, loading, error } = useWidgetGlobals({
300
388
  Submit a new review programmatically.
301
389
 
302
390
  ```tsx
303
- import { useSubmitReview } from '@revova/hydrogen';
391
+ import { useSubmitReview, resolveProxyUrl } from '@revova/hydrogen';
304
392
 
305
- const { submit, submitting, success, error, result, reset } = useSubmitReview('/apps/revova');
393
+ const { submit, submitting, success, error, result, reset } = useSubmitReview(
394
+ resolveProxyUrl({ storeDomain })
395
+ );
306
396
 
307
397
  await submit({
308
398
  productId: 'gid://shopify/Product/123',
@@ -321,7 +411,7 @@ await submit({
321
411
  Fetch and submit Q&A for a product.
322
412
 
323
413
  ```tsx
324
- import { useQnA } from '@revova/hydrogen';
414
+ import { useQnA, resolveProxyUrl } from '@revova/hydrogen';
325
415
 
326
416
  const {
327
417
  data,
@@ -331,9 +421,11 @@ const {
331
421
  submitAnswer,
332
422
  submitState,
333
423
  resetSubmit,
334
- } = useQnA({ proxyUrl: '/apps/revova', productId: product.id });
424
+ } = useQnA({
425
+ proxyUrl: resolveProxyUrl({ storeDomain }),
426
+ productId: product.id,
427
+ });
335
428
 
336
- // Submit a question
337
429
  await submitQuestion({
338
430
  intent: 'question',
339
431
  productId: product.id,
@@ -347,9 +439,9 @@ await submitQuestion({
347
439
  Cast a helpful / not-helpful vote on a review.
348
440
 
349
441
  ```tsx
350
- import { useHelpfulVote } from '@revova/hydrogen';
442
+ import { useHelpfulVote, resolveProxyUrl } from '@revova/hydrogen';
351
443
 
352
- const { vote, loading, voted } = useHelpfulVote('/apps/revova');
444
+ const { vote, loading, voted } = useHelpfulVote(resolveProxyUrl({ storeDomain }));
353
445
 
354
446
  <button onClick={() => vote({ reviewId: review.id, vote: 'helpful' })} disabled={voted}>
355
447
  Helpful ({review.helpfulCount})
package/dist/index.cjs CHANGED
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  SocialProofPopup: () => SocialProofPopup,
43
43
  StarRating: () => StarRating,
44
44
  TrustBadge: () => TrustBadge,
45
+ resolveProxyUrl: () => resolveProxyUrl,
45
46
  useForm: () => useForm,
46
47
  useHelpfulVote: () => useHelpfulVote,
47
48
  useQnA: () => useQnA,
@@ -54,6 +55,14 @@ module.exports = __toCommonJS(index_exports);
54
55
  // src/components/ReviewWidget.tsx
55
56
  var import_react4 = require("react");
56
57
 
58
+ // src/utils.ts
59
+ var PROXY_PATH = "/apps/revova";
60
+ function resolveProxyUrl(props) {
61
+ if (props.storeDomain) return `https://${props.storeDomain}${PROXY_PATH}`;
62
+ if (props.proxyUrl) return props.proxyUrl;
63
+ throw new Error("[Revova] Provide either proxyUrl or storeDomain.");
64
+ }
65
+
57
66
  // src/hooks/useReviews.ts
58
67
  var import_react = require("react");
59
68
  function useReviews({
@@ -479,8 +488,9 @@ function FieldRenderer(props) {
479
488
  return null;
480
489
  }
481
490
  }
482
- function ReviewForm({ proxyUrl, productId, form, onSuccess, className }) {
483
- const { submit, submitting, success, error, result } = useSubmitReview(proxyUrl);
491
+ function ReviewForm({ proxyUrl, storeDomain, productId, form, onSuccess, className }) {
492
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
493
+ const { submit, submitting, success, error, result } = useSubmitReview(resolvedUrl);
484
494
  const [answers, setAnswers] = (0, import_react3.useState)({});
485
495
  const [email, setEmail] = (0, import_react3.useState)("");
486
496
  const starField = form.fields.find((f) => f.type === "STAR_RATING");
@@ -588,6 +598,7 @@ function ReviewForm({ proxyUrl, productId, form, onSuccess, className }) {
588
598
  var import_jsx_runtime3 = require("react/jsx-runtime");
589
599
  function ReviewWidget({
590
600
  proxyUrl,
601
+ storeDomain,
591
602
  productId,
592
603
  locale,
593
604
  pageSize = 10,
@@ -595,9 +606,10 @@ function ReviewWidget({
595
606
  starColor,
596
607
  className
597
608
  }) {
609
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
598
610
  const [showingForm, setShowingForm] = (0, import_react4.useState)(false);
599
611
  const { data, loading, error, setPage, setSort, currentPage, currentSort } = useReviews({
600
- proxyUrl,
612
+ proxyUrl: resolvedUrl,
601
613
  productId,
602
614
  limit: pageSize,
603
615
  ...locale !== void 0 ? { locale } : {}
@@ -632,7 +644,7 @@ function ReviewWidget({
632
644
  showingForm && form && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginBottom: 24 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
633
645
  ReviewForm,
634
646
  {
635
- proxyUrl,
647
+ proxyUrl: resolvedUrl,
636
648
  productId,
637
649
  form,
638
650
  onSuccess: () => setShowingForm(false)
@@ -715,8 +727,9 @@ function useWidgetGlobals({ proxyUrl, limit = 20 }) {
715
727
 
716
728
  // src/components/ReviewCount.tsx
717
729
  var import_jsx_runtime4 = require("react/jsx-runtime");
718
- function ReviewCount({ proxyUrl, starColor, starSize, className }) {
719
- const { data, loading } = useWidgetGlobals({ proxyUrl });
730
+ function ReviewCount({ proxyUrl, storeDomain, starColor, starSize, className }) {
731
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
732
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl });
720
733
  if (loading || !data?.stats?.averageRating) return null;
721
734
  const avg = parseFloat(data.stats.averageRating);
722
735
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className, style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
@@ -735,13 +748,15 @@ var import_react6 = __toESM(require("react"), 1);
735
748
  var import_jsx_runtime5 = require("react/jsx-runtime");
736
749
  function ReviewCarousel({
737
750
  proxyUrl,
751
+ storeDomain,
738
752
  limit = 10,
739
753
  autoPlay = true,
740
754
  intervalMs = 4e3,
741
755
  starColor,
742
756
  className
743
757
  }) {
744
- const { data, loading } = useWidgetGlobals({ proxyUrl, limit });
758
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
759
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
745
760
  const [index, setIndex] = (0, import_react6.useState)(0);
746
761
  const reviews = data?.reviews ?? [];
747
762
  import_react6.default.useEffect(() => {
@@ -790,12 +805,14 @@ var import_react7 = require("react");
790
805
  var import_jsx_runtime6 = require("react/jsx-runtime");
791
806
  function ReviewGallery({
792
807
  proxyUrl,
808
+ storeDomain,
793
809
  limit = 20,
794
810
  columns = 3,
795
811
  starColor,
796
812
  className
797
813
  }) {
798
- const { data, loading } = useWidgetGlobals({ proxyUrl, limit });
814
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
815
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
799
816
  const [lightbox, setLightbox] = (0, import_react7.useState)(null);
800
817
  const items = (data?.reviews ?? []).filter((r) => r.image).map((r) => ({ url: r.image, review: r })).slice(0, limit);
801
818
  if (loading) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className, children: "Loading gallery\u2026" });
@@ -979,9 +996,10 @@ function useQnA({ proxyUrl, productId, page: initialPage = 1, sort = "recent" })
979
996
 
980
997
  // src/components/QnAWidget.tsx
981
998
  var import_jsx_runtime7 = require("react/jsx-runtime");
982
- function QnAWidget({ proxyUrl, productId, className }) {
999
+ function QnAWidget({ proxyUrl, storeDomain, productId, className }) {
1000
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
983
1001
  const { data, loading, error, setPage, currentPage, submitQuestion, submitState, resetSubmit } = useQnA({
984
- proxyUrl,
1002
+ proxyUrl: resolvedUrl,
985
1003
  productId
986
1004
  });
987
1005
  const [showForm, setShowForm] = (0, import_react9.useState)(false);
@@ -1074,13 +1092,15 @@ var import_react10 = require("react");
1074
1092
  var import_jsx_runtime8 = require("react/jsx-runtime");
1075
1093
  function SocialProofPopup({
1076
1094
  proxyUrl,
1095
+ storeDomain,
1077
1096
  position = "bottom-left",
1078
1097
  intervalMs = 8e3,
1079
1098
  displayMs = 5e3,
1080
1099
  starColor,
1081
1100
  className
1082
1101
  }) {
1083
- const { data } = useWidgetGlobals({ proxyUrl });
1102
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1103
+ const { data } = useWidgetGlobals({ proxyUrl: resolvedUrl });
1084
1104
  const [current, setCurrent] = (0, import_react10.useState)(null);
1085
1105
  const [visible, setVisible] = (0, import_react10.useState)(false);
1086
1106
  const [dismissed, setDismissed] = (0, import_react10.useState)(false);
@@ -1183,12 +1203,14 @@ var import_react11 = require("react");
1183
1203
  var import_jsx_runtime9 = require("react/jsx-runtime");
1184
1204
  function ReviewTicker({
1185
1205
  proxyUrl,
1206
+ storeDomain,
1186
1207
  limit = 20,
1187
1208
  speedSeconds = 30,
1188
1209
  starColor,
1189
1210
  className
1190
1211
  }) {
1191
- const { data, loading } = useWidgetGlobals({ proxyUrl, limit });
1212
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1213
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
1192
1214
  const trackRef = (0, import_react11.useRef)(null);
1193
1215
  const reviews = data?.reviews ?? [];
1194
1216
  if (loading || reviews.length === 0) return null;
@@ -1250,6 +1272,7 @@ var import_react12 = require("react");
1250
1272
  var import_jsx_runtime10 = require("react/jsx-runtime");
1251
1273
  function FloatingReviewsTab({
1252
1274
  proxyUrl,
1275
+ storeDomain,
1253
1276
  label = "Reviews",
1254
1277
  position = "right",
1255
1278
  color = "#111827",
@@ -1257,7 +1280,8 @@ function FloatingReviewsTab({
1257
1280
  starColor,
1258
1281
  className
1259
1282
  }) {
1260
- const { data } = useWidgetGlobals({ proxyUrl, limit });
1283
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1284
+ const { data } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
1261
1285
  const [open, setOpen] = (0, import_react12.useState)(false);
1262
1286
  const reviews = data?.reviews ?? [];
1263
1287
  const stats = data?.stats;
@@ -1334,8 +1358,9 @@ function FloatingReviewsTab({
1334
1358
 
1335
1359
  // src/components/TrustBadge.tsx
1336
1360
  var import_jsx_runtime11 = require("react/jsx-runtime");
1337
- function TrustBadge({ proxyUrl, style: badgeStyle = "pill", starColor, className }) {
1338
- const { data, loading } = useWidgetGlobals({ proxyUrl });
1361
+ function TrustBadge({ proxyUrl, storeDomain, style: badgeStyle = "pill", starColor, className }) {
1362
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1363
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl });
1339
1364
  if (loading || !data?.stats?.averageRating) return null;
1340
1365
  const avg = parseFloat(data.stats.averageRating);
1341
1366
  const count = data.stats.totalReviews;
@@ -1441,14 +1466,16 @@ function useForm(proxyUrl, productId) {
1441
1466
  var import_jsx_runtime12 = require("react/jsx-runtime");
1442
1467
  function FloatingReviewButton({
1443
1468
  proxyUrl,
1469
+ storeDomain,
1444
1470
  productId,
1445
1471
  text = "Write a Review",
1446
1472
  color = "#111827",
1447
1473
  position = "bottom-right",
1448
1474
  className
1449
1475
  }) {
1476
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1450
1477
  const [open, setOpen] = (0, import_react14.useState)(false);
1451
- const { form, loading } = useForm(proxyUrl, productId);
1478
+ const { form, loading } = useForm(resolvedUrl, productId);
1452
1479
  const posStyle = position === "bottom-right" ? { bottom: 24, right: 24 } : { bottom: 24, left: 24 };
1453
1480
  if (!loading && !form) return null;
1454
1481
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
@@ -1526,7 +1553,7 @@ function FloatingReviewButton({
1526
1553
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1527
1554
  ReviewForm,
1528
1555
  {
1529
- proxyUrl,
1556
+ proxyUrl: resolvedUrl,
1530
1557
  productId,
1531
1558
  form,
1532
1559
  onSuccess: () => setOpen(false)
@@ -1578,6 +1605,7 @@ function useHelpfulVote(proxyUrl) {
1578
1605
  SocialProofPopup,
1579
1606
  StarRating,
1580
1607
  TrustBadge,
1608
+ resolveProxyUrl,
1581
1609
  useForm,
1582
1610
  useHelpfulVote,
1583
1611
  useQnA,
package/dist/index.d.cts CHANGED
@@ -1,16 +1,5 @@
1
1
  import React from 'react';
2
2
 
3
- type ReviewWidgetProps = {
4
- proxyUrl: string;
5
- productId: string;
6
- locale?: string;
7
- pageSize?: number;
8
- showForm?: boolean;
9
- starColor?: string;
10
- className?: string;
11
- };
12
- declare function ReviewWidget({ proxyUrl, productId, locale, pageSize, showForm, starColor, className, }: ReviewWidgetProps): React.JSX.Element | null;
13
-
14
3
  type ReviewMedia = {
15
4
  url: string;
16
5
  mimeType: string;
@@ -162,6 +151,25 @@ type HelpfulVoteResponse = {
162
151
  helpfulCount: number;
163
152
  };
164
153
  type SortKey = 'recent' | 'helpful' | 'rating_high' | 'rating_low';
154
+ /**
155
+ * Every public component and hook accepts either an explicit proxyUrl or a
156
+ * storeDomain shorthand. Provide exactly one of the two.
157
+ *
158
+ * @example
159
+ * // explicit
160
+ * proxyUrl={`https://${env.PUBLIC_STORE_DOMAIN}/apps/revova`}
161
+ *
162
+ * @example
163
+ * // shorthand (Hydrogen-friendly)
164
+ * storeDomain={env.PUBLIC_STORE_DOMAIN}
165
+ */
166
+ type ProxyProps = {
167
+ proxyUrl: string;
168
+ storeDomain?: never;
169
+ } | {
170
+ storeDomain: string;
171
+ proxyUrl?: never;
172
+ };
165
173
  type UseReviewsOptions = {
166
174
  proxyUrl: string;
167
175
  productId: string;
@@ -223,22 +231,30 @@ type UseQnAOptions = {
223
231
  sort?: 'recent' | 'helpful';
224
232
  };
225
233
 
226
- type ReviewFormProps = {
227
- proxyUrl: string;
234
+ type ReviewWidgetProps = ProxyProps & {
235
+ productId: string;
236
+ locale?: string;
237
+ pageSize?: number;
238
+ showForm?: boolean;
239
+ starColor?: string;
240
+ className?: string;
241
+ };
242
+ declare function ReviewWidget({ proxyUrl, storeDomain, productId, locale, pageSize, showForm, starColor, className, }: ReviewWidgetProps): React.JSX.Element | null;
243
+
244
+ type ReviewFormProps = ProxyProps & {
228
245
  productId: string;
229
246
  form: ResolvedForm;
230
247
  onSuccess?: () => void;
231
248
  className?: string;
232
249
  };
233
- declare function ReviewForm({ proxyUrl, productId, form, onSuccess, className }: ReviewFormProps): React.JSX.Element;
250
+ declare function ReviewForm({ proxyUrl, storeDomain, productId, form, onSuccess, className }: ReviewFormProps): React.JSX.Element;
234
251
 
235
- type ReviewCountProps = {
236
- proxyUrl: string;
252
+ type ReviewCountProps = ProxyProps & {
237
253
  starColor?: string;
238
254
  starSize?: number;
239
255
  className?: string;
240
256
  };
241
- declare function ReviewCount({ proxyUrl, starColor, starSize, className }: ReviewCountProps): React.JSX.Element | null;
257
+ declare function ReviewCount({ proxyUrl, storeDomain, starColor, starSize, className }: ReviewCountProps): React.JSX.Element | null;
242
258
 
243
259
  type StarRatingProps = {
244
260
  rating: number;
@@ -249,53 +265,47 @@ type StarRatingProps = {
249
265
  };
250
266
  declare function StarRating({ rating, max, size, color, className }: StarRatingProps): React.JSX.Element;
251
267
 
252
- type ReviewCarouselProps = {
253
- proxyUrl: string;
268
+ type ReviewCarouselProps = ProxyProps & {
254
269
  limit?: number;
255
270
  autoPlay?: boolean;
256
271
  intervalMs?: number;
257
272
  starColor?: string;
258
273
  className?: string;
259
274
  };
260
- declare function ReviewCarousel({ proxyUrl, limit, autoPlay, intervalMs, starColor, className, }: ReviewCarouselProps): React.JSX.Element | null;
275
+ declare function ReviewCarousel({ proxyUrl, storeDomain, limit, autoPlay, intervalMs, starColor, className, }: ReviewCarouselProps): React.JSX.Element | null;
261
276
 
262
- type ReviewGalleryProps = {
263
- proxyUrl: string;
277
+ type ReviewGalleryProps = ProxyProps & {
264
278
  limit?: number;
265
279
  columns?: number;
266
280
  starColor?: string;
267
281
  className?: string;
268
282
  };
269
- declare function ReviewGallery({ proxyUrl, limit, columns, starColor, className, }: ReviewGalleryProps): React.JSX.Element | null;
283
+ declare function ReviewGallery({ proxyUrl, storeDomain, limit, columns, starColor, className, }: ReviewGalleryProps): React.JSX.Element | null;
270
284
 
271
- type QnAWidgetProps = {
272
- proxyUrl: string;
285
+ type QnAWidgetProps = ProxyProps & {
273
286
  productId: string;
274
287
  className?: string;
275
288
  };
276
- declare function QnAWidget({ proxyUrl, productId, className }: QnAWidgetProps): React.JSX.Element | null;
289
+ declare function QnAWidget({ proxyUrl, storeDomain, productId, className }: QnAWidgetProps): React.JSX.Element | null;
277
290
 
278
- type SocialProofPopupProps = {
279
- proxyUrl: string;
291
+ type SocialProofPopupProps = ProxyProps & {
280
292
  position?: 'bottom-left' | 'bottom-right';
281
293
  intervalMs?: number;
282
294
  displayMs?: number;
283
295
  starColor?: string;
284
296
  className?: string;
285
297
  };
286
- declare function SocialProofPopup({ proxyUrl, position, intervalMs, displayMs, starColor, className, }: SocialProofPopupProps): React.JSX.Element | null;
298
+ declare function SocialProofPopup({ proxyUrl, storeDomain, position, intervalMs, displayMs, starColor, className, }: SocialProofPopupProps): React.JSX.Element | null;
287
299
 
288
- type ReviewTickerProps = {
289
- proxyUrl: string;
300
+ type ReviewTickerProps = ProxyProps & {
290
301
  limit?: number;
291
302
  speedSeconds?: number;
292
303
  starColor?: string;
293
304
  className?: string;
294
305
  };
295
- declare function ReviewTicker({ proxyUrl, limit, speedSeconds, starColor, className, }: ReviewTickerProps): React.JSX.Element | null;
306
+ declare function ReviewTicker({ proxyUrl, storeDomain, limit, speedSeconds, starColor, className, }: ReviewTickerProps): React.JSX.Element | null;
296
307
 
297
- type FloatingReviewsTabProps = {
298
- proxyUrl: string;
308
+ type FloatingReviewsTabProps = ProxyProps & {
299
309
  label?: string;
300
310
  position?: 'left' | 'right';
301
311
  color?: string;
@@ -303,25 +313,28 @@ type FloatingReviewsTabProps = {
303
313
  starColor?: string;
304
314
  className?: string;
305
315
  };
306
- declare function FloatingReviewsTab({ proxyUrl, label, position, color, limit, starColor, className, }: FloatingReviewsTabProps): React.JSX.Element;
316
+ declare function FloatingReviewsTab({ proxyUrl, storeDomain, label, position, color, limit, starColor, className, }: FloatingReviewsTabProps): React.JSX.Element;
307
317
 
308
- type TrustBadgeProps = {
309
- proxyUrl: string;
318
+ type TrustBadgeProps = ProxyProps & {
310
319
  style?: 'inline' | 'pill' | 'card';
311
320
  starColor?: string;
312
321
  className?: string;
313
322
  };
314
- declare function TrustBadge({ proxyUrl, style: badgeStyle, starColor, className }: TrustBadgeProps): React.JSX.Element | null;
323
+ declare function TrustBadge({ proxyUrl, storeDomain, style: badgeStyle, starColor, className }: TrustBadgeProps): React.JSX.Element | null;
315
324
 
316
- type FloatingReviewButtonProps = {
317
- proxyUrl: string;
325
+ type FloatingReviewButtonProps = ProxyProps & {
318
326
  productId: string;
319
327
  text?: string;
320
328
  color?: string;
321
329
  position?: 'bottom-left' | 'bottom-right';
322
330
  className?: string;
323
331
  };
324
- declare function FloatingReviewButton({ proxyUrl, productId, text, color, position, className, }: FloatingReviewButtonProps): React.JSX.Element | null;
332
+ declare function FloatingReviewButton({ proxyUrl, storeDomain, productId, text, color, position, className, }: FloatingReviewButtonProps): React.JSX.Element | null;
333
+
334
+ declare function resolveProxyUrl(props: {
335
+ proxyUrl?: string | undefined;
336
+ storeDomain?: string | undefined;
337
+ }): string;
325
338
 
326
339
  type State$3 = {
327
340
  data: ReviewsResponse | null;
@@ -395,4 +408,4 @@ type State = {
395
408
  */
396
409
  declare function useForm(proxyUrl: string, productId: string): State;
397
410
 
398
- export { type AdminReply, type AttributeRating, FloatingReviewButton, FloatingReviewsTab, type FormField, type GlobalReview, type HelpfulVotePayload, type HelpfulVoteResponse, type ProductQuestion, type QnAAnswer, type QnAPagination, type QnAResponse, QnAWidget, type ResolvedForm, type Review, ReviewCarousel, ReviewCount, type ReviewFieldAnswer, ReviewForm, ReviewGallery, type ReviewMedia, ReviewTicker, ReviewWidget, type ReviewsPagination, type ReviewsResponse, type ShopStats, SocialProofPopup, type SortKey, StarRating, type SubmitAnswerPayload, type SubmitQuestionPayload, type SubmitReviewPayload, type SubmitReviewResponse, TrustBadge, type UseQnAOptions, type UseReviewsOptions, type UseWidgetGlobalsOptions, type WidgetConfig, type WidgetGlobalsResponse, type WidgetTheme, useForm, useHelpfulVote, useQnA, useReviews, useSubmitReview, useWidgetGlobals };
411
+ export { type AdminReply, type AttributeRating, FloatingReviewButton, FloatingReviewsTab, type FormField, type GlobalReview, type HelpfulVotePayload, type HelpfulVoteResponse, type ProductQuestion, type ProxyProps, type QnAAnswer, type QnAPagination, type QnAResponse, QnAWidget, type ResolvedForm, type Review, ReviewCarousel, ReviewCount, type ReviewFieldAnswer, ReviewForm, ReviewGallery, type ReviewMedia, ReviewTicker, ReviewWidget, type ReviewsPagination, type ReviewsResponse, type ShopStats, SocialProofPopup, type SortKey, StarRating, type SubmitAnswerPayload, type SubmitQuestionPayload, type SubmitReviewPayload, type SubmitReviewResponse, TrustBadge, type UseQnAOptions, type UseReviewsOptions, type UseWidgetGlobalsOptions, type WidgetConfig, type WidgetGlobalsResponse, type WidgetTheme, resolveProxyUrl, useForm, useHelpfulVote, useQnA, useReviews, useSubmitReview, useWidgetGlobals };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,5 @@
1
1
  import React from 'react';
2
2
 
3
- type ReviewWidgetProps = {
4
- proxyUrl: string;
5
- productId: string;
6
- locale?: string;
7
- pageSize?: number;
8
- showForm?: boolean;
9
- starColor?: string;
10
- className?: string;
11
- };
12
- declare function ReviewWidget({ proxyUrl, productId, locale, pageSize, showForm, starColor, className, }: ReviewWidgetProps): React.JSX.Element | null;
13
-
14
3
  type ReviewMedia = {
15
4
  url: string;
16
5
  mimeType: string;
@@ -162,6 +151,25 @@ type HelpfulVoteResponse = {
162
151
  helpfulCount: number;
163
152
  };
164
153
  type SortKey = 'recent' | 'helpful' | 'rating_high' | 'rating_low';
154
+ /**
155
+ * Every public component and hook accepts either an explicit proxyUrl or a
156
+ * storeDomain shorthand. Provide exactly one of the two.
157
+ *
158
+ * @example
159
+ * // explicit
160
+ * proxyUrl={`https://${env.PUBLIC_STORE_DOMAIN}/apps/revova`}
161
+ *
162
+ * @example
163
+ * // shorthand (Hydrogen-friendly)
164
+ * storeDomain={env.PUBLIC_STORE_DOMAIN}
165
+ */
166
+ type ProxyProps = {
167
+ proxyUrl: string;
168
+ storeDomain?: never;
169
+ } | {
170
+ storeDomain: string;
171
+ proxyUrl?: never;
172
+ };
165
173
  type UseReviewsOptions = {
166
174
  proxyUrl: string;
167
175
  productId: string;
@@ -223,22 +231,30 @@ type UseQnAOptions = {
223
231
  sort?: 'recent' | 'helpful';
224
232
  };
225
233
 
226
- type ReviewFormProps = {
227
- proxyUrl: string;
234
+ type ReviewWidgetProps = ProxyProps & {
235
+ productId: string;
236
+ locale?: string;
237
+ pageSize?: number;
238
+ showForm?: boolean;
239
+ starColor?: string;
240
+ className?: string;
241
+ };
242
+ declare function ReviewWidget({ proxyUrl, storeDomain, productId, locale, pageSize, showForm, starColor, className, }: ReviewWidgetProps): React.JSX.Element | null;
243
+
244
+ type ReviewFormProps = ProxyProps & {
228
245
  productId: string;
229
246
  form: ResolvedForm;
230
247
  onSuccess?: () => void;
231
248
  className?: string;
232
249
  };
233
- declare function ReviewForm({ proxyUrl, productId, form, onSuccess, className }: ReviewFormProps): React.JSX.Element;
250
+ declare function ReviewForm({ proxyUrl, storeDomain, productId, form, onSuccess, className }: ReviewFormProps): React.JSX.Element;
234
251
 
235
- type ReviewCountProps = {
236
- proxyUrl: string;
252
+ type ReviewCountProps = ProxyProps & {
237
253
  starColor?: string;
238
254
  starSize?: number;
239
255
  className?: string;
240
256
  };
241
- declare function ReviewCount({ proxyUrl, starColor, starSize, className }: ReviewCountProps): React.JSX.Element | null;
257
+ declare function ReviewCount({ proxyUrl, storeDomain, starColor, starSize, className }: ReviewCountProps): React.JSX.Element | null;
242
258
 
243
259
  type StarRatingProps = {
244
260
  rating: number;
@@ -249,53 +265,47 @@ type StarRatingProps = {
249
265
  };
250
266
  declare function StarRating({ rating, max, size, color, className }: StarRatingProps): React.JSX.Element;
251
267
 
252
- type ReviewCarouselProps = {
253
- proxyUrl: string;
268
+ type ReviewCarouselProps = ProxyProps & {
254
269
  limit?: number;
255
270
  autoPlay?: boolean;
256
271
  intervalMs?: number;
257
272
  starColor?: string;
258
273
  className?: string;
259
274
  };
260
- declare function ReviewCarousel({ proxyUrl, limit, autoPlay, intervalMs, starColor, className, }: ReviewCarouselProps): React.JSX.Element | null;
275
+ declare function ReviewCarousel({ proxyUrl, storeDomain, limit, autoPlay, intervalMs, starColor, className, }: ReviewCarouselProps): React.JSX.Element | null;
261
276
 
262
- type ReviewGalleryProps = {
263
- proxyUrl: string;
277
+ type ReviewGalleryProps = ProxyProps & {
264
278
  limit?: number;
265
279
  columns?: number;
266
280
  starColor?: string;
267
281
  className?: string;
268
282
  };
269
- declare function ReviewGallery({ proxyUrl, limit, columns, starColor, className, }: ReviewGalleryProps): React.JSX.Element | null;
283
+ declare function ReviewGallery({ proxyUrl, storeDomain, limit, columns, starColor, className, }: ReviewGalleryProps): React.JSX.Element | null;
270
284
 
271
- type QnAWidgetProps = {
272
- proxyUrl: string;
285
+ type QnAWidgetProps = ProxyProps & {
273
286
  productId: string;
274
287
  className?: string;
275
288
  };
276
- declare function QnAWidget({ proxyUrl, productId, className }: QnAWidgetProps): React.JSX.Element | null;
289
+ declare function QnAWidget({ proxyUrl, storeDomain, productId, className }: QnAWidgetProps): React.JSX.Element | null;
277
290
 
278
- type SocialProofPopupProps = {
279
- proxyUrl: string;
291
+ type SocialProofPopupProps = ProxyProps & {
280
292
  position?: 'bottom-left' | 'bottom-right';
281
293
  intervalMs?: number;
282
294
  displayMs?: number;
283
295
  starColor?: string;
284
296
  className?: string;
285
297
  };
286
- declare function SocialProofPopup({ proxyUrl, position, intervalMs, displayMs, starColor, className, }: SocialProofPopupProps): React.JSX.Element | null;
298
+ declare function SocialProofPopup({ proxyUrl, storeDomain, position, intervalMs, displayMs, starColor, className, }: SocialProofPopupProps): React.JSX.Element | null;
287
299
 
288
- type ReviewTickerProps = {
289
- proxyUrl: string;
300
+ type ReviewTickerProps = ProxyProps & {
290
301
  limit?: number;
291
302
  speedSeconds?: number;
292
303
  starColor?: string;
293
304
  className?: string;
294
305
  };
295
- declare function ReviewTicker({ proxyUrl, limit, speedSeconds, starColor, className, }: ReviewTickerProps): React.JSX.Element | null;
306
+ declare function ReviewTicker({ proxyUrl, storeDomain, limit, speedSeconds, starColor, className, }: ReviewTickerProps): React.JSX.Element | null;
296
307
 
297
- type FloatingReviewsTabProps = {
298
- proxyUrl: string;
308
+ type FloatingReviewsTabProps = ProxyProps & {
299
309
  label?: string;
300
310
  position?: 'left' | 'right';
301
311
  color?: string;
@@ -303,25 +313,28 @@ type FloatingReviewsTabProps = {
303
313
  starColor?: string;
304
314
  className?: string;
305
315
  };
306
- declare function FloatingReviewsTab({ proxyUrl, label, position, color, limit, starColor, className, }: FloatingReviewsTabProps): React.JSX.Element;
316
+ declare function FloatingReviewsTab({ proxyUrl, storeDomain, label, position, color, limit, starColor, className, }: FloatingReviewsTabProps): React.JSX.Element;
307
317
 
308
- type TrustBadgeProps = {
309
- proxyUrl: string;
318
+ type TrustBadgeProps = ProxyProps & {
310
319
  style?: 'inline' | 'pill' | 'card';
311
320
  starColor?: string;
312
321
  className?: string;
313
322
  };
314
- declare function TrustBadge({ proxyUrl, style: badgeStyle, starColor, className }: TrustBadgeProps): React.JSX.Element | null;
323
+ declare function TrustBadge({ proxyUrl, storeDomain, style: badgeStyle, starColor, className }: TrustBadgeProps): React.JSX.Element | null;
315
324
 
316
- type FloatingReviewButtonProps = {
317
- proxyUrl: string;
325
+ type FloatingReviewButtonProps = ProxyProps & {
318
326
  productId: string;
319
327
  text?: string;
320
328
  color?: string;
321
329
  position?: 'bottom-left' | 'bottom-right';
322
330
  className?: string;
323
331
  };
324
- declare function FloatingReviewButton({ proxyUrl, productId, text, color, position, className, }: FloatingReviewButtonProps): React.JSX.Element | null;
332
+ declare function FloatingReviewButton({ proxyUrl, storeDomain, productId, text, color, position, className, }: FloatingReviewButtonProps): React.JSX.Element | null;
333
+
334
+ declare function resolveProxyUrl(props: {
335
+ proxyUrl?: string | undefined;
336
+ storeDomain?: string | undefined;
337
+ }): string;
325
338
 
326
339
  type State$3 = {
327
340
  data: ReviewsResponse | null;
@@ -395,4 +408,4 @@ type State = {
395
408
  */
396
409
  declare function useForm(proxyUrl: string, productId: string): State;
397
410
 
398
- export { type AdminReply, type AttributeRating, FloatingReviewButton, FloatingReviewsTab, type FormField, type GlobalReview, type HelpfulVotePayload, type HelpfulVoteResponse, type ProductQuestion, type QnAAnswer, type QnAPagination, type QnAResponse, QnAWidget, type ResolvedForm, type Review, ReviewCarousel, ReviewCount, type ReviewFieldAnswer, ReviewForm, ReviewGallery, type ReviewMedia, ReviewTicker, ReviewWidget, type ReviewsPagination, type ReviewsResponse, type ShopStats, SocialProofPopup, type SortKey, StarRating, type SubmitAnswerPayload, type SubmitQuestionPayload, type SubmitReviewPayload, type SubmitReviewResponse, TrustBadge, type UseQnAOptions, type UseReviewsOptions, type UseWidgetGlobalsOptions, type WidgetConfig, type WidgetGlobalsResponse, type WidgetTheme, useForm, useHelpfulVote, useQnA, useReviews, useSubmitReview, useWidgetGlobals };
411
+ export { type AdminReply, type AttributeRating, FloatingReviewButton, FloatingReviewsTab, type FormField, type GlobalReview, type HelpfulVotePayload, type HelpfulVoteResponse, type ProductQuestion, type ProxyProps, type QnAAnswer, type QnAPagination, type QnAResponse, QnAWidget, type ResolvedForm, type Review, ReviewCarousel, ReviewCount, type ReviewFieldAnswer, ReviewForm, ReviewGallery, type ReviewMedia, ReviewTicker, ReviewWidget, type ReviewsPagination, type ReviewsResponse, type ShopStats, SocialProofPopup, type SortKey, StarRating, type SubmitAnswerPayload, type SubmitQuestionPayload, type SubmitReviewPayload, type SubmitReviewResponse, TrustBadge, type UseQnAOptions, type UseReviewsOptions, type UseWidgetGlobalsOptions, type WidgetConfig, type WidgetGlobalsResponse, type WidgetTheme, resolveProxyUrl, useForm, useHelpfulVote, useQnA, useReviews, useSubmitReview, useWidgetGlobals };
package/dist/index.js CHANGED
@@ -1,6 +1,14 @@
1
1
  // src/components/ReviewWidget.tsx
2
2
  import { useState as useState4 } from "react";
3
3
 
4
+ // src/utils.ts
5
+ var PROXY_PATH = "/apps/revova";
6
+ function resolveProxyUrl(props) {
7
+ if (props.storeDomain) return `https://${props.storeDomain}${PROXY_PATH}`;
8
+ if (props.proxyUrl) return props.proxyUrl;
9
+ throw new Error("[Revova] Provide either proxyUrl or storeDomain.");
10
+ }
11
+
4
12
  // src/hooks/useReviews.ts
5
13
  import { useEffect, useState, useCallback } from "react";
6
14
  function useReviews({
@@ -426,8 +434,9 @@ function FieldRenderer(props) {
426
434
  return null;
427
435
  }
428
436
  }
429
- function ReviewForm({ proxyUrl, productId, form, onSuccess, className }) {
430
- const { submit, submitting, success, error, result } = useSubmitReview(proxyUrl);
437
+ function ReviewForm({ proxyUrl, storeDomain, productId, form, onSuccess, className }) {
438
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
439
+ const { submit, submitting, success, error, result } = useSubmitReview(resolvedUrl);
431
440
  const [answers, setAnswers] = useState3({});
432
441
  const [email, setEmail] = useState3("");
433
442
  const starField = form.fields.find((f) => f.type === "STAR_RATING");
@@ -535,6 +544,7 @@ function ReviewForm({ proxyUrl, productId, form, onSuccess, className }) {
535
544
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
536
545
  function ReviewWidget({
537
546
  proxyUrl,
547
+ storeDomain,
538
548
  productId,
539
549
  locale,
540
550
  pageSize = 10,
@@ -542,9 +552,10 @@ function ReviewWidget({
542
552
  starColor,
543
553
  className
544
554
  }) {
555
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
545
556
  const [showingForm, setShowingForm] = useState4(false);
546
557
  const { data, loading, error, setPage, setSort, currentPage, currentSort } = useReviews({
547
- proxyUrl,
558
+ proxyUrl: resolvedUrl,
548
559
  productId,
549
560
  limit: pageSize,
550
561
  ...locale !== void 0 ? { locale } : {}
@@ -579,7 +590,7 @@ function ReviewWidget({
579
590
  showingForm && form && /* @__PURE__ */ jsx3("div", { style: { marginBottom: 24 }, children: /* @__PURE__ */ jsx3(
580
591
  ReviewForm,
581
592
  {
582
- proxyUrl,
593
+ proxyUrl: resolvedUrl,
583
594
  productId,
584
595
  form,
585
596
  onSuccess: () => setShowingForm(false)
@@ -662,8 +673,9 @@ function useWidgetGlobals({ proxyUrl, limit = 20 }) {
662
673
 
663
674
  // src/components/ReviewCount.tsx
664
675
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
665
- function ReviewCount({ proxyUrl, starColor, starSize, className }) {
666
- const { data, loading } = useWidgetGlobals({ proxyUrl });
676
+ function ReviewCount({ proxyUrl, storeDomain, starColor, starSize, className }) {
677
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
678
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl });
667
679
  if (loading || !data?.stats?.averageRating) return null;
668
680
  const avg = parseFloat(data.stats.averageRating);
669
681
  return /* @__PURE__ */ jsxs4("span", { className, style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
@@ -682,13 +694,15 @@ import React3, { useState as useState6 } from "react";
682
694
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
683
695
  function ReviewCarousel({
684
696
  proxyUrl,
697
+ storeDomain,
685
698
  limit = 10,
686
699
  autoPlay = true,
687
700
  intervalMs = 4e3,
688
701
  starColor,
689
702
  className
690
703
  }) {
691
- const { data, loading } = useWidgetGlobals({ proxyUrl, limit });
704
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
705
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
692
706
  const [index, setIndex] = useState6(0);
693
707
  const reviews = data?.reviews ?? [];
694
708
  React3.useEffect(() => {
@@ -737,12 +751,14 @@ import { useState as useState7 } from "react";
737
751
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
738
752
  function ReviewGallery({
739
753
  proxyUrl,
754
+ storeDomain,
740
755
  limit = 20,
741
756
  columns = 3,
742
757
  starColor,
743
758
  className
744
759
  }) {
745
- const { data, loading } = useWidgetGlobals({ proxyUrl, limit });
760
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
761
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
746
762
  const [lightbox, setLightbox] = useState7(null);
747
763
  const items = (data?.reviews ?? []).filter((r) => r.image).map((r) => ({ url: r.image, review: r })).slice(0, limit);
748
764
  if (loading) return /* @__PURE__ */ jsx6("div", { className, children: "Loading gallery\u2026" });
@@ -926,9 +942,10 @@ function useQnA({ proxyUrl, productId, page: initialPage = 1, sort = "recent" })
926
942
 
927
943
  // src/components/QnAWidget.tsx
928
944
  import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
929
- function QnAWidget({ proxyUrl, productId, className }) {
945
+ function QnAWidget({ proxyUrl, storeDomain, productId, className }) {
946
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
930
947
  const { data, loading, error, setPage, currentPage, submitQuestion, submitState, resetSubmit } = useQnA({
931
- proxyUrl,
948
+ proxyUrl: resolvedUrl,
932
949
  productId
933
950
  });
934
951
  const [showForm, setShowForm] = useState9(false);
@@ -1021,13 +1038,15 @@ import { useEffect as useEffect4, useState as useState10 } from "react";
1021
1038
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1022
1039
  function SocialProofPopup({
1023
1040
  proxyUrl,
1041
+ storeDomain,
1024
1042
  position = "bottom-left",
1025
1043
  intervalMs = 8e3,
1026
1044
  displayMs = 5e3,
1027
1045
  starColor,
1028
1046
  className
1029
1047
  }) {
1030
- const { data } = useWidgetGlobals({ proxyUrl });
1048
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1049
+ const { data } = useWidgetGlobals({ proxyUrl: resolvedUrl });
1031
1050
  const [current, setCurrent] = useState10(null);
1032
1051
  const [visible, setVisible] = useState10(false);
1033
1052
  const [dismissed, setDismissed] = useState10(false);
@@ -1130,12 +1149,14 @@ import { useRef } from "react";
1130
1149
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1131
1150
  function ReviewTicker({
1132
1151
  proxyUrl,
1152
+ storeDomain,
1133
1153
  limit = 20,
1134
1154
  speedSeconds = 30,
1135
1155
  starColor,
1136
1156
  className
1137
1157
  }) {
1138
- const { data, loading } = useWidgetGlobals({ proxyUrl, limit });
1158
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1159
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
1139
1160
  const trackRef = useRef(null);
1140
1161
  const reviews = data?.reviews ?? [];
1141
1162
  if (loading || reviews.length === 0) return null;
@@ -1197,6 +1218,7 @@ import { useState as useState11 } from "react";
1197
1218
  import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1198
1219
  function FloatingReviewsTab({
1199
1220
  proxyUrl,
1221
+ storeDomain,
1200
1222
  label = "Reviews",
1201
1223
  position = "right",
1202
1224
  color = "#111827",
@@ -1204,7 +1226,8 @@ function FloatingReviewsTab({
1204
1226
  starColor,
1205
1227
  className
1206
1228
  }) {
1207
- const { data } = useWidgetGlobals({ proxyUrl, limit });
1229
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1230
+ const { data } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
1208
1231
  const [open, setOpen] = useState11(false);
1209
1232
  const reviews = data?.reviews ?? [];
1210
1233
  const stats = data?.stats;
@@ -1281,8 +1304,9 @@ function FloatingReviewsTab({
1281
1304
 
1282
1305
  // src/components/TrustBadge.tsx
1283
1306
  import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1284
- function TrustBadge({ proxyUrl, style: badgeStyle = "pill", starColor, className }) {
1285
- const { data, loading } = useWidgetGlobals({ proxyUrl });
1307
+ function TrustBadge({ proxyUrl, storeDomain, style: badgeStyle = "pill", starColor, className }) {
1308
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1309
+ const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl });
1286
1310
  if (loading || !data?.stats?.averageRating) return null;
1287
1311
  const avg = parseFloat(data.stats.averageRating);
1288
1312
  const count = data.stats.totalReviews;
@@ -1388,14 +1412,16 @@ function useForm(proxyUrl, productId) {
1388
1412
  import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1389
1413
  function FloatingReviewButton({
1390
1414
  proxyUrl,
1415
+ storeDomain,
1391
1416
  productId,
1392
1417
  text = "Write a Review",
1393
1418
  color = "#111827",
1394
1419
  position = "bottom-right",
1395
1420
  className
1396
1421
  }) {
1422
+ const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1397
1423
  const [open, setOpen] = useState13(false);
1398
- const { form, loading } = useForm(proxyUrl, productId);
1424
+ const { form, loading } = useForm(resolvedUrl, productId);
1399
1425
  const posStyle = position === "bottom-right" ? { bottom: 24, right: 24 } : { bottom: 24, left: 24 };
1400
1426
  if (!loading && !form) return null;
1401
1427
  return /* @__PURE__ */ jsxs12(Fragment5, { children: [
@@ -1473,7 +1499,7 @@ function FloatingReviewButton({
1473
1499
  /* @__PURE__ */ jsx12(
1474
1500
  ReviewForm,
1475
1501
  {
1476
- proxyUrl,
1502
+ proxyUrl: resolvedUrl,
1477
1503
  productId,
1478
1504
  form,
1479
1505
  onSuccess: () => setOpen(false)
@@ -1524,6 +1550,7 @@ export {
1524
1550
  SocialProofPopup,
1525
1551
  StarRating,
1526
1552
  TrustBadge,
1553
+ resolveProxyUrl,
1527
1554
  useForm,
1528
1555
  useHelpfulVote,
1529
1556
  useQnA,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revova/hydrogen",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Official Revova review widgets for Shopify Hydrogen storefronts",
5
5
  "author": "Revova",
6
6
  "license": "MIT",