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