@revova/hydrogen 1.0.2 → 1.1.0

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