@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/dist/index.js CHANGED
@@ -1,18 +1,27 @@
1
1
  // src/components/ReviewWidget.tsx
2
2
  import { useState as useState4 } from "react";
3
3
 
4
+ // src/hooks/useReviews.ts
5
+ import { useEffect, useState, useCallback } from "react";
6
+
4
7
  // 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.");
8
+ function apiFetch({ apiUrl, shop, apiToken }, path, params, init) {
9
+ const searchParams = new URLSearchParams({ shop, ...params });
10
+ return fetch(`${apiUrl}${path}?${searchParams}`, {
11
+ ...init,
12
+ headers: {
13
+ Authorization: `Bearer ${apiToken}`,
14
+ "Content-Type": "application/json",
15
+ ...init?.headers ?? {}
16
+ }
17
+ });
10
18
  }
11
19
 
12
20
  // src/hooks/useReviews.ts
13
- import { useEffect, useState, useCallback } from "react";
14
21
  function useReviews({
15
- proxyUrl,
22
+ apiUrl,
23
+ shop,
24
+ apiToken,
16
25
  productId,
17
26
  page: initialPage = 1,
18
27
  limit = 10,
@@ -21,24 +30,20 @@ function useReviews({
21
30
  }) {
22
31
  const [page, setPage] = useState(initialPage);
23
32
  const [sort, setSort] = useState(initialSort);
24
- const [state, setState] = useState({
25
- data: null,
26
- loading: true,
27
- error: null
28
- });
33
+ const [state, setState] = useState({ data: null, loading: true, error: null });
29
34
  const [tick, setTick] = useState(0);
30
35
  const refetch = useCallback(() => setTick((t) => t + 1), []);
31
36
  useEffect(() => {
32
37
  let cancelled = false;
33
38
  setState((s) => ({ ...s, loading: true, error: null }));
34
- const params = new URLSearchParams({
39
+ const params = {
35
40
  productId,
36
41
  page: String(page),
37
42
  limit: String(limit),
38
43
  sort,
39
44
  ...locale ? { locale } : {}
40
- });
41
- fetch(`${proxyUrl}/reviews?${params.toString()}`).then((res) => {
45
+ };
46
+ apiFetch({ apiUrl, shop, apiToken }, "/api/reviews", params).then((res) => {
42
47
  if (!res.ok) throw new Error(`Revova: reviews fetch failed (${res.status})`);
43
48
  return res.json();
44
49
  }).then((data) => {
@@ -50,56 +55,45 @@ function useReviews({
50
55
  return () => {
51
56
  cancelled = true;
52
57
  };
53
- }, [proxyUrl, productId, page, limit, sort, locale, tick]);
54
- return {
55
- ...state,
56
- refetch,
57
- setPage,
58
- setSort,
59
- currentPage: page,
60
- currentSort: sort
61
- };
58
+ }, [apiUrl, shop, apiToken, productId, page, limit, sort, locale, tick]);
59
+ return { ...state, refetch, setPage, setSort, currentPage: page, currentSort: sort };
62
60
  }
63
61
 
64
62
  // src/components/StarRating.tsx
63
+ import React from "react";
65
64
  import { jsx, jsxs } from "react/jsx-runtime";
66
- function StarRating({ rating, max = 5, size = 16, color = "#f59e0b", className }) {
67
- const stars = Array.from({ length: max }, (_, i) => {
68
- const fill = Math.min(1, Math.max(0, rating - i));
69
- return { index: i, fill };
70
- });
65
+ function StarRating({
66
+ rating,
67
+ max = 5,
68
+ size = 16,
69
+ color = "#f59e0b",
70
+ emptyColor = "#e5e7eb",
71
+ className
72
+ }) {
73
+ const uid = React.useId().replace(/:/g, "");
71
74
  return /* @__PURE__ */ jsx(
72
75
  "span",
73
76
  {
74
- className,
75
- style: { display: "inline-flex", gap: 2 },
76
- "aria-label": `${rating} out of ${max} stars`,
77
+ className: `rv-stars${className ? ` ${className}` : ""}`,
78
+ "aria-label": `${Number(rating).toFixed(1)} out of ${max} stars`,
77
79
  role: "img",
78
- children: stars.map(({ index, fill }) => /* @__PURE__ */ jsxs(
79
- "svg",
80
- {
81
- width: size,
82
- height: size,
83
- viewBox: "0 0 24 24",
84
- "aria-hidden": "true",
85
- children: [
86
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: `star-fill-${index}`, x1: "0", x2: "1", y1: "0", y2: "0", children: [
87
- /* @__PURE__ */ jsx("stop", { offset: `${fill * 100}%`, stopColor: color }),
88
- /* @__PURE__ */ jsx("stop", { offset: `${fill * 100}%`, stopColor: "#e5e7eb" })
89
- ] }) }),
90
- /* @__PURE__ */ jsx(
91
- "path",
92
- {
93
- d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
94
- fill: `url(#star-fill-${index})`,
95
- stroke: color,
96
- strokeWidth: "1"
97
- }
98
- )
99
- ]
100
- },
101
- index
102
- ))
80
+ children: Array.from({ length: max }, (_, i) => {
81
+ const fill = Math.min(1, Math.max(0, rating - i));
82
+ const gradId = `rk-sg-${uid}-${i}`;
83
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true", children: [
84
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradId, x1: "0", x2: "1", y1: "0", y2: "0", children: [
85
+ /* @__PURE__ */ jsx("stop", { offset: `${(fill * 100).toFixed(2)}%`, stopColor: color }),
86
+ /* @__PURE__ */ jsx("stop", { offset: `${(fill * 100).toFixed(2)}%`, stopColor: emptyColor })
87
+ ] }) }),
88
+ /* @__PURE__ */ jsx(
89
+ "path",
90
+ {
91
+ d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
92
+ fill: `url(#${gradId})`
93
+ }
94
+ )
95
+ ] }, i);
96
+ })
103
97
  }
104
98
  );
105
99
  }
@@ -109,44 +103,28 @@ import { useState as useState3 } from "react";
109
103
 
110
104
  // src/hooks/useSubmitReview.ts
111
105
  import { useState as useState2, useCallback as useCallback2 } from "react";
112
- var INITIAL_STATE = {
113
- submitting: false,
114
- success: false,
115
- error: null,
116
- result: null
117
- };
118
- function useSubmitReview(proxyUrl) {
106
+ var INITIAL_STATE = { submitting: false, success: false, error: null, result: null };
107
+ function useSubmitReview(creds) {
119
108
  const [state, setState] = useState2(INITIAL_STATE);
120
109
  const submit = useCallback2(
121
110
  async (payload) => {
122
111
  setState({ submitting: true, success: false, error: null, result: null });
123
112
  try {
124
- const res = await fetch(`${proxyUrl}/reviews`, {
113
+ const res = await apiFetch(creds, "/api/reviews", {}, {
125
114
  method: "POST",
126
- headers: { "Content-Type": "application/json" },
127
115
  body: JSON.stringify(payload)
128
116
  });
129
117
  const json = await res.json();
130
118
  if (!res.ok) {
131
- setState({
132
- submitting: false,
133
- success: false,
134
- error: json.error ?? `Submission failed (${res.status})`,
135
- result: null
136
- });
119
+ setState({ submitting: false, success: false, error: json.error ?? `Submission failed (${res.status})`, result: null });
137
120
  return;
138
121
  }
139
122
  setState({ submitting: false, success: true, error: null, result: json });
140
123
  } catch (err) {
141
- setState({
142
- submitting: false,
143
- success: false,
144
- error: err instanceof Error ? err.message : "An unexpected error occurred.",
145
- result: null
146
- });
124
+ setState({ submitting: false, success: false, error: err instanceof Error ? err.message : "An unexpected error occurred.", result: null });
147
125
  }
148
126
  },
149
- [proxyUrl]
127
+ [creds]
150
128
  );
151
129
  const reset = useCallback2(() => setState(INITIAL_STATE), []);
152
130
  return { ...state, submit, reset };
@@ -160,45 +138,34 @@ function cfg(field) {
160
138
  function AttributeRatingField({ field, value, onChange }) {
161
139
  const { max = 5 } = cfg(field);
162
140
  const current = Number(value ?? 0);
163
- return /* @__PURE__ */ jsxs2("div", { children: [
164
- /* @__PURE__ */ jsx2("label", { children: field.label }),
165
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [
166
- Array.from({ length: max }, (_, i) => i + 1).map((val) => /* @__PURE__ */ jsx2(
167
- "button",
141
+ const STAR_HINTS = ["", "Terrible", "Bad", "Okay", "Good", "Excellent"];
142
+ return /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
143
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", children: [
144
+ field.label,
145
+ field.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
146
+ ] }),
147
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
148
+ Array.from({ length: max }, (_, i) => i + 1).map((val) => /* @__PURE__ */ jsx2("button", { type: "button", className: "rv-star-btn", onClick: () => onChange(val), "aria-label": `${val} out of ${max}`, children: /* @__PURE__ */ jsx2("svg", { width: "26", height: "26", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
149
+ "path",
168
150
  {
169
- type: "button",
170
- onClick: () => onChange(val),
171
- "aria-label": `${val} out of ${max}`,
172
- style: { background: "none", border: "none", cursor: "pointer", padding: 2 },
173
- children: /* @__PURE__ */ jsx2(StarRating, { rating: val <= current ? 1 : 0, size: 22 })
174
- },
175
- val
176
- )),
177
- current > 0 && /* @__PURE__ */ jsxs2("span", { style: { fontSize: 12, color: "#6b7280", marginLeft: 4 }, children: [
178
- current,
179
- "/",
180
- max
181
- ] })
151
+ d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
152
+ fill: val <= current ? "#f59e0b" : "#e5e7eb"
153
+ }
154
+ ) }) }, val)),
155
+ current > 0 && /* @__PURE__ */ jsx2("span", { className: "rv-star-input__hint", children: STAR_HINTS[current] ?? "" })
182
156
  ] })
183
157
  ] });
184
158
  }
185
159
  function SingleSelectField({ field, value, onChange }) {
186
160
  const { options = [] } = cfg(field);
187
- const id = `revova-${field.id}`;
188
- return /* @__PURE__ */ jsxs2("div", { children: [
189
- /* @__PURE__ */ jsx2("label", { htmlFor: id, children: field.label }),
190
- /* @__PURE__ */ jsx2("div", { role: "radiogroup", "aria-labelledby": id, style: { display: "flex", flexDirection: "column", gap: 6 }, children: options.map((opt) => /* @__PURE__ */ jsxs2("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 14 }, children: [
191
- /* @__PURE__ */ jsx2(
192
- "input",
193
- {
194
- type: "radio",
195
- name: id,
196
- value: opt,
197
- checked: value === opt,
198
- onChange: () => onChange(opt),
199
- required: field.required && !value
200
- }
201
- ),
161
+ const id = `rv-f-${field.id}`;
162
+ return /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
163
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", id, children: [
164
+ field.label,
165
+ field.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
166
+ ] }),
167
+ /* @__PURE__ */ jsx2("div", { className: "rv-options", role: "radiogroup", "aria-labelledby": id, children: options.map((opt) => /* @__PURE__ */ jsxs2("label", { className: `rv-option-pill${value === opt ? " rv-option-pill--checked" : ""}`, children: [
168
+ /* @__PURE__ */ jsx2("input", { type: "radio", name: id, value: opt, checked: value === opt, onChange: () => onChange(opt) }),
202
169
  opt
203
170
  ] }, opt)) })
204
171
  ] });
@@ -207,34 +174,24 @@ function MultiSelectField({ field, value, onChange }) {
207
174
  const { options = [], maxSelections } = cfg(field);
208
175
  const selected = Array.isArray(value) ? value : [];
209
176
  function toggle(opt) {
210
- if (selected.includes(opt)) {
211
- onChange(selected.filter((v) => v !== opt));
212
- } else if (!maxSelections || selected.length < maxSelections) {
213
- onChange([...selected, opt]);
214
- }
177
+ if (selected.includes(opt)) onChange(selected.filter((v) => v !== opt));
178
+ else if (!maxSelections || selected.length < maxSelections) onChange([...selected, opt]);
215
179
  }
216
- return /* @__PURE__ */ jsxs2("div", { children: [
217
- /* @__PURE__ */ jsxs2("label", { children: [
180
+ return /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
181
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", children: [
218
182
  field.label,
219
- maxSelections && /* @__PURE__ */ jsxs2("span", { style: { fontSize: 12, color: "#6b7280", marginLeft: 6 }, children: [
183
+ field.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" }),
184
+ maxSelections && /* @__PURE__ */ jsxs2("span", { className: "rv-label__hint", children: [
220
185
  "(pick up to ",
221
186
  maxSelections,
222
187
  ")"
223
188
  ] })
224
189
  ] }),
225
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: options.map((opt) => {
190
+ /* @__PURE__ */ jsx2("div", { className: "rv-options", children: options.map((opt) => {
226
191
  const checked = selected.includes(opt);
227
192
  const disabled = !checked && !!maxSelections && selected.length >= maxSelections;
228
- return /* @__PURE__ */ jsxs2("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: disabled ? "not-allowed" : "pointer", fontSize: 14, opacity: disabled ? 0.5 : 1 }, children: [
229
- /* @__PURE__ */ jsx2(
230
- "input",
231
- {
232
- type: "checkbox",
233
- checked,
234
- disabled,
235
- onChange: () => toggle(opt)
236
- }
237
- ),
193
+ return /* @__PURE__ */ jsxs2("label", { className: `rv-option-pill${checked ? " rv-option-pill--checked" : ""}${disabled ? " rv-option-pill--disabled" : ""}`, children: [
194
+ /* @__PURE__ */ jsx2("input", { type: "checkbox", checked, disabled, onChange: () => toggle(opt) }),
238
195
  opt
239
196
  ] }, opt);
240
197
  }) })
@@ -244,49 +201,43 @@ function ScaleField({ field, value, onChange }) {
244
201
  const { min = 1, max = 10, minLabel, maxLabel } = cfg(field);
245
202
  const current = value;
246
203
  const steps = Array.from({ length: max - min + 1 }, (_, i) => min + i);
247
- return /* @__PURE__ */ jsxs2("div", { children: [
248
- /* @__PURE__ */ jsx2("label", { children: field.label }),
249
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
250
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 4, flexWrap: "wrap" }, children: steps.map((val) => /* @__PURE__ */ jsx2(
251
- "button",
252
- {
253
- type: "button",
254
- onClick: () => onChange(val),
255
- "aria-label": String(val),
256
- "aria-pressed": current === val,
257
- style: {
258
- width: 36,
259
- height: 36,
260
- border: "1px solid",
261
- borderColor: current === val ? "#111827" : "#d1d5db",
262
- borderRadius: 6,
263
- background: current === val ? "#111827" : "#fff",
264
- color: current === val ? "#fff" : "#374151",
265
- cursor: "pointer",
266
- fontSize: 13,
267
- fontWeight: current === val ? 600 : 400
268
- },
269
- children: val
270
- },
271
- val
272
- )) }),
273
- (minLabel || maxLabel) && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 11, color: "#6b7280" }, children: [
274
- /* @__PURE__ */ jsx2("span", { children: minLabel }),
275
- /* @__PURE__ */ jsx2("span", { children: maxLabel })
276
- ] })
204
+ return /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
205
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", children: [
206
+ field.label,
207
+ field.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
208
+ ] }),
209
+ /* @__PURE__ */ jsx2("div", { className: "rv-scale", children: steps.map((val) => /* @__PURE__ */ jsx2(
210
+ "button",
211
+ {
212
+ type: "button",
213
+ "aria-pressed": current === val,
214
+ "aria-label": String(val),
215
+ className: `rv-scale__btn${current === val ? " rv-scale__btn--active" : ""}`,
216
+ onClick: () => onChange(val),
217
+ children: val
218
+ },
219
+ val
220
+ )) }),
221
+ (minLabel || maxLabel) && /* @__PURE__ */ jsxs2("div", { className: "rv-scale__labels", children: [
222
+ /* @__PURE__ */ jsx2("span", { children: minLabel }),
223
+ /* @__PURE__ */ jsx2("span", { children: maxLabel })
277
224
  ] })
278
225
  ] });
279
226
  }
280
227
  function TextShortField({ field, value, onChange }) {
281
228
  const { placeholder } = cfg(field);
282
- const id = `revova-${field.id}`;
283
- return /* @__PURE__ */ jsxs2("div", { children: [
284
- /* @__PURE__ */ jsx2("label", { htmlFor: id, children: field.label }),
229
+ const id = `rv-f-${field.id}`;
230
+ return /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
231
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", htmlFor: id, children: [
232
+ field.label,
233
+ field.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
234
+ ] }),
285
235
  /* @__PURE__ */ jsx2(
286
236
  "input",
287
237
  {
288
238
  id,
289
239
  type: "text",
240
+ className: "rv-input",
290
241
  placeholder,
291
242
  required: field.required,
292
243
  value: String(value ?? ""),
@@ -297,13 +248,17 @@ function TextShortField({ field, value, onChange }) {
297
248
  }
298
249
  function TextLongField({ field, value, onChange }) {
299
250
  const { placeholder, minLength } = cfg(field);
300
- const id = `revova-${field.id}`;
301
- return /* @__PURE__ */ jsxs2("div", { children: [
302
- /* @__PURE__ */ jsx2("label", { htmlFor: id, children: field.label }),
251
+ const id = `rv-f-${field.id}`;
252
+ return /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
253
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", htmlFor: id, children: [
254
+ field.label,
255
+ field.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
256
+ ] }),
303
257
  /* @__PURE__ */ jsx2(
304
258
  "textarea",
305
259
  {
306
260
  id,
261
+ className: "rv-textarea",
307
262
  rows: 4,
308
263
  placeholder,
309
264
  required: field.required,
@@ -315,74 +270,52 @@ function TextLongField({ field, value, onChange }) {
315
270
  ] });
316
271
  }
317
272
  function BooleanField({ field, value, onChange }) {
318
- const id = `revova-${field.id}`;
319
- return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
273
+ const on = value === true;
274
+ const id = `rv-f-${field.id}`;
275
+ return /* @__PURE__ */ jsxs2("div", { className: "rv-toggle-row", children: [
320
276
  /* @__PURE__ */ jsx2(
321
277
  "button",
322
278
  {
323
279
  id,
324
280
  type: "button",
325
281
  role: "switch",
326
- "aria-checked": value === true,
327
- onClick: () => onChange(value === true ? false : true),
328
- style: {
329
- width: 44,
330
- height: 24,
331
- borderRadius: 999,
332
- border: "none",
333
- background: value === true ? "#111827" : "#d1d5db",
334
- cursor: "pointer",
335
- position: "relative",
336
- transition: "background 0.2s",
337
- flexShrink: 0
338
- },
339
- children: /* @__PURE__ */ jsx2(
340
- "span",
341
- {
342
- style: {
343
- position: "absolute",
344
- top: 3,
345
- left: value === true ? 23 : 3,
346
- width: 18,
347
- height: 18,
348
- borderRadius: "50%",
349
- background: "#fff",
350
- transition: "left 0.2s"
351
- }
352
- }
353
- )
282
+ "aria-checked": on,
283
+ className: `rv-toggle${on ? " rv-toggle--on" : ""}`,
284
+ onClick: () => onChange(!on),
285
+ children: /* @__PURE__ */ jsx2("span", { className: "rv-toggle__knob" })
354
286
  }
355
287
  ),
356
- /* @__PURE__ */ jsx2("label", { htmlFor: id, style: { cursor: "pointer", fontSize: 14 }, children: field.label })
288
+ /* @__PURE__ */ jsx2("label", { htmlFor: id, className: "rv-toggle__label", children: field.label })
357
289
  ] });
358
290
  }
359
291
  function MediaUploadField({ field, value, onChange }) {
360
292
  const { maxFiles = 1, maxSizeMb = 2, allowVideo = false } = cfg(field);
361
293
  const files = Array.isArray(value) ? value : [];
362
- const id = `revova-${field.id}`;
294
+ const id = `rv-f-${field.id}`;
363
295
  const accept = allowVideo ? "image/*,video/*" : "image/*";
364
296
  function handleChange(e) {
365
- const selected = Array.from(e.target.files ?? []).slice(0, maxFiles);
366
- const tooBig = selected.filter((f) => f.size > maxSizeMb * 1024 * 1024);
367
- if (tooBig.length > 0) {
297
+ const sel = Array.from(e.target.files ?? []).slice(0, maxFiles);
298
+ if (sel.some((f) => f.size > maxSizeMb * 1024 * 1024)) {
368
299
  alert(`Each file must be under ${maxSizeMb}MB.`);
369
300
  return;
370
301
  }
371
- onChange(selected);
302
+ onChange(sel);
372
303
  }
373
- return /* @__PURE__ */ jsxs2("div", { children: [
374
- /* @__PURE__ */ jsxs2("label", { htmlFor: id, children: [
304
+ function removeFile(i) {
305
+ onChange(files.filter((_, idx) => idx !== i));
306
+ }
307
+ return /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
308
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", htmlFor: id, children: [
375
309
  field.label,
376
- /* @__PURE__ */ jsxs2("span", { style: { fontSize: 12, color: "#6b7280", marginLeft: 6 }, children: [
377
- "(max ",
310
+ field.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" }),
311
+ /* @__PURE__ */ jsxs2("span", { className: "rv-label__hint", children: [
312
+ "(",
378
313
  maxFiles,
379
314
  " file",
380
315
  maxFiles > 1 ? "s" : "",
381
- ", ",
316
+ " max, ",
382
317
  maxSizeMb,
383
- "MB each",
384
- allowVideo ? ", photos & videos" : ", photos only",
385
- ")"
318
+ "MB)"
386
319
  ] })
387
320
  ] }),
388
321
  /* @__PURE__ */ jsx2(
@@ -392,11 +325,18 @@ function MediaUploadField({ field, value, onChange }) {
392
325
  type: "file",
393
326
  accept,
394
327
  multiple: maxFiles > 1,
395
- required: field.required,
328
+ required: field.required && files.length === 0,
329
+ style: { display: "none" },
396
330
  onChange: handleChange
397
331
  }
398
332
  ),
399
- files.length > 0 && /* @__PURE__ */ jsx2("ul", { style: { listStyle: "none", padding: 0, margin: "6px 0 0", display: "flex", gap: 6, flexWrap: "wrap" }, children: files.map((file, i) => /* @__PURE__ */ jsx2("li", { style: { fontSize: 12, color: "#374151" }, children: file.name }, i)) })
333
+ /* @__PURE__ */ jsxs2("div", { className: "rv-photo-strip", children: [
334
+ files.map((file, i) => /* @__PURE__ */ jsxs2("div", { className: "rv-photo-cell rv-photo-cell--filled", children: [
335
+ file.type.startsWith("image/") ? /* @__PURE__ */ jsx2("img", { src: URL.createObjectURL(file), alt: "" }) : /* @__PURE__ */ jsx2("span", { style: { fontSize: 28 }, children: "\u{1F3A5}" }),
336
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "rv-photo-remove", onClick: () => removeFile(i), "aria-label": "Remove", children: "\u2715" })
337
+ ] }, i)),
338
+ files.length < maxFiles && /* @__PURE__ */ jsx2("button", { type: "button", className: "rv-photo-cell", onClick: () => document.getElementById(id)?.click(), "aria-label": "Add photo", children: /* @__PURE__ */ jsx2("span", { className: "rv-photo-add-icon", children: "+" }) })
339
+ ] })
400
340
  ] });
401
341
  }
402
342
  function FieldRenderer(props) {
@@ -417,110 +357,128 @@ function FieldRenderer(props) {
417
357
  return /* @__PURE__ */ jsx2(BooleanField, { ...props });
418
358
  case "MEDIA_UPLOAD":
419
359
  return /* @__PURE__ */ jsx2(MediaUploadField, { ...props });
420
- // VERIFIED_ONLY is a wrapper — render its inner fields inline
421
360
  case "VERIFIED_ONLY": {
422
361
  const inner = cfg(props.field).fields ?? [];
423
- return /* @__PURE__ */ jsx2(Fragment, { children: inner.map((f) => /* @__PURE__ */ jsx2(
424
- FieldRenderer,
425
- {
426
- field: f,
427
- value: props.value,
428
- onChange: props.onChange
429
- },
430
- f.id
431
- )) });
362
+ return /* @__PURE__ */ jsx2(Fragment, { children: inner.map((f) => /* @__PURE__ */ jsx2(FieldRenderer, { field: f, value: props.value, onChange: props.onChange }, f.id)) });
432
363
  }
433
364
  default:
434
365
  return null;
435
366
  }
436
367
  }
437
- function ReviewForm({ proxyUrl, storeDomain, productId, form, onSuccess, className }) {
438
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
439
- const { submit, submitting, success, error, result } = useSubmitReview(resolvedUrl);
368
+ function ReviewForm({ apiUrl, shop, apiToken, productId, form, onSuccess, className }) {
369
+ const { submit, submitting, success, error, result } = useSubmitReview({ apiUrl, shop, apiToken });
440
370
  const [answers, setAnswers] = useState3({});
441
371
  const [email, setEmail] = useState3("");
372
+ const [isAnon, setIsAnon] = useState3(false);
442
373
  const starField = form.fields.find((f) => f.type === "STAR_RATING");
443
374
  const titleField = form.fields.find((f) => f.type === "TITLE");
444
375
  const descField = form.fields.find((f) => f.type === "DESCRIPTION");
445
376
  const customFields = form.fields.filter(
446
377
  (f) => !["STAR_RATING", "TITLE", "DESCRIPTION"].includes(f.type)
447
378
  );
379
+ const starRating = Number(answers[starField?.id ?? ""] ?? 0);
380
+ const STAR_HINTS = ["", "Terrible", "Bad", "Okay", "Good", "Excellent"];
448
381
  function setAnswer(fieldId, value) {
449
382
  setAnswers((prev) => ({ ...prev, [fieldId]: value }));
450
383
  }
451
384
  async function handleSubmit(e) {
452
385
  e.preventDefault();
453
386
  const fieldAnswers = Object.entries(answers).map(([fieldId, value]) => ({ fieldId, value }));
454
- await submit({ productId, email, formId: form.id, fieldAnswers });
387
+ await submit({ productId, email, formId: form.id, fieldAnswers, isAnonymous: isAnon });
455
388
  onSuccess?.();
456
389
  }
457
390
  if (success && result) {
458
- return /* @__PURE__ */ jsx2("div", { className, children: /* @__PURE__ */ jsx2("p", { children: result.requiresEmailVerification ? result.message ?? "Check your email to verify your review." : "Thanks for your review!" }) });
391
+ return /* @__PURE__ */ jsxs2("div", { className: `rv-success${className ? ` ${className}` : ""}`, children: [
392
+ /* @__PURE__ */ jsx2("div", { className: "rv-success__icon", children: "\u{1F389}" }),
393
+ /* @__PURE__ */ jsx2("div", { className: "rv-success__title", children: result.requiresEmailVerification ? "Check your email" : "Thank you!" }),
394
+ /* @__PURE__ */ jsx2("p", { className: "rv-success__sub", children: result.requiresEmailVerification ? result.message ?? "Check your email to verify and publish your review." : "Your review has been submitted." })
395
+ ] });
459
396
  }
460
- return /* @__PURE__ */ jsxs2("form", { className, onSubmit: handleSubmit, style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
461
- starField && /* @__PURE__ */ jsxs2("div", { children: [
462
- /* @__PURE__ */ jsxs2("label", { children: [
463
- "Rating ",
464
- starField.required && /* @__PURE__ */ jsx2("span", { "aria-hidden": true, children: "*" })
397
+ return /* @__PURE__ */ jsxs2("form", { className: `rv-form${className ? ` ${className}` : ""}`, onSubmit: handleSubmit, noValidate: true, children: [
398
+ starField && /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
399
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", children: [
400
+ "Your Rating ",
401
+ /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
465
402
  ] }),
466
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 4 }, children: [1, 2, 3, 4, 5].map((val) => /* @__PURE__ */ jsx2(
467
- "button",
468
- {
469
- type: "button",
470
- onClick: () => setAnswer(starField.id, val),
471
- "aria-label": `${val} star`,
472
- style: { background: "none", border: "none", cursor: "pointer", padding: 2 },
473
- children: /* @__PURE__ */ jsx2(
474
- StarRating,
475
- {
476
- rating: val <= Number(answers[starField.id] ?? 0) ? 1 : 0,
477
- size: 28
478
- }
479
- )
480
- },
481
- val
482
- )) })
403
+ /* @__PURE__ */ jsxs2("div", { className: "rv-star-input", children: [
404
+ [1, 2, 3, 4, 5].map((val) => /* @__PURE__ */ jsx2(
405
+ "button",
406
+ {
407
+ type: "button",
408
+ className: "rv-star-btn",
409
+ onClick: () => setAnswer(starField.id, val),
410
+ "aria-label": `${val} star${val > 1 ? "s" : ""}`,
411
+ children: /* @__PURE__ */ jsx2("svg", { width: "32", height: "32", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(
412
+ "path",
413
+ {
414
+ d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
415
+ fill: val <= starRating ? "#f59e0b" : "#e5e7eb",
416
+ stroke: val <= starRating ? "#f59e0b" : "#e5e7eb",
417
+ strokeWidth: "0.5"
418
+ }
419
+ ) })
420
+ },
421
+ val
422
+ )),
423
+ starRating > 0 && /* @__PURE__ */ jsx2("span", { className: "rv-star-input__hint", children: STAR_HINTS[starRating] })
424
+ ] })
483
425
  ] }),
484
- /* @__PURE__ */ jsxs2("div", { children: [
485
- /* @__PURE__ */ jsx2("label", { htmlFor: "revova-email", children: "Email *" }),
486
- /* @__PURE__ */ jsx2(
487
- "input",
488
- {
489
- id: "revova-email",
490
- type: "email",
491
- required: true,
492
- value: email,
493
- onChange: (e) => setEmail(e.target.value)
494
- }
495
- )
426
+ /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
427
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", htmlFor: "rv-form-email", children: [
428
+ "Email ",
429
+ /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
430
+ ] }),
431
+ /* @__PURE__ */ jsxs2("div", { className: "rv-input-wrap", children: [
432
+ /* @__PURE__ */ jsxs2("svg", { className: "rv-input-wrap__icon", width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
433
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "4", width: "16", height: "12", rx: "2" }),
434
+ /* @__PURE__ */ jsx2("path", { d: "M2 7l8 5 8-5" })
435
+ ] }),
436
+ /* @__PURE__ */ jsx2(
437
+ "input",
438
+ {
439
+ id: "rv-form-email",
440
+ type: "email",
441
+ className: "rv-input rv-input--icon",
442
+ required: true,
443
+ value: email,
444
+ onChange: (e) => setEmail(e.target.value),
445
+ placeholder: "your@email.com",
446
+ autoComplete: "email"
447
+ }
448
+ )
449
+ ] })
496
450
  ] }),
497
- titleField && /* @__PURE__ */ jsxs2("div", { children: [
498
- /* @__PURE__ */ jsxs2("label", { htmlFor: `revova-${titleField.id}`, children: [
451
+ titleField && /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
452
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", htmlFor: `rv-f-${titleField.id}`, children: [
499
453
  titleField.label ?? "Title",
500
- titleField.required && /* @__PURE__ */ jsx2("span", { "aria-hidden": true, children: " *" })
454
+ titleField.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
501
455
  ] }),
502
456
  /* @__PURE__ */ jsx2(
503
457
  "input",
504
458
  {
505
- id: `revova-${titleField.id}`,
459
+ id: `rv-f-${titleField.id}`,
506
460
  type: "text",
461
+ className: "rv-input",
507
462
  required: titleField.required,
463
+ placeholder: "Sum up your experience",
508
464
  value: String(answers[titleField.id] ?? ""),
509
465
  onChange: (e) => setAnswer(titleField.id, e.target.value)
510
466
  }
511
467
  )
512
468
  ] }),
513
- descField && /* @__PURE__ */ jsxs2("div", { children: [
514
- /* @__PURE__ */ jsxs2("label", { htmlFor: `revova-${descField.id}`, children: [
515
- descField.label ?? "Review",
516
- descField.required && /* @__PURE__ */ jsx2("span", { "aria-hidden": true, children: " *" })
469
+ descField && /* @__PURE__ */ jsxs2("div", { className: "rv-field", children: [
470
+ /* @__PURE__ */ jsxs2("label", { className: "rv-label", htmlFor: `rv-f-${descField.id}`, children: [
471
+ descField.label ?? "Your Review",
472
+ descField.required && /* @__PURE__ */ jsx2("span", { className: "rv-label__required", children: "*" })
517
473
  ] }),
518
474
  /* @__PURE__ */ jsx2(
519
475
  "textarea",
520
476
  {
521
- id: `revova-${descField.id}`,
522
- required: descField.required,
477
+ id: `rv-f-${descField.id}`,
478
+ className: "rv-textarea",
523
479
  rows: 4,
480
+ required: descField.required,
481
+ placeholder: "Tell others about your experience\u2026",
524
482
  value: String(answers[descField.id] ?? ""),
525
483
  onChange: (e) => setAnswer(descField.id, e.target.value)
526
484
  }
@@ -531,131 +489,515 @@ function ReviewForm({ proxyUrl, storeDomain, productId, form, onSuccess, classNa
531
489
  {
532
490
  field,
533
491
  value: answers[field.id],
534
- onChange: (val) => setAnswer(field.id, val)
492
+ onChange: (v) => setAnswer(field.id, v)
535
493
  },
536
494
  field.id
537
495
  )),
538
- error && /* @__PURE__ */ jsx2("p", { style: { color: "red", margin: 0 }, children: error }),
539
- /* @__PURE__ */ jsx2("button", { type: "submit", disabled: submitting, children: submitting ? "Submitting\u2026" : "Submit Review" })
496
+ /* @__PURE__ */ jsxs2("label", { className: "rv-anon", children: [
497
+ /* @__PURE__ */ jsx2("input", { type: "checkbox", checked: isAnon, onChange: (e) => setIsAnon(e.target.checked) }),
498
+ "Post anonymously"
499
+ ] }),
500
+ error && /* @__PURE__ */ jsx2("p", { className: "rv-error", children: error }),
501
+ /* @__PURE__ */ jsx2("button", { type: "submit", className: "rv-btn--submit", disabled: submitting, children: submitting ? "Submitting\u2026" : "Submit Review" })
540
502
  ] });
541
503
  }
542
504
 
505
+ // src/rkStyles.ts
506
+ var AVATAR_PALETTE = [
507
+ "#6366f1",
508
+ "#f97316",
509
+ "#10b981",
510
+ "#3b82f6",
511
+ "#a855f7",
512
+ "#ef4444",
513
+ "#f59e0b",
514
+ "#ec4899"
515
+ ];
516
+ function avatarColor(initial) {
517
+ return AVATAR_PALETTE[initial.toUpperCase().charCodeAt(0) % AVATAR_PALETTE.length] ?? "#6366f1";
518
+ }
519
+ function formatDate(iso) {
520
+ return new Date(iso).toLocaleDateString("en-US", {
521
+ year: "numeric",
522
+ month: "short",
523
+ day: "numeric"
524
+ });
525
+ }
526
+ var CheckSvg = () => (
527
+ // biome-ignore lint: inline SVG
528
+ ` <svg width="12" height="12" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
529
+ <path d="M2 7l3 3 7-6" />
530
+ </svg>`
531
+ );
532
+ var ThumbSvg = () => (
533
+ // biome-ignore lint: inline SVG
534
+ ` <svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
535
+ <path d="M7 11V5a2 2 0 014 0v3h4a1 1 0 011 1l-1.5 6A1 1 0 0113.5 16H5a2 2 0 01-2-2v-3a2 2 0 012-2h2z" />
536
+ </svg>`
537
+ );
538
+
543
539
  // src/components/ReviewWidget.tsx
544
540
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
541
+ function RatingSummary({
542
+ reviews,
543
+ total,
544
+ productId
545
+ }) {
546
+ if (total === 0) return null;
547
+ const avg = reviews.length > 0 ? reviews.reduce((s, r2) => s + r2.rating, 0) / reviews.length : 0;
548
+ const dist = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
549
+ reviews.forEach((r2) => {
550
+ const k = Math.round(r2.rating);
551
+ if (k >= 1 && k <= 5) dist[k] = (dist[k] ?? 0) + 1;
552
+ });
553
+ const r = 48, circ = 2 * Math.PI * r;
554
+ const totalArc = circ * 0.75;
555
+ const valueDash = (totalArc * Math.min(avg / 5, 1)).toFixed(2);
556
+ const gradId = `rv-arc-${productId.replace(/\W/g, "")}`;
557
+ return /* @__PURE__ */ jsxs3("div", { className: "rv-summary", children: [
558
+ /* @__PURE__ */ jsxs3("div", { className: "rv-summary__gauge", children: [
559
+ /* @__PURE__ */ jsxs3("svg", { viewBox: "0 0 120 120", width: "120", height: "120", "aria-hidden": "true", children: [
560
+ /* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsxs3("linearGradient", { id: gradId, x1: "0%", y1: "100%", x2: "100%", y2: "0%", children: [
561
+ /* @__PURE__ */ jsx3("stop", { offset: "0%", stopColor: "#ef4444" }),
562
+ /* @__PURE__ */ jsx3("stop", { offset: "45%", stopColor: "#f97316" }),
563
+ /* @__PURE__ */ jsx3("stop", { offset: "100%", stopColor: "#f59e0b" })
564
+ ] }) }),
565
+ /* @__PURE__ */ jsx3(
566
+ "circle",
567
+ {
568
+ cx: "60",
569
+ cy: "60",
570
+ r,
571
+ fill: "none",
572
+ stroke: "#e5e7eb",
573
+ strokeWidth: "9",
574
+ strokeDasharray: `${totalArc.toFixed(2)} ${(circ - totalArc).toFixed(2)}`,
575
+ strokeLinecap: "round",
576
+ transform: "rotate(135 60 60)"
577
+ }
578
+ ),
579
+ /* @__PURE__ */ jsx3(
580
+ "circle",
581
+ {
582
+ cx: "60",
583
+ cy: "60",
584
+ r,
585
+ fill: "none",
586
+ stroke: `url(#${gradId})`,
587
+ strokeWidth: "9",
588
+ strokeDasharray: `${valueDash} ${(circ - Number(valueDash)).toFixed(2)}`,
589
+ strokeLinecap: "round",
590
+ transform: "rotate(135 60 60)"
591
+ }
592
+ )
593
+ ] }),
594
+ /* @__PURE__ */ jsxs3("div", { className: "rv-summary__gauge-inner", children: [
595
+ /* @__PURE__ */ jsx3("span", { className: "rv-summary__score", children: avg.toFixed(1) }),
596
+ /* @__PURE__ */ jsx3("span", { className: "rv-summary__out-of", children: "Out of 5" })
597
+ ] })
598
+ ] }),
599
+ /* @__PURE__ */ jsxs3("div", { className: "rv-summary__info", children: [
600
+ /* @__PURE__ */ jsx3(StarRating, { rating: avg, size: 20 }),
601
+ /* @__PURE__ */ jsxs3("p", { className: "rv-summary__count", children: [
602
+ total,
603
+ " review",
604
+ total !== 1 ? "s" : ""
605
+ ] }),
606
+ /* @__PURE__ */ jsxs3("span", { className: "rv-summary__verified", children: [
607
+ /* @__PURE__ */ jsx3(CheckSvg, {}),
608
+ " Based on verified reviews"
609
+ ] })
610
+ ] }),
611
+ /* @__PURE__ */ jsx3("div", { className: "rv-summary__divider", "aria-hidden": "true" }),
612
+ /* @__PURE__ */ jsx3("div", { className: "rv-dist", children: [5, 4, 3, 2, 1].map((star) => {
613
+ const count = dist[star] ?? 0;
614
+ const pct = reviews.length > 0 ? Math.round(count / reviews.length * 100) : 0;
615
+ return /* @__PURE__ */ jsxs3("div", { className: "rv-dist__row", children: [
616
+ /* @__PURE__ */ jsx3("span", { children: star }),
617
+ /* @__PURE__ */ jsx3("div", { className: "rv-dist__bar-wrap", children: /* @__PURE__ */ jsx3("div", { className: "rv-dist__bar", style: { width: `${pct}%` } }) }),
618
+ /* @__PURE__ */ jsx3("span", { className: "rv-dist__count", children: count })
619
+ ] }, star);
620
+ }) })
621
+ ] });
622
+ }
623
+ var VOTED_KEY = "rv_helpful_votes";
624
+ function getVotedIds() {
625
+ try {
626
+ return new Set(JSON.parse(localStorage.getItem(VOTED_KEY) ?? "[]"));
627
+ } catch {
628
+ return /* @__PURE__ */ new Set();
629
+ }
630
+ }
631
+ function saveVotedId(id) {
632
+ try {
633
+ const s = getVotedIds();
634
+ s.add(id);
635
+ localStorage.setItem(VOTED_KEY, JSON.stringify([...s]));
636
+ } catch {
637
+ }
638
+ }
639
+ function ReviewCard({
640
+ review,
641
+ starColor,
642
+ onVote
643
+ }) {
644
+ const name = review.isAnonymous ? "Anonymous" : review.email?.split("@")[0] ?? "Customer";
645
+ const initial = name[0]?.toUpperCase() ?? "?";
646
+ const voted = getVotedIds().has(review.id);
647
+ const [helpfulCount, setHelpfulCount] = useState4(review.helpfulCount);
648
+ const [hasVoted, setHasVoted] = useState4(voted);
649
+ const textAnswers = review.fieldAnswers.filter(
650
+ (fa) => fa.field.type === "TEXT_LONG" || fa.field.type === "TEXT_SHORT"
651
+ );
652
+ const tagAnswers = review.fieldAnswers.filter(
653
+ (fa) => ![
654
+ "STAR_RATING",
655
+ "MEDIA_UPLOAD",
656
+ "VERIFIED_ONLY",
657
+ "TEXT_LONG",
658
+ "TEXT_SHORT"
659
+ ].includes(fa.field.type)
660
+ );
661
+ const displayTitle = review.translatedTitle ?? review.title;
662
+ const displayBody = review.translatedBody ?? review.body ?? (textAnswers[0] ? String(textAnswers[0].value) : null);
663
+ async function handleHelpful() {
664
+ if (hasVoted) return;
665
+ setHasVoted(true);
666
+ saveVotedId(review.id);
667
+ try {
668
+ await onVote(review.id);
669
+ setHelpfulCount((c) => c + 1);
670
+ } catch {
671
+ setHasVoted(false);
672
+ }
673
+ }
674
+ return /* @__PURE__ */ jsxs3("article", { className: "rv-card", children: [
675
+ /* @__PURE__ */ jsx3(
676
+ "div",
677
+ {
678
+ className: "rv-card__avatar",
679
+ style: { background: avatarColor(initial) },
680
+ children: initial
681
+ }
682
+ ),
683
+ /* @__PURE__ */ jsxs3("div", { className: "rv-card__content", children: [
684
+ /* @__PURE__ */ jsx3("div", { className: "rv-card__top", children: /* @__PURE__ */ jsxs3("div", { className: "rv-card__author-col", children: [
685
+ /* @__PURE__ */ jsxs3("div", { className: "rv-card__author-row", children: [
686
+ /* @__PURE__ */ jsx3("span", { className: "rv-card__author", children: name }),
687
+ /* @__PURE__ */ jsx3("span", { className: "rv-card__dot", children: "\xB7" }),
688
+ /* @__PURE__ */ jsx3("span", { className: "rv-card__date", children: formatDate(review.createdAt) })
689
+ ] }),
690
+ /* @__PURE__ */ jsxs3("div", { className: "rv-card__badges", children: [
691
+ review.verifiedBuyer && /* @__PURE__ */ jsxs3("span", { className: "rv-badge--verified", children: [
692
+ /* @__PURE__ */ jsx3(CheckSvg, {}),
693
+ " Verified Buyer"
694
+ ] }),
695
+ review.isPinned && /* @__PURE__ */ jsx3("span", { className: "rv-badge--pinned", children: "\u{1F4CC} Pinned" })
696
+ ] })
697
+ ] }) }),
698
+ /* @__PURE__ */ jsx3(
699
+ StarRating,
700
+ {
701
+ rating: review.rating,
702
+ size: 15,
703
+ ...starColor !== void 0 ? { color: starColor } : {}
704
+ }
705
+ ),
706
+ displayTitle && /* @__PURE__ */ jsx3("p", { className: "rv-card__title", children: displayTitle }),
707
+ displayBody && /* @__PURE__ */ jsx3("p", { className: "rv-card__body", children: displayBody }),
708
+ (review.translatedTitle !== void 0 || review.translatedBody !== void 0) && /* @__PURE__ */ jsx3("p", { className: "rv-card__translated", children: "\u{1F310} Translated" }),
709
+ review.variantTitle && /* @__PURE__ */ jsxs3("p", { className: "rv-card__variant", children: [
710
+ "Variant: ",
711
+ review.variantTitle
712
+ ] }),
713
+ review.media.length > 0 && /* @__PURE__ */ jsx3("div", { className: "rv-card__media", children: review.media.map(
714
+ (m, i) => m.mimeType.startsWith("video/") ? /* @__PURE__ */ jsx3(
715
+ "video",
716
+ {
717
+ className: "rv-card__media-vid",
718
+ src: m.url,
719
+ controls: true,
720
+ preload: "metadata"
721
+ },
722
+ i
723
+ ) : /* @__PURE__ */ jsx3(
724
+ "img",
725
+ {
726
+ className: "rv-card__media-img",
727
+ src: m.url,
728
+ alt: `Review photo ${i + 1}`,
729
+ loading: "lazy"
730
+ },
731
+ i
732
+ )
733
+ ) }),
734
+ tagAnswers.length > 0 && /* @__PURE__ */ jsx3("div", { className: "rv-card__tags", children: tagAnswers.map((fa) => /* @__PURE__ */ jsx3("span", { className: "rv-tag", children: /* @__PURE__ */ jsxs3("span", { children: [
735
+ fa.field.label,
736
+ ": ",
737
+ String(fa.value)
738
+ ] }) }, fa.fieldId)) }),
739
+ review.reply && /* @__PURE__ */ jsxs3("div", { className: "rv-card__reply", children: [
740
+ /* @__PURE__ */ jsxs3("div", { className: "rv-card__reply-label", children: [
741
+ "Response from store \xB7 ",
742
+ formatDate(review.reply.createdAt)
743
+ ] }),
744
+ /* @__PURE__ */ jsx3("p", { style: { margin: 0 }, children: review.reply.body })
745
+ ] }),
746
+ /* @__PURE__ */ jsxs3("div", { className: "rv-card__footer", children: [
747
+ /* @__PURE__ */ jsx3("div", {}),
748
+ /* @__PURE__ */ jsxs3(
749
+ "button",
750
+ {
751
+ className: `rv-helpful${hasVoted ? " rv-helpful--voted" : ""}`,
752
+ onClick: handleHelpful,
753
+ disabled: hasVoted,
754
+ "aria-label": "Mark as helpful",
755
+ children: [
756
+ /* @__PURE__ */ jsx3(ThumbSvg, {}),
757
+ helpfulCount > 0 ? `Helpful \xB7 ${helpfulCount}` : "Helpful"
758
+ ]
759
+ }
760
+ )
761
+ ] })
762
+ ] })
763
+ ] });
764
+ }
545
765
  function ReviewWidget({
546
- proxyUrl,
547
- storeDomain,
766
+ apiUrl,
767
+ shop,
768
+ apiToken,
548
769
  productId,
770
+ productTitle,
771
+ productImage,
549
772
  locale,
550
773
  pageSize = 10,
551
774
  showForm = true,
552
775
  starColor,
553
776
  className
554
777
  }) {
555
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
556
- const [showingForm, setShowingForm] = useState4(false);
778
+ const creds = { apiUrl, shop, apiToken };
779
+ const [showModal, setShowModal] = useState4(false);
557
780
  const { data, loading, error, setPage, setSort, currentPage, currentSort } = useReviews({
558
- proxyUrl: resolvedUrl,
781
+ ...creds,
559
782
  productId,
560
783
  limit: pageSize,
561
784
  ...locale !== void 0 ? { locale } : {}
562
785
  });
563
- if (loading) return /* @__PURE__ */ jsx3("div", { className, children: "Loading reviews\u2026" });
564
- if (error) return /* @__PURE__ */ jsx3("div", { className, children: "Could not load reviews." });
786
+ async function handleVote(_id) {
787
+ }
788
+ if (loading) {
789
+ return /* @__PURE__ */ jsx3("div", { className: `rv-widget${className ? ` ${className}` : ""}`, children: /* @__PURE__ */ jsxs3("div", { className: "rv-loading", children: [
790
+ /* @__PURE__ */ jsx3("span", { className: "rv-spinner" }),
791
+ "Loading reviews\u2026"
792
+ ] }) });
793
+ }
794
+ if (error)
795
+ return /* @__PURE__ */ jsx3("div", { className: `rv-widget${className ? ` ${className}` : ""}`, children: /* @__PURE__ */ jsx3("p", { className: "rv-empty", children: "Could not load reviews." }) });
565
796
  if (!data) return null;
566
797
  const { reviews, pagination, form } = data;
567
- return /* @__PURE__ */ jsxs3("div", { className, children: [
568
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16 }, children: [
569
- /* @__PURE__ */ jsx3("div", { children: /* @__PURE__ */ jsxs3("strong", { children: [
570
- pagination.total,
571
- " review",
572
- pagination.total !== 1 ? "s" : ""
573
- ] }) }),
574
- /* @__PURE__ */ jsxs3(
575
- "select",
798
+ const FilterSvg = () => /* @__PURE__ */ jsx3(
799
+ "svg",
800
+ {
801
+ width: "16",
802
+ height: "16",
803
+ viewBox: "0 0 20 20",
804
+ fill: "none",
805
+ stroke: "currentColor",
806
+ strokeWidth: "1.5",
807
+ strokeLinecap: "round",
808
+ "aria-hidden": "true",
809
+ children: /* @__PURE__ */ jsx3("path", { d: "M3 5h14M6 10h8M9 15h2" })
810
+ }
811
+ );
812
+ const ChevronSvg = () => /* @__PURE__ */ jsx3(
813
+ "svg",
814
+ {
815
+ width: "13",
816
+ height: "13",
817
+ viewBox: "0 0 20 20",
818
+ fill: "none",
819
+ stroke: "currentColor",
820
+ strokeWidth: "2",
821
+ strokeLinecap: "round",
822
+ strokeLinejoin: "round",
823
+ "aria-hidden": "true",
824
+ children: /* @__PURE__ */ jsx3("path", { d: "M5 8l5 5 5-5" })
825
+ }
826
+ );
827
+ const PencilSvg = () => /* @__PURE__ */ jsx3(
828
+ "svg",
829
+ {
830
+ width: "14",
831
+ height: "14",
832
+ viewBox: "0 0 20 20",
833
+ fill: "none",
834
+ stroke: "currentColor",
835
+ strokeWidth: "1.6",
836
+ strokeLinecap: "round",
837
+ strokeLinejoin: "round",
838
+ "aria-hidden": "true",
839
+ children: /* @__PURE__ */ jsx3("path", { d: "M13.5 2.5a2.121 2.121 0 013 3L7 15l-4 1 1-4 12.5-12.5z" })
840
+ }
841
+ );
842
+ return /* @__PURE__ */ jsxs3("div", { className: `rv-widget${className ? ` ${className}` : ""}`, children: [
843
+ /* @__PURE__ */ jsx3(
844
+ RatingSummary,
845
+ {
846
+ reviews,
847
+ total: pagination.total,
848
+ productId
849
+ }
850
+ ),
851
+ /* @__PURE__ */ jsxs3("div", { className: "rv-toolbar", children: [
852
+ /* @__PURE__ */ jsxs3("label", { className: "rv-sort-wrap", htmlFor: `rv-sort-${productId}`, children: [
853
+ /* @__PURE__ */ jsx3(FilterSvg, {}),
854
+ /* @__PURE__ */ jsxs3(
855
+ "select",
856
+ {
857
+ id: `rv-sort-${productId}`,
858
+ value: currentSort,
859
+ onChange: (e) => setSort(e.target.value),
860
+ "aria-label": "Sort reviews",
861
+ children: [
862
+ /* @__PURE__ */ jsx3("option", { value: "recent", children: "Most Recent" }),
863
+ /* @__PURE__ */ jsx3("option", { value: "helpful", children: "Most Helpful" }),
864
+ /* @__PURE__ */ jsx3("option", { value: "rating_high", children: "Highest Rating" }),
865
+ /* @__PURE__ */ jsx3("option", { value: "rating_low", children: "Lowest Rating" })
866
+ ]
867
+ }
868
+ ),
869
+ /* @__PURE__ */ jsx3(ChevronSvg, {})
870
+ ] }),
871
+ showForm && form && /* @__PURE__ */ jsxs3(
872
+ "button",
576
873
  {
577
- value: currentSort,
578
- onChange: (e) => setSort(e.target.value),
579
- "aria-label": "Sort reviews",
874
+ className: "rv-btn rv-btn--primary",
875
+ onClick: () => setShowModal(true),
580
876
  children: [
581
- /* @__PURE__ */ jsx3("option", { value: "recent", children: "Most Recent" }),
582
- /* @__PURE__ */ jsx3("option", { value: "helpful", children: "Most Helpful" }),
583
- /* @__PURE__ */ jsx3("option", { value: "rating_high", children: "Highest Rated" }),
584
- /* @__PURE__ */ jsx3("option", { value: "rating_low", children: "Lowest Rated" })
877
+ /* @__PURE__ */ jsx3(PencilSvg, {}),
878
+ " Write a Review"
585
879
  ]
586
880
  }
587
881
  )
588
882
  ] }),
589
- showForm && form && !showingForm && /* @__PURE__ */ jsx3("button", { onClick: () => setShowingForm(true), style: { marginBottom: 16 }, children: "Write a Review" }),
590
- showingForm && form && /* @__PURE__ */ jsx3("div", { style: { marginBottom: 24 }, children: /* @__PURE__ */ jsx3(
591
- ReviewForm,
883
+ reviews.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "rv-empty", children: "No reviews yet. Be the first!" }) : /* @__PURE__ */ jsx3(
884
+ "ul",
592
885
  {
593
- proxyUrl: resolvedUrl,
594
- productId,
595
- form,
596
- onSuccess: () => setShowingForm(false)
597
- }
598
- ) }),
599
- reviews.length === 0 ? /* @__PURE__ */ jsx3("p", { children: "No reviews yet. Be the first!" }) : /* @__PURE__ */ jsx3("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: reviews.map((review) => /* @__PURE__ */ jsxs3("li", { style: { borderBottom: "1px solid #e5e7eb", paddingBottom: 16, marginBottom: 16 }, children: [
600
- /* @__PURE__ */ jsx3(StarRating, { rating: review.rating, ...starColor !== void 0 ? { color: starColor } : {} }),
601
- review.isPinned && /* @__PURE__ */ jsx3("span", { children: "Pinned" }),
602
- review.verifiedBuyer && /* @__PURE__ */ jsx3("span", { children: "Verified Buyer" }),
603
- review.title && /* @__PURE__ */ jsx3("strong", { style: { display: "block", marginTop: 4 }, children: review.translatedTitle ?? review.title }),
604
- review.body && /* @__PURE__ */ jsx3("p", { style: { margin: "4px 0" }, children: review.translatedBody ?? review.body }),
605
- /* @__PURE__ */ jsxs3("small", { children: [
606
- review.isAnonymous ? "Anonymous" : review.email,
607
- " \xB7",
608
- " ",
609
- new Date(review.createdAt).toLocaleDateString(),
610
- review.variantTitle ? ` \xB7 ${review.variantTitle}` : ""
611
- ] }),
612
- review.media.length > 0 && /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: 8, marginTop: 8 }, children: review.media.map(
613
- (m, i) => m.mimeType.startsWith("image") ? /* @__PURE__ */ jsx3(
614
- "img",
886
+ className: "rv-list",
887
+ style: { listStyle: "none", padding: 0, margin: 0 },
888
+ children: reviews.map((review) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(
889
+ ReviewCard,
615
890
  {
616
- src: m.url,
617
- alt: `Review image ${i + 1}`,
618
- width: 80,
619
- height: 80,
620
- style: { objectFit: "cover", borderRadius: 4 }
621
- },
622
- i
623
- ) : null
624
- ) }),
625
- review.reply && /* @__PURE__ */ jsxs3("div", { style: { marginTop: 8, paddingLeft: 12, borderLeft: "2px solid #e5e7eb" }, children: [
626
- /* @__PURE__ */ jsxs3("small", { children: [
627
- /* @__PURE__ */ jsx3("strong", { children: "Store Reply" }),
628
- " \xB7 ",
629
- new Date(review.reply.createdAt).toLocaleDateString()
630
- ] }),
631
- /* @__PURE__ */ jsx3("p", { style: { margin: "2px 0" }, children: review.reply.body })
632
- ] })
633
- ] }, review.id)) }),
634
- pagination.totalPages > 1 && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 16 }, children: [
635
- /* @__PURE__ */ jsx3("button", { onClick: () => setPage(currentPage - 1), disabled: currentPage <= 1, children: "Previous" }),
636
- /* @__PURE__ */ jsxs3("span", { children: [
891
+ review,
892
+ ...starColor !== void 0 && { starColor },
893
+ onVote: handleVote
894
+ }
895
+ ) }, review.id))
896
+ }
897
+ ),
898
+ pagination.totalPages > 1 && /* @__PURE__ */ jsxs3("div", { className: "rv-pagination", children: [
899
+ /* @__PURE__ */ jsx3(
900
+ "button",
901
+ {
902
+ className: "rv-btn rv-btn--outline rv-btn--sm",
903
+ onClick: () => setPage(currentPage - 1),
904
+ disabled: currentPage <= 1,
905
+ children: "\u2190 Prev"
906
+ }
907
+ ),
908
+ /* @__PURE__ */ jsxs3("span", { className: "rv-pagination__info", children: [
637
909
  "Page ",
638
910
  currentPage,
639
911
  " of ",
640
912
  pagination.totalPages
641
913
  ] }),
642
- /* @__PURE__ */ jsx3("button", { onClick: () => setPage(currentPage + 1), disabled: currentPage >= pagination.totalPages, children: "Next" })
643
- ] })
914
+ /* @__PURE__ */ jsx3(
915
+ "button",
916
+ {
917
+ className: "rv-btn rv-btn--outline rv-btn--sm",
918
+ onClick: () => setPage(currentPage + 1),
919
+ disabled: currentPage >= pagination.totalPages,
920
+ children: "Next \u2192"
921
+ }
922
+ )
923
+ ] }),
924
+ showModal && form && /* @__PURE__ */ jsx3(
925
+ "div",
926
+ {
927
+ className: `rv-overlay rv-overlay--open`,
928
+ onClick: (e) => {
929
+ if (e.target === e.currentTarget) setShowModal(false);
930
+ },
931
+ role: "presentation",
932
+ children: /* @__PURE__ */ jsxs3(
933
+ "div",
934
+ {
935
+ className: "rv-modal",
936
+ role: "dialog",
937
+ "aria-modal": "true",
938
+ "aria-label": "Write a review",
939
+ children: [
940
+ /* @__PURE__ */ jsxs3("div", { className: "rv-modal__nav", children: [
941
+ /* @__PURE__ */ jsx3(
942
+ "button",
943
+ {
944
+ className: "rv-modal__nav-btn",
945
+ "aria-label": "Close",
946
+ onClick: () => setShowModal(false),
947
+ children: /* @__PURE__ */ jsx3(
948
+ "svg",
949
+ {
950
+ width: "14",
951
+ height: "14",
952
+ viewBox: "0 0 20 20",
953
+ fill: "none",
954
+ stroke: "currentColor",
955
+ strokeWidth: "2.5",
956
+ strokeLinecap: "round",
957
+ "aria-hidden": "true",
958
+ children: /* @__PURE__ */ jsx3("path", { d: "M4 4l12 12M16 4L4 16" })
959
+ }
960
+ )
961
+ }
962
+ ),
963
+ /* @__PURE__ */ jsx3("span", { className: "rv-modal__nav-title", children: "Write a Review" }),
964
+ /* @__PURE__ */ jsx3("div", { style: { width: 36 } })
965
+ ] }),
966
+ (productTitle || productImage) && /* @__PURE__ */ jsxs3("div", { className: "rv-product-strip", children: [
967
+ productImage && /* @__PURE__ */ jsx3(
968
+ "img",
969
+ {
970
+ className: "rv-product-strip__img",
971
+ src: productImage,
972
+ alt: ""
973
+ }
974
+ ),
975
+ productTitle && /* @__PURE__ */ jsx3("div", { children: /* @__PURE__ */ jsx3("div", { className: "rv-product-strip__name", children: productTitle }) })
976
+ ] }),
977
+ /* @__PURE__ */ jsx3("div", { className: "rv-modal__body", children: /* @__PURE__ */ jsx3(
978
+ ReviewForm,
979
+ {
980
+ ...creds,
981
+ productId,
982
+ form,
983
+ onSuccess: () => setShowModal(false)
984
+ }
985
+ ) })
986
+ ]
987
+ }
988
+ )
989
+ }
990
+ )
644
991
  ] });
645
992
  }
646
993
 
647
994
  // src/hooks/useWidgetGlobals.ts
648
995
  import { useEffect as useEffect2, useState as useState5 } from "react";
649
- function useWidgetGlobals({ proxyUrl, limit = 20 }) {
650
- const [state, setState] = useState5({
651
- data: null,
652
- loading: true,
653
- error: null
654
- });
996
+ function useWidgetGlobals({ apiUrl, shop, apiToken, limit = 20 }) {
997
+ const [state, setState] = useState5({ data: null, loading: true, error: null });
655
998
  useEffect2(() => {
656
999
  let cancelled = false;
657
- const params = new URLSearchParams({ limit: String(limit) });
658
- fetch(`${proxyUrl}/widget-globals?${params.toString()}`).then((res) => {
1000
+ apiFetch({ apiUrl, shop, apiToken }, "/api/widget-globals", { limit: String(limit) }).then((res) => {
659
1001
  if (!res.ok) throw new Error(`Revova: widget-globals fetch failed (${res.status})`);
660
1002
  return res.json();
661
1003
  }).then((data) => {
@@ -667,21 +1009,21 @@ function useWidgetGlobals({ proxyUrl, limit = 20 }) {
667
1009
  return () => {
668
1010
  cancelled = true;
669
1011
  };
670
- }, [proxyUrl, limit]);
1012
+ }, [apiUrl, shop, apiToken, limit]);
671
1013
  return state;
672
1014
  }
673
1015
 
674
1016
  // src/components/ReviewCount.tsx
675
1017
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
676
- function ReviewCount({ proxyUrl, storeDomain, starColor, starSize, className }) {
677
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
678
- const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl });
1018
+ function ReviewCount({ apiUrl, shop, apiToken, starColor, starSize, className }) {
1019
+ const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken });
679
1020
  if (loading || !data?.stats?.averageRating) return null;
680
1021
  const avg = parseFloat(data.stats.averageRating);
681
- return /* @__PURE__ */ jsxs4("span", { className, style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
682
- /* @__PURE__ */ jsx4(StarRating, { rating: avg, color: starColor, size: starSize }),
683
- /* @__PURE__ */ jsx4("span", { children: data.stats.averageRating }),
684
- /* @__PURE__ */ jsxs4("span", { children: [
1022
+ const starProps = starColor !== void 0 ? { color: starColor } : {};
1023
+ return /* @__PURE__ */ jsxs4("span", { className: `rv-count-badge${className ? ` ${className}` : ""}`, children: [
1024
+ /* @__PURE__ */ jsx4(StarRating, { rating: avg, ...starSize !== void 0 && { size: starSize }, ...starProps }),
1025
+ /* @__PURE__ */ jsx4("span", { className: "rv-count-badge__score", children: data.stats.averageRating }),
1026
+ /* @__PURE__ */ jsxs4("span", { className: "rv-count-badge__total", children: [
685
1027
  "(",
686
1028
  data.stats.totalReviews,
687
1029
  ")"
@@ -690,56 +1032,59 @@ function ReviewCount({ proxyUrl, storeDomain, starColor, starSize, className })
690
1032
  }
691
1033
 
692
1034
  // src/components/ReviewCarousel.tsx
693
- import React3, { useState as useState6 } from "react";
1035
+ import { useState as useState6, useEffect as useEffect3 } from "react";
694
1036
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
695
1037
  function ReviewCarousel({
696
- proxyUrl,
697
- storeDomain,
1038
+ apiUrl,
1039
+ shop,
1040
+ apiToken,
698
1041
  limit = 10,
699
1042
  autoPlay = true,
700
1043
  intervalMs = 4e3,
701
1044
  starColor,
702
1045
  className
703
1046
  }) {
704
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
705
- const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
1047
+ const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken, limit });
706
1048
  const [index, setIndex] = useState6(0);
707
1049
  const reviews = data?.reviews ?? [];
708
- React3.useEffect(() => {
1050
+ useEffect3(() => {
709
1051
  if (!autoPlay || reviews.length < 2) return;
710
1052
  const id = setInterval(() => setIndex((i) => (i + 1) % reviews.length), intervalMs);
711
1053
  return () => clearInterval(id);
712
1054
  }, [autoPlay, intervalMs, reviews.length]);
713
- if (loading) return /* @__PURE__ */ jsx5("div", { className, children: "Loading\u2026" });
1055
+ if (loading) {
1056
+ return /* @__PURE__ */ jsx5("div", { className: `rv-widget${className ? ` ${className}` : ""}`, children: /* @__PURE__ */ jsxs5("div", { className: "rv-loading", children: [
1057
+ /* @__PURE__ */ jsx5("span", { className: "rv-spinner" }),
1058
+ "Loading\u2026"
1059
+ ] }) });
1060
+ }
714
1061
  if (reviews.length === 0) return null;
1062
+ const review = reviews[index];
1063
+ const name = review.authorName;
1064
+ const initial = name[0]?.toUpperCase() ?? "?";
715
1065
  const prev = () => setIndex((i) => (i - 1 + reviews.length) % reviews.length);
716
1066
  const next = () => setIndex((i) => (i + 1) % reviews.length);
717
- const review = reviews[index];
718
- return /* @__PURE__ */ jsxs5("div", { className, style: { position: "relative", overflow: "hidden" }, children: [
719
- /* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
720
- /* @__PURE__ */ jsx5("button", { onClick: prev, "aria-label": "Previous review", style: { flexShrink: 0 }, children: "\u2039" }),
721
- /* @__PURE__ */ jsxs5("div", { style: { flex: 1, textAlign: "center", padding: "8px 0" }, children: [
722
- /* @__PURE__ */ jsx5(StarRating, { rating: review.rating, color: starColor }),
723
- review.title && /* @__PURE__ */ jsx5("strong", { style: { display: "block", marginTop: 8 }, children: review.title }),
724
- review.body && /* @__PURE__ */ jsx5("p", { style: { margin: "6px 0" }, children: review.body }),
725
- /* @__PURE__ */ jsx5("small", { children: review.authorName })
1067
+ return /* @__PURE__ */ jsxs5("div", { className: `rv-widget rv-carousel${className ? ` ${className}` : ""}`, style: { padding: "0 22px" }, children: [
1068
+ /* @__PURE__ */ jsx5("div", { className: "rv-carousel__inner", children: /* @__PURE__ */ jsx5("div", { className: "rv-carousel__slide", children: /* @__PURE__ */ jsxs5("div", { className: "rv-card", style: { flexDirection: "column", alignItems: "flex-start" }, children: [
1069
+ /* @__PURE__ */ jsxs5("div", { style: { display: "flex", gap: 12, alignItems: "center", marginBottom: 10 }, children: [
1070
+ /* @__PURE__ */ jsx5("div", { className: "rv-card__avatar", style: { background: avatarColor(initial) }, children: initial }),
1071
+ /* @__PURE__ */ jsxs5("div", { children: [
1072
+ /* @__PURE__ */ jsx5("div", { className: "rv-card__author", children: name }),
1073
+ /* @__PURE__ */ jsx5("div", { className: "rv-card__date", children: formatDate(review.createdAt) })
1074
+ ] })
726
1075
  ] }),
727
- /* @__PURE__ */ jsx5("button", { onClick: next, "aria-label": "Next review", style: { flexShrink: 0 }, children: "\u203A" })
728
- ] }),
729
- /* @__PURE__ */ jsx5("div", { style: { display: "flex", justifyContent: "center", gap: 6, marginTop: 8 }, children: reviews.map((_, i) => /* @__PURE__ */ jsx5(
1076
+ /* @__PURE__ */ jsx5(StarRating, { rating: review.rating, size: 16, ...starColor !== void 0 ? { color: starColor } : {} }),
1077
+ review.title && /* @__PURE__ */ jsx5("p", { className: "rv-card__title", children: review.title }),
1078
+ review.body && /* @__PURE__ */ jsx5("p", { className: "rv-card__body", style: { WebkitLineClamp: 4, display: "-webkit-box", WebkitBoxOrient: "vertical", overflow: "hidden" }, children: review.body })
1079
+ ] }) }) }),
1080
+ /* @__PURE__ */ jsx5("button", { className: "rv-carousel__nav rv-carousel__nav--prev", onClick: prev, "aria-label": "Previous review", children: /* @__PURE__ */ jsx5("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("path", { d: "M13 16l-6-6 6-6" }) }) }),
1081
+ /* @__PURE__ */ jsx5("button", { className: "rv-carousel__nav rv-carousel__nav--next", onClick: next, "aria-label": "Next review", children: /* @__PURE__ */ jsx5("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("path", { d: "M7 4l6 6-6 6" }) }) }),
1082
+ /* @__PURE__ */ jsx5("div", { className: "rv-carousel__dots", children: reviews.map((_, i) => /* @__PURE__ */ jsx5(
730
1083
  "button",
731
1084
  {
1085
+ className: `rv-carousel__dot${i === index ? " rv-carousel__dot--active" : ""}`,
732
1086
  onClick: () => setIndex(i),
733
- "aria-label": `Go to review ${i + 1}`,
734
- style: {
735
- width: 8,
736
- height: 8,
737
- borderRadius: "50%",
738
- border: "none",
739
- cursor: "pointer",
740
- background: i === index ? "#374151" : "#d1d5db",
741
- padding: 0
742
- }
1087
+ "aria-label": `Go to review ${i + 1}`
743
1088
  },
744
1089
  i
745
1090
  )) })
@@ -750,49 +1095,48 @@ function ReviewCarousel({
750
1095
  import { useState as useState7 } from "react";
751
1096
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
752
1097
  function ReviewGallery({
753
- proxyUrl,
754
- storeDomain,
1098
+ apiUrl,
1099
+ shop,
1100
+ apiToken,
755
1101
  limit = 20,
756
1102
  columns = 3,
757
1103
  starColor,
758
1104
  className
759
1105
  }) {
760
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
761
- const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
1106
+ const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken, limit });
762
1107
  const [lightbox, setLightbox] = useState7(null);
763
1108
  const items = (data?.reviews ?? []).filter((r) => r.image).map((r) => ({ url: r.image, review: r })).slice(0, limit);
764
- if (loading) return /* @__PURE__ */ jsx6("div", { className, children: "Loading gallery\u2026" });
1109
+ if (loading) {
1110
+ return /* @__PURE__ */ jsx6("div", { className: `rv-widget${className ? ` ${className}` : ""}`, children: /* @__PURE__ */ jsxs6("div", { className: "rv-loading", children: [
1111
+ /* @__PURE__ */ jsx6("span", { className: "rv-spinner" }),
1112
+ "Loading gallery\u2026"
1113
+ ] }) });
1114
+ }
765
1115
  if (items.length === 0) return null;
766
1116
  return /* @__PURE__ */ jsxs6(Fragment2, { children: [
767
1117
  /* @__PURE__ */ jsx6(
768
1118
  "div",
769
1119
  {
770
- className,
1120
+ className: `rv-widget rv-gallery${className ? ` ${className}` : ""}`,
771
1121
  style: { columns, columnGap: 8 },
772
- children: items.map((item, i) => /* @__PURE__ */ jsx6(
1122
+ children: items.map((item, i) => /* @__PURE__ */ jsxs6(
773
1123
  "button",
774
1124
  {
1125
+ className: "rv-gallery__item",
775
1126
  onClick: () => setLightbox(item),
776
- style: {
777
- display: "block",
778
- width: "100%",
779
- marginBottom: 8,
780
- border: "none",
781
- padding: 0,
782
- cursor: "pointer",
783
- background: "none",
784
- breakInside: "avoid"
785
- },
786
1127
  "aria-label": `View review photo ${i + 1}`,
787
- children: /* @__PURE__ */ jsx6(
788
- "img",
789
- {
790
- src: item.url,
791
- alt: `Review photo ${i + 1}`,
792
- style: { width: "100%", display: "block", borderRadius: 4 },
793
- loading: "lazy"
794
- }
795
- )
1128
+ children: [
1129
+ /* @__PURE__ */ jsx6(
1130
+ "img",
1131
+ {
1132
+ src: item.url,
1133
+ alt: `Review photo ${i + 1}`,
1134
+ className: "rv-gallery__img",
1135
+ loading: "lazy"
1136
+ }
1137
+ ),
1138
+ /* @__PURE__ */ jsx6("div", { className: "rv-gallery__overlay", children: /* @__PURE__ */ jsx6(StarRating, { rating: item.review.rating, size: 13, ...starColor !== void 0 ? { color: starColor } : {} }) })
1139
+ ]
796
1140
  },
797
1141
  i
798
1142
  ))
@@ -801,66 +1145,53 @@ function ReviewGallery({
801
1145
  lightbox && /* @__PURE__ */ jsx6(
802
1146
  "div",
803
1147
  {
1148
+ className: "rv-overlay rv-overlay--open rv-lightbox",
804
1149
  role: "dialog",
805
1150
  "aria-modal": "true",
806
1151
  "aria-label": "Review photo",
807
- style: {
808
- position: "fixed",
809
- inset: 0,
810
- background: "rgba(0,0,0,0.85)",
811
- zIndex: 99999,
812
- display: "flex",
813
- alignItems: "center",
814
- justifyContent: "center",
815
- padding: 16
816
- },
817
1152
  onClick: () => setLightbox(null),
818
1153
  children: /* @__PURE__ */ jsxs6(
819
1154
  "div",
820
1155
  {
821
- style: {
822
- background: "#fff",
823
- borderRadius: 12,
824
- maxWidth: 540,
825
- width: "100%",
826
- overflow: "hidden"
827
- },
1156
+ className: "rv-lightbox__card",
828
1157
  onClick: (e) => e.stopPropagation(),
829
1158
  children: [
830
1159
  /* @__PURE__ */ jsx6(
831
- "img",
1160
+ "button",
832
1161
  {
833
- src: lightbox.url,
834
- alt: "Review",
835
- style: { width: "100%", display: "block", maxHeight: 320, objectFit: "cover" }
1162
+ className: "rv-lightbox__close",
1163
+ onClick: () => setLightbox(null),
1164
+ "aria-label": "Close",
1165
+ children: /* @__PURE__ */ jsx6("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("path", { d: "M4 4l12 12M16 4L4 16" }) })
836
1166
  }
837
1167
  ),
838
- /* @__PURE__ */ jsxs6("div", { style: { padding: 16 }, children: [
839
- /* @__PURE__ */ jsx6(StarRating, { rating: lightbox.review.rating, color: starColor }),
840
- lightbox.review.title && /* @__PURE__ */ jsx6("strong", { style: { display: "block", marginTop: 6 }, children: lightbox.review.title }),
841
- lightbox.review.body && /* @__PURE__ */ jsx6("p", { style: { margin: "6px 0" }, children: lightbox.review.body }),
842
- /* @__PURE__ */ jsx6("small", { children: lightbox.review.authorName })
843
- ] }),
844
1168
  /* @__PURE__ */ jsx6(
845
- "button",
1169
+ "img",
846
1170
  {
847
- onClick: () => setLightbox(null),
848
- style: {
849
- position: "absolute",
850
- top: 12,
851
- right: 12,
852
- background: "#fff",
853
- border: "none",
854
- borderRadius: "50%",
855
- width: 32,
856
- height: 32,
857
- cursor: "pointer",
858
- fontSize: 18
859
- },
860
- "aria-label": "Close",
861
- children: "\xD7"
1171
+ src: lightbox.url,
1172
+ alt: "Review",
1173
+ className: "rv-lightbox__img"
862
1174
  }
863
- )
1175
+ ),
1176
+ /* @__PURE__ */ jsxs6("div", { className: "rv-lightbox__body", children: [
1177
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 10, alignItems: "center", marginBottom: 8 }, children: [
1178
+ /* @__PURE__ */ jsx6(
1179
+ "div",
1180
+ {
1181
+ className: "rv-card__avatar",
1182
+ style: { background: avatarColor(lightbox.review.authorName[0]?.toUpperCase() ?? "?"), width: 36, height: 36, fontSize: 14 },
1183
+ children: lightbox.review.authorName[0]?.toUpperCase() ?? "?"
1184
+ }
1185
+ ),
1186
+ /* @__PURE__ */ jsxs6("div", { children: [
1187
+ /* @__PURE__ */ jsx6("div", { className: "rv-card__author", children: lightbox.review.authorName }),
1188
+ /* @__PURE__ */ jsx6("div", { className: "rv-card__date", children: formatDate(lightbox.review.createdAt) })
1189
+ ] })
1190
+ ] }),
1191
+ /* @__PURE__ */ jsx6(StarRating, { rating: lightbox.review.rating, size: 15, ...starColor !== void 0 ? { color: starColor } : {} }),
1192
+ lightbox.review.title && /* @__PURE__ */ jsx6("p", { className: "rv-card__title", style: { marginTop: 8 }, children: lightbox.review.title }),
1193
+ lightbox.review.body && /* @__PURE__ */ jsx6("p", { className: "rv-card__body", children: lightbox.review.body })
1194
+ ] })
864
1195
  ]
865
1196
  }
866
1197
  )
@@ -873,19 +1204,19 @@ function ReviewGallery({
873
1204
  import { useState as useState9 } from "react";
874
1205
 
875
1206
  // src/hooks/useQnA.ts
876
- import { useEffect as useEffect3, useState as useState8, useCallback as useCallback3 } from "react";
1207
+ import { useEffect as useEffect4, useState as useState8, useCallback as useCallback3 } from "react";
877
1208
  var INITIAL_SUBMIT = { submitting: false, success: false, error: null };
878
- function useQnA({ proxyUrl, productId, page: initialPage = 1, sort = "recent" }) {
1209
+ function useQnA({ apiUrl, shop, apiToken, productId, page: initialPage = 1, sort = "recent" }) {
1210
+ const creds = { apiUrl, shop, apiToken };
879
1211
  const [page, setPage] = useState8(initialPage);
880
1212
  const [tick, setTick] = useState8(0);
881
1213
  const [state, setState] = useState8({ data: null, loading: true, error: null });
882
1214
  const [submitState, setSubmitState] = useState8(INITIAL_SUBMIT);
883
1215
  const refetch = useCallback3(() => setTick((t) => t + 1), []);
884
- useEffect3(() => {
1216
+ useEffect4(() => {
885
1217
  let cancelled = false;
886
1218
  setState((s) => ({ ...s, loading: true, error: null }));
887
- const params = new URLSearchParams({ productId, page: String(page), sort });
888
- fetch(`${proxyUrl}/qna?${params.toString()}`).then((res) => {
1219
+ apiFetch(creds, "/api/qna", { productId, page: String(page), sort }).then((res) => {
889
1220
  if (!res.ok) throw new Error(`Revova: qna fetch failed (${res.status})`);
890
1221
  return res.json();
891
1222
  }).then((data) => {
@@ -897,15 +1228,11 @@ function useQnA({ proxyUrl, productId, page: initialPage = 1, sort = "recent" })
897
1228
  return () => {
898
1229
  cancelled = true;
899
1230
  };
900
- }, [proxyUrl, productId, page, sort, tick]);
901
- const submitQuestion = useCallback3(async (payload) => {
1231
+ }, [apiUrl, shop, apiToken, productId, page, sort, tick]);
1232
+ const postQnA = useCallback3(async (payload) => {
902
1233
  setSubmitState({ submitting: true, success: false, error: null });
903
1234
  try {
904
- const res = await fetch(`${proxyUrl}/qna`, {
905
- method: "POST",
906
- headers: { "Content-Type": "application/json" },
907
- body: JSON.stringify(payload)
908
- });
1235
+ const res = await apiFetch(creds, "/api/qna", {}, { method: "POST", body: JSON.stringify(payload) });
909
1236
  const json = await res.json();
910
1237
  if (!res.ok) {
911
1238
  setSubmitState({ submitting: false, success: false, error: json.error ?? `Failed (${res.status})` });
@@ -916,36 +1243,27 @@ function useQnA({ proxyUrl, productId, page: initialPage = 1, sort = "recent" })
916
1243
  } catch (err) {
917
1244
  setSubmitState({ submitting: false, success: false, error: err instanceof Error ? err.message : "An error occurred." });
918
1245
  }
919
- }, [proxyUrl, refetch]);
920
- const submitAnswer = useCallback3(async (payload) => {
921
- setSubmitState({ submitting: true, success: false, error: null });
922
- try {
923
- const res = await fetch(`${proxyUrl}/qna`, {
924
- method: "POST",
925
- headers: { "Content-Type": "application/json" },
926
- body: JSON.stringify(payload)
927
- });
928
- const json = await res.json();
929
- if (!res.ok) {
930
- setSubmitState({ submitting: false, success: false, error: json.error ?? `Failed (${res.status})` });
931
- return;
932
- }
933
- setSubmitState({ submitting: false, success: true, error: null });
934
- refetch();
935
- } catch (err) {
936
- setSubmitState({ submitting: false, success: false, error: err instanceof Error ? err.message : "An error occurred." });
937
- }
938
- }, [proxyUrl, refetch]);
1246
+ }, [apiUrl, shop, apiToken, refetch]);
939
1247
  const resetSubmit = useCallback3(() => setSubmitState(INITIAL_SUBMIT), []);
940
- return { ...state, setPage, currentPage: page, refetch, submitQuestion, submitAnswer, submitState, resetSubmit };
1248
+ return {
1249
+ ...state,
1250
+ setPage,
1251
+ currentPage: page,
1252
+ refetch,
1253
+ submitQuestion: postQnA,
1254
+ submitAnswer: postQnA,
1255
+ submitState,
1256
+ resetSubmit
1257
+ };
941
1258
  }
942
1259
 
943
1260
  // src/components/QnAWidget.tsx
944
1261
  import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
945
- function QnAWidget({ proxyUrl, storeDomain, productId, className }) {
946
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1262
+ function QnAWidget({ apiUrl, shop, apiToken, productId, className }) {
947
1263
  const { data, loading, error, setPage, currentPage, submitQuestion, submitState, resetSubmit } = useQnA({
948
- proxyUrl: resolvedUrl,
1264
+ apiUrl,
1265
+ shop,
1266
+ apiToken,
949
1267
  productId
950
1268
  });
951
1269
  const [showForm, setShowForm] = useState9(false);
@@ -959,98 +1277,182 @@ function QnAWidget({ proxyUrl, storeDomain, productId, className }) {
959
1277
  setBody("");
960
1278
  setEmail("");
961
1279
  setDisplayName("");
962
- setShowForm(false);
963
1280
  }
964
- if (loading) return /* @__PURE__ */ jsx7("div", { className, children: "Loading Q&A\u2026" });
965
- if (error) return /* @__PURE__ */ jsx7("div", { className, children: "Could not load Q&A." });
1281
+ if (loading) {
1282
+ return /* @__PURE__ */ jsx7("div", { className: `rv-widget rv-qna${className ? ` ${className}` : ""}`, children: /* @__PURE__ */ jsxs7("div", { className: "rv-loading", children: [
1283
+ /* @__PURE__ */ jsx7("span", { className: "rv-spinner" }),
1284
+ "Loading Q&A\u2026"
1285
+ ] }) });
1286
+ }
1287
+ if (error) {
1288
+ return /* @__PURE__ */ jsx7("div", { className: `rv-widget rv-qna${className ? ` ${className}` : ""}`, children: /* @__PURE__ */ jsx7("p", { className: "rv-empty", children: "Could not load Q&A." }) });
1289
+ }
966
1290
  if (!data) return null;
967
1291
  const { questions, pagination } = data;
968
- return /* @__PURE__ */ jsxs7("div", { className, children: [
969
- /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16 }, children: [
970
- /* @__PURE__ */ jsx7("h3", { style: { margin: 0 }, children: "Questions & Answers" }),
971
- /* @__PURE__ */ jsx7("button", { onClick: () => {
972
- setShowForm((s) => !s);
973
- resetSubmit();
974
- }, children: "Ask a Question" })
1292
+ return /* @__PURE__ */ jsxs7("div", { className: `rv-widget rv-qna${className ? ` ${className}` : ""}`, children: [
1293
+ /* @__PURE__ */ jsxs7("div", { className: "rv-qna__header", children: [
1294
+ /* @__PURE__ */ jsx7("h3", { className: "rv-qna__title", children: "Questions & Answers" }),
1295
+ /* @__PURE__ */ jsx7(
1296
+ "button",
1297
+ {
1298
+ className: "rv-btn rv-btn--primary",
1299
+ onClick: () => {
1300
+ setShowForm((s) => !s);
1301
+ resetSubmit();
1302
+ },
1303
+ children: "Ask a Question"
1304
+ }
1305
+ )
975
1306
  ] }),
976
- showForm && /* @__PURE__ */ jsx7("form", { onSubmit: handleAsk, style: { marginBottom: 24 }, children: submitState.success ? /* @__PURE__ */ jsx7("p", { children: "Thanks! Your question has been submitted." }) : /* @__PURE__ */ jsxs7(Fragment3, { children: [
977
- /* @__PURE__ */ jsxs7("div", { children: [
978
- /* @__PURE__ */ jsx7("label", { htmlFor: "qna-email", children: "Email" }),
979
- /* @__PURE__ */ jsx7("input", { id: "qna-email", type: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value) })
1307
+ showForm && /* @__PURE__ */ jsx7("form", { className: "rv-qna__form", onSubmit: handleAsk, noValidate: true, children: submitState.success ? /* @__PURE__ */ jsxs7("div", { className: "rv-success", children: [
1308
+ /* @__PURE__ */ jsx7("div", { className: "rv-success__icon", children: "\u2713" }),
1309
+ /* @__PURE__ */ jsx7("div", { className: "rv-success__title", children: "Thanks!" }),
1310
+ /* @__PURE__ */ jsx7("p", { className: "rv-success__sub", children: "Your question has been submitted." })
1311
+ ] }) : /* @__PURE__ */ jsxs7(Fragment3, { children: [
1312
+ /* @__PURE__ */ jsxs7("div", { className: "rv-field", children: [
1313
+ /* @__PURE__ */ jsxs7("label", { className: "rv-label", htmlFor: "qna-email", children: [
1314
+ "Email ",
1315
+ /* @__PURE__ */ jsx7("span", { className: "rv-label__required", children: "*" })
1316
+ ] }),
1317
+ /* @__PURE__ */ jsxs7("div", { className: "rv-input-wrap", children: [
1318
+ /* @__PURE__ */ jsxs7("svg", { className: "rv-input-wrap__icon", width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1319
+ /* @__PURE__ */ jsx7("rect", { x: "2", y: "4", width: "16", height: "12", rx: "2" }),
1320
+ /* @__PURE__ */ jsx7("path", { d: "M2 7l8 5 8-5" })
1321
+ ] }),
1322
+ /* @__PURE__ */ jsx7(
1323
+ "input",
1324
+ {
1325
+ id: "qna-email",
1326
+ type: "email",
1327
+ className: "rv-input rv-input--icon",
1328
+ required: true,
1329
+ value: email,
1330
+ onChange: (e) => setEmail(e.target.value),
1331
+ placeholder: "your@email.com",
1332
+ autoComplete: "email"
1333
+ }
1334
+ )
1335
+ ] })
980
1336
  ] }),
981
- /* @__PURE__ */ jsxs7("div", { children: [
982
- /* @__PURE__ */ jsx7("label", { htmlFor: "qna-name", children: "Name (optional)" }),
983
- /* @__PURE__ */ jsx7("input", { id: "qna-name", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value) })
1337
+ /* @__PURE__ */ jsxs7("div", { className: "rv-field", children: [
1338
+ /* @__PURE__ */ jsx7("label", { className: "rv-label", htmlFor: "qna-name", children: "Name (optional)" }),
1339
+ /* @__PURE__ */ jsx7(
1340
+ "input",
1341
+ {
1342
+ id: "qna-name",
1343
+ type: "text",
1344
+ className: "rv-input",
1345
+ value: displayName,
1346
+ onChange: (e) => setDisplayName(e.target.value),
1347
+ placeholder: "Your name"
1348
+ }
1349
+ )
984
1350
  ] }),
985
- /* @__PURE__ */ jsxs7("div", { children: [
986
- /* @__PURE__ */ jsx7("label", { htmlFor: "qna-body", children: "Your Question" }),
1351
+ /* @__PURE__ */ jsxs7("div", { className: "rv-field", children: [
1352
+ /* @__PURE__ */ jsxs7("label", { className: "rv-label", htmlFor: "qna-body", children: [
1353
+ "Your Question ",
1354
+ /* @__PURE__ */ jsx7("span", { className: "rv-label__required", children: "*" })
1355
+ ] }),
987
1356
  /* @__PURE__ */ jsx7(
988
1357
  "textarea",
989
1358
  {
990
1359
  id: "qna-body",
1360
+ className: "rv-textarea",
991
1361
  required: true,
992
1362
  rows: 3,
993
1363
  value: body,
994
- onChange: (e) => setBody(e.target.value)
1364
+ onChange: (e) => setBody(e.target.value),
1365
+ placeholder: "What would you like to know?"
995
1366
  }
996
1367
  )
997
1368
  ] }),
998
- /* @__PURE__ */ jsxs7("label", { children: [
999
- /* @__PURE__ */ jsx7("input", { type: "checkbox", checked: isAnonymous, onChange: (e) => setIsAnonymous(e.target.checked) }),
1000
- " ",
1369
+ /* @__PURE__ */ jsxs7("label", { className: "rv-anon", children: [
1370
+ /* @__PURE__ */ jsx7(
1371
+ "input",
1372
+ {
1373
+ type: "checkbox",
1374
+ checked: isAnonymous,
1375
+ onChange: (e) => setIsAnonymous(e.target.checked)
1376
+ }
1377
+ ),
1001
1378
  "Post anonymously"
1002
1379
  ] }),
1003
- submitState.error && /* @__PURE__ */ jsx7("p", { style: { color: "red" }, children: submitState.error }),
1004
- /* @__PURE__ */ jsx7("button", { type: "submit", disabled: submitState.submitting, children: submitState.submitting ? "Submitting\u2026" : "Submit Question" })
1380
+ submitState.error && /* @__PURE__ */ jsx7("p", { className: "rv-error", children: submitState.error }),
1381
+ /* @__PURE__ */ jsx7("button", { type: "submit", className: "rv-btn--submit", disabled: submitState.submitting, children: submitState.submitting ? "Submitting\u2026" : "Submit Question" })
1005
1382
  ] }) }),
1006
- questions.length === 0 ? /* @__PURE__ */ jsx7("p", { children: "No questions yet. Be the first to ask!" }) : /* @__PURE__ */ jsx7("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: questions.map((q) => /* @__PURE__ */ jsxs7("li", { style: { borderBottom: "1px solid #e5e7eb", paddingBottom: 16, marginBottom: 16 }, children: [
1007
- /* @__PURE__ */ jsxs7("p", { style: { fontWeight: 600, margin: "0 0 4px" }, children: [
1008
- "Q: ",
1009
- q.body
1010
- ] }),
1011
- /* @__PURE__ */ jsxs7("small", { children: [
1012
- q.displayName,
1013
- q.verifiedBuyer ? " \xB7 Verified Buyer" : ""
1383
+ questions.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "rv-empty", children: "No questions yet. Be the first to ask!" }) : /* @__PURE__ */ jsx7("ul", { className: "rv-qna__list", children: questions.map((q) => /* @__PURE__ */ jsxs7("li", { className: "rv-qna__item", children: [
1384
+ /* @__PURE__ */ jsxs7("div", { className: "rv-qna__question-row", children: [
1385
+ /* @__PURE__ */ jsx7("span", { className: "rv-qna__q-mark", children: "Q" }),
1386
+ /* @__PURE__ */ jsxs7("div", { className: "rv-qna__question-body", children: [
1387
+ /* @__PURE__ */ jsx7("p", { className: "rv-qna__question-text", children: q.body }),
1388
+ /* @__PURE__ */ jsxs7("span", { className: "rv-qna__meta", children: [
1389
+ q.displayName,
1390
+ q.verifiedBuyer ? " \xB7 Verified Buyer" : "",
1391
+ " \xB7 ",
1392
+ formatDate(q.createdAt)
1393
+ ] })
1394
+ ] })
1014
1395
  ] }),
1015
- q.answers.length > 0 && /* @__PURE__ */ jsx7("ul", { style: { listStyle: "none", padding: "8px 0 0 16px", margin: 0 }, children: q.answers.map((a) => /* @__PURE__ */ jsxs7("li", { style: { marginBottom: 8 }, children: [
1016
- /* @__PURE__ */ jsxs7("p", { style: { margin: "0 0 2px" }, children: [
1017
- a.isAdminReply && /* @__PURE__ */ jsx7("strong", { children: "Store: " }),
1018
- a.body
1019
- ] }),
1020
- /* @__PURE__ */ jsx7("small", { children: a.displayName })
1396
+ q.answers.length > 0 && /* @__PURE__ */ jsx7("ul", { className: "rv-qna__answers", children: q.answers.map((a) => /* @__PURE__ */ jsxs7("li", { className: "rv-qna__answer", children: [
1397
+ /* @__PURE__ */ jsx7("span", { className: `rv-qna__a-mark${a.isAdminReply ? " rv-qna__a-mark--store" : ""}`, children: "A" }),
1398
+ /* @__PURE__ */ jsxs7("div", { className: "rv-qna__answer-body", children: [
1399
+ a.isAdminReply && /* @__PURE__ */ jsx7("span", { className: "rv-qna__store-label", children: "Store \xB7 " }),
1400
+ /* @__PURE__ */ jsx7("p", { className: "rv-qna__answer-text", children: a.body }),
1401
+ /* @__PURE__ */ jsxs7("span", { className: "rv-qna__meta", children: [
1402
+ a.displayName,
1403
+ " \xB7 ",
1404
+ formatDate(a.createdAt)
1405
+ ] })
1406
+ ] })
1021
1407
  ] }, a.id)) })
1022
1408
  ] }, q.id)) }),
1023
- pagination.totalPages > 1 && /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 16 }, children: [
1024
- /* @__PURE__ */ jsx7("button", { onClick: () => setPage(currentPage - 1), disabled: currentPage <= 1, children: "Previous" }),
1025
- /* @__PURE__ */ jsxs7("span", { children: [
1409
+ pagination.totalPages > 1 && /* @__PURE__ */ jsxs7("div", { className: "rv-pagination", children: [
1410
+ /* @__PURE__ */ jsx7(
1411
+ "button",
1412
+ {
1413
+ className: "rv-btn rv-btn--outline rv-btn--sm",
1414
+ onClick: () => setPage(currentPage - 1),
1415
+ disabled: currentPage <= 1,
1416
+ children: "\u2190 Prev"
1417
+ }
1418
+ ),
1419
+ /* @__PURE__ */ jsxs7("span", { className: "rv-pagination__info", children: [
1026
1420
  "Page ",
1027
1421
  currentPage,
1028
1422
  " of ",
1029
1423
  pagination.totalPages
1030
1424
  ] }),
1031
- /* @__PURE__ */ jsx7("button", { onClick: () => setPage(currentPage + 1), disabled: currentPage >= pagination.totalPages, children: "Next" })
1425
+ /* @__PURE__ */ jsx7(
1426
+ "button",
1427
+ {
1428
+ className: "rv-btn rv-btn--outline rv-btn--sm",
1429
+ onClick: () => setPage(currentPage + 1),
1430
+ disabled: currentPage >= pagination.totalPages,
1431
+ children: "Next \u2192"
1432
+ }
1433
+ )
1032
1434
  ] })
1033
1435
  ] });
1034
1436
  }
1035
1437
 
1036
1438
  // src/components/SocialProofPopup.tsx
1037
- import { useEffect as useEffect4, useState as useState10 } from "react";
1439
+ import { useEffect as useEffect5, useState as useState10 } from "react";
1038
1440
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1039
1441
  function SocialProofPopup({
1040
- proxyUrl,
1041
- storeDomain,
1442
+ apiUrl,
1443
+ shop,
1444
+ apiToken,
1042
1445
  position = "bottom-left",
1043
1446
  intervalMs = 8e3,
1044
1447
  displayMs = 5e3,
1045
1448
  starColor,
1046
1449
  className
1047
1450
  }) {
1048
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1049
- const { data } = useWidgetGlobals({ proxyUrl: resolvedUrl });
1451
+ const { data } = useWidgetGlobals({ apiUrl, shop, apiToken });
1050
1452
  const [current, setCurrent] = useState10(null);
1051
1453
  const [visible, setVisible] = useState10(false);
1052
1454
  const [dismissed, setDismissed] = useState10(false);
1053
- useEffect4(() => {
1455
+ useEffect5(() => {
1054
1456
  const reviews = data?.reviews;
1055
1457
  if (!reviews || reviews.length === 0 || dismissed) return;
1056
1458
  let idx = 0;
@@ -1063,33 +1465,20 @@ function SocialProofPopup({
1063
1465
  setTimeout(() => setVisible(false), displayMs);
1064
1466
  }
1065
1467
  }
1066
- const initial = setTimeout(show, 2e3);
1468
+ const initial2 = setTimeout(show, 2e3);
1067
1469
  const interval = setInterval(show, intervalMs);
1068
1470
  return () => {
1069
- clearTimeout(initial);
1471
+ clearTimeout(initial2);
1070
1472
  clearInterval(interval);
1071
1473
  };
1072
1474
  }, [data, intervalMs, displayMs, dismissed]);
1073
1475
  if (!current || !visible) return null;
1074
- const posStyle = position === "bottom-right" ? { bottom: 20, right: 20 } : { bottom: 20, left: 20 };
1476
+ const initial = current.authorName[0]?.toUpperCase() ?? "?";
1075
1477
  return /* @__PURE__ */ jsxs8(
1076
1478
  "div",
1077
1479
  {
1078
- className,
1079
- style: {
1080
- position: "fixed",
1081
- zIndex: 99999,
1082
- ...posStyle,
1083
- background: "#fff",
1084
- borderRadius: 12,
1085
- boxShadow: "0 4px 20px rgba(0,0,0,0.12)",
1086
- padding: "12px 16px",
1087
- maxWidth: 280,
1088
- display: "flex",
1089
- gap: 10,
1090
- alignItems: "flex-start",
1091
- animation: "fadeInUp 0.3s ease"
1092
- },
1480
+ className: `rv-popup rv-popup--${position}${className ? ` ${className}` : ""}`,
1481
+ style: { animation: "rv-fade-up 0.35s ease" },
1093
1482
  role: "status",
1094
1483
  "aria-live": "polite",
1095
1484
  children: [
@@ -1098,31 +1487,20 @@ function SocialProofPopup({
1098
1487
  {
1099
1488
  src: current.image,
1100
1489
  alt: "",
1101
- width: 44,
1102
- height: 44,
1103
- style: { borderRadius: "50%", objectFit: "cover", flexShrink: 0 }
1490
+ className: "rv-popup__avatar rv-popup__avatar--img"
1104
1491
  }
1105
1492
  ) : /* @__PURE__ */ jsx8(
1106
1493
  "div",
1107
1494
  {
1108
- style: {
1109
- width: 44,
1110
- height: 44,
1111
- borderRadius: "50%",
1112
- background: "#e5e7eb",
1113
- flexShrink: 0,
1114
- display: "flex",
1115
- alignItems: "center",
1116
- justifyContent: "center",
1117
- fontSize: 18
1118
- },
1119
- children: current.authorName[0]?.toUpperCase() ?? "?"
1495
+ className: "rv-popup__avatar",
1496
+ style: { background: avatarColor(initial) },
1497
+ children: initial
1120
1498
  }
1121
1499
  ),
1122
- /* @__PURE__ */ jsxs8("div", { style: { flex: 1, minWidth: 0 }, children: [
1123
- /* @__PURE__ */ jsx8(StarRating, { rating: current.rating, size: 13, color: starColor }),
1124
- current.body && /* @__PURE__ */ jsx8("p", { style: { margin: "4px 0", fontSize: 13, lineHeight: 1.4, overflow: "hidden", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" }, children: current.body }),
1125
- /* @__PURE__ */ jsxs8("small", { style: { fontSize: 11, color: "#6b7280" }, children: [
1500
+ /* @__PURE__ */ jsxs8("div", { className: "rv-popup__content", children: [
1501
+ /* @__PURE__ */ jsx8(StarRating, { rating: current.rating, size: 13, ...starColor !== void 0 ? { color: starColor } : {} }),
1502
+ current.body && /* @__PURE__ */ jsx8("p", { className: "rv-popup__body", children: current.body }),
1503
+ /* @__PURE__ */ jsxs8("span", { className: "rv-popup__author", children: [
1126
1504
  current.authorName,
1127
1505
  current.verifiedBuyer ? " \xB7 Verified Buyer" : ""
1128
1506
  ] })
@@ -1130,13 +1508,13 @@ function SocialProofPopup({
1130
1508
  /* @__PURE__ */ jsx8(
1131
1509
  "button",
1132
1510
  {
1511
+ className: "rv-popup__dismiss",
1133
1512
  onClick: () => {
1134
1513
  setVisible(false);
1135
1514
  setDismissed(true);
1136
1515
  },
1137
1516
  "aria-label": "Dismiss",
1138
- style: { background: "none", border: "none", cursor: "pointer", fontSize: 16, color: "#9ca3af", padding: 0, flexShrink: 0 },
1139
- children: "\xD7"
1517
+ children: /* @__PURE__ */ jsx8("svg", { width: "10", height: "10", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M4 4l12 12M16 4L4 16" }) })
1140
1518
  }
1141
1519
  )
1142
1520
  ]
@@ -1145,70 +1523,54 @@ function SocialProofPopup({
1145
1523
  }
1146
1524
 
1147
1525
  // src/components/ReviewTicker.tsx
1148
- import { useRef } from "react";
1149
1526
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1150
1527
  function ReviewTicker({
1151
- proxyUrl,
1152
- storeDomain,
1528
+ apiUrl,
1529
+ shop,
1530
+ apiToken,
1153
1531
  limit = 20,
1154
1532
  speedSeconds = 30,
1155
1533
  starColor,
1156
1534
  className
1157
1535
  }) {
1158
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1159
- const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
1160
- const trackRef = useRef(null);
1536
+ const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken, limit });
1161
1537
  const reviews = data?.reviews ?? [];
1162
1538
  if (loading || reviews.length === 0) return null;
1163
1539
  const items = [...reviews, ...reviews];
1164
- return /* @__PURE__ */ jsxs9(
1540
+ return /* @__PURE__ */ jsx9(
1165
1541
  "div",
1166
1542
  {
1167
- className,
1168
- style: { overflow: "hidden", position: "relative" },
1543
+ className: `rv-ticker-wrap${className ? ` ${className}` : ""}`,
1169
1544
  "aria-label": "Review ticker",
1170
- children: [
1171
- /* @__PURE__ */ jsx9("style", { children: `
1172
- @keyframes rvTicker {
1173
- from { transform: translateX(0); }
1174
- to { transform: translateX(-50%); }
1545
+ children: /* @__PURE__ */ jsx9(
1546
+ "div",
1547
+ {
1548
+ className: "rv-ticker__track",
1549
+ style: { animation: `rv-ticker ${speedSeconds}s linear infinite` },
1550
+ children: items.map((review, i) => {
1551
+ const initial = review.authorName[0]?.toUpperCase() ?? "?";
1552
+ return /* @__PURE__ */ jsxs9("div", { className: "rv-ticker__item", children: [
1553
+ /* @__PURE__ */ jsx9(
1554
+ "div",
1555
+ {
1556
+ className: "rv-ticker__avatar",
1557
+ style: { background: avatarColor(initial) },
1558
+ children: initial
1559
+ }
1560
+ ),
1561
+ /* @__PURE__ */ jsxs9("div", { className: "rv-ticker__content", children: [
1562
+ /* @__PURE__ */ jsx9(StarRating, { rating: review.rating, size: 13, ...starColor !== void 0 ? { color: starColor } : {} }),
1563
+ review.body && /* @__PURE__ */ jsxs9("p", { className: "rv-ticker__body", children: [
1564
+ "\u201C",
1565
+ review.body,
1566
+ "\u201D"
1567
+ ] }),
1568
+ /* @__PURE__ */ jsx9("span", { className: "rv-ticker__author", children: review.authorName })
1569
+ ] })
1570
+ ] }, `${review.id}-${i}`);
1571
+ })
1175
1572
  }
1176
- ` }),
1177
- /* @__PURE__ */ jsx9(
1178
- "div",
1179
- {
1180
- ref: trackRef,
1181
- style: {
1182
- display: "flex",
1183
- gap: 32,
1184
- animation: `rvTicker ${speedSeconds}s linear infinite`,
1185
- width: "max-content"
1186
- },
1187
- children: items.map((review, i) => /* @__PURE__ */ jsxs9(
1188
- "div",
1189
- {
1190
- style: {
1191
- display: "flex",
1192
- flexDirection: "column",
1193
- gap: 4,
1194
- minWidth: 220,
1195
- flexShrink: 0
1196
- },
1197
- children: [
1198
- /* @__PURE__ */ jsx9(StarRating, { rating: review.rating, size: 14, color: starColor }),
1199
- review.body && /* @__PURE__ */ jsxs9("p", { style: { margin: 0, fontSize: 13, overflow: "hidden", whiteSpace: "nowrap", maxWidth: 220, textOverflow: "ellipsis" }, children: [
1200
- "\u201C",
1201
- review.body,
1202
- "\u201D"
1203
- ] }),
1204
- /* @__PURE__ */ jsx9("small", { style: { fontSize: 11, color: "#6b7280" }, children: review.authorName })
1205
- ]
1206
- },
1207
- `${review.id}-${i}`
1208
- ))
1209
- }
1210
- )
1211
- ]
1573
+ )
1212
1574
  }
1213
1575
  );
1214
1576
  }
@@ -1217,8 +1579,9 @@ function ReviewTicker({
1217
1579
  import { useState as useState11 } from "react";
1218
1580
  import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1219
1581
  function FloatingReviewsTab({
1220
- proxyUrl,
1221
- storeDomain,
1582
+ apiUrl,
1583
+ shop,
1584
+ apiToken,
1222
1585
  label = "Reviews",
1223
1586
  position = "right",
1224
1587
  color = "#111827",
@@ -1226,76 +1589,74 @@ function FloatingReviewsTab({
1226
1589
  starColor,
1227
1590
  className
1228
1591
  }) {
1229
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1230
- const { data } = useWidgetGlobals({ proxyUrl: resolvedUrl, limit });
1592
+ const { data } = useWidgetGlobals({ apiUrl, shop, apiToken, limit });
1231
1593
  const [open, setOpen] = useState11(false);
1232
1594
  const reviews = data?.reviews ?? [];
1233
1595
  const stats = data?.stats;
1234
- const sideStyle = position === "right" ? { right: 0, top: "50%", transform: "translateY(-50%)" } : { left: 0, top: "50%", transform: "translateY(-50%)" };
1235
- const panelStyle = position === "right" ? { right: 0, top: "50%", transform: "translateY(-50%)" } : { left: 0, top: "50%", transform: "translateY(-50%)" };
1596
+ const starProps = starColor !== void 0 ? { color: starColor } : {};
1236
1597
  return /* @__PURE__ */ jsxs10(Fragment4, { children: [
1237
1598
  /* @__PURE__ */ jsx10(
1238
1599
  "button",
1239
1600
  {
1240
- className,
1601
+ className: `rv-tab__btn rv-tab__btn--${position}${className ? ` ${className}` : ""}`,
1602
+ style: { background: color },
1241
1603
  onClick: () => setOpen((s) => !s),
1242
1604
  "aria-expanded": open,
1243
1605
  "aria-label": `${label} panel`,
1244
- style: {
1245
- position: "fixed",
1246
- zIndex: 9999,
1247
- ...sideStyle,
1248
- background: color,
1249
- color: "#fff",
1250
- border: "none",
1251
- cursor: "pointer",
1252
- padding: "10px 14px",
1253
- writingMode: "vertical-rl",
1254
- textOrientation: "mixed",
1255
- transform: `translateY(-50%) rotate(${position === "right" ? 180 : 0}deg)`,
1256
- borderRadius: position === "right" ? "8px 0 0 8px" : "0 8px 8px 0",
1257
- fontSize: 13,
1258
- fontWeight: 600
1259
- },
1260
1606
  children: label
1261
1607
  }
1262
1608
  ),
1263
- open && /* @__PURE__ */ jsxs10(
1609
+ /* @__PURE__ */ jsxs10(
1264
1610
  "div",
1265
1611
  {
1612
+ className: `rv-tab__panel rv-tab__panel--${position}${open ? " rv-tab__panel--open" : ""}`,
1266
1613
  role: "dialog",
1267
1614
  "aria-label": label,
1268
- style: {
1269
- position: "fixed",
1270
- zIndex: 99999,
1271
- ...panelStyle,
1272
- background: "#fff",
1273
- boxShadow: "0 8px 32px rgba(0,0,0,0.16)",
1274
- width: 300,
1275
- maxHeight: "80vh",
1276
- overflowY: "auto",
1277
- padding: 20,
1278
- borderRadius: 8
1279
- },
1615
+ "aria-hidden": !open,
1280
1616
  children: [
1281
- /* @__PURE__ */ jsxs10("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
1282
- /* @__PURE__ */ jsx10("strong", { children: label }),
1283
- /* @__PURE__ */ jsx10("button", { onClick: () => setOpen(false), "aria-label": "Close", style: { background: "none", border: "none", cursor: "pointer", fontSize: 18 }, children: "\xD7" })
1617
+ /* @__PURE__ */ jsxs10("div", { className: "rv-tab__panel-header", children: [
1618
+ /* @__PURE__ */ jsx10("strong", { className: "rv-tab__panel-title", children: label }),
1619
+ /* @__PURE__ */ jsx10(
1620
+ "button",
1621
+ {
1622
+ className: "rv-modal__nav-btn",
1623
+ onClick: () => setOpen(false),
1624
+ "aria-label": "Close",
1625
+ children: /* @__PURE__ */ jsx10("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx10("path", { d: "M4 4l12 12M16 4L4 16" }) })
1626
+ }
1627
+ )
1284
1628
  ] }),
1285
- stats && /* @__PURE__ */ jsxs10("div", { style: { marginBottom: 12, display: "flex", alignItems: "center", gap: 8 }, children: [
1286
- /* @__PURE__ */ jsx10(StarRating, { rating: parseFloat(stats.averageRating ?? "0"), color: starColor }),
1287
- /* @__PURE__ */ jsxs10("span", { style: { fontSize: 13 }, children: [
1629
+ stats && /* @__PURE__ */ jsxs10("div", { className: "rv-tab__stats", children: [
1630
+ /* @__PURE__ */ jsx10(StarRating, { rating: parseFloat(stats.averageRating ?? "0"), size: 16, ...starProps }),
1631
+ /* @__PURE__ */ jsxs10("span", { className: "rv-tab__stats-text", children: [
1288
1632
  stats.averageRating,
1289
- " (",
1633
+ " \xB7 ",
1290
1634
  stats.totalReviews,
1291
- ")"
1635
+ " review",
1636
+ stats.totalReviews !== 1 ? "s" : ""
1292
1637
  ] })
1293
1638
  ] }),
1294
- /* @__PURE__ */ jsx10("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: reviews.map((r) => /* @__PURE__ */ jsxs10("li", { style: { borderBottom: "1px solid #f3f4f6", paddingBottom: 12, marginBottom: 12 }, children: [
1295
- /* @__PURE__ */ jsx10(StarRating, { rating: r.rating, size: 13, color: starColor }),
1296
- r.body && /* @__PURE__ */ jsx10("p", { style: { margin: "4px 0", fontSize: 13 }, children: r.body }),
1297
- /* @__PURE__ */ jsx10("small", { style: { fontSize: 11, color: "#6b7280" }, children: r.authorName })
1298
- ] }, r.id)) })
1639
+ /* @__PURE__ */ jsx10("ul", { className: "rv-tab__list", children: reviews.map((r) => {
1640
+ const initial = r.authorName[0]?.toUpperCase() ?? "?";
1641
+ return /* @__PURE__ */ jsxs10("li", { className: "rv-tab__item", children: [
1642
+ /* @__PURE__ */ jsxs10("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 6 }, children: [
1643
+ /* @__PURE__ */ jsx10(
1644
+ "div",
1645
+ {
1646
+ className: "rv-card__avatar",
1647
+ style: { background: avatarColor(initial), width: 30, height: 30, fontSize: 12 },
1648
+ children: initial
1649
+ }
1650
+ ),
1651
+ /* @__PURE__ */ jsxs10("div", { children: [
1652
+ /* @__PURE__ */ jsx10("div", { className: "rv-card__author", style: { fontSize: 13 }, children: r.authorName }),
1653
+ /* @__PURE__ */ jsx10("div", { className: "rv-card__date", children: formatDate(r.createdAt) })
1654
+ ] })
1655
+ ] }),
1656
+ /* @__PURE__ */ jsx10(StarRating, { rating: r.rating, size: 13, ...starProps }),
1657
+ r.body && /* @__PURE__ */ jsx10("p", { className: "rv-card__body", style: { marginTop: 4, fontSize: 13 }, children: r.body })
1658
+ ] }, r.id);
1659
+ }) })
1299
1660
  ]
1300
1661
  }
1301
1662
  )
@@ -1304,17 +1665,17 @@ function FloatingReviewsTab({
1304
1665
 
1305
1666
  // src/components/TrustBadge.tsx
1306
1667
  import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1307
- function TrustBadge({ proxyUrl, storeDomain, style: badgeStyle = "pill", starColor, className }) {
1308
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1309
- const { data, loading } = useWidgetGlobals({ proxyUrl: resolvedUrl });
1668
+ function TrustBadge({ apiUrl, shop, apiToken, style: badgeStyle = "pill", starColor, className }) {
1669
+ const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken });
1310
1670
  if (loading || !data?.stats?.averageRating) return null;
1311
1671
  const avg = parseFloat(data.stats.averageRating);
1312
1672
  const count = data.stats.totalReviews;
1673
+ const starProps = starColor !== void 0 ? { color: starColor } : {};
1313
1674
  if (badgeStyle === "inline") {
1314
- return /* @__PURE__ */ jsxs11("span", { className, style: { display: "inline-flex", alignItems: "center", gap: 6, fontSize: 14 }, children: [
1315
- /* @__PURE__ */ jsx11(StarRating, { rating: avg, color: starColor, size: 14 }),
1675
+ return /* @__PURE__ */ jsxs11("span", { className: `rv-trust-badge--inline${className ? ` ${className}` : ""}`, children: [
1676
+ /* @__PURE__ */ jsx11(StarRating, { rating: avg, size: 14, ...starProps }),
1316
1677
  /* @__PURE__ */ jsx11("strong", { children: data.stats.averageRating }),
1317
- /* @__PURE__ */ jsxs11("span", { style: { color: "#6b7280" }, children: [
1678
+ /* @__PURE__ */ jsxs11("span", { className: "rv-trust-badge__count", children: [
1318
1679
  "(",
1319
1680
  count,
1320
1681
  " review",
@@ -1324,131 +1685,78 @@ function TrustBadge({ proxyUrl, storeDomain, style: badgeStyle = "pill", starCol
1324
1685
  ] });
1325
1686
  }
1326
1687
  if (badgeStyle === "card") {
1327
- return /* @__PURE__ */ jsxs11(
1328
- "div",
1329
- {
1330
- className,
1331
- style: {
1332
- display: "inline-flex",
1333
- flexDirection: "column",
1334
- alignItems: "center",
1335
- gap: 6,
1336
- padding: "16px 24px",
1337
- background: "#fff",
1338
- borderRadius: 12,
1339
- boxShadow: "0 2px 12px rgba(0,0,0,0.08)",
1340
- textAlign: "center"
1341
- },
1342
- children: [
1343
- /* @__PURE__ */ jsx11("strong", { style: { fontSize: 32, lineHeight: 1 }, children: data.stats.averageRating }),
1344
- /* @__PURE__ */ jsx11(StarRating, { rating: avg, size: 20, color: starColor }),
1345
- /* @__PURE__ */ jsxs11("span", { style: { fontSize: 13, color: "#6b7280" }, children: [
1346
- count,
1347
- " review",
1348
- count !== 1 ? "s" : ""
1349
- ] })
1350
- ]
1351
- }
1352
- );
1353
- }
1354
- return /* @__PURE__ */ jsxs11(
1355
- "span",
1356
- {
1357
- className,
1358
- style: {
1359
- display: "inline-flex",
1360
- alignItems: "center",
1361
- gap: 6,
1362
- background: "#fff",
1363
- border: "1px solid #e5e7eb",
1364
- borderRadius: 999,
1365
- padding: "4px 12px",
1366
- fontSize: 13,
1367
- fontWeight: 600
1368
- },
1369
- children: [
1370
- /* @__PURE__ */ jsx11(StarRating, { rating: avg, size: 13, color: starColor }),
1371
- data.stats.averageRating,
1372
- " \xB7 ",
1688
+ return /* @__PURE__ */ jsxs11("div", { className: `rv-trust-badge--card${className ? ` ${className}` : ""}`, children: [
1689
+ /* @__PURE__ */ jsx11("strong", { className: "rv-trust-badge__score", children: data.stats.averageRating }),
1690
+ /* @__PURE__ */ jsx11(StarRating, { rating: avg, size: 20, ...starProps }),
1691
+ /* @__PURE__ */ jsxs11("span", { className: "rv-trust-badge__count", children: [
1373
1692
  count,
1374
1693
  " review",
1375
1694
  count !== 1 ? "s" : ""
1376
- ]
1377
- }
1378
- );
1695
+ ] })
1696
+ ] });
1697
+ }
1698
+ return /* @__PURE__ */ jsxs11("span", { className: `rv-trust-badge--pill${className ? ` ${className}` : ""}`, children: [
1699
+ /* @__PURE__ */ jsx11(StarRating, { rating: avg, size: 13, ...starProps }),
1700
+ data.stats.averageRating,
1701
+ " \xB7 ",
1702
+ count,
1703
+ " review",
1704
+ count !== 1 ? "s" : ""
1705
+ ] });
1379
1706
  }
1380
1707
 
1381
1708
  // src/components/FloatingReviewButton.tsx
1382
1709
  import { useState as useState13 } from "react";
1383
1710
 
1384
1711
  // src/hooks/useForm.ts
1385
- import { useEffect as useEffect5, useState as useState12 } from "react";
1386
- function useForm(proxyUrl, productId) {
1712
+ import { useEffect as useEffect6, useState as useState12 } from "react";
1713
+ function useForm(creds, productId) {
1387
1714
  const [state, setState] = useState12({ form: null, loading: true, error: null });
1388
- useEffect5(() => {
1715
+ useEffect6(() => {
1389
1716
  let cancelled = false;
1390
- const params = new URLSearchParams({ productId, limit: "1", page: "1" });
1391
- fetch(`${proxyUrl}/reviews?${params.toString()}`).then((res) => {
1717
+ apiFetch(creds, "/api/reviews", { productId, limit: "1", page: "1" }).then((res) => {
1392
1718
  if (!res.ok) throw new Error(`Revova: form fetch failed (${res.status})`);
1393
1719
  return res.json();
1394
1720
  }).then(({ form }) => {
1395
1721
  if (!cancelled) setState({ form, loading: false, error: null });
1396
1722
  }).catch((err) => {
1397
1723
  if (!cancelled)
1398
- setState({
1399
- form: null,
1400
- loading: false,
1401
- error: err instanceof Error ? err : new Error(String(err))
1402
- });
1724
+ setState({ form: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) });
1403
1725
  });
1404
1726
  return () => {
1405
1727
  cancelled = true;
1406
1728
  };
1407
- }, [proxyUrl, productId]);
1729
+ }, [creds.apiUrl, creds.shop, creds.apiToken, productId]);
1408
1730
  return state;
1409
1731
  }
1410
1732
 
1411
1733
  // src/components/FloatingReviewButton.tsx
1412
1734
  import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1413
1735
  function FloatingReviewButton({
1414
- proxyUrl,
1415
- storeDomain,
1736
+ apiUrl,
1737
+ shop,
1738
+ apiToken,
1416
1739
  productId,
1417
1740
  text = "Write a Review",
1418
- color = "#111827",
1741
+ color = "#1a6b3c",
1419
1742
  position = "bottom-right",
1420
1743
  className
1421
1744
  }) {
1422
- const resolvedUrl = resolveProxyUrl({ proxyUrl, storeDomain });
1745
+ const creds = { apiUrl, shop, apiToken };
1423
1746
  const [open, setOpen] = useState13(false);
1424
- const { form, loading } = useForm(resolvedUrl, productId);
1425
- const posStyle = position === "bottom-right" ? { bottom: 24, right: 24 } : { bottom: 24, left: 24 };
1747
+ const { form, loading } = useForm(creds, productId);
1426
1748
  if (!loading && !form) return null;
1427
1749
  return /* @__PURE__ */ jsxs12(Fragment5, { children: [
1428
1750
  /* @__PURE__ */ jsxs12(
1429
1751
  "button",
1430
1752
  {
1431
- className,
1753
+ className: `rv-fab rv-fab--${position}${className ? ` ${className}` : ""}`,
1754
+ style: { background: color, opacity: loading ? 0.7 : 1 },
1432
1755
  onClick: () => setOpen(true),
1433
1756
  disabled: loading,
1434
1757
  "aria-label": text,
1435
- style: {
1436
- position: "fixed",
1437
- zIndex: 9998,
1438
- ...posStyle,
1439
- background: color,
1440
- color: "#fff",
1441
- border: "none",
1442
- borderRadius: 999,
1443
- padding: "12px 20px",
1444
- fontSize: 14,
1445
- fontWeight: 600,
1446
- cursor: loading ? "wait" : "pointer",
1447
- boxShadow: "0 4px 16px rgba(0,0,0,0.2)",
1448
- opacity: loading ? 0.7 : 1
1449
- },
1450
1758
  children: [
1451
- "\u270E ",
1759
+ /* @__PURE__ */ jsx12("svg", { width: "15", height: "15", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx12("path", { d: "M13.5 2.5a2.121 2.121 0 013 3L7 15l-4 1 1-4 12.5-12.5z" }) }),
1452
1760
  text
1453
1761
  ]
1454
1762
  }
@@ -1456,58 +1764,37 @@ function FloatingReviewButton({
1456
1764
  open && form && /* @__PURE__ */ jsx12(
1457
1765
  "div",
1458
1766
  {
1767
+ className: "rv-overlay rv-overlay--open",
1459
1768
  role: "dialog",
1460
1769
  "aria-modal": "true",
1461
1770
  "aria-label": "Write a Review",
1462
- style: {
1463
- position: "fixed",
1464
- inset: 0,
1465
- background: "rgba(0,0,0,0.5)",
1466
- zIndex: 99999,
1467
- display: "flex",
1468
- alignItems: "center",
1469
- justifyContent: "center",
1470
- padding: 16
1771
+ onClick: (e) => {
1772
+ if (e.target === e.currentTarget) setOpen(false);
1471
1773
  },
1472
- onClick: () => setOpen(false),
1473
- children: /* @__PURE__ */ jsxs12(
1474
- "div",
1475
- {
1476
- style: {
1477
- background: "#fff",
1478
- borderRadius: 12,
1479
- padding: 24,
1480
- maxWidth: 480,
1481
- width: "100%",
1482
- maxHeight: "90vh",
1483
- overflowY: "auto"
1484
- },
1485
- onClick: (e) => e.stopPropagation(),
1486
- children: [
1487
- /* @__PURE__ */ jsxs12("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }, children: [
1488
- /* @__PURE__ */ jsx12("h2", { style: { margin: 0, fontSize: 18 }, children: "Write a Review" }),
1489
- /* @__PURE__ */ jsx12(
1490
- "button",
1491
- {
1492
- onClick: () => setOpen(false),
1493
- "aria-label": "Close",
1494
- style: { background: "none", border: "none", cursor: "pointer", fontSize: 20, lineHeight: 1 },
1495
- children: "\xD7"
1496
- }
1497
- )
1498
- ] }),
1499
- /* @__PURE__ */ jsx12(
1500
- ReviewForm,
1501
- {
1502
- proxyUrl: resolvedUrl,
1503
- productId,
1504
- form,
1505
- onSuccess: () => setOpen(false)
1506
- }
1507
- )
1508
- ]
1509
- }
1510
- )
1774
+ children: /* @__PURE__ */ jsxs12("div", { className: "rv-modal", children: [
1775
+ /* @__PURE__ */ jsxs12("div", { className: "rv-modal__nav", children: [
1776
+ /* @__PURE__ */ jsx12(
1777
+ "button",
1778
+ {
1779
+ className: "rv-modal__nav-btn",
1780
+ "aria-label": "Close",
1781
+ onClick: () => setOpen(false),
1782
+ children: /* @__PURE__ */ jsx12("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx12("path", { d: "M4 4l12 12M16 4L4 16" }) })
1783
+ }
1784
+ ),
1785
+ /* @__PURE__ */ jsx12("span", { className: "rv-modal__nav-title", children: "Write a Review" }),
1786
+ /* @__PURE__ */ jsx12("div", { style: { width: 36 } })
1787
+ ] }),
1788
+ /* @__PURE__ */ jsx12("div", { className: "rv-modal__body", children: /* @__PURE__ */ jsx12(
1789
+ ReviewForm,
1790
+ {
1791
+ ...creds,
1792
+ productId,
1793
+ form,
1794
+ onSuccess: () => setOpen(false)
1795
+ }
1796
+ ) })
1797
+ ] })
1511
1798
  }
1512
1799
  )
1513
1800
  ] });
@@ -1515,7 +1802,7 @@ function FloatingReviewButton({
1515
1802
 
1516
1803
  // src/hooks/useHelpfulVote.ts
1517
1804
  import { useState as useState14, useCallback as useCallback4 } from "react";
1518
- function useHelpfulVote(proxyUrl) {
1805
+ function useHelpfulVote(creds) {
1519
1806
  const [loading, setLoading] = useState14(false);
1520
1807
  const [voted, setVoted] = useState14(false);
1521
1808
  const vote = useCallback4(
@@ -1523,9 +1810,8 @@ function useHelpfulVote(proxyUrl) {
1523
1810
  if (voted || loading) return;
1524
1811
  setLoading(true);
1525
1812
  try {
1526
- await fetch(`${proxyUrl}/helpful`, {
1813
+ await apiFetch(creds, "/api/helpful", {}, {
1527
1814
  method: "POST",
1528
- headers: { "Content-Type": "application/json" },
1529
1815
  body: JSON.stringify(payload)
1530
1816
  });
1531
1817
  setVoted(true);
@@ -1533,7 +1819,7 @@ function useHelpfulVote(proxyUrl) {
1533
1819
  setLoading(false);
1534
1820
  }
1535
1821
  },
1536
- [proxyUrl, voted, loading]
1822
+ [creds, voted, loading]
1537
1823
  );
1538
1824
  return { vote, loading, voted };
1539
1825
  }
@@ -1550,7 +1836,7 @@ export {
1550
1836
  SocialProofPopup,
1551
1837
  StarRating,
1552
1838
  TrustBadge,
1553
- resolveProxyUrl,
1839
+ apiFetch,
1554
1840
  useForm,
1555
1841
  useHelpfulVote,
1556
1842
  useQnA,