@revova/hydrogen 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -88
- package/dist/index.cjs +102 -117
- package/dist/index.d.cts +59 -58
- package/dist/index.d.ts +59 -58
- package/dist/index.js +101 -117
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
SocialProofPopup: () => SocialProofPopup,
|
|
43
43
|
StarRating: () => StarRating,
|
|
44
44
|
TrustBadge: () => TrustBadge,
|
|
45
|
+
apiFetch: () => apiFetch,
|
|
45
46
|
useForm: () => useForm,
|
|
46
47
|
useHelpfulVote: () => useHelpfulVote,
|
|
47
48
|
useQnA: () => useQnA,
|
|
@@ -56,8 +57,25 @@ var import_react4 = require("react");
|
|
|
56
57
|
|
|
57
58
|
// src/hooks/useReviews.ts
|
|
58
59
|
var import_react = require("react");
|
|
60
|
+
|
|
61
|
+
// src/utils.ts
|
|
62
|
+
function apiFetch({ apiUrl, shop, apiToken }, path, params, init) {
|
|
63
|
+
const searchParams = new URLSearchParams({ shop, ...params });
|
|
64
|
+
return fetch(`${apiUrl}${path}?${searchParams}`, {
|
|
65
|
+
...init,
|
|
66
|
+
headers: {
|
|
67
|
+
Authorization: `Bearer ${apiToken}`,
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
...init?.headers ?? {}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/hooks/useReviews.ts
|
|
59
75
|
function useReviews({
|
|
60
|
-
|
|
76
|
+
apiUrl,
|
|
77
|
+
shop,
|
|
78
|
+
apiToken,
|
|
61
79
|
productId,
|
|
62
80
|
page: initialPage = 1,
|
|
63
81
|
limit = 10,
|
|
@@ -66,24 +84,20 @@ function useReviews({
|
|
|
66
84
|
}) {
|
|
67
85
|
const [page, setPage] = (0, import_react.useState)(initialPage);
|
|
68
86
|
const [sort, setSort] = (0, import_react.useState)(initialSort);
|
|
69
|
-
const [state, setState] = (0, import_react.useState)({
|
|
70
|
-
data: null,
|
|
71
|
-
loading: true,
|
|
72
|
-
error: null
|
|
73
|
-
});
|
|
87
|
+
const [state, setState] = (0, import_react.useState)({ data: null, loading: true, error: null });
|
|
74
88
|
const [tick, setTick] = (0, import_react.useState)(0);
|
|
75
89
|
const refetch = (0, import_react.useCallback)(() => setTick((t) => t + 1), []);
|
|
76
90
|
(0, import_react.useEffect)(() => {
|
|
77
91
|
let cancelled = false;
|
|
78
92
|
setState((s) => ({ ...s, loading: true, error: null }));
|
|
79
|
-
const params =
|
|
93
|
+
const params = {
|
|
80
94
|
productId,
|
|
81
95
|
page: String(page),
|
|
82
96
|
limit: String(limit),
|
|
83
97
|
sort,
|
|
84
98
|
...locale ? { locale } : {}
|
|
85
|
-
}
|
|
86
|
-
|
|
99
|
+
};
|
|
100
|
+
apiFetch({ apiUrl, shop, apiToken }, "/api/reviews", params).then((res) => {
|
|
87
101
|
if (!res.ok) throw new Error(`Revova: reviews fetch failed (${res.status})`);
|
|
88
102
|
return res.json();
|
|
89
103
|
}).then((data) => {
|
|
@@ -95,15 +109,8 @@ function useReviews({
|
|
|
95
109
|
return () => {
|
|
96
110
|
cancelled = true;
|
|
97
111
|
};
|
|
98
|
-
}, [
|
|
99
|
-
return {
|
|
100
|
-
...state,
|
|
101
|
-
refetch,
|
|
102
|
-
setPage,
|
|
103
|
-
setSort,
|
|
104
|
-
currentPage: page,
|
|
105
|
-
currentSort: sort
|
|
106
|
-
};
|
|
112
|
+
}, [apiUrl, shop, apiToken, productId, page, limit, sort, locale, tick]);
|
|
113
|
+
return { ...state, refetch, setPage, setSort, currentPage: page, currentSort: sort };
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
// src/components/StarRating.tsx
|
|
@@ -154,44 +161,28 @@ var import_react3 = require("react");
|
|
|
154
161
|
|
|
155
162
|
// src/hooks/useSubmitReview.ts
|
|
156
163
|
var import_react2 = require("react");
|
|
157
|
-
var INITIAL_STATE = {
|
|
158
|
-
|
|
159
|
-
success: false,
|
|
160
|
-
error: null,
|
|
161
|
-
result: null
|
|
162
|
-
};
|
|
163
|
-
function useSubmitReview(proxyUrl) {
|
|
164
|
+
var INITIAL_STATE = { submitting: false, success: false, error: null, result: null };
|
|
165
|
+
function useSubmitReview(creds) {
|
|
164
166
|
const [state, setState] = (0, import_react2.useState)(INITIAL_STATE);
|
|
165
167
|
const submit = (0, import_react2.useCallback)(
|
|
166
168
|
async (payload) => {
|
|
167
169
|
setState({ submitting: true, success: false, error: null, result: null });
|
|
168
170
|
try {
|
|
169
|
-
const res = await
|
|
171
|
+
const res = await apiFetch(creds, "/api/reviews", {}, {
|
|
170
172
|
method: "POST",
|
|
171
|
-
headers: { "Content-Type": "application/json" },
|
|
172
173
|
body: JSON.stringify(payload)
|
|
173
174
|
});
|
|
174
175
|
const json = await res.json();
|
|
175
176
|
if (!res.ok) {
|
|
176
|
-
setState({
|
|
177
|
-
submitting: false,
|
|
178
|
-
success: false,
|
|
179
|
-
error: json.error ?? `Submission failed (${res.status})`,
|
|
180
|
-
result: null
|
|
181
|
-
});
|
|
177
|
+
setState({ submitting: false, success: false, error: json.error ?? `Submission failed (${res.status})`, result: null });
|
|
182
178
|
return;
|
|
183
179
|
}
|
|
184
180
|
setState({ submitting: false, success: true, error: null, result: json });
|
|
185
181
|
} catch (err) {
|
|
186
|
-
setState({
|
|
187
|
-
submitting: false,
|
|
188
|
-
success: false,
|
|
189
|
-
error: err instanceof Error ? err.message : "An unexpected error occurred.",
|
|
190
|
-
result: null
|
|
191
|
-
});
|
|
182
|
+
setState({ submitting: false, success: false, error: err instanceof Error ? err.message : "An unexpected error occurred.", result: null });
|
|
192
183
|
}
|
|
193
184
|
},
|
|
194
|
-
[
|
|
185
|
+
[creds]
|
|
195
186
|
);
|
|
196
187
|
const reset = (0, import_react2.useCallback)(() => setState(INITIAL_STATE), []);
|
|
197
188
|
return { ...state, submit, reset };
|
|
@@ -479,8 +470,8 @@ function FieldRenderer(props) {
|
|
|
479
470
|
return null;
|
|
480
471
|
}
|
|
481
472
|
}
|
|
482
|
-
function ReviewForm({
|
|
483
|
-
const { submit, submitting, success, error, result } = useSubmitReview(
|
|
473
|
+
function ReviewForm({ apiUrl, shop, apiToken, productId, form, onSuccess, className }) {
|
|
474
|
+
const { submit, submitting, success, error, result } = useSubmitReview({ apiUrl, shop, apiToken });
|
|
484
475
|
const [answers, setAnswers] = (0, import_react3.useState)({});
|
|
485
476
|
const [email, setEmail] = (0, import_react3.useState)("");
|
|
486
477
|
const starField = form.fields.find((f) => f.type === "STAR_RATING");
|
|
@@ -587,7 +578,9 @@ function ReviewForm({ proxyUrl, productId, form, onSuccess, className }) {
|
|
|
587
578
|
// src/components/ReviewWidget.tsx
|
|
588
579
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
589
580
|
function ReviewWidget({
|
|
590
|
-
|
|
581
|
+
apiUrl,
|
|
582
|
+
shop,
|
|
583
|
+
apiToken,
|
|
591
584
|
productId,
|
|
592
585
|
locale,
|
|
593
586
|
pageSize = 10,
|
|
@@ -595,9 +588,10 @@ function ReviewWidget({
|
|
|
595
588
|
starColor,
|
|
596
589
|
className
|
|
597
590
|
}) {
|
|
591
|
+
const creds = { apiUrl, shop, apiToken };
|
|
598
592
|
const [showingForm, setShowingForm] = (0, import_react4.useState)(false);
|
|
599
593
|
const { data, loading, error, setPage, setSort, currentPage, currentSort } = useReviews({
|
|
600
|
-
|
|
594
|
+
...creds,
|
|
601
595
|
productId,
|
|
602
596
|
limit: pageSize,
|
|
603
597
|
...locale !== void 0 ? { locale } : {}
|
|
@@ -632,7 +626,7 @@ function ReviewWidget({
|
|
|
632
626
|
showingForm && form && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginBottom: 24 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
633
627
|
ReviewForm,
|
|
634
628
|
{
|
|
635
|
-
|
|
629
|
+
...creds,
|
|
636
630
|
productId,
|
|
637
631
|
form,
|
|
638
632
|
onSuccess: () => setShowingForm(false)
|
|
@@ -688,16 +682,11 @@ function ReviewWidget({
|
|
|
688
682
|
|
|
689
683
|
// src/hooks/useWidgetGlobals.ts
|
|
690
684
|
var import_react5 = require("react");
|
|
691
|
-
function useWidgetGlobals({
|
|
692
|
-
const [state, setState] = (0, import_react5.useState)({
|
|
693
|
-
data: null,
|
|
694
|
-
loading: true,
|
|
695
|
-
error: null
|
|
696
|
-
});
|
|
685
|
+
function useWidgetGlobals({ apiUrl, shop, apiToken, limit = 20 }) {
|
|
686
|
+
const [state, setState] = (0, import_react5.useState)({ data: null, loading: true, error: null });
|
|
697
687
|
(0, import_react5.useEffect)(() => {
|
|
698
688
|
let cancelled = false;
|
|
699
|
-
|
|
700
|
-
fetch(`${proxyUrl}/widget-globals?${params.toString()}`).then((res) => {
|
|
689
|
+
apiFetch({ apiUrl, shop, apiToken }, "/api/widget-globals", { limit: String(limit) }).then((res) => {
|
|
701
690
|
if (!res.ok) throw new Error(`Revova: widget-globals fetch failed (${res.status})`);
|
|
702
691
|
return res.json();
|
|
703
692
|
}).then((data) => {
|
|
@@ -709,14 +698,14 @@ function useWidgetGlobals({ proxyUrl, limit = 20 }) {
|
|
|
709
698
|
return () => {
|
|
710
699
|
cancelled = true;
|
|
711
700
|
};
|
|
712
|
-
}, [
|
|
701
|
+
}, [apiUrl, shop, apiToken, limit]);
|
|
713
702
|
return state;
|
|
714
703
|
}
|
|
715
704
|
|
|
716
705
|
// src/components/ReviewCount.tsx
|
|
717
706
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
718
|
-
function ReviewCount({
|
|
719
|
-
const { data, loading } = useWidgetGlobals({
|
|
707
|
+
function ReviewCount({ apiUrl, shop, apiToken, starColor, starSize, className }) {
|
|
708
|
+
const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken });
|
|
720
709
|
if (loading || !data?.stats?.averageRating) return null;
|
|
721
710
|
const avg = parseFloat(data.stats.averageRating);
|
|
722
711
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className, style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
|
|
@@ -734,14 +723,16 @@ function ReviewCount({ proxyUrl, starColor, starSize, className }) {
|
|
|
734
723
|
var import_react6 = __toESM(require("react"), 1);
|
|
735
724
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
736
725
|
function ReviewCarousel({
|
|
737
|
-
|
|
726
|
+
apiUrl,
|
|
727
|
+
shop,
|
|
728
|
+
apiToken,
|
|
738
729
|
limit = 10,
|
|
739
730
|
autoPlay = true,
|
|
740
731
|
intervalMs = 4e3,
|
|
741
732
|
starColor,
|
|
742
733
|
className
|
|
743
734
|
}) {
|
|
744
|
-
const { data, loading } = useWidgetGlobals({
|
|
735
|
+
const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken, limit });
|
|
745
736
|
const [index, setIndex] = (0, import_react6.useState)(0);
|
|
746
737
|
const reviews = data?.reviews ?? [];
|
|
747
738
|
import_react6.default.useEffect(() => {
|
|
@@ -789,13 +780,15 @@ function ReviewCarousel({
|
|
|
789
780
|
var import_react7 = require("react");
|
|
790
781
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
791
782
|
function ReviewGallery({
|
|
792
|
-
|
|
783
|
+
apiUrl,
|
|
784
|
+
shop,
|
|
785
|
+
apiToken,
|
|
793
786
|
limit = 20,
|
|
794
787
|
columns = 3,
|
|
795
788
|
starColor,
|
|
796
789
|
className
|
|
797
790
|
}) {
|
|
798
|
-
const { data, loading } = useWidgetGlobals({
|
|
791
|
+
const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken, limit });
|
|
799
792
|
const [lightbox, setLightbox] = (0, import_react7.useState)(null);
|
|
800
793
|
const items = (data?.reviews ?? []).filter((r) => r.image).map((r) => ({ url: r.image, review: r })).slice(0, limit);
|
|
801
794
|
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className, children: "Loading gallery\u2026" });
|
|
@@ -912,7 +905,8 @@ var import_react9 = require("react");
|
|
|
912
905
|
// src/hooks/useQnA.ts
|
|
913
906
|
var import_react8 = require("react");
|
|
914
907
|
var INITIAL_SUBMIT = { submitting: false, success: false, error: null };
|
|
915
|
-
function useQnA({
|
|
908
|
+
function useQnA({ apiUrl, shop, apiToken, productId, page: initialPage = 1, sort = "recent" }) {
|
|
909
|
+
const creds = { apiUrl, shop, apiToken };
|
|
916
910
|
const [page, setPage] = (0, import_react8.useState)(initialPage);
|
|
917
911
|
const [tick, setTick] = (0, import_react8.useState)(0);
|
|
918
912
|
const [state, setState] = (0, import_react8.useState)({ data: null, loading: true, error: null });
|
|
@@ -921,8 +915,7 @@ function useQnA({ proxyUrl, productId, page: initialPage = 1, sort = "recent" })
|
|
|
921
915
|
(0, import_react8.useEffect)(() => {
|
|
922
916
|
let cancelled = false;
|
|
923
917
|
setState((s) => ({ ...s, loading: true, error: null }));
|
|
924
|
-
|
|
925
|
-
fetch(`${proxyUrl}/qna?${params.toString()}`).then((res) => {
|
|
918
|
+
apiFetch(creds, "/api/qna", { productId, page: String(page), sort }).then((res) => {
|
|
926
919
|
if (!res.ok) throw new Error(`Revova: qna fetch failed (${res.status})`);
|
|
927
920
|
return res.json();
|
|
928
921
|
}).then((data) => {
|
|
@@ -934,15 +927,11 @@ function useQnA({ proxyUrl, productId, page: initialPage = 1, sort = "recent" })
|
|
|
934
927
|
return () => {
|
|
935
928
|
cancelled = true;
|
|
936
929
|
};
|
|
937
|
-
}, [
|
|
938
|
-
const
|
|
930
|
+
}, [apiUrl, shop, apiToken, productId, page, sort, tick]);
|
|
931
|
+
const postQnA = (0, import_react8.useCallback)(async (payload) => {
|
|
939
932
|
setSubmitState({ submitting: true, success: false, error: null });
|
|
940
933
|
try {
|
|
941
|
-
const res = await
|
|
942
|
-
method: "POST",
|
|
943
|
-
headers: { "Content-Type": "application/json" },
|
|
944
|
-
body: JSON.stringify(payload)
|
|
945
|
-
});
|
|
934
|
+
const res = await apiFetch(creds, "/api/qna", {}, { method: "POST", body: JSON.stringify(payload) });
|
|
946
935
|
const json = await res.json();
|
|
947
936
|
if (!res.ok) {
|
|
948
937
|
setSubmitState({ submitting: false, success: false, error: json.error ?? `Failed (${res.status})` });
|
|
@@ -953,35 +942,27 @@ function useQnA({ proxyUrl, productId, page: initialPage = 1, sort = "recent" })
|
|
|
953
942
|
} catch (err) {
|
|
954
943
|
setSubmitState({ submitting: false, success: false, error: err instanceof Error ? err.message : "An error occurred." });
|
|
955
944
|
}
|
|
956
|
-
}, [
|
|
957
|
-
const submitAnswer = (0, import_react8.useCallback)(async (payload) => {
|
|
958
|
-
setSubmitState({ submitting: true, success: false, error: null });
|
|
959
|
-
try {
|
|
960
|
-
const res = await fetch(`${proxyUrl}/qna`, {
|
|
961
|
-
method: "POST",
|
|
962
|
-
headers: { "Content-Type": "application/json" },
|
|
963
|
-
body: JSON.stringify(payload)
|
|
964
|
-
});
|
|
965
|
-
const json = await res.json();
|
|
966
|
-
if (!res.ok) {
|
|
967
|
-
setSubmitState({ submitting: false, success: false, error: json.error ?? `Failed (${res.status})` });
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
setSubmitState({ submitting: false, success: true, error: null });
|
|
971
|
-
refetch();
|
|
972
|
-
} catch (err) {
|
|
973
|
-
setSubmitState({ submitting: false, success: false, error: err instanceof Error ? err.message : "An error occurred." });
|
|
974
|
-
}
|
|
975
|
-
}, [proxyUrl, refetch]);
|
|
945
|
+
}, [apiUrl, shop, apiToken, refetch]);
|
|
976
946
|
const resetSubmit = (0, import_react8.useCallback)(() => setSubmitState(INITIAL_SUBMIT), []);
|
|
977
|
-
return {
|
|
947
|
+
return {
|
|
948
|
+
...state,
|
|
949
|
+
setPage,
|
|
950
|
+
currentPage: page,
|
|
951
|
+
refetch,
|
|
952
|
+
submitQuestion: postQnA,
|
|
953
|
+
submitAnswer: postQnA,
|
|
954
|
+
submitState,
|
|
955
|
+
resetSubmit
|
|
956
|
+
};
|
|
978
957
|
}
|
|
979
958
|
|
|
980
959
|
// src/components/QnAWidget.tsx
|
|
981
960
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
982
|
-
function QnAWidget({
|
|
961
|
+
function QnAWidget({ apiUrl, shop, apiToken, productId, className }) {
|
|
983
962
|
const { data, loading, error, setPage, currentPage, submitQuestion, submitState, resetSubmit } = useQnA({
|
|
984
|
-
|
|
963
|
+
apiUrl,
|
|
964
|
+
shop,
|
|
965
|
+
apiToken,
|
|
985
966
|
productId
|
|
986
967
|
});
|
|
987
968
|
const [showForm, setShowForm] = (0, import_react9.useState)(false);
|
|
@@ -1073,14 +1054,16 @@ function QnAWidget({ proxyUrl, productId, className }) {
|
|
|
1073
1054
|
var import_react10 = require("react");
|
|
1074
1055
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1075
1056
|
function SocialProofPopup({
|
|
1076
|
-
|
|
1057
|
+
apiUrl,
|
|
1058
|
+
shop,
|
|
1059
|
+
apiToken,
|
|
1077
1060
|
position = "bottom-left",
|
|
1078
1061
|
intervalMs = 8e3,
|
|
1079
1062
|
displayMs = 5e3,
|
|
1080
1063
|
starColor,
|
|
1081
1064
|
className
|
|
1082
1065
|
}) {
|
|
1083
|
-
const { data } = useWidgetGlobals({
|
|
1066
|
+
const { data } = useWidgetGlobals({ apiUrl, shop, apiToken });
|
|
1084
1067
|
const [current, setCurrent] = (0, import_react10.useState)(null);
|
|
1085
1068
|
const [visible, setVisible] = (0, import_react10.useState)(false);
|
|
1086
1069
|
const [dismissed, setDismissed] = (0, import_react10.useState)(false);
|
|
@@ -1182,13 +1165,15 @@ function SocialProofPopup({
|
|
|
1182
1165
|
var import_react11 = require("react");
|
|
1183
1166
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1184
1167
|
function ReviewTicker({
|
|
1185
|
-
|
|
1168
|
+
apiUrl,
|
|
1169
|
+
shop,
|
|
1170
|
+
apiToken,
|
|
1186
1171
|
limit = 20,
|
|
1187
1172
|
speedSeconds = 30,
|
|
1188
1173
|
starColor,
|
|
1189
1174
|
className
|
|
1190
1175
|
}) {
|
|
1191
|
-
const { data, loading } = useWidgetGlobals({
|
|
1176
|
+
const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken, limit });
|
|
1192
1177
|
const trackRef = (0, import_react11.useRef)(null);
|
|
1193
1178
|
const reviews = data?.reviews ?? [];
|
|
1194
1179
|
if (loading || reviews.length === 0) return null;
|
|
@@ -1249,7 +1234,9 @@ function ReviewTicker({
|
|
|
1249
1234
|
var import_react12 = require("react");
|
|
1250
1235
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1251
1236
|
function FloatingReviewsTab({
|
|
1252
|
-
|
|
1237
|
+
apiUrl,
|
|
1238
|
+
shop,
|
|
1239
|
+
apiToken,
|
|
1253
1240
|
label = "Reviews",
|
|
1254
1241
|
position = "right",
|
|
1255
1242
|
color = "#111827",
|
|
@@ -1257,7 +1244,7 @@ function FloatingReviewsTab({
|
|
|
1257
1244
|
starColor,
|
|
1258
1245
|
className
|
|
1259
1246
|
}) {
|
|
1260
|
-
const { data } = useWidgetGlobals({
|
|
1247
|
+
const { data } = useWidgetGlobals({ apiUrl, shop, apiToken, limit });
|
|
1261
1248
|
const [open, setOpen] = (0, import_react12.useState)(false);
|
|
1262
1249
|
const reviews = data?.reviews ?? [];
|
|
1263
1250
|
const stats = data?.stats;
|
|
@@ -1334,8 +1321,8 @@ function FloatingReviewsTab({
|
|
|
1334
1321
|
|
|
1335
1322
|
// src/components/TrustBadge.tsx
|
|
1336
1323
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1337
|
-
function TrustBadge({
|
|
1338
|
-
const { data, loading } = useWidgetGlobals({
|
|
1324
|
+
function TrustBadge({ apiUrl, shop, apiToken, style: badgeStyle = "pill", starColor, className }) {
|
|
1325
|
+
const { data, loading } = useWidgetGlobals({ apiUrl, shop, apiToken });
|
|
1339
1326
|
if (loading || !data?.stats?.averageRating) return null;
|
|
1340
1327
|
const avg = parseFloat(data.stats.averageRating);
|
|
1341
1328
|
const count = data.stats.totalReviews;
|
|
@@ -1412,43 +1399,41 @@ var import_react14 = require("react");
|
|
|
1412
1399
|
|
|
1413
1400
|
// src/hooks/useForm.ts
|
|
1414
1401
|
var import_react13 = require("react");
|
|
1415
|
-
function useForm(
|
|
1402
|
+
function useForm(creds, productId) {
|
|
1416
1403
|
const [state, setState] = (0, import_react13.useState)({ form: null, loading: true, error: null });
|
|
1417
1404
|
(0, import_react13.useEffect)(() => {
|
|
1418
1405
|
let cancelled = false;
|
|
1419
|
-
|
|
1420
|
-
fetch(`${proxyUrl}/reviews?${params.toString()}`).then((res) => {
|
|
1406
|
+
apiFetch(creds, "/api/reviews", { productId, limit: "1", page: "1" }).then((res) => {
|
|
1421
1407
|
if (!res.ok) throw new Error(`Revova: form fetch failed (${res.status})`);
|
|
1422
1408
|
return res.json();
|
|
1423
1409
|
}).then(({ form }) => {
|
|
1424
1410
|
if (!cancelled) setState({ form, loading: false, error: null });
|
|
1425
1411
|
}).catch((err) => {
|
|
1426
1412
|
if (!cancelled)
|
|
1427
|
-
setState({
|
|
1428
|
-
form: null,
|
|
1429
|
-
loading: false,
|
|
1430
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
1431
|
-
});
|
|
1413
|
+
setState({ form: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) });
|
|
1432
1414
|
});
|
|
1433
1415
|
return () => {
|
|
1434
1416
|
cancelled = true;
|
|
1435
1417
|
};
|
|
1436
|
-
}, [
|
|
1418
|
+
}, [creds.apiUrl, creds.shop, creds.apiToken, productId]);
|
|
1437
1419
|
return state;
|
|
1438
1420
|
}
|
|
1439
1421
|
|
|
1440
1422
|
// src/components/FloatingReviewButton.tsx
|
|
1441
1423
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1442
1424
|
function FloatingReviewButton({
|
|
1443
|
-
|
|
1425
|
+
apiUrl,
|
|
1426
|
+
shop,
|
|
1427
|
+
apiToken,
|
|
1444
1428
|
productId,
|
|
1445
1429
|
text = "Write a Review",
|
|
1446
1430
|
color = "#111827",
|
|
1447
1431
|
position = "bottom-right",
|
|
1448
1432
|
className
|
|
1449
1433
|
}) {
|
|
1434
|
+
const creds = { apiUrl, shop, apiToken };
|
|
1450
1435
|
const [open, setOpen] = (0, import_react14.useState)(false);
|
|
1451
|
-
const { form, loading } = useForm(
|
|
1436
|
+
const { form, loading } = useForm(creds, productId);
|
|
1452
1437
|
const posStyle = position === "bottom-right" ? { bottom: 24, right: 24 } : { bottom: 24, left: 24 };
|
|
1453
1438
|
if (!loading && !form) return null;
|
|
1454
1439
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
@@ -1526,7 +1511,7 @@ function FloatingReviewButton({
|
|
|
1526
1511
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1527
1512
|
ReviewForm,
|
|
1528
1513
|
{
|
|
1529
|
-
|
|
1514
|
+
...creds,
|
|
1530
1515
|
productId,
|
|
1531
1516
|
form,
|
|
1532
1517
|
onSuccess: () => setOpen(false)
|
|
@@ -1542,7 +1527,7 @@ function FloatingReviewButton({
|
|
|
1542
1527
|
|
|
1543
1528
|
// src/hooks/useHelpfulVote.ts
|
|
1544
1529
|
var import_react15 = require("react");
|
|
1545
|
-
function useHelpfulVote(
|
|
1530
|
+
function useHelpfulVote(creds) {
|
|
1546
1531
|
const [loading, setLoading] = (0, import_react15.useState)(false);
|
|
1547
1532
|
const [voted, setVoted] = (0, import_react15.useState)(false);
|
|
1548
1533
|
const vote = (0, import_react15.useCallback)(
|
|
@@ -1550,9 +1535,8 @@ function useHelpfulVote(proxyUrl) {
|
|
|
1550
1535
|
if (voted || loading) return;
|
|
1551
1536
|
setLoading(true);
|
|
1552
1537
|
try {
|
|
1553
|
-
await
|
|
1538
|
+
await apiFetch(creds, "/api/helpful", {}, {
|
|
1554
1539
|
method: "POST",
|
|
1555
|
-
headers: { "Content-Type": "application/json" },
|
|
1556
1540
|
body: JSON.stringify(payload)
|
|
1557
1541
|
});
|
|
1558
1542
|
setVoted(true);
|
|
@@ -1560,7 +1544,7 @@ function useHelpfulVote(proxyUrl) {
|
|
|
1560
1544
|
setLoading(false);
|
|
1561
1545
|
}
|
|
1562
1546
|
},
|
|
1563
|
-
[
|
|
1547
|
+
[creds, voted, loading]
|
|
1564
1548
|
);
|
|
1565
1549
|
return { vote, loading, voted };
|
|
1566
1550
|
}
|
|
@@ -1578,6 +1562,7 @@ function useHelpfulVote(proxyUrl) {
|
|
|
1578
1562
|
SocialProofPopup,
|
|
1579
1563
|
StarRating,
|
|
1580
1564
|
TrustBadge,
|
|
1565
|
+
apiFetch,
|
|
1581
1566
|
useForm,
|
|
1582
1567
|
useHelpfulVote,
|
|
1583
1568
|
useQnA,
|