@mushi-mushi/react-native 0.14.0 → 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/dist/index.cjs CHANGED
@@ -333,10 +333,47 @@ var MushiBottomSheet = ({
333
333
  const [category, setCategory] = (0, import_react.useState)(null);
334
334
  const [description, setDescription] = (0, import_react.useState)("");
335
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)("");
336
342
  const [screenshotAttached, setScreenshotAttached] = (0, import_react.useState)(true);
337
343
  (0, import_react.useEffect)(() => {
338
- if (visible) setScreenshotAttached(true);
339
- }, [visible]);
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]);
340
377
  const resetForm = (0, import_react.useCallback)(() => {
341
378
  setCategory(null);
342
379
  setDescription("");
@@ -444,7 +481,44 @@ var MushiBottomSheet = ({
444
481
  ],
445
482
  children: [
446
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 }] }) }),
447
- phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
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: [
448
522
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
449
523
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
450
524
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.body, children: [
@@ -671,6 +745,43 @@ var s = import_react_native2.StyleSheet.create({
671
745
  fontSize: 16,
672
746
  fontWeight: "700",
673
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"
674
785
  }
675
786
  });
676
787
 
@@ -993,6 +1104,18 @@ function MushiProvider({ children, ...config }) {
993
1104
  const res = await client.replyToReporterReport(reportId, reporterTokenRef.current, body);
994
1105
  return res.ok ? res.data.comment : null;
995
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
+ },
996
1119
  // Leaderboard — SDK-public anonymized hall-of-fame
997
1120
  async getHallOfFame(limit = 10) {
998
1121
  const client = apiClientRef.current;
package/dist/index.d.cts CHANGED
@@ -107,6 +107,10 @@ interface MushiRNInstance {
107
107
  listMyComments(reportId: string): Promise<MushiReporterComment[]>;
108
108
  /** Post a reporter reply on a report thread. Returns the new comment or null on failure. */
109
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>;
110
114
  /** Fetch the project's top contributors by points (max 50). */
111
115
  getHallOfFame(limit?: number): Promise<MushiHallOfFameEntry[]>;
112
116
  /** Manually record a host-defined activity event. */
package/dist/index.d.ts CHANGED
@@ -107,6 +107,10 @@ interface MushiRNInstance {
107
107
  listMyComments(reportId: string): Promise<MushiReporterComment[]>;
108
108
  /** Post a reporter reply on a report thread. Returns the new comment or null on failure. */
109
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>;
110
114
  /** Fetch the project's top contributors by points (max 50). */
111
115
  getHallOfFame(limit?: number): Promise<MushiHallOfFameEntry[]>;
112
116
  /** Manually record a host-defined activity event. */
package/dist/index.js CHANGED
@@ -299,7 +299,7 @@ import {
299
299
  KeyboardAvoidingView,
300
300
  useColorScheme
301
301
  } from "react-native";
302
- import { jsx, jsxs } from "react/jsx-runtime";
302
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
303
303
  var { height: SCREEN_HEIGHT } = Dimensions2.get("window");
304
304
  var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
305
305
  var DISMISS_THRESHOLD = 80;
@@ -324,10 +324,47 @@ var MushiBottomSheet = ({
324
324
  const [category, setCategory] = useState(null);
325
325
  const [description, setDescription] = useState("");
326
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("");
327
333
  const [screenshotAttached, setScreenshotAttached] = useState(true);
328
334
  useEffect(() => {
329
- if (visible) setScreenshotAttached(true);
330
- }, [visible]);
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]);
331
368
  const resetForm = useCallback(() => {
332
369
  setCategory(null);
333
370
  setDescription("");
@@ -435,7 +472,44 @@ var MushiBottomSheet = ({
435
472
  ],
436
473
  children: [
437
474
  /* @__PURE__ */ jsx(View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
438
- phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
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: [
439
513
  /* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
440
514
  /* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
441
515
  ] }) : /* @__PURE__ */ jsxs(View, { style: s.body, children: [
@@ -662,6 +736,43 @@ var s = StyleSheet.create({
662
736
  fontSize: 16,
663
737
  fontWeight: "700",
664
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"
665
776
  }
666
777
  });
667
778
 
@@ -990,6 +1101,18 @@ function MushiProvider({ children, ...config }) {
990
1101
  const res = await client.replyToReporterReport(reportId, reporterTokenRef.current, body);
991
1102
  return res.ok ? res.data.comment : null;
992
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
+ },
993
1116
  // Leaderboard — SDK-public anonymized hall-of-fame
994
1117
  async getHallOfFame(limit = 10) {
995
1118
  const client = apiClientRef.current;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/react-native",
3
- "version": "0.14.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",
@@ -33,7 +33,7 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@mushi-mushi/core": "^1.10.0"
36
+ "@mushi-mushi/core": "^1.11.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/react": "^19.2.14",