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