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