@shellapps/experience-react 1.2.0 → 1.3.1
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.d.mts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +531 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +529 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -103,10 +103,527 @@ var ErrorBoundary = class extends React2.Component {
|
|
|
103
103
|
};
|
|
104
104
|
ErrorBoundary.contextType = ExperienceContext;
|
|
105
105
|
|
|
106
|
+
// src/FeedbackButton.tsx
|
|
107
|
+
import { useState, useEffect as useEffect2, useCallback, useRef as useRef2, useContext } from "react";
|
|
108
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
109
|
+
var TYPE_META = {
|
|
110
|
+
"bug": { icon: "\u{1F41B}", label: "Bug Report", color: "#ef4444" },
|
|
111
|
+
"feature-request": { icon: "\u{1F4A1}", label: "Feature Request", color: "#8b5cf6" },
|
|
112
|
+
"comment": { icon: "\u{1F4AC}", label: "Comment", color: "#3b82f6" },
|
|
113
|
+
"praise": { icon: "\u{1F389}", label: "Praise", color: "#10b981" }
|
|
114
|
+
};
|
|
115
|
+
var STATUS_LABELS = {
|
|
116
|
+
"new": "\u{1F195} New",
|
|
117
|
+
"acknowledged": "\u{1F440} Acknowledged",
|
|
118
|
+
"in-progress": "\u{1F527} In Progress",
|
|
119
|
+
"resolved": "\u2705 Resolved",
|
|
120
|
+
"wont-fix": "\u{1F6AB} Won't Fix"
|
|
121
|
+
};
|
|
122
|
+
function FeedbackButton({
|
|
123
|
+
position = "bottom-right",
|
|
124
|
+
label = "Feedback",
|
|
125
|
+
color = "#6366f1",
|
|
126
|
+
hidden = false,
|
|
127
|
+
apiUrl,
|
|
128
|
+
authToken
|
|
129
|
+
}) {
|
|
130
|
+
const experience = useContext(ExperienceContext);
|
|
131
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
132
|
+
const [view, setView] = useState("list");
|
|
133
|
+
const [selectedType, setSelectedType] = useState("comment");
|
|
134
|
+
const [title, setTitle] = useState("");
|
|
135
|
+
const [content, setContent] = useState("");
|
|
136
|
+
const [submitting, setSubmitting] = useState(false);
|
|
137
|
+
const [items, setItems] = useState([]);
|
|
138
|
+
const [selectedItem, setSelectedItem] = useState(null);
|
|
139
|
+
const [replies, setReplies] = useState([]);
|
|
140
|
+
const [replyText, setReplyText] = useState("");
|
|
141
|
+
const [loading, setLoading] = useState(false);
|
|
142
|
+
const [screenshot, setScreenshot] = useState(null);
|
|
143
|
+
const panelRef = useRef2(null);
|
|
144
|
+
const baseUrl = apiUrl || experience?.config?.apiUrl || "https://experience.shellapps.com";
|
|
145
|
+
const appId = experience?.config?.appId || "";
|
|
146
|
+
const headers = useCallback(() => {
|
|
147
|
+
const h = { "Content-Type": "application/json" };
|
|
148
|
+
if (authToken) h["Authorization"] = `Bearer ${authToken}`;
|
|
149
|
+
return h;
|
|
150
|
+
}, [authToken]);
|
|
151
|
+
useEffect2(() => {
|
|
152
|
+
if (experience) {
|
|
153
|
+
experience.openFeedback = () => setIsOpen(true);
|
|
154
|
+
}
|
|
155
|
+
return () => {
|
|
156
|
+
if (experience) delete experience.openFeedback;
|
|
157
|
+
};
|
|
158
|
+
}, [experience]);
|
|
159
|
+
useEffect2(() => {
|
|
160
|
+
if (isOpen && view === "list") {
|
|
161
|
+
loadFeedback();
|
|
162
|
+
}
|
|
163
|
+
}, [isOpen, view]);
|
|
164
|
+
const loadFeedback = async () => {
|
|
165
|
+
setLoading(true);
|
|
166
|
+
try {
|
|
167
|
+
const res = await fetch(
|
|
168
|
+
`${baseUrl}/api/v1/feedback?appId=${appId}&limit=20`,
|
|
169
|
+
{ headers: headers() }
|
|
170
|
+
);
|
|
171
|
+
if (res.ok) {
|
|
172
|
+
const data = await res.json();
|
|
173
|
+
setItems(data.items || []);
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
setLoading(false);
|
|
178
|
+
};
|
|
179
|
+
const loadDetail = async (item) => {
|
|
180
|
+
setSelectedItem(item);
|
|
181
|
+
setView("detail");
|
|
182
|
+
try {
|
|
183
|
+
const res = await fetch(
|
|
184
|
+
`${baseUrl}/api/v1/feedback/${item._id}`,
|
|
185
|
+
{ headers: headers() }
|
|
186
|
+
);
|
|
187
|
+
if (res.ok) {
|
|
188
|
+
const data = await res.json();
|
|
189
|
+
setReplies(data.replies || []);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const captureScreenshot = async () => {
|
|
195
|
+
try {
|
|
196
|
+
const html2canvas = window.html2canvas;
|
|
197
|
+
if (!html2canvas) return;
|
|
198
|
+
const canvas = await html2canvas(document.body, { scale: 0.5, logging: false });
|
|
199
|
+
setScreenshot(canvas.toDataURL("image/jpeg", 0.6));
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const submit = async () => {
|
|
204
|
+
if (!title.trim() || !content.trim()) return;
|
|
205
|
+
setSubmitting(true);
|
|
206
|
+
try {
|
|
207
|
+
const ctx = {
|
|
208
|
+
userAgent: navigator.userAgent,
|
|
209
|
+
url: window.location.href,
|
|
210
|
+
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
211
|
+
locale: navigator.language,
|
|
212
|
+
referrer: document.referrer || void 0
|
|
213
|
+
};
|
|
214
|
+
const res = await fetch(`${baseUrl}/api/v1/feedback`, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: headers(),
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
appId,
|
|
219
|
+
type: selectedType,
|
|
220
|
+
title: title.trim(),
|
|
221
|
+
content: content.trim(),
|
|
222
|
+
context: ctx,
|
|
223
|
+
screenshot: screenshot || void 0
|
|
224
|
+
})
|
|
225
|
+
});
|
|
226
|
+
if (res.ok) {
|
|
227
|
+
setTitle("");
|
|
228
|
+
setContent("");
|
|
229
|
+
setScreenshot(null);
|
|
230
|
+
setView("list");
|
|
231
|
+
loadFeedback();
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
setSubmitting(false);
|
|
236
|
+
};
|
|
237
|
+
const submitReply = async () => {
|
|
238
|
+
if (!replyText.trim() || !selectedItem) return;
|
|
239
|
+
try {
|
|
240
|
+
const res = await fetch(`${baseUrl}/api/v1/feedback/${selectedItem._id}/reply`, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: headers(),
|
|
243
|
+
body: JSON.stringify({ content: replyText.trim() })
|
|
244
|
+
});
|
|
245
|
+
if (res.ok) {
|
|
246
|
+
const reply = await res.json();
|
|
247
|
+
setReplies((prev) => [...prev, reply]);
|
|
248
|
+
setReplyText("");
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const vote = async (id) => {
|
|
254
|
+
try {
|
|
255
|
+
await fetch(`${baseUrl}/api/v1/feedback/${id}/vote`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: headers(),
|
|
258
|
+
body: JSON.stringify({ type: "upvote" })
|
|
259
|
+
});
|
|
260
|
+
loadFeedback();
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const posStyles = {
|
|
265
|
+
"bottom-right": { bottom: 20, right: 20 },
|
|
266
|
+
"bottom-left": { bottom: 20, left: 20 },
|
|
267
|
+
"top-right": { top: 20, right: 20 },
|
|
268
|
+
"top-left": { top: 20, left: 20 }
|
|
269
|
+
};
|
|
270
|
+
const panelPos = {
|
|
271
|
+
"bottom-right": { bottom: 70, right: 20 },
|
|
272
|
+
"bottom-left": { bottom: 70, left: 20 },
|
|
273
|
+
"top-right": { top: 70, right: 20 },
|
|
274
|
+
"top-left": { top: 70, left: 20 }
|
|
275
|
+
};
|
|
276
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
277
|
+
!hidden && /* @__PURE__ */ jsxs2(
|
|
278
|
+
"button",
|
|
279
|
+
{
|
|
280
|
+
onClick: () => setIsOpen(!isOpen),
|
|
281
|
+
style: {
|
|
282
|
+
position: "fixed",
|
|
283
|
+
...posStyles[position],
|
|
284
|
+
zIndex: 99999,
|
|
285
|
+
background: color,
|
|
286
|
+
color: "#fff",
|
|
287
|
+
border: "none",
|
|
288
|
+
borderRadius: 28,
|
|
289
|
+
padding: "10px 20px",
|
|
290
|
+
fontSize: 14,
|
|
291
|
+
fontWeight: 600,
|
|
292
|
+
cursor: "pointer",
|
|
293
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
294
|
+
display: "flex",
|
|
295
|
+
alignItems: "center",
|
|
296
|
+
gap: 6
|
|
297
|
+
},
|
|
298
|
+
children: [
|
|
299
|
+
"\u{1F4AC} ",
|
|
300
|
+
label
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
),
|
|
304
|
+
isOpen && /* @__PURE__ */ jsxs2(
|
|
305
|
+
"div",
|
|
306
|
+
{
|
|
307
|
+
ref: panelRef,
|
|
308
|
+
style: {
|
|
309
|
+
position: "fixed",
|
|
310
|
+
...panelPos[position],
|
|
311
|
+
zIndex: 1e5,
|
|
312
|
+
width: 380,
|
|
313
|
+
maxHeight: 520,
|
|
314
|
+
background: "#fff",
|
|
315
|
+
borderRadius: 12,
|
|
316
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
|
|
317
|
+
display: "flex",
|
|
318
|
+
flexDirection: "column",
|
|
319
|
+
overflow: "hidden",
|
|
320
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
321
|
+
fontSize: 14,
|
|
322
|
+
color: "#1f2937"
|
|
323
|
+
},
|
|
324
|
+
children: [
|
|
325
|
+
/* @__PURE__ */ jsxs2(
|
|
326
|
+
"div",
|
|
327
|
+
{
|
|
328
|
+
style: {
|
|
329
|
+
padding: "14px 16px",
|
|
330
|
+
background: color,
|
|
331
|
+
color: "#fff",
|
|
332
|
+
display: "flex",
|
|
333
|
+
justifyContent: "space-between",
|
|
334
|
+
alignItems: "center"
|
|
335
|
+
},
|
|
336
|
+
children: [
|
|
337
|
+
/* @__PURE__ */ jsx3("span", { style: { fontWeight: 600 }, children: view === "new" ? "New Feedback" : view === "detail" ? "Feedback" : "Feedback" }),
|
|
338
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
|
|
339
|
+
view !== "list" && /* @__PURE__ */ jsx3(
|
|
340
|
+
"button",
|
|
341
|
+
{
|
|
342
|
+
onClick: () => {
|
|
343
|
+
setView("list");
|
|
344
|
+
setSelectedItem(null);
|
|
345
|
+
},
|
|
346
|
+
style: { background: "none", border: "none", color: "#fff", cursor: "pointer", fontSize: 14 },
|
|
347
|
+
children: "\u2190 Back"
|
|
348
|
+
}
|
|
349
|
+
),
|
|
350
|
+
/* @__PURE__ */ jsx3(
|
|
351
|
+
"button",
|
|
352
|
+
{
|
|
353
|
+
onClick: () => setIsOpen(false),
|
|
354
|
+
style: { background: "none", border: "none", color: "#fff", cursor: "pointer", fontSize: 18 },
|
|
355
|
+
children: "\u2715"
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
] })
|
|
359
|
+
]
|
|
360
|
+
}
|
|
361
|
+
),
|
|
362
|
+
/* @__PURE__ */ jsxs2("div", { style: { flex: 1, overflow: "auto", padding: 16 }, children: [
|
|
363
|
+
view === "list" && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
364
|
+
/* @__PURE__ */ jsx3(
|
|
365
|
+
"button",
|
|
366
|
+
{
|
|
367
|
+
onClick: () => setView("new"),
|
|
368
|
+
style: {
|
|
369
|
+
width: "100%",
|
|
370
|
+
padding: "10px",
|
|
371
|
+
background: color,
|
|
372
|
+
color: "#fff",
|
|
373
|
+
border: "none",
|
|
374
|
+
borderRadius: 8,
|
|
375
|
+
cursor: "pointer",
|
|
376
|
+
fontWeight: 600,
|
|
377
|
+
marginBottom: 12
|
|
378
|
+
},
|
|
379
|
+
children: "+ New Feedback"
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
loading ? /* @__PURE__ */ jsx3("p", { style: { textAlign: "center", color: "#9ca3af" }, children: "Loading\u2026" }) : items.length === 0 ? /* @__PURE__ */ jsx3("p", { style: { textAlign: "center", color: "#9ca3af" }, children: "No feedback yet" }) : items.map((item) => /* @__PURE__ */ jsxs2(
|
|
383
|
+
"div",
|
|
384
|
+
{
|
|
385
|
+
onClick: () => loadDetail(item),
|
|
386
|
+
style: {
|
|
387
|
+
padding: 10,
|
|
388
|
+
borderRadius: 8,
|
|
389
|
+
border: "1px solid #e5e7eb",
|
|
390
|
+
marginBottom: 8,
|
|
391
|
+
cursor: "pointer"
|
|
392
|
+
},
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 4 }, children: [
|
|
395
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
396
|
+
TYPE_META[item.type]?.icon,
|
|
397
|
+
" ",
|
|
398
|
+
/* @__PURE__ */ jsx3("strong", { children: item.title })
|
|
399
|
+
] }),
|
|
400
|
+
item.type === "feature-request" && /* @__PURE__ */ jsxs2(
|
|
401
|
+
"button",
|
|
402
|
+
{
|
|
403
|
+
onClick: (e) => {
|
|
404
|
+
e.stopPropagation();
|
|
405
|
+
vote(item._id);
|
|
406
|
+
},
|
|
407
|
+
style: {
|
|
408
|
+
background: "none",
|
|
409
|
+
border: "1px solid #d1d5db",
|
|
410
|
+
borderRadius: 4,
|
|
411
|
+
padding: "2px 6px",
|
|
412
|
+
cursor: "pointer",
|
|
413
|
+
fontSize: 12
|
|
414
|
+
},
|
|
415
|
+
children: [
|
|
416
|
+
"\u25B2 ",
|
|
417
|
+
item.vote_count
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
] }),
|
|
422
|
+
/* @__PURE__ */ jsxs2("div", { style: { fontSize: 12, color: "#6b7280" }, children: [
|
|
423
|
+
STATUS_LABELS[item.status],
|
|
424
|
+
" \xB7 ",
|
|
425
|
+
item.reply_count,
|
|
426
|
+
" replies \xB7 ",
|
|
427
|
+
new Date(item.created_at).toLocaleDateString()
|
|
428
|
+
] })
|
|
429
|
+
]
|
|
430
|
+
},
|
|
431
|
+
item._id
|
|
432
|
+
))
|
|
433
|
+
] }),
|
|
434
|
+
view === "new" && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
435
|
+
/* @__PURE__ */ jsx3("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginBottom: 12 }, children: Object.entries(TYPE_META).map(
|
|
436
|
+
([type, meta]) => /* @__PURE__ */ jsxs2(
|
|
437
|
+
"button",
|
|
438
|
+
{
|
|
439
|
+
onClick: () => setSelectedType(type),
|
|
440
|
+
style: {
|
|
441
|
+
padding: 8,
|
|
442
|
+
borderRadius: 8,
|
|
443
|
+
border: `2px solid ${selectedType === type ? meta.color : "#e5e7eb"}`,
|
|
444
|
+
background: selectedType === type ? `${meta.color}10` : "#fff",
|
|
445
|
+
cursor: "pointer",
|
|
446
|
+
fontSize: 13,
|
|
447
|
+
textAlign: "center"
|
|
448
|
+
},
|
|
449
|
+
children: [
|
|
450
|
+
meta.icon,
|
|
451
|
+
" ",
|
|
452
|
+
meta.label
|
|
453
|
+
]
|
|
454
|
+
},
|
|
455
|
+
type
|
|
456
|
+
)
|
|
457
|
+
) }),
|
|
458
|
+
/* @__PURE__ */ jsx3(
|
|
459
|
+
"input",
|
|
460
|
+
{
|
|
461
|
+
placeholder: "Title",
|
|
462
|
+
value: title,
|
|
463
|
+
onChange: (e) => setTitle(e.target.value),
|
|
464
|
+
style: {
|
|
465
|
+
width: "100%",
|
|
466
|
+
padding: 8,
|
|
467
|
+
borderRadius: 6,
|
|
468
|
+
border: "1px solid #d1d5db",
|
|
469
|
+
marginBottom: 8,
|
|
470
|
+
fontSize: 14,
|
|
471
|
+
boxSizing: "border-box"
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
),
|
|
475
|
+
/* @__PURE__ */ jsx3(
|
|
476
|
+
"textarea",
|
|
477
|
+
{
|
|
478
|
+
placeholder: "Describe your feedback\u2026",
|
|
479
|
+
value: content,
|
|
480
|
+
onChange: (e) => setContent(e.target.value),
|
|
481
|
+
rows: 4,
|
|
482
|
+
style: {
|
|
483
|
+
width: "100%",
|
|
484
|
+
padding: 8,
|
|
485
|
+
borderRadius: 6,
|
|
486
|
+
border: "1px solid #d1d5db",
|
|
487
|
+
marginBottom: 8,
|
|
488
|
+
fontSize: 14,
|
|
489
|
+
resize: "vertical",
|
|
490
|
+
boxSizing: "border-box"
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
),
|
|
494
|
+
window.html2canvas && /* @__PURE__ */ jsxs2(
|
|
495
|
+
"button",
|
|
496
|
+
{
|
|
497
|
+
onClick: captureScreenshot,
|
|
498
|
+
style: {
|
|
499
|
+
background: "none",
|
|
500
|
+
border: "1px solid #d1d5db",
|
|
501
|
+
borderRadius: 6,
|
|
502
|
+
padding: "6px 10px",
|
|
503
|
+
cursor: "pointer",
|
|
504
|
+
fontSize: 12,
|
|
505
|
+
marginBottom: 8,
|
|
506
|
+
color: screenshot ? "#10b981" : "#6b7280"
|
|
507
|
+
},
|
|
508
|
+
children: [
|
|
509
|
+
"\u{1F4F7} ",
|
|
510
|
+
screenshot ? "Screenshot captured \u2713" : "Capture screenshot"
|
|
511
|
+
]
|
|
512
|
+
}
|
|
513
|
+
),
|
|
514
|
+
/* @__PURE__ */ jsx3(
|
|
515
|
+
"button",
|
|
516
|
+
{
|
|
517
|
+
onClick: submit,
|
|
518
|
+
disabled: submitting || !title.trim() || !content.trim(),
|
|
519
|
+
style: {
|
|
520
|
+
width: "100%",
|
|
521
|
+
padding: 10,
|
|
522
|
+
background: submitting ? "#9ca3af" : color,
|
|
523
|
+
color: "#fff",
|
|
524
|
+
border: "none",
|
|
525
|
+
borderRadius: 8,
|
|
526
|
+
cursor: submitting ? "default" : "pointer",
|
|
527
|
+
fontWeight: 600
|
|
528
|
+
},
|
|
529
|
+
children: submitting ? "Submitting\u2026" : "Submit"
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
] }),
|
|
533
|
+
view === "detail" && selectedItem && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
534
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 12 }, children: [
|
|
535
|
+
/* @__PURE__ */ jsxs2("div", { style: { fontSize: 12, color: TYPE_META[selectedItem.type]?.color, fontWeight: 600, marginBottom: 4 }, children: [
|
|
536
|
+
TYPE_META[selectedItem.type]?.icon,
|
|
537
|
+
" ",
|
|
538
|
+
TYPE_META[selectedItem.type]?.label
|
|
539
|
+
] }),
|
|
540
|
+
/* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 4px", fontSize: 16 }, children: selectedItem.title }),
|
|
541
|
+
/* @__PURE__ */ jsxs2("div", { style: { fontSize: 12, color: "#6b7280", marginBottom: 8 }, children: [
|
|
542
|
+
STATUS_LABELS[selectedItem.status],
|
|
543
|
+
" \xB7 ",
|
|
544
|
+
new Date(selectedItem.created_at).toLocaleDateString()
|
|
545
|
+
] }),
|
|
546
|
+
/* @__PURE__ */ jsx3("p", { style: { margin: 0, lineHeight: 1.5 }, children: selectedItem.content })
|
|
547
|
+
] }),
|
|
548
|
+
/* @__PURE__ */ jsx3("hr", { style: { border: "none", borderTop: "1px solid #e5e7eb", margin: "12px 0" } }),
|
|
549
|
+
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: 12 }, children: [
|
|
550
|
+
/* @__PURE__ */ jsxs2("strong", { style: { fontSize: 13 }, children: [
|
|
551
|
+
"Replies (",
|
|
552
|
+
replies.length,
|
|
553
|
+
")"
|
|
554
|
+
] }),
|
|
555
|
+
replies.map((r) => /* @__PURE__ */ jsxs2(
|
|
556
|
+
"div",
|
|
557
|
+
{
|
|
558
|
+
style: {
|
|
559
|
+
padding: 8,
|
|
560
|
+
marginTop: 8,
|
|
561
|
+
borderRadius: 6,
|
|
562
|
+
background: r.author_type === "developer" ? "#eff6ff" : "#f9fafb",
|
|
563
|
+
borderLeft: `3px solid ${r.author_type === "developer" ? "#3b82f6" : "#d1d5db"}`
|
|
564
|
+
},
|
|
565
|
+
children: [
|
|
566
|
+
/* @__PURE__ */ jsxs2("div", { style: { fontSize: 11, color: "#6b7280", marginBottom: 2 }, children: [
|
|
567
|
+
r.author_type === "developer" ? "\u{1F6E0}\uFE0F Developer" : "\u{1F464} You",
|
|
568
|
+
r.author_name ? ` \xB7 ${r.author_name}` : "",
|
|
569
|
+
" \xB7 ",
|
|
570
|
+
new Date(r.created_at).toLocaleDateString()
|
|
571
|
+
] }),
|
|
572
|
+
/* @__PURE__ */ jsx3("div", { style: { fontSize: 13 }, children: r.content })
|
|
573
|
+
]
|
|
574
|
+
},
|
|
575
|
+
r._id
|
|
576
|
+
))
|
|
577
|
+
] }),
|
|
578
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 6 }, children: [
|
|
579
|
+
/* @__PURE__ */ jsx3(
|
|
580
|
+
"input",
|
|
581
|
+
{
|
|
582
|
+
placeholder: "Write a reply\u2026",
|
|
583
|
+
value: replyText,
|
|
584
|
+
onChange: (e) => setReplyText(e.target.value),
|
|
585
|
+
onKeyDown: (e) => {
|
|
586
|
+
if (e.key === "Enter") submitReply();
|
|
587
|
+
},
|
|
588
|
+
style: {
|
|
589
|
+
flex: 1,
|
|
590
|
+
padding: 8,
|
|
591
|
+
borderRadius: 6,
|
|
592
|
+
border: "1px solid #d1d5db",
|
|
593
|
+
fontSize: 13
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
),
|
|
597
|
+
/* @__PURE__ */ jsx3(
|
|
598
|
+
"button",
|
|
599
|
+
{
|
|
600
|
+
onClick: submitReply,
|
|
601
|
+
disabled: !replyText.trim(),
|
|
602
|
+
style: {
|
|
603
|
+
padding: "8px 12px",
|
|
604
|
+
background: color,
|
|
605
|
+
color: "#fff",
|
|
606
|
+
border: "none",
|
|
607
|
+
borderRadius: 6,
|
|
608
|
+
cursor: "pointer",
|
|
609
|
+
fontSize: 13
|
|
610
|
+
},
|
|
611
|
+
children: "Send"
|
|
612
|
+
}
|
|
613
|
+
)
|
|
614
|
+
] })
|
|
615
|
+
] })
|
|
616
|
+
] })
|
|
617
|
+
]
|
|
618
|
+
}
|
|
619
|
+
)
|
|
620
|
+
] });
|
|
621
|
+
}
|
|
622
|
+
|
|
106
623
|
// src/hooks.ts
|
|
107
|
-
import { useContext, useState, useCallback, useMemo, useEffect as
|
|
624
|
+
import { useContext as useContext2, useState as useState2, useCallback as useCallback2, useMemo, useEffect as useEffect3 } from "react";
|
|
108
625
|
function useExperience() {
|
|
109
|
-
const ctx =
|
|
626
|
+
const ctx = useContext2(ExperienceContext);
|
|
110
627
|
if (!ctx) throw new Error("useExperience must be used within ExperienceProvider");
|
|
111
628
|
return ctx;
|
|
112
629
|
}
|
|
@@ -128,13 +645,13 @@ function useTranslation() {
|
|
|
128
645
|
const config = experience.config || {};
|
|
129
646
|
const appId = config.appId || "";
|
|
130
647
|
const apiBaseUrl = config.apiUrl || config.baseUrl || "https://experience-api.shellapps.com";
|
|
131
|
-
const [locale, setLocaleState] =
|
|
648
|
+
const [locale, setLocaleState] = useState2(() => {
|
|
132
649
|
if (typeof window === "undefined") return "en";
|
|
133
650
|
const saved = localStorage.getItem(`exp_locale_${appId}`);
|
|
134
651
|
if (saved) return saved;
|
|
135
652
|
return navigator.language?.split("-")[0] || "en";
|
|
136
653
|
});
|
|
137
|
-
const [translations, setTranslations] =
|
|
654
|
+
const [translations, setTranslations] = useState2(() => {
|
|
138
655
|
if (translationCache[locale]) return translationCache[locale];
|
|
139
656
|
if (typeof window !== "undefined") {
|
|
140
657
|
try {
|
|
@@ -149,9 +666,9 @@ function useTranslation() {
|
|
|
149
666
|
}
|
|
150
667
|
return {};
|
|
151
668
|
});
|
|
152
|
-
const [locales, setLocales] =
|
|
153
|
-
const [isLoading, setIsLoading] =
|
|
154
|
-
const fetchTranslations =
|
|
669
|
+
const [locales, setLocales] = useState2([]);
|
|
670
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
671
|
+
const fetchTranslations = useCallback2(async (loc) => {
|
|
155
672
|
if (!appId) return {};
|
|
156
673
|
const cacheKey = `${appId}_${loc}`;
|
|
157
674
|
const existing = fetchPromises.get(cacheKey);
|
|
@@ -178,12 +695,12 @@ function useTranslation() {
|
|
|
178
695
|
fetchPromises.set(cacheKey, promise);
|
|
179
696
|
return promise;
|
|
180
697
|
}, [appId, apiBaseUrl]);
|
|
181
|
-
|
|
698
|
+
useEffect3(() => {
|
|
182
699
|
if (!appId) return;
|
|
183
700
|
fetch(`${apiBaseUrl}/api/v1/translations/${appId}/locales`).then((res) => res.ok ? res.json() : { locales: [] }).then((data) => setLocales(data.locales || [])).catch(() => {
|
|
184
701
|
});
|
|
185
702
|
}, [appId, apiBaseUrl]);
|
|
186
|
-
|
|
703
|
+
useEffect3(() => {
|
|
187
704
|
if (!appId) return;
|
|
188
705
|
if (translationCache[locale]) {
|
|
189
706
|
setTranslations(translationCache[locale]);
|
|
@@ -195,7 +712,7 @@ function useTranslation() {
|
|
|
195
712
|
setIsLoading(false);
|
|
196
713
|
});
|
|
197
714
|
}, [locale, appId, fetchTranslations]);
|
|
198
|
-
const t =
|
|
715
|
+
const t = useCallback2((key, params) => {
|
|
199
716
|
let result = translations[key] ?? key;
|
|
200
717
|
if (result.includes("||") && params && "count" in params) {
|
|
201
718
|
const parts = result.split("||");
|
|
@@ -209,7 +726,7 @@ function useTranslation() {
|
|
|
209
726
|
}
|
|
210
727
|
return result;
|
|
211
728
|
}, [translations]);
|
|
212
|
-
const setLocale =
|
|
729
|
+
const setLocale = useCallback2((newLocale) => {
|
|
213
730
|
setLocaleState(newLocale);
|
|
214
731
|
if (typeof window !== "undefined") {
|
|
215
732
|
localStorage.setItem(`exp_locale_${appId}`, newLocale);
|
|
@@ -221,6 +738,7 @@ export {
|
|
|
221
738
|
ErrorBoundary,
|
|
222
739
|
ExperienceContext,
|
|
223
740
|
ExperienceProvider,
|
|
741
|
+
FeedbackButton,
|
|
224
742
|
useExperience,
|
|
225
743
|
useFlag,
|
|
226
744
|
useTrack,
|