@mushi-mushi/react-native 0.13.1 → 0.15.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/README.md +6 -0
- package/dist/index.cjs +298 -9
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +302 -11
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -115,3 +115,9 @@ See the full [CHANGELOG](https://github.com/kensaurus/mushi-mushi/blob/master/pa
|
|
|
115
115
|
## License
|
|
116
116
|
|
|
117
117
|
MIT
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
<!-- mushi-readme-stats-footer -->
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
<sub>Monorepo scale (June 2026): 43 edge functions · 234 SQL migrations · 13 outbound plugins · 11 inbound adapters. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
|
package/dist/index.cjs
CHANGED
|
@@ -319,7 +319,12 @@ var CATEGORIES = [
|
|
|
319
319
|
{ key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
|
|
320
320
|
{ key: "other", emoji: "\u{1F4AC}", label: "Other" }
|
|
321
321
|
];
|
|
322
|
-
var MushiBottomSheet = ({
|
|
322
|
+
var MushiBottomSheet = ({
|
|
323
|
+
visible,
|
|
324
|
+
onClose,
|
|
325
|
+
screenshotDataUrl,
|
|
326
|
+
onClearScreenshot
|
|
327
|
+
}) => {
|
|
323
328
|
const mushi = useMushiContext();
|
|
324
329
|
const scheme = (0, import_react_native2.useColorScheme)();
|
|
325
330
|
const dark = scheme === "dark";
|
|
@@ -328,6 +333,47 @@ var MushiBottomSheet = ({ visible, onClose }) => {
|
|
|
328
333
|
const [category, setCategory] = (0, import_react.useState)(null);
|
|
329
334
|
const [description, setDescription] = (0, import_react.useState)("");
|
|
330
335
|
const [phase, setPhase] = (0, import_react.useState)("form");
|
|
336
|
+
const [sheetTab, setSheetTab] = (0, import_react.useState)("report");
|
|
337
|
+
const [inboxReports, setInboxReports] = (0, import_react.useState)([]);
|
|
338
|
+
const [inboxLoading, setInboxLoading] = (0, import_react.useState)(false);
|
|
339
|
+
const [selectedReportId, setSelectedReportId] = (0, import_react.useState)(null);
|
|
340
|
+
const [threadComments, setThreadComments] = (0, import_react.useState)([]);
|
|
341
|
+
const [replyText, setReplyText] = (0, import_react.useState)("");
|
|
342
|
+
const [screenshotAttached, setScreenshotAttached] = (0, import_react.useState)(true);
|
|
343
|
+
(0, import_react.useEffect)(() => {
|
|
344
|
+
if (visible) {
|
|
345
|
+
setScreenshotAttached(true);
|
|
346
|
+
if (sheetTab === "inbox") void loadInbox();
|
|
347
|
+
}
|
|
348
|
+
}, [visible, sheetTab]);
|
|
349
|
+
const loadInbox = (0, import_react.useCallback)(async () => {
|
|
350
|
+
if (!mushi?.listMyReports) return;
|
|
351
|
+
setInboxLoading(true);
|
|
352
|
+
try {
|
|
353
|
+
const rows = await mushi.listMyReports();
|
|
354
|
+
setInboxReports(rows);
|
|
355
|
+
} finally {
|
|
356
|
+
setInboxLoading(false);
|
|
357
|
+
}
|
|
358
|
+
}, [mushi]);
|
|
359
|
+
const openThread = (0, import_react.useCallback)(async (reportId) => {
|
|
360
|
+
setSelectedReportId(reportId);
|
|
361
|
+
if (!mushi?.listMyComments) return;
|
|
362
|
+
const comments = await mushi.listMyComments(reportId);
|
|
363
|
+
setThreadComments(comments);
|
|
364
|
+
}, [mushi]);
|
|
365
|
+
const sendReply = (0, import_react.useCallback)(async () => {
|
|
366
|
+
if (!mushi?.replyToReport || !selectedReportId || !replyText.trim()) return;
|
|
367
|
+
await mushi.replyToReport(selectedReportId, replyText.trim());
|
|
368
|
+
setReplyText("");
|
|
369
|
+
await openThread(selectedReportId);
|
|
370
|
+
}, [mushi, selectedReportId, replyText, openThread]);
|
|
371
|
+
const submitFeedback = (0, import_react.useCallback)(async (signal) => {
|
|
372
|
+
if (!mushi?.submitFeedbackSignal || !selectedReportId) return;
|
|
373
|
+
await mushi.submitFeedbackSignal(selectedReportId, signal);
|
|
374
|
+
await loadInbox();
|
|
375
|
+
await openThread(selectedReportId);
|
|
376
|
+
}, [mushi, selectedReportId, loadInbox, openThread]);
|
|
331
377
|
const resetForm = (0, import_react.useCallback)(() => {
|
|
332
378
|
setCategory(null);
|
|
333
379
|
setDescription("");
|
|
@@ -399,13 +445,18 @@ var MushiBottomSheet = ({ visible, onClose }) => {
|
|
|
399
445
|
if (!category || !description.trim() || !mushi) return;
|
|
400
446
|
setPhase("sending");
|
|
401
447
|
try {
|
|
402
|
-
await mushi.submitReport({
|
|
448
|
+
await mushi.submitReport({
|
|
449
|
+
category,
|
|
450
|
+
description: description.trim(),
|
|
451
|
+
screenshotDataUrl: screenshotDataUrl && screenshotAttached ? screenshotDataUrl : void 0
|
|
452
|
+
});
|
|
403
453
|
setPhase("sent");
|
|
404
454
|
setTimeout(handleClose, 1400);
|
|
405
455
|
} catch {
|
|
406
456
|
setPhase("form");
|
|
407
457
|
}
|
|
408
458
|
};
|
|
459
|
+
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
409
460
|
const colors = dark ? { bg: "#1c1c1e", text: "#f2f2f7", sub: "#8e8e93", card: "#2c2c2e", accent: "#0a84ff", border: "#38383a", backdrop: "rgba(0,0,0,0.6)" } : { bg: "#ffffff", text: "#1c1c1e", sub: "#8e8e93", card: "#f2f2f7", accent: "#007aff", border: "#e5e5ea", backdrop: "rgba(0,0,0,0.35)" };
|
|
410
461
|
const canSubmit = !!category && description.trim().length > 0 && phase === "form";
|
|
411
462
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
@@ -430,7 +481,44 @@ var MushiBottomSheet = ({ visible, onClose }) => {
|
|
|
430
481
|
],
|
|
431
482
|
children: [
|
|
432
483
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
|
|
433
|
-
|
|
484
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.tabRow, children: ["report", "inbox"].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
485
|
+
import_react_native2.TouchableOpacity,
|
|
486
|
+
{
|
|
487
|
+
onPress: () => {
|
|
488
|
+
setSheetTab(tab);
|
|
489
|
+
if (tab === "inbox") void loadInbox();
|
|
490
|
+
else setSelectedReportId(null);
|
|
491
|
+
},
|
|
492
|
+
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
493
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.accent : colors.sub }], children: tab === "report" ? "Report" : "Inbox" })
|
|
494
|
+
},
|
|
495
|
+
tab
|
|
496
|
+
)) }),
|
|
497
|
+
sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.body, children: inboxLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub }, children: "Loading\u2026" }) : selectedReportId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.accent, marginBottom: 8 }, children: "\u2190 Back" }) }),
|
|
499
|
+
threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
|
|
500
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
|
|
501
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text }, children: c.body })
|
|
502
|
+
] }, c.id)),
|
|
503
|
+
inboxReports.find((r) => r.id === selectedReportId)?.status === "fixed" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
|
|
504
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: "Yes, fixed" }) }),
|
|
505
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.text }], children: "Not fixed" }) })
|
|
506
|
+
] }),
|
|
507
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
508
|
+
import_react_native2.TextInput,
|
|
509
|
+
{
|
|
510
|
+
style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
|
|
511
|
+
placeholder: "Reply\u2026",
|
|
512
|
+
placeholderTextColor: colors.sub,
|
|
513
|
+
value: replyText,
|
|
514
|
+
onChangeText: setReplyText
|
|
515
|
+
}
|
|
516
|
+
),
|
|
517
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: "Send" }) })
|
|
518
|
+
] }) : inboxReports.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
519
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
|
|
520
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
|
|
521
|
+
] }, r.id)) }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
|
|
434
522
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
435
523
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
|
|
436
524
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.body, children: [
|
|
@@ -486,6 +574,32 @@ var MushiBottomSheet = ({ visible, onClose }) => {
|
|
|
486
574
|
editable: phase === "form"
|
|
487
575
|
}
|
|
488
576
|
),
|
|
577
|
+
activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
|
|
578
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
579
|
+
import_react_native2.Image,
|
|
580
|
+
{
|
|
581
|
+
source: { uri: activeScreenshot },
|
|
582
|
+
style: s.screenshotThumb,
|
|
583
|
+
accessibilityLabel: "Attached screenshot"
|
|
584
|
+
}
|
|
585
|
+
),
|
|
586
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
|
|
587
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
588
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
589
|
+
] }),
|
|
590
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
591
|
+
import_react_native2.TouchableOpacity,
|
|
592
|
+
{
|
|
593
|
+
onPress: () => {
|
|
594
|
+
setScreenshotAttached(false);
|
|
595
|
+
onClearScreenshot?.();
|
|
596
|
+
},
|
|
597
|
+
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
598
|
+
accessibilityLabel: "Remove screenshot",
|
|
599
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
600
|
+
}
|
|
601
|
+
)
|
|
602
|
+
] }) : null,
|
|
489
603
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
490
604
|
import_react_native2.TouchableOpacity,
|
|
491
605
|
{
|
|
@@ -598,6 +712,76 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
598
712
|
sentText: {
|
|
599
713
|
fontSize: 20,
|
|
600
714
|
fontWeight: "600"
|
|
715
|
+
},
|
|
716
|
+
screenshotRow: {
|
|
717
|
+
flexDirection: "row",
|
|
718
|
+
alignItems: "center",
|
|
719
|
+
gap: 10,
|
|
720
|
+
marginBottom: 12,
|
|
721
|
+
paddingHorizontal: 10,
|
|
722
|
+
paddingVertical: 8,
|
|
723
|
+
borderRadius: 10,
|
|
724
|
+
backgroundColor: "rgba(128,128,128,0.1)"
|
|
725
|
+
},
|
|
726
|
+
screenshotThumb: {
|
|
727
|
+
width: 48,
|
|
728
|
+
height: 36,
|
|
729
|
+
borderRadius: 4,
|
|
730
|
+
backgroundColor: "#000",
|
|
731
|
+
flexShrink: 0
|
|
732
|
+
},
|
|
733
|
+
screenshotMeta: {
|
|
734
|
+
flex: 1,
|
|
735
|
+
gap: 2
|
|
736
|
+
},
|
|
737
|
+
screenshotLabel: {
|
|
738
|
+
fontSize: 13,
|
|
739
|
+
fontWeight: "600"
|
|
740
|
+
},
|
|
741
|
+
screenshotSub: {
|
|
742
|
+
fontSize: 11
|
|
743
|
+
},
|
|
744
|
+
screenshotRemove: {
|
|
745
|
+
fontSize: 16,
|
|
746
|
+
fontWeight: "700",
|
|
747
|
+
paddingHorizontal: 4
|
|
748
|
+
},
|
|
749
|
+
tabRow: {
|
|
750
|
+
flexDirection: "row",
|
|
751
|
+
borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth,
|
|
752
|
+
borderBottomColor: "#ccc",
|
|
753
|
+
marginBottom: 8
|
|
754
|
+
},
|
|
755
|
+
tabBtn: {
|
|
756
|
+
flex: 1,
|
|
757
|
+
alignItems: "center",
|
|
758
|
+
paddingVertical: 10,
|
|
759
|
+
borderBottomWidth: 2,
|
|
760
|
+
borderBottomColor: "transparent"
|
|
761
|
+
},
|
|
762
|
+
tabLabel: {
|
|
763
|
+
fontSize: 14,
|
|
764
|
+
fontWeight: "600"
|
|
765
|
+
},
|
|
766
|
+
inboxRow: {
|
|
767
|
+
paddingVertical: 12,
|
|
768
|
+
borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth
|
|
769
|
+
},
|
|
770
|
+
threadBubble: {
|
|
771
|
+
borderRadius: 10,
|
|
772
|
+
padding: 10,
|
|
773
|
+
marginBottom: 8
|
|
774
|
+
},
|
|
775
|
+
verifyRow: {
|
|
776
|
+
flexDirection: "row",
|
|
777
|
+
gap: 8,
|
|
778
|
+
marginBottom: 12
|
|
779
|
+
},
|
|
780
|
+
verifyBtn: {
|
|
781
|
+
flex: 1,
|
|
782
|
+
borderRadius: 10,
|
|
783
|
+
paddingVertical: 10,
|
|
784
|
+
alignItems: "center"
|
|
601
785
|
}
|
|
602
786
|
});
|
|
603
787
|
|
|
@@ -683,6 +867,15 @@ function MushiProvider({ children, ...config }) {
|
|
|
683
867
|
const queueRef = (0, import_react3.useRef)(null);
|
|
684
868
|
const apiClientRef = (0, import_react3.useRef)(null);
|
|
685
869
|
const [sheetVisible, setSheetVisible] = (0, import_react3.useState)(false);
|
|
870
|
+
const [sheetScreenshot, setSheetScreenshot] = (0, import_react3.useState)(null);
|
|
871
|
+
const reporterTokenRef = (0, import_react3.useRef)(`rn-${config.projectId}-anon`);
|
|
872
|
+
let resolveReporterTokenReady = () => {
|
|
873
|
+
};
|
|
874
|
+
const reporterTokenReadyRef = (0, import_react3.useRef)(
|
|
875
|
+
new Promise((resolve) => {
|
|
876
|
+
resolveReporterTokenReady = resolve;
|
|
877
|
+
})
|
|
878
|
+
);
|
|
686
879
|
if (config.endpoint !== void 0 && config.endpoint.trim() === "") {
|
|
687
880
|
throw new Error(
|
|
688
881
|
"[MushiProvider] endpoint is set to an empty string. Set endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api, or omit it to use the cloud default."
|
|
@@ -708,6 +901,27 @@ function MushiProvider({ children, ...config }) {
|
|
|
708
901
|
});
|
|
709
902
|
queueRef.current.flush().catch(() => {
|
|
710
903
|
});
|
|
904
|
+
(async () => {
|
|
905
|
+
const TOKEN_KEY = "@mushi:reporter_token";
|
|
906
|
+
try {
|
|
907
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
908
|
+
const existing = await AsyncStorage.getItem(TOKEN_KEY);
|
|
909
|
+
if (existing && existing.startsWith("mushi_")) {
|
|
910
|
+
reporterTokenRef.current = existing;
|
|
911
|
+
} else {
|
|
912
|
+
const fresh = `mushi_${(0, import_core.newUuid)()}`;
|
|
913
|
+
reporterTokenRef.current = fresh;
|
|
914
|
+
await AsyncStorage.setItem(TOKEN_KEY, fresh);
|
|
915
|
+
}
|
|
916
|
+
resolveReporterTokenReady();
|
|
917
|
+
} catch {
|
|
918
|
+
if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
|
|
919
|
+
reporterTokenRef.current = `mushi_${(0, import_core.newUuid)()}`;
|
|
920
|
+
}
|
|
921
|
+
resolveReporterTokenReady();
|
|
922
|
+
}
|
|
923
|
+
})().catch(() => {
|
|
924
|
+
});
|
|
711
925
|
return () => {
|
|
712
926
|
consoleRef.current?.restore();
|
|
713
927
|
networkRef.current?.restore();
|
|
@@ -727,7 +941,25 @@ function MushiProvider({ children, ...config }) {
|
|
|
727
941
|
}
|
|
728
942
|
return () => unsubscribe?.();
|
|
729
943
|
}, []);
|
|
730
|
-
const open = (0, import_react3.useCallback)(() =>
|
|
944
|
+
const open = (0, import_react3.useCallback)(() => {
|
|
945
|
+
let vshot = null;
|
|
946
|
+
try {
|
|
947
|
+
vshot = require("react-native-view-shot");
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
if (!vshot) {
|
|
951
|
+
setSheetScreenshot(null);
|
|
952
|
+
setSheetVisible(true);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
vshot.captureScreen({ format: "jpg", quality: 0.7, result: "data-uri" }).then((url) => {
|
|
956
|
+
setSheetScreenshot(url);
|
|
957
|
+
}).catch(() => {
|
|
958
|
+
setSheetScreenshot(null);
|
|
959
|
+
}).finally(() => {
|
|
960
|
+
setSheetVisible(true);
|
|
961
|
+
});
|
|
962
|
+
}, []);
|
|
731
963
|
const close = (0, import_react3.useCallback)(() => setSheetVisible(false), []);
|
|
732
964
|
const attachTo = (0, import_react3.useCallback)(() => ({ onPress: open }), [open]);
|
|
733
965
|
(0, import_react3.useEffect)(() => {
|
|
@@ -761,8 +993,16 @@ function MushiProvider({ children, ...config }) {
|
|
|
761
993
|
const submitReport = (0, import_react3.useCallback)(
|
|
762
994
|
async (data) => {
|
|
763
995
|
const deviceInfo = getDeviceInfo();
|
|
996
|
+
const contextMeta = {};
|
|
997
|
+
if (userRef.current?.id) contextMeta.userId = userRef.current.id;
|
|
998
|
+
if (userRef.current?.email) contextMeta.userEmail = userRef.current.email;
|
|
999
|
+
if (userRef.current?.name) contextMeta.userName = userRef.current.name;
|
|
1000
|
+
if (screenRef.current?.name) contextMeta.screenName = screenRef.current.name;
|
|
1001
|
+
if (screenRef.current?.route) contextMeta.screenRoute = screenRef.current.route;
|
|
1002
|
+
if (screenRef.current?.feature) contextMeta.screenFeature = screenRef.current.feature;
|
|
1003
|
+
const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
|
|
764
1004
|
const report = {
|
|
765
|
-
id:
|
|
1005
|
+
id: (0, import_core.newUuid)(),
|
|
766
1006
|
projectId: config.projectId,
|
|
767
1007
|
category: data.category,
|
|
768
1008
|
description: data.description,
|
|
@@ -771,7 +1011,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
771
1011
|
platform: deviceInfo.platform ?? "mobile",
|
|
772
1012
|
language: deviceInfo.locale ?? "en",
|
|
773
1013
|
viewport: { width: deviceInfo.screenWidth ?? 0, height: deviceInfo.screenHeight ?? 0 },
|
|
774
|
-
url: "",
|
|
1014
|
+
url: screenRef.current?.route ?? "",
|
|
775
1015
|
referrer: "",
|
|
776
1016
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
777
1017
|
timezone: deviceInfo.timezone ?? "UTC"
|
|
@@ -781,7 +1021,9 @@ function MushiProvider({ children, ...config }) {
|
|
|
781
1021
|
...entry,
|
|
782
1022
|
status: entry.status ?? 0
|
|
783
1023
|
})) ?? [],
|
|
784
|
-
|
|
1024
|
+
screenshotDataUrl: data.screenshotDataUrl,
|
|
1025
|
+
metadata: merged,
|
|
1026
|
+
reporterToken: reporterTokenRef.current,
|
|
785
1027
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
786
1028
|
};
|
|
787
1029
|
const client = apiClientRef.current;
|
|
@@ -791,7 +1033,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
791
1033
|
await queueRef.current?.enqueue(report);
|
|
792
1034
|
}
|
|
793
1035
|
},
|
|
794
|
-
[config.projectId,
|
|
1036
|
+
[config.projectId, apiEndpoint]
|
|
795
1037
|
);
|
|
796
1038
|
const userRef = (0, import_react3.useRef)(null);
|
|
797
1039
|
const metadataRef = (0, import_react3.useRef)({});
|
|
@@ -842,6 +1084,45 @@ function MushiProvider({ children, ...config }) {
|
|
|
842
1084
|
setScreen(screen) {
|
|
843
1085
|
screenRef.current = screen;
|
|
844
1086
|
},
|
|
1087
|
+
// Reporter API — scoped to the device's persistent reporter token
|
|
1088
|
+
async listMyReports() {
|
|
1089
|
+
const client = apiClientRef.current;
|
|
1090
|
+
if (!client) return [];
|
|
1091
|
+
await reporterTokenReadyRef.current;
|
|
1092
|
+
const res = await client.listReporterReports(reporterTokenRef.current);
|
|
1093
|
+
return res.ok ? res.data?.reports ?? [] : [];
|
|
1094
|
+
},
|
|
1095
|
+
async listMyComments(reportId) {
|
|
1096
|
+
const client = apiClientRef.current;
|
|
1097
|
+
if (!client) return [];
|
|
1098
|
+
const res = await client.listReporterComments(reportId, reporterTokenRef.current);
|
|
1099
|
+
return res.ok ? res.data.comments ?? [] : [];
|
|
1100
|
+
},
|
|
1101
|
+
async replyToReport(reportId, body) {
|
|
1102
|
+
const client = apiClientRef.current;
|
|
1103
|
+
if (!client) return null;
|
|
1104
|
+
const res = await client.replyToReporterReport(reportId, reporterTokenRef.current, body);
|
|
1105
|
+
return res.ok ? res.data.comment : null;
|
|
1106
|
+
},
|
|
1107
|
+
async submitFeedbackSignal(reportId, signal, note) {
|
|
1108
|
+
const client = apiClientRef.current;
|
|
1109
|
+
if (!client) return null;
|
|
1110
|
+
const res = await client.replyToReporterReport(reportId, reporterTokenRef.current, note ?? "", signal);
|
|
1111
|
+
return res.ok ? res.data.feedback ?? null : null;
|
|
1112
|
+
},
|
|
1113
|
+
async reopenReport(reportId, note) {
|
|
1114
|
+
const client = apiClientRef.current;
|
|
1115
|
+
if (!client) return null;
|
|
1116
|
+
const res = await client.reopenReporterReport(reportId, reporterTokenRef.current, note);
|
|
1117
|
+
return res.ok ? res.data.outcome : null;
|
|
1118
|
+
},
|
|
1119
|
+
// Leaderboard — SDK-public anonymized hall-of-fame
|
|
1120
|
+
async getHallOfFame(limit = 10) {
|
|
1121
|
+
const client = apiClientRef.current;
|
|
1122
|
+
if (!client) return [];
|
|
1123
|
+
const res = await client.getHallOfFame(limit);
|
|
1124
|
+
return res.ok ? res.data.data ?? [] : [];
|
|
1125
|
+
},
|
|
845
1126
|
// Rewards (P1)
|
|
846
1127
|
recordActivity(action, metadata) {
|
|
847
1128
|
if (!config.rewards?.enabled) return;
|
|
@@ -883,7 +1164,15 @@ function MushiProvider({ children, ...config }) {
|
|
|
883
1164
|
inset: config.widget?.inset
|
|
884
1165
|
}
|
|
885
1166
|
),
|
|
886
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1167
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1168
|
+
MushiBottomSheet,
|
|
1169
|
+
{
|
|
1170
|
+
visible: sheetVisible,
|
|
1171
|
+
onClose: close,
|
|
1172
|
+
screenshotDataUrl: sheetScreenshot ?? void 0,
|
|
1173
|
+
onClearScreenshot: () => setSheetScreenshot(null)
|
|
1174
|
+
}
|
|
1175
|
+
)
|
|
887
1176
|
] });
|
|
888
1177
|
}
|
|
889
1178
|
function useMushiContext() {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, FC, ReactElement } from 'react';
|
|
4
|
-
import { MushiRewardsConfig, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
4
|
+
import { MushiRewardsConfig, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
5
|
+
export { MushiHallOfFameEntry, MushiReporterComment, MushiReporterReport } from '@mushi-mushi/core';
|
|
5
6
|
|
|
6
7
|
interface ConsoleEntry {
|
|
7
8
|
level: 'log' | 'warn' | 'error';
|
|
@@ -69,15 +70,18 @@ interface MushiRNConfig {
|
|
|
69
70
|
};
|
|
70
71
|
rewards?: MushiRewardsConfig;
|
|
71
72
|
}
|
|
73
|
+
|
|
72
74
|
interface MushiRNInstance {
|
|
73
75
|
open(): void;
|
|
74
76
|
close(): void;
|
|
75
77
|
attachTo(): {
|
|
76
78
|
onPress: () => void;
|
|
77
79
|
};
|
|
80
|
+
/** screenshotDataUrl is optional — pass `undefined` if not captured or removed by the user. */
|
|
78
81
|
submitReport(data: {
|
|
79
82
|
description: string;
|
|
80
83
|
category: string;
|
|
84
|
+
screenshotDataUrl?: string;
|
|
81
85
|
}): Promise<void>;
|
|
82
86
|
getDeviceInfo(): ReturnType<typeof getDeviceInfo>;
|
|
83
87
|
getConsoleEntries(): ReturnType<ReturnType<typeof setupConsoleCapture>['getEntries']>;
|
|
@@ -97,6 +101,18 @@ interface MushiRNInstance {
|
|
|
97
101
|
route?: string;
|
|
98
102
|
feature?: string;
|
|
99
103
|
}): void;
|
|
104
|
+
/** List this device's reports ordered by most recent. Returns [] on failure. */
|
|
105
|
+
listMyReports(): Promise<MushiReporterReport[]>;
|
|
106
|
+
/** List admin + reporter comments on a specific report. */
|
|
107
|
+
listMyComments(reportId: string): Promise<MushiReporterComment[]>;
|
|
108
|
+
/** Post a reporter reply on a report thread. Returns the new comment or null on failure. */
|
|
109
|
+
replyToReport(reportId: string, body: string): Promise<MushiReporterComment | null>;
|
|
110
|
+
/** Record a reporter feedback signal (e.g. `confirms`, `not_fixed`) on a report. Returns the outcome or null. */
|
|
111
|
+
submitFeedbackSignal(reportId: string, signal: string, note?: string): Promise<Record<string, unknown> | null>;
|
|
112
|
+
/** Reopen a report the reporter flagged as not fixed. Returns the reopen outcome or null. */
|
|
113
|
+
reopenReport(reportId: string, note?: string): Promise<Record<string, unknown> | null>;
|
|
114
|
+
/** Fetch the project's top contributors by points (max 50). */
|
|
115
|
+
getHallOfFame(limit?: number): Promise<MushiHallOfFameEntry[]>;
|
|
100
116
|
/** Manually record a host-defined activity event. */
|
|
101
117
|
recordActivity(action: string, metadata?: Record<string, unknown>): void;
|
|
102
118
|
/** Returns the current user's tier. */
|
|
@@ -153,6 +169,10 @@ declare function useMushiWidget(): {
|
|
|
153
169
|
interface MushiBottomSheetProps {
|
|
154
170
|
visible: boolean;
|
|
155
171
|
onClose: () => void;
|
|
172
|
+
/** Base64 data-URI of the screenshot captured before the sheet opened. Optional. */
|
|
173
|
+
screenshotDataUrl?: string;
|
|
174
|
+
/** Called when the user removes the attached screenshot. */
|
|
175
|
+
onClearScreenshot?: () => void;
|
|
156
176
|
}
|
|
157
177
|
declare const MushiBottomSheet: FC<MushiBottomSheetProps>;
|
|
158
178
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, FC, ReactElement } from 'react';
|
|
4
|
-
import { MushiRewardsConfig, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
4
|
+
import { MushiRewardsConfig, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
5
|
+
export { MushiHallOfFameEntry, MushiReporterComment, MushiReporterReport } from '@mushi-mushi/core';
|
|
5
6
|
|
|
6
7
|
interface ConsoleEntry {
|
|
7
8
|
level: 'log' | 'warn' | 'error';
|
|
@@ -69,15 +70,18 @@ interface MushiRNConfig {
|
|
|
69
70
|
};
|
|
70
71
|
rewards?: MushiRewardsConfig;
|
|
71
72
|
}
|
|
73
|
+
|
|
72
74
|
interface MushiRNInstance {
|
|
73
75
|
open(): void;
|
|
74
76
|
close(): void;
|
|
75
77
|
attachTo(): {
|
|
76
78
|
onPress: () => void;
|
|
77
79
|
};
|
|
80
|
+
/** screenshotDataUrl is optional — pass `undefined` if not captured or removed by the user. */
|
|
78
81
|
submitReport(data: {
|
|
79
82
|
description: string;
|
|
80
83
|
category: string;
|
|
84
|
+
screenshotDataUrl?: string;
|
|
81
85
|
}): Promise<void>;
|
|
82
86
|
getDeviceInfo(): ReturnType<typeof getDeviceInfo>;
|
|
83
87
|
getConsoleEntries(): ReturnType<ReturnType<typeof setupConsoleCapture>['getEntries']>;
|
|
@@ -97,6 +101,18 @@ interface MushiRNInstance {
|
|
|
97
101
|
route?: string;
|
|
98
102
|
feature?: string;
|
|
99
103
|
}): void;
|
|
104
|
+
/** List this device's reports ordered by most recent. Returns [] on failure. */
|
|
105
|
+
listMyReports(): Promise<MushiReporterReport[]>;
|
|
106
|
+
/** List admin + reporter comments on a specific report. */
|
|
107
|
+
listMyComments(reportId: string): Promise<MushiReporterComment[]>;
|
|
108
|
+
/** Post a reporter reply on a report thread. Returns the new comment or null on failure. */
|
|
109
|
+
replyToReport(reportId: string, body: string): Promise<MushiReporterComment | null>;
|
|
110
|
+
/** Record a reporter feedback signal (e.g. `confirms`, `not_fixed`) on a report. Returns the outcome or null. */
|
|
111
|
+
submitFeedbackSignal(reportId: string, signal: string, note?: string): Promise<Record<string, unknown> | null>;
|
|
112
|
+
/** Reopen a report the reporter flagged as not fixed. Returns the reopen outcome or null. */
|
|
113
|
+
reopenReport(reportId: string, note?: string): Promise<Record<string, unknown> | null>;
|
|
114
|
+
/** Fetch the project's top contributors by points (max 50). */
|
|
115
|
+
getHallOfFame(limit?: number): Promise<MushiHallOfFameEntry[]>;
|
|
100
116
|
/** Manually record a host-defined activity event. */
|
|
101
117
|
recordActivity(action: string, metadata?: Record<string, unknown>): void;
|
|
102
118
|
/** Returns the current user's tier. */
|
|
@@ -153,6 +169,10 @@ declare function useMushiWidget(): {
|
|
|
153
169
|
interface MushiBottomSheetProps {
|
|
154
170
|
visible: boolean;
|
|
155
171
|
onClose: () => void;
|
|
172
|
+
/** Base64 data-URI of the screenshot captured before the sheet opened. Optional. */
|
|
173
|
+
screenshotDataUrl?: string;
|
|
174
|
+
/** Called when the user removes the attached screenshot. */
|
|
175
|
+
onClearScreenshot?: () => void;
|
|
156
176
|
}
|
|
157
177
|
declare const MushiBottomSheet: FC<MushiBottomSheetProps>;
|
|
158
178
|
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
} from "react";
|
|
18
18
|
import {
|
|
19
19
|
createApiClient,
|
|
20
|
-
DEFAULT_API_ENDPOINT
|
|
20
|
+
DEFAULT_API_ENDPOINT,
|
|
21
|
+
newUuid
|
|
21
22
|
} from "@mushi-mushi/core";
|
|
22
23
|
|
|
23
24
|
// src/capture/console-capture.ts
|
|
@@ -288,6 +289,7 @@ import {
|
|
|
288
289
|
View,
|
|
289
290
|
Text,
|
|
290
291
|
TextInput,
|
|
292
|
+
Image,
|
|
291
293
|
TouchableOpacity,
|
|
292
294
|
Animated,
|
|
293
295
|
PanResponder,
|
|
@@ -297,7 +299,7 @@ import {
|
|
|
297
299
|
KeyboardAvoidingView,
|
|
298
300
|
useColorScheme
|
|
299
301
|
} from "react-native";
|
|
300
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
302
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
301
303
|
var { height: SCREEN_HEIGHT } = Dimensions2.get("window");
|
|
302
304
|
var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
|
|
303
305
|
var DISMISS_THRESHOLD = 80;
|
|
@@ -308,7 +310,12 @@ var CATEGORIES = [
|
|
|
308
310
|
{ key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
|
|
309
311
|
{ key: "other", emoji: "\u{1F4AC}", label: "Other" }
|
|
310
312
|
];
|
|
311
|
-
var MushiBottomSheet = ({
|
|
313
|
+
var MushiBottomSheet = ({
|
|
314
|
+
visible,
|
|
315
|
+
onClose,
|
|
316
|
+
screenshotDataUrl,
|
|
317
|
+
onClearScreenshot
|
|
318
|
+
}) => {
|
|
312
319
|
const mushi = useMushiContext();
|
|
313
320
|
const scheme = useColorScheme();
|
|
314
321
|
const dark = scheme === "dark";
|
|
@@ -317,6 +324,47 @@ var MushiBottomSheet = ({ visible, onClose }) => {
|
|
|
317
324
|
const [category, setCategory] = useState(null);
|
|
318
325
|
const [description, setDescription] = useState("");
|
|
319
326
|
const [phase, setPhase] = useState("form");
|
|
327
|
+
const [sheetTab, setSheetTab] = useState("report");
|
|
328
|
+
const [inboxReports, setInboxReports] = useState([]);
|
|
329
|
+
const [inboxLoading, setInboxLoading] = useState(false);
|
|
330
|
+
const [selectedReportId, setSelectedReportId] = useState(null);
|
|
331
|
+
const [threadComments, setThreadComments] = useState([]);
|
|
332
|
+
const [replyText, setReplyText] = useState("");
|
|
333
|
+
const [screenshotAttached, setScreenshotAttached] = useState(true);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (visible) {
|
|
336
|
+
setScreenshotAttached(true);
|
|
337
|
+
if (sheetTab === "inbox") void loadInbox();
|
|
338
|
+
}
|
|
339
|
+
}, [visible, sheetTab]);
|
|
340
|
+
const loadInbox = useCallback(async () => {
|
|
341
|
+
if (!mushi?.listMyReports) return;
|
|
342
|
+
setInboxLoading(true);
|
|
343
|
+
try {
|
|
344
|
+
const rows = await mushi.listMyReports();
|
|
345
|
+
setInboxReports(rows);
|
|
346
|
+
} finally {
|
|
347
|
+
setInboxLoading(false);
|
|
348
|
+
}
|
|
349
|
+
}, [mushi]);
|
|
350
|
+
const openThread = useCallback(async (reportId) => {
|
|
351
|
+
setSelectedReportId(reportId);
|
|
352
|
+
if (!mushi?.listMyComments) return;
|
|
353
|
+
const comments = await mushi.listMyComments(reportId);
|
|
354
|
+
setThreadComments(comments);
|
|
355
|
+
}, [mushi]);
|
|
356
|
+
const sendReply = useCallback(async () => {
|
|
357
|
+
if (!mushi?.replyToReport || !selectedReportId || !replyText.trim()) return;
|
|
358
|
+
await mushi.replyToReport(selectedReportId, replyText.trim());
|
|
359
|
+
setReplyText("");
|
|
360
|
+
await openThread(selectedReportId);
|
|
361
|
+
}, [mushi, selectedReportId, replyText, openThread]);
|
|
362
|
+
const submitFeedback = useCallback(async (signal) => {
|
|
363
|
+
if (!mushi?.submitFeedbackSignal || !selectedReportId) return;
|
|
364
|
+
await mushi.submitFeedbackSignal(selectedReportId, signal);
|
|
365
|
+
await loadInbox();
|
|
366
|
+
await openThread(selectedReportId);
|
|
367
|
+
}, [mushi, selectedReportId, loadInbox, openThread]);
|
|
320
368
|
const resetForm = useCallback(() => {
|
|
321
369
|
setCategory(null);
|
|
322
370
|
setDescription("");
|
|
@@ -388,13 +436,18 @@ var MushiBottomSheet = ({ visible, onClose }) => {
|
|
|
388
436
|
if (!category || !description.trim() || !mushi) return;
|
|
389
437
|
setPhase("sending");
|
|
390
438
|
try {
|
|
391
|
-
await mushi.submitReport({
|
|
439
|
+
await mushi.submitReport({
|
|
440
|
+
category,
|
|
441
|
+
description: description.trim(),
|
|
442
|
+
screenshotDataUrl: screenshotDataUrl && screenshotAttached ? screenshotDataUrl : void 0
|
|
443
|
+
});
|
|
392
444
|
setPhase("sent");
|
|
393
445
|
setTimeout(handleClose, 1400);
|
|
394
446
|
} catch {
|
|
395
447
|
setPhase("form");
|
|
396
448
|
}
|
|
397
449
|
};
|
|
450
|
+
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
398
451
|
const colors = dark ? { bg: "#1c1c1e", text: "#f2f2f7", sub: "#8e8e93", card: "#2c2c2e", accent: "#0a84ff", border: "#38383a", backdrop: "rgba(0,0,0,0.6)" } : { bg: "#ffffff", text: "#1c1c1e", sub: "#8e8e93", card: "#f2f2f7", accent: "#007aff", border: "#e5e5ea", backdrop: "rgba(0,0,0,0.35)" };
|
|
399
452
|
const canSubmit = !!category && description.trim().length > 0 && phase === "form";
|
|
400
453
|
return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ jsxs(
|
|
@@ -419,7 +472,44 @@ var MushiBottomSheet = ({ visible, onClose }) => {
|
|
|
419
472
|
],
|
|
420
473
|
children: [
|
|
421
474
|
/* @__PURE__ */ jsx(View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
|
|
422
|
-
|
|
475
|
+
/* @__PURE__ */ jsx(View, { style: s.tabRow, children: ["report", "inbox"].map((tab) => /* @__PURE__ */ jsx(
|
|
476
|
+
TouchableOpacity,
|
|
477
|
+
{
|
|
478
|
+
onPress: () => {
|
|
479
|
+
setSheetTab(tab);
|
|
480
|
+
if (tab === "inbox") void loadInbox();
|
|
481
|
+
else setSelectedReportId(null);
|
|
482
|
+
},
|
|
483
|
+
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
484
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.accent : colors.sub }], children: tab === "report" ? "Report" : "Inbox" })
|
|
485
|
+
},
|
|
486
|
+
tab
|
|
487
|
+
)) }),
|
|
488
|
+
sheetTab === "inbox" ? /* @__PURE__ */ jsx(View, { style: s.body, children: inboxLoading ? /* @__PURE__ */ jsx(Text, { style: { color: colors.sub }, children: "Loading\u2026" }) : selectedReportId ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
489
|
+
/* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ jsx(Text, { style: { color: colors.accent, marginBottom: 8 }, children: "\u2190 Back" }) }),
|
|
490
|
+
threadComments.map((c) => /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
|
|
491
|
+
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
|
|
492
|
+
/* @__PURE__ */ jsx(Text, { style: { color: colors.text }, children: c.body })
|
|
493
|
+
] }, c.id)),
|
|
494
|
+
inboxReports.find((r) => r.id === selectedReportId)?.status === "fixed" && /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
|
|
495
|
+
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: "Yes, fixed" }) }),
|
|
496
|
+
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.text }], children: "Not fixed" }) })
|
|
497
|
+
] }),
|
|
498
|
+
/* @__PURE__ */ jsx(
|
|
499
|
+
TextInput,
|
|
500
|
+
{
|
|
501
|
+
style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
|
|
502
|
+
placeholder: "Reply\u2026",
|
|
503
|
+
placeholderTextColor: colors.sub,
|
|
504
|
+
value: replyText,
|
|
505
|
+
onChangeText: setReplyText
|
|
506
|
+
}
|
|
507
|
+
),
|
|
508
|
+
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: "Send" }) })
|
|
509
|
+
] }) : inboxReports.map((r) => /* @__PURE__ */ jsxs(TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
510
|
+
/* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
|
|
511
|
+
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
|
|
512
|
+
] }, r.id)) }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
|
|
423
513
|
/* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
424
514
|
/* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
|
|
425
515
|
] }) : /* @__PURE__ */ jsxs(View, { style: s.body, children: [
|
|
@@ -475,6 +565,32 @@ var MushiBottomSheet = ({ visible, onClose }) => {
|
|
|
475
565
|
editable: phase === "form"
|
|
476
566
|
}
|
|
477
567
|
),
|
|
568
|
+
activeScreenshot ? /* @__PURE__ */ jsxs(View, { style: s.screenshotRow, children: [
|
|
569
|
+
/* @__PURE__ */ jsx(
|
|
570
|
+
Image,
|
|
571
|
+
{
|
|
572
|
+
source: { uri: activeScreenshot },
|
|
573
|
+
style: s.screenshotThumb,
|
|
574
|
+
accessibilityLabel: "Attached screenshot"
|
|
575
|
+
}
|
|
576
|
+
),
|
|
577
|
+
/* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
|
|
578
|
+
/* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
579
|
+
/* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
580
|
+
] }),
|
|
581
|
+
/* @__PURE__ */ jsx(
|
|
582
|
+
TouchableOpacity,
|
|
583
|
+
{
|
|
584
|
+
onPress: () => {
|
|
585
|
+
setScreenshotAttached(false);
|
|
586
|
+
onClearScreenshot?.();
|
|
587
|
+
},
|
|
588
|
+
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
589
|
+
accessibilityLabel: "Remove screenshot",
|
|
590
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
591
|
+
}
|
|
592
|
+
)
|
|
593
|
+
] }) : null,
|
|
478
594
|
/* @__PURE__ */ jsx(
|
|
479
595
|
TouchableOpacity,
|
|
480
596
|
{
|
|
@@ -587,6 +703,76 @@ var s = StyleSheet.create({
|
|
|
587
703
|
sentText: {
|
|
588
704
|
fontSize: 20,
|
|
589
705
|
fontWeight: "600"
|
|
706
|
+
},
|
|
707
|
+
screenshotRow: {
|
|
708
|
+
flexDirection: "row",
|
|
709
|
+
alignItems: "center",
|
|
710
|
+
gap: 10,
|
|
711
|
+
marginBottom: 12,
|
|
712
|
+
paddingHorizontal: 10,
|
|
713
|
+
paddingVertical: 8,
|
|
714
|
+
borderRadius: 10,
|
|
715
|
+
backgroundColor: "rgba(128,128,128,0.1)"
|
|
716
|
+
},
|
|
717
|
+
screenshotThumb: {
|
|
718
|
+
width: 48,
|
|
719
|
+
height: 36,
|
|
720
|
+
borderRadius: 4,
|
|
721
|
+
backgroundColor: "#000",
|
|
722
|
+
flexShrink: 0
|
|
723
|
+
},
|
|
724
|
+
screenshotMeta: {
|
|
725
|
+
flex: 1,
|
|
726
|
+
gap: 2
|
|
727
|
+
},
|
|
728
|
+
screenshotLabel: {
|
|
729
|
+
fontSize: 13,
|
|
730
|
+
fontWeight: "600"
|
|
731
|
+
},
|
|
732
|
+
screenshotSub: {
|
|
733
|
+
fontSize: 11
|
|
734
|
+
},
|
|
735
|
+
screenshotRemove: {
|
|
736
|
+
fontSize: 16,
|
|
737
|
+
fontWeight: "700",
|
|
738
|
+
paddingHorizontal: 4
|
|
739
|
+
},
|
|
740
|
+
tabRow: {
|
|
741
|
+
flexDirection: "row",
|
|
742
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
743
|
+
borderBottomColor: "#ccc",
|
|
744
|
+
marginBottom: 8
|
|
745
|
+
},
|
|
746
|
+
tabBtn: {
|
|
747
|
+
flex: 1,
|
|
748
|
+
alignItems: "center",
|
|
749
|
+
paddingVertical: 10,
|
|
750
|
+
borderBottomWidth: 2,
|
|
751
|
+
borderBottomColor: "transparent"
|
|
752
|
+
},
|
|
753
|
+
tabLabel: {
|
|
754
|
+
fontSize: 14,
|
|
755
|
+
fontWeight: "600"
|
|
756
|
+
},
|
|
757
|
+
inboxRow: {
|
|
758
|
+
paddingVertical: 12,
|
|
759
|
+
borderBottomWidth: StyleSheet.hairlineWidth
|
|
760
|
+
},
|
|
761
|
+
threadBubble: {
|
|
762
|
+
borderRadius: 10,
|
|
763
|
+
padding: 10,
|
|
764
|
+
marginBottom: 8
|
|
765
|
+
},
|
|
766
|
+
verifyRow: {
|
|
767
|
+
flexDirection: "row",
|
|
768
|
+
gap: 8,
|
|
769
|
+
marginBottom: 12
|
|
770
|
+
},
|
|
771
|
+
verifyBtn: {
|
|
772
|
+
flex: 1,
|
|
773
|
+
borderRadius: 10,
|
|
774
|
+
paddingVertical: 10,
|
|
775
|
+
alignItems: "center"
|
|
590
776
|
}
|
|
591
777
|
});
|
|
592
778
|
|
|
@@ -678,6 +864,15 @@ function MushiProvider({ children, ...config }) {
|
|
|
678
864
|
const queueRef = useRef3(null);
|
|
679
865
|
const apiClientRef = useRef3(null);
|
|
680
866
|
const [sheetVisible, setSheetVisible] = useState2(false);
|
|
867
|
+
const [sheetScreenshot, setSheetScreenshot] = useState2(null);
|
|
868
|
+
const reporterTokenRef = useRef3(`rn-${config.projectId}-anon`);
|
|
869
|
+
let resolveReporterTokenReady = () => {
|
|
870
|
+
};
|
|
871
|
+
const reporterTokenReadyRef = useRef3(
|
|
872
|
+
new Promise((resolve) => {
|
|
873
|
+
resolveReporterTokenReady = resolve;
|
|
874
|
+
})
|
|
875
|
+
);
|
|
681
876
|
if (config.endpoint !== void 0 && config.endpoint.trim() === "") {
|
|
682
877
|
throw new Error(
|
|
683
878
|
"[MushiProvider] endpoint is set to an empty string. Set endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api, or omit it to use the cloud default."
|
|
@@ -703,6 +898,27 @@ function MushiProvider({ children, ...config }) {
|
|
|
703
898
|
});
|
|
704
899
|
queueRef.current.flush().catch(() => {
|
|
705
900
|
});
|
|
901
|
+
(async () => {
|
|
902
|
+
const TOKEN_KEY = "@mushi:reporter_token";
|
|
903
|
+
try {
|
|
904
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
905
|
+
const existing = await AsyncStorage.getItem(TOKEN_KEY);
|
|
906
|
+
if (existing && existing.startsWith("mushi_")) {
|
|
907
|
+
reporterTokenRef.current = existing;
|
|
908
|
+
} else {
|
|
909
|
+
const fresh = `mushi_${newUuid()}`;
|
|
910
|
+
reporterTokenRef.current = fresh;
|
|
911
|
+
await AsyncStorage.setItem(TOKEN_KEY, fresh);
|
|
912
|
+
}
|
|
913
|
+
resolveReporterTokenReady();
|
|
914
|
+
} catch {
|
|
915
|
+
if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
|
|
916
|
+
reporterTokenRef.current = `mushi_${newUuid()}`;
|
|
917
|
+
}
|
|
918
|
+
resolveReporterTokenReady();
|
|
919
|
+
}
|
|
920
|
+
})().catch(() => {
|
|
921
|
+
});
|
|
706
922
|
return () => {
|
|
707
923
|
consoleRef.current?.restore();
|
|
708
924
|
networkRef.current?.restore();
|
|
@@ -722,7 +938,25 @@ function MushiProvider({ children, ...config }) {
|
|
|
722
938
|
}
|
|
723
939
|
return () => unsubscribe?.();
|
|
724
940
|
}, []);
|
|
725
|
-
const open = useCallback3(() =>
|
|
941
|
+
const open = useCallback3(() => {
|
|
942
|
+
let vshot = null;
|
|
943
|
+
try {
|
|
944
|
+
vshot = __require("react-native-view-shot");
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
if (!vshot) {
|
|
948
|
+
setSheetScreenshot(null);
|
|
949
|
+
setSheetVisible(true);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
vshot.captureScreen({ format: "jpg", quality: 0.7, result: "data-uri" }).then((url) => {
|
|
953
|
+
setSheetScreenshot(url);
|
|
954
|
+
}).catch(() => {
|
|
955
|
+
setSheetScreenshot(null);
|
|
956
|
+
}).finally(() => {
|
|
957
|
+
setSheetVisible(true);
|
|
958
|
+
});
|
|
959
|
+
}, []);
|
|
726
960
|
const close = useCallback3(() => setSheetVisible(false), []);
|
|
727
961
|
const attachTo = useCallback3(() => ({ onPress: open }), [open]);
|
|
728
962
|
useEffect2(() => {
|
|
@@ -756,8 +990,16 @@ function MushiProvider({ children, ...config }) {
|
|
|
756
990
|
const submitReport = useCallback3(
|
|
757
991
|
async (data) => {
|
|
758
992
|
const deviceInfo = getDeviceInfo();
|
|
993
|
+
const contextMeta = {};
|
|
994
|
+
if (userRef.current?.id) contextMeta.userId = userRef.current.id;
|
|
995
|
+
if (userRef.current?.email) contextMeta.userEmail = userRef.current.email;
|
|
996
|
+
if (userRef.current?.name) contextMeta.userName = userRef.current.name;
|
|
997
|
+
if (screenRef.current?.name) contextMeta.screenName = screenRef.current.name;
|
|
998
|
+
if (screenRef.current?.route) contextMeta.screenRoute = screenRef.current.route;
|
|
999
|
+
if (screenRef.current?.feature) contextMeta.screenFeature = screenRef.current.feature;
|
|
1000
|
+
const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
|
|
759
1001
|
const report = {
|
|
760
|
-
id:
|
|
1002
|
+
id: newUuid(),
|
|
761
1003
|
projectId: config.projectId,
|
|
762
1004
|
category: data.category,
|
|
763
1005
|
description: data.description,
|
|
@@ -766,7 +1008,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
766
1008
|
platform: deviceInfo.platform ?? "mobile",
|
|
767
1009
|
language: deviceInfo.locale ?? "en",
|
|
768
1010
|
viewport: { width: deviceInfo.screenWidth ?? 0, height: deviceInfo.screenHeight ?? 0 },
|
|
769
|
-
url: "",
|
|
1011
|
+
url: screenRef.current?.route ?? "",
|
|
770
1012
|
referrer: "",
|
|
771
1013
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
772
1014
|
timezone: deviceInfo.timezone ?? "UTC"
|
|
@@ -776,7 +1018,9 @@ function MushiProvider({ children, ...config }) {
|
|
|
776
1018
|
...entry,
|
|
777
1019
|
status: entry.status ?? 0
|
|
778
1020
|
})) ?? [],
|
|
779
|
-
|
|
1021
|
+
screenshotDataUrl: data.screenshotDataUrl,
|
|
1022
|
+
metadata: merged,
|
|
1023
|
+
reporterToken: reporterTokenRef.current,
|
|
780
1024
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
781
1025
|
};
|
|
782
1026
|
const client = apiClientRef.current;
|
|
@@ -786,7 +1030,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
786
1030
|
await queueRef.current?.enqueue(report);
|
|
787
1031
|
}
|
|
788
1032
|
},
|
|
789
|
-
[config.projectId,
|
|
1033
|
+
[config.projectId, apiEndpoint]
|
|
790
1034
|
);
|
|
791
1035
|
const userRef = useRef3(null);
|
|
792
1036
|
const metadataRef = useRef3({});
|
|
@@ -837,6 +1081,45 @@ function MushiProvider({ children, ...config }) {
|
|
|
837
1081
|
setScreen(screen) {
|
|
838
1082
|
screenRef.current = screen;
|
|
839
1083
|
},
|
|
1084
|
+
// Reporter API — scoped to the device's persistent reporter token
|
|
1085
|
+
async listMyReports() {
|
|
1086
|
+
const client = apiClientRef.current;
|
|
1087
|
+
if (!client) return [];
|
|
1088
|
+
await reporterTokenReadyRef.current;
|
|
1089
|
+
const res = await client.listReporterReports(reporterTokenRef.current);
|
|
1090
|
+
return res.ok ? res.data?.reports ?? [] : [];
|
|
1091
|
+
},
|
|
1092
|
+
async listMyComments(reportId) {
|
|
1093
|
+
const client = apiClientRef.current;
|
|
1094
|
+
if (!client) return [];
|
|
1095
|
+
const res = await client.listReporterComments(reportId, reporterTokenRef.current);
|
|
1096
|
+
return res.ok ? res.data.comments ?? [] : [];
|
|
1097
|
+
},
|
|
1098
|
+
async replyToReport(reportId, body) {
|
|
1099
|
+
const client = apiClientRef.current;
|
|
1100
|
+
if (!client) return null;
|
|
1101
|
+
const res = await client.replyToReporterReport(reportId, reporterTokenRef.current, body);
|
|
1102
|
+
return res.ok ? res.data.comment : null;
|
|
1103
|
+
},
|
|
1104
|
+
async submitFeedbackSignal(reportId, signal, note) {
|
|
1105
|
+
const client = apiClientRef.current;
|
|
1106
|
+
if (!client) return null;
|
|
1107
|
+
const res = await client.replyToReporterReport(reportId, reporterTokenRef.current, note ?? "", signal);
|
|
1108
|
+
return res.ok ? res.data.feedback ?? null : null;
|
|
1109
|
+
},
|
|
1110
|
+
async reopenReport(reportId, note) {
|
|
1111
|
+
const client = apiClientRef.current;
|
|
1112
|
+
if (!client) return null;
|
|
1113
|
+
const res = await client.reopenReporterReport(reportId, reporterTokenRef.current, note);
|
|
1114
|
+
return res.ok ? res.data.outcome : null;
|
|
1115
|
+
},
|
|
1116
|
+
// Leaderboard — SDK-public anonymized hall-of-fame
|
|
1117
|
+
async getHallOfFame(limit = 10) {
|
|
1118
|
+
const client = apiClientRef.current;
|
|
1119
|
+
if (!client) return [];
|
|
1120
|
+
const res = await client.getHallOfFame(limit);
|
|
1121
|
+
return res.ok ? res.data.data ?? [] : [];
|
|
1122
|
+
},
|
|
840
1123
|
// Rewards (P1)
|
|
841
1124
|
recordActivity(action, metadata) {
|
|
842
1125
|
if (!config.rewards?.enabled) return;
|
|
@@ -878,7 +1161,15 @@ function MushiProvider({ children, ...config }) {
|
|
|
878
1161
|
inset: config.widget?.inset
|
|
879
1162
|
}
|
|
880
1163
|
),
|
|
881
|
-
/* @__PURE__ */ jsx3(
|
|
1164
|
+
/* @__PURE__ */ jsx3(
|
|
1165
|
+
MushiBottomSheet,
|
|
1166
|
+
{
|
|
1167
|
+
visible: sheetVisible,
|
|
1168
|
+
onClose: close,
|
|
1169
|
+
screenshotDataUrl: sheetScreenshot ?? void 0,
|
|
1170
|
+
onClearScreenshot: () => setSheetScreenshot(null)
|
|
1171
|
+
}
|
|
1172
|
+
)
|
|
882
1173
|
] });
|
|
883
1174
|
}
|
|
884
1175
|
function useMushiContext() {
|
package/package.json
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native SDK for Mushi Mushi — shake-to-report, bottom sheet widget, full context capture",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
|
-
"scripts": {
|
|
10
|
-
"build": "tsup",
|
|
11
|
-
"test": "vitest run",
|
|
12
|
-
"typecheck": "tsc --noEmit"
|
|
13
|
-
},
|
|
14
9
|
"peerDependencies": {
|
|
15
10
|
"@react-native-async-storage/async-storage": ">=1.19",
|
|
16
11
|
"@react-native-community/netinfo": ">=11",
|
|
17
12
|
"@react-navigation/native": ">=6",
|
|
18
13
|
"expo-sensors": ">=13",
|
|
19
14
|
"react": ">=18",
|
|
20
|
-
"react-native": ">=0.72"
|
|
15
|
+
"react-native": ">=0.72",
|
|
16
|
+
"react-native-view-shot": ">=3.8"
|
|
21
17
|
},
|
|
22
18
|
"peerDependenciesMeta": {
|
|
23
19
|
"@react-navigation/native": {
|
|
@@ -31,10 +27,13 @@
|
|
|
31
27
|
},
|
|
32
28
|
"@react-native-community/netinfo": {
|
|
33
29
|
"optional": true
|
|
30
|
+
},
|
|
31
|
+
"react-native-view-shot": {
|
|
32
|
+
"optional": true
|
|
34
33
|
}
|
|
35
34
|
},
|
|
36
35
|
"dependencies": {
|
|
37
|
-
"@mushi-mushi/core": "
|
|
36
|
+
"@mushi-mushi/core": "^1.11.0"
|
|
38
37
|
},
|
|
39
38
|
"devDependencies": {
|
|
40
39
|
"@types/react": "^19.2.14",
|
|
@@ -104,5 +103,10 @@
|
|
|
104
103
|
"type": "module",
|
|
105
104
|
"engines": {
|
|
106
105
|
"node": ">=20"
|
|
106
|
+
},
|
|
107
|
+
"scripts": {
|
|
108
|
+
"build": "tsup",
|
|
109
|
+
"test": "vitest run",
|
|
110
|
+
"typecheck": "tsc --noEmit"
|
|
107
111
|
}
|
|
108
|
-
}
|
|
112
|
+
}
|