@mushi-mushi/react-native 0.21.0 → 0.23.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 +18 -1
- package/dist/index.cjs +997 -214
- package/dist/index.d.cts +116 -4
- package/dist/index.d.ts +116 -4
- package/dist/index.js +967 -182
- package/package.json +9 -17
- package/CHANGELOG.md +0 -1062
- package/CODE_OF_CONDUCT.md +0 -51
- package/CONTRIBUTING.md +0 -226
- package/SECURITY.md +0 -310
package/dist/index.cjs
CHANGED
|
@@ -49,7 +49,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
49
49
|
|
|
50
50
|
// src/provider.tsx
|
|
51
51
|
var import_react4 = require("react");
|
|
52
|
-
var
|
|
52
|
+
var import_react_native5 = require("react-native");
|
|
53
|
+
var import_core9 = require("@mushi-mushi/core");
|
|
53
54
|
|
|
54
55
|
// src/capture/console-capture.ts
|
|
55
56
|
var import_core = require("@mushi-mushi/core");
|
|
@@ -477,32 +478,99 @@ function sleep(ms) {
|
|
|
477
478
|
// src/components/MushiBottomSheet.tsx
|
|
478
479
|
var import_react = require("react");
|
|
479
480
|
var import_react_native2 = require("react-native");
|
|
480
|
-
var
|
|
481
|
+
var import_core4 = require("@mushi-mushi/core");
|
|
482
|
+
var import_reporter_ui = require("@mushi-mushi/core/reporter-ui");
|
|
481
483
|
var import_i18n = require("@mushi-mushi/web/i18n");
|
|
482
484
|
|
|
483
|
-
// src/
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
485
|
+
// src/theme.ts
|
|
486
|
+
var import_core3 = require("@mushi-mushi/core");
|
|
487
|
+
function hexToRgb(hex) {
|
|
488
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})([0-9a-f]{2})?$/i.exec(hex.trim());
|
|
489
|
+
if (!m) return null;
|
|
490
|
+
const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
|
|
491
|
+
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
492
|
+
}
|
|
493
|
+
function luminance([r, g, b]) {
|
|
494
|
+
const lin = (c) => {
|
|
495
|
+
const s4 = c / 255;
|
|
496
|
+
return s4 <= 0.03928 ? s4 / 12.92 : ((s4 + 0.055) / 1.055) ** 2.4;
|
|
497
|
+
};
|
|
498
|
+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
499
|
+
}
|
|
500
|
+
function contrastingText(bg, fallback) {
|
|
501
|
+
const rgb = hexToRgb(bg);
|
|
502
|
+
if (!rgb) return fallback;
|
|
503
|
+
const l = luminance(rgb);
|
|
504
|
+
return 1.05 / (l + 0.05) >= (l + 0.05) / 0.05 ? "#FFFFFF" : "#000000";
|
|
505
|
+
}
|
|
506
|
+
function resolveRNTheme(dark, override = {}) {
|
|
507
|
+
const pal = (0, import_core3.mushiPalette)(dark ? "dark" : "light");
|
|
508
|
+
const base = {
|
|
509
|
+
bg: dark ? pal.paper : pal.paperRaised,
|
|
510
|
+
fg: pal.ink,
|
|
511
|
+
muted: pal.inkMuted,
|
|
512
|
+
surface: dark ? pal.paperRaised : pal.paper,
|
|
513
|
+
border: pal.ruleStrong,
|
|
514
|
+
accent: pal.ink,
|
|
515
|
+
accentFg: dark ? pal.paper : pal.paperRaised,
|
|
516
|
+
success: pal.ok,
|
|
517
|
+
error: pal.danger,
|
|
518
|
+
fontFamily: void 0,
|
|
519
|
+
radius: import_core3.MUSHI_RADIUS.card
|
|
520
|
+
};
|
|
521
|
+
const defined = Object.fromEntries(
|
|
522
|
+
Object.entries(override).filter(([, v]) => v !== void 0 && v !== null && v !== "")
|
|
523
|
+
);
|
|
524
|
+
const merged = { ...base, ...defined };
|
|
525
|
+
if (defined.accent && !defined.accentFg) merged.accentFg = contrastingText(defined.accent, base.accentFg);
|
|
526
|
+
return {
|
|
527
|
+
...merged,
|
|
528
|
+
backdrop: dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.35)",
|
|
529
|
+
disabled: dark ? import_core3.MUSHI_CONTROL_DISABLED.dark : import_core3.MUSHI_CONTROL_DISABLED.light,
|
|
530
|
+
disabledFg: pal.inkFaint
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/reporter-thread.ts
|
|
535
|
+
var THREAD_LOAD_TIMEOUT_MS = 12e3;
|
|
536
|
+
function settleWithin(p, ms) {
|
|
537
|
+
return new Promise((resolve) => {
|
|
538
|
+
const timer = setTimeout(() => resolve(null), ms);
|
|
539
|
+
p.then(
|
|
540
|
+
(value) => {
|
|
541
|
+
clearTimeout(timer);
|
|
542
|
+
resolve(value);
|
|
543
|
+
},
|
|
544
|
+
() => {
|
|
545
|
+
clearTimeout(timer);
|
|
546
|
+
resolve(null);
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function loadReporterThread(load, reportId, timeoutMs = THREAD_LOAD_TIMEOUT_MS) {
|
|
552
|
+
let started;
|
|
553
|
+
try {
|
|
554
|
+
started = load(reportId);
|
|
555
|
+
} catch {
|
|
556
|
+
return Promise.resolve(null);
|
|
557
|
+
}
|
|
558
|
+
return settleWithin(started, timeoutMs);
|
|
559
|
+
}
|
|
560
|
+
async function markReporterReportRead(client, reporterToken, reportId) {
|
|
561
|
+
try {
|
|
562
|
+
const res = await client.listNotifications(reporterToken, { limit: 100 });
|
|
563
|
+
if (!res.ok) return 0;
|
|
564
|
+
const rows = res.data?.notifications ?? [];
|
|
565
|
+
const ids = rows.filter((n) => !n.read_at && n.payload?.reportId === reportId).map((n) => String(n.id));
|
|
566
|
+
let marked = 0;
|
|
567
|
+
for (const id of ids) {
|
|
568
|
+
const r = await client.markNotificationRead(id, reporterToken);
|
|
569
|
+
if (r.ok) marked++;
|
|
570
|
+
}
|
|
571
|
+
return marked;
|
|
572
|
+
} catch {
|
|
573
|
+
return 0;
|
|
506
574
|
}
|
|
507
575
|
}
|
|
508
576
|
|
|
@@ -511,6 +579,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
511
579
|
var { height: SCREEN_HEIGHT } = import_react_native2.Dimensions.get("window");
|
|
512
580
|
var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
|
|
513
581
|
var DISMISS_THRESHOLD = 80;
|
|
582
|
+
var MIN_DESCRIPTION = 20;
|
|
514
583
|
var CATEGORY_KEYS = ["bug", "slow", "visual", "confusing", "other"];
|
|
515
584
|
var CATEGORY_EMOJI = {
|
|
516
585
|
bug: "\u{1F41B}",
|
|
@@ -519,7 +588,6 @@ var CATEGORY_EMOJI = {
|
|
|
519
588
|
confusing: "\u{1F615}",
|
|
520
589
|
other: "\u{1F4AC}"
|
|
521
590
|
};
|
|
522
|
-
var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
|
|
523
591
|
var MushiBottomSheet = ({
|
|
524
592
|
visible,
|
|
525
593
|
onClose,
|
|
@@ -528,13 +596,18 @@ var MushiBottomSheet = ({
|
|
|
528
596
|
onClearScreenshot,
|
|
529
597
|
screenshotSensitiveHint,
|
|
530
598
|
assistantEnabled = false,
|
|
531
|
-
assistantLabel =
|
|
599
|
+
assistantLabel = import_core4.MUSHI_COPY.assistantTab,
|
|
532
600
|
assistantGreeting,
|
|
533
601
|
assistantSuggestions = [],
|
|
534
|
-
inboxPollIntervalMs = 0
|
|
602
|
+
inboxPollIntervalMs = 0,
|
|
603
|
+
theme
|
|
535
604
|
}) => {
|
|
536
605
|
const mushi = useMushiContext();
|
|
537
606
|
const t = (0, import_i18n.getLocale)();
|
|
607
|
+
const locale = (0, import_reporter_ui.resolveReporterLocale)(
|
|
608
|
+
typeof navigator !== "undefined" ? navigator.language : void 0
|
|
609
|
+
);
|
|
610
|
+
const rc = (0, import_reporter_ui.reporterCopy)(locale);
|
|
538
611
|
const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
|
|
539
612
|
const scheme = (0, import_react_native2.useColorScheme)();
|
|
540
613
|
const dark = scheme === "dark";
|
|
@@ -546,26 +619,37 @@ var MushiBottomSheet = ({
|
|
|
546
619
|
const [submitError, setSubmitError] = (0, import_react.useState)(null);
|
|
547
620
|
const [sheetTab, setSheetTab] = (0, import_react.useState)("report");
|
|
548
621
|
const [inboxReports, setInboxReports] = (0, import_react.useState)([]);
|
|
622
|
+
const [inboxLoaded, setInboxLoaded] = (0, import_react.useState)(false);
|
|
549
623
|
const [inboxLoading, setInboxLoading] = (0, import_react.useState)(false);
|
|
550
624
|
const [selectedReportId, setSelectedReportId] = (0, import_react.useState)(null);
|
|
625
|
+
const selectedRef = (0, import_react.useRef)(null);
|
|
551
626
|
const [threadComments, setThreadComments] = (0, import_react.useState)([]);
|
|
627
|
+
const [threadStatus, setThreadStatus] = (0, import_react.useState)("idle");
|
|
552
628
|
const [replyText, setReplyText] = (0, import_react.useState)("");
|
|
629
|
+
const [replyState, setReplyState] = (0, import_react.useState)("idle");
|
|
630
|
+
const [feedbackSending, setFeedbackSending] = (0, import_react.useState)(false);
|
|
631
|
+
const [feedbackError, setFeedbackError] = (0, import_react.useState)(false);
|
|
553
632
|
const [assistantInput, setAssistantInput] = (0, import_react.useState)("");
|
|
554
633
|
const [assistantThreadId, setAssistantThreadId] = (0, import_react.useState)(null);
|
|
555
634
|
const [assistantSending, setAssistantSending] = (0, import_react.useState)(false);
|
|
556
635
|
const [assistantError, setAssistantError] = (0, import_react.useState)(null);
|
|
557
636
|
const [assistantTurns, setAssistantTurns] = (0, import_react.useState)([]);
|
|
558
637
|
const [screenshotAttached, setScreenshotAttached] = (0, import_react.useState)(true);
|
|
559
|
-
const loadInbox = (0, import_react.useCallback)(
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
638
|
+
const loadInbox = (0, import_react.useCallback)(
|
|
639
|
+
async (opts = {}) => {
|
|
640
|
+
if (!mushi?.listMyReports) return;
|
|
641
|
+
if (!opts.silent) setInboxLoading(true);
|
|
642
|
+
try {
|
|
643
|
+
const rows = await mushi.listMyReports();
|
|
644
|
+
setInboxReports(rows.filter((r) => !(0, import_reporter_ui.reporterStatus)(r, locale).hidden));
|
|
645
|
+
setInboxLoaded(true);
|
|
646
|
+
} catch {
|
|
647
|
+
} finally {
|
|
648
|
+
if (!opts.silent) setInboxLoading(false);
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
[mushi, locale]
|
|
652
|
+
);
|
|
569
653
|
(0, import_react.useEffect)(() => {
|
|
570
654
|
if (visible) {
|
|
571
655
|
setScreenshotAttached(true);
|
|
@@ -576,28 +660,87 @@ var MushiBottomSheet = ({
|
|
|
576
660
|
(0, import_react.useEffect)(() => {
|
|
577
661
|
if (!visible || sheetTab !== "inbox" || inboxPollIntervalMs <= 0) return;
|
|
578
662
|
const timer = setInterval(() => {
|
|
579
|
-
void loadInbox();
|
|
663
|
+
void loadInbox({ silent: true });
|
|
580
664
|
}, inboxPollIntervalMs);
|
|
581
665
|
return () => clearInterval(timer);
|
|
582
666
|
}, [visible, sheetTab, inboxPollIntervalMs, loadInbox]);
|
|
583
|
-
const openThread = (0, import_react.useCallback)(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
667
|
+
const openThread = (0, import_react.useCallback)(
|
|
668
|
+
async (reportId, opts = {}) => {
|
|
669
|
+
selectedRef.current = reportId;
|
|
670
|
+
setSelectedReportId(reportId);
|
|
671
|
+
if (!opts.silent) {
|
|
672
|
+
setThreadComments([]);
|
|
673
|
+
setThreadStatus("loading");
|
|
674
|
+
setReplyState("idle");
|
|
675
|
+
setFeedbackError(false);
|
|
676
|
+
}
|
|
677
|
+
const load = mushi?.loadMyThread ?? mushi?.listMyComments;
|
|
678
|
+
const comments = load ? await loadReporterThread(load, reportId) : null;
|
|
679
|
+
if (selectedRef.current !== reportId) return;
|
|
680
|
+
if (comments === null) {
|
|
681
|
+
setThreadStatus((prev) => opts.silent && prev === "ready" ? "ready" : "error");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
setThreadComments(comments);
|
|
685
|
+
setThreadStatus("ready");
|
|
686
|
+
if (!opts.silent && mushi?.markReportRead) {
|
|
687
|
+
mushi.markReportRead(reportId).then((marked) => {
|
|
688
|
+
if (marked > 0) {
|
|
689
|
+
setInboxReports((rows) => rows.map((r) => r.id === reportId ? { ...r, unread_count: 0 } : r));
|
|
690
|
+
}
|
|
691
|
+
}).catch(() => void 0);
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
[mushi]
|
|
695
|
+
);
|
|
696
|
+
const closeThread = (0, import_react.useCallback)(() => {
|
|
697
|
+
selectedRef.current = null;
|
|
698
|
+
setSelectedReportId(null);
|
|
699
|
+
setThreadStatus("idle");
|
|
700
|
+
}, []);
|
|
589
701
|
const sendReply = (0, import_react.useCallback)(async () => {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
702
|
+
const body = replyText.trim();
|
|
703
|
+
const reportId = selectedReportId;
|
|
704
|
+
if (!mushi?.replyToReport || !reportId || !body || replyState === "sending") return;
|
|
705
|
+
setReplyState("sending");
|
|
706
|
+
try {
|
|
707
|
+
const comment = await settleWithin(mushi.replyToReport(reportId, body), THREAD_LOAD_TIMEOUT_MS);
|
|
708
|
+
if (!comment) {
|
|
709
|
+
setReplyState("failed");
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
setReplyText("");
|
|
713
|
+
setReplyState("idle");
|
|
714
|
+
if (selectedRef.current === reportId) {
|
|
715
|
+
setThreadComments((prev) => [...prev, comment]);
|
|
716
|
+
void openThread(reportId, { silent: true });
|
|
717
|
+
}
|
|
718
|
+
} catch {
|
|
719
|
+
setReplyState("failed");
|
|
720
|
+
}
|
|
721
|
+
}, [mushi, selectedReportId, replyText, replyState, openThread]);
|
|
722
|
+
const submitFeedback = (0, import_react.useCallback)(
|
|
723
|
+
async (signal) => {
|
|
724
|
+
const reportId = selectedReportId;
|
|
725
|
+
if (!mushi?.submitFeedbackSignal || !reportId || feedbackSending) return;
|
|
726
|
+
setFeedbackSending(true);
|
|
727
|
+
setFeedbackError(false);
|
|
728
|
+
try {
|
|
729
|
+
const outcome = await mushi.submitFeedbackSignal(reportId, signal);
|
|
730
|
+
if (!outcome) {
|
|
731
|
+
setFeedbackError(true);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
await loadInbox({ silent: true });
|
|
735
|
+
await openThread(reportId, { silent: true });
|
|
736
|
+
} catch {
|
|
737
|
+
setFeedbackError(true);
|
|
738
|
+
} finally {
|
|
739
|
+
setFeedbackSending(false);
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
[mushi, selectedReportId, feedbackSending, loadInbox, openThread]
|
|
743
|
+
);
|
|
601
744
|
const sendAssistant = (0, import_react.useCallback)(async (message) => {
|
|
602
745
|
if (!mushi?.askAssistant || !message.trim() || assistantSending) return;
|
|
603
746
|
const trimmed = message.trim();
|
|
@@ -693,7 +836,7 @@ var MushiBottomSheet = ({
|
|
|
693
836
|
const handleSubmit = async () => {
|
|
694
837
|
if (!category || !description.trim() || !mushi) return;
|
|
695
838
|
const trimmed = description.trim();
|
|
696
|
-
if (trimmed.length <
|
|
839
|
+
if (trimmed.length < MIN_DESCRIPTION) {
|
|
697
840
|
setSubmitError("Please write at least 20 characters so we can understand the issue.");
|
|
698
841
|
return;
|
|
699
842
|
}
|
|
@@ -728,11 +871,181 @@ var MushiBottomSheet = ({
|
|
|
728
871
|
}
|
|
729
872
|
};
|
|
730
873
|
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
731
|
-
const
|
|
732
|
-
const
|
|
874
|
+
const colors = resolveRNTheme(dark, theme);
|
|
875
|
+
const font = colors.fontFamily ? { fontFamily: colors.fontFamily } : null;
|
|
876
|
+
const radius = colors.radius;
|
|
733
877
|
const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
|
|
734
|
-
const canSubmit = !!category && description.trim().length >=
|
|
735
|
-
|
|
878
|
+
const canSubmit = !!category && description.trim().length >= MIN_DESCRIPTION && (phase === "form" || phase === "error");
|
|
879
|
+
const submitHint = canSubmit || phase === "sending" ? void 0 : !category ? t.step1.heading : rc.ui.addWords;
|
|
880
|
+
const selectedReport = selectedReportId ? inboxReports.find((r) => r.id === selectedReportId) ?? null : null;
|
|
881
|
+
const selectedView = selectedReport ? (0, import_reporter_ui.reporterStatus)(selectedReport, locale) : null;
|
|
882
|
+
const headerTitle = sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title;
|
|
883
|
+
const renderThread = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
884
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
885
|
+
import_react_native2.TouchableOpacity,
|
|
886
|
+
{
|
|
887
|
+
onPress: closeThread,
|
|
888
|
+
accessibilityRole: "button",
|
|
889
|
+
accessibilityLabel: t.widget.back,
|
|
890
|
+
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
891
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: [{ color: colors.accent, marginBottom: 8 }, font], children: [
|
|
892
|
+
"\u2190 ",
|
|
893
|
+
t.widget.back
|
|
894
|
+
] })
|
|
895
|
+
}
|
|
896
|
+
),
|
|
897
|
+
selectedReport && selectedView ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.headerCard, { backgroundColor: colors.surface, borderRadius: radius, borderColor: colors.border }], children: [
|
|
898
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.pill, { color: colors.fg, borderColor: colors.border }, font], children: selectedView.label }),
|
|
899
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, fontSize: 12, marginTop: 4 }, font], children: selectedView.detail }),
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, marginTop: 8 }, font], numberOfLines: 4, children: selectedReport.title ?? selectedReport.summary ?? selectedReport.description ?? "" })
|
|
901
|
+
] }) : null,
|
|
902
|
+
threadStatus === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { accessibilityLabel: t.flows.reports.loading, accessibilityLiveRegion: "polite", children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.skeleton, { backgroundColor: colors.surface, width: `${90 - i * 18}%` }] }, i)) }) : threadStatus === "error" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.errorRow, accessibilityLiveRegion: "polite", children: [
|
|
903
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.error, flex: 1 }, font], accessibilityRole: "alert", children: rc.ui.loadError }),
|
|
904
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
905
|
+
import_react_native2.TouchableOpacity,
|
|
906
|
+
{
|
|
907
|
+
onPress: () => selectedReportId && void openThread(selectedReportId),
|
|
908
|
+
accessibilityRole: "button",
|
|
909
|
+
style: [s.retryBtn, { borderColor: colors.border, borderRadius: radius }],
|
|
910
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontWeight: "600" }, font], children: rc.ui.retry })
|
|
911
|
+
}
|
|
912
|
+
)
|
|
913
|
+
] }) : threadComments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, marginBottom: 8 }, font], children: rc.ui.noReplies }) : threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
914
|
+
import_react_native2.View,
|
|
915
|
+
{
|
|
916
|
+
style: [
|
|
917
|
+
s.threadBubble,
|
|
918
|
+
{
|
|
919
|
+
backgroundColor: colors.surface,
|
|
920
|
+
alignSelf: c.author_kind === "reporter" ? "flex-end" : "flex-start"
|
|
921
|
+
}
|
|
922
|
+
],
|
|
923
|
+
children: [
|
|
924
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: c.author_kind === "reporter" ? rc.ui.you : rc.ui.developer }),
|
|
925
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg }, font], children: c.body })
|
|
926
|
+
]
|
|
927
|
+
},
|
|
928
|
+
c.id
|
|
929
|
+
)),
|
|
930
|
+
replyState === "sending" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.surface, alignSelf: "flex-end", opacity: 0.7 }], children: [
|
|
931
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: rc.ui.sending }),
|
|
932
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg }, font], children: replyText.trim() })
|
|
933
|
+
] }) : null,
|
|
934
|
+
selectedView?.canVerify ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
|
|
935
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
936
|
+
import_react_native2.TouchableOpacity,
|
|
937
|
+
{
|
|
938
|
+
style: [s.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
|
|
939
|
+
onPress: () => void submitFeedback("confirms"),
|
|
940
|
+
disabled: feedbackSending,
|
|
941
|
+
accessibilityRole: "button",
|
|
942
|
+
accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
|
|
943
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentFg }, font], children: rc.ui.yes })
|
|
944
|
+
}
|
|
945
|
+
),
|
|
946
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
947
|
+
import_react_native2.TouchableOpacity,
|
|
948
|
+
{
|
|
949
|
+
style: [s.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
|
|
950
|
+
onPress: () => void submitFeedback("not_fixed"),
|
|
951
|
+
disabled: feedbackSending,
|
|
952
|
+
accessibilityRole: "button",
|
|
953
|
+
accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
|
|
954
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.fg }, font], children: rc.ui.notYet })
|
|
955
|
+
}
|
|
956
|
+
)
|
|
957
|
+
] }) : null,
|
|
958
|
+
feedbackError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.error, marginBottom: 8 }, font], accessibilityRole: "alert", children: rc.ui.sendFailed }) : null,
|
|
959
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.composer, children: [
|
|
960
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
961
|
+
import_react_native2.TextInput,
|
|
962
|
+
{
|
|
963
|
+
style: [
|
|
964
|
+
s.input,
|
|
965
|
+
s.composerInput,
|
|
966
|
+
{ backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius },
|
|
967
|
+
font
|
|
968
|
+
],
|
|
969
|
+
placeholder: t.flows.thread.replyPlaceholder,
|
|
970
|
+
placeholderTextColor: colors.muted,
|
|
971
|
+
value: replyText,
|
|
972
|
+
onChangeText: (text) => {
|
|
973
|
+
setReplyText(text.slice(0, 2e3));
|
|
974
|
+
if (replyState === "failed") setReplyState("idle");
|
|
975
|
+
},
|
|
976
|
+
multiline: true,
|
|
977
|
+
maxLength: 2e3,
|
|
978
|
+
editable: replyState !== "sending",
|
|
979
|
+
accessibilityLabel: t.flows.thread.replyPlaceholder
|
|
980
|
+
}
|
|
981
|
+
),
|
|
982
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
983
|
+
import_react_native2.TouchableOpacity,
|
|
984
|
+
{
|
|
985
|
+
style: [
|
|
986
|
+
s.sendBtn,
|
|
987
|
+
{
|
|
988
|
+
backgroundColor: replyText.trim() && replyState !== "sending" ? colors.accent : colors.disabled,
|
|
989
|
+
borderRadius: radius
|
|
990
|
+
}
|
|
991
|
+
],
|
|
992
|
+
onPress: () => void sendReply(),
|
|
993
|
+
disabled: !replyText.trim() || replyState === "sending",
|
|
994
|
+
accessibilityRole: "button",
|
|
995
|
+
accessibilityLabel: replyState === "failed" ? rc.ui.retry : t.flows.thread.send,
|
|
996
|
+
accessibilityState: { disabled: !replyText.trim() || replyState === "sending", busy: replyState === "sending" },
|
|
997
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
998
|
+
import_react_native2.Text,
|
|
999
|
+
{
|
|
1000
|
+
style: [
|
|
1001
|
+
s.submitText,
|
|
1002
|
+
{ color: replyText.trim() && replyState !== "sending" ? colors.accentFg : colors.disabledFg },
|
|
1003
|
+
font
|
|
1004
|
+
],
|
|
1005
|
+
children: replyState === "failed" ? rc.ui.retry : t.flows.thread.send
|
|
1006
|
+
}
|
|
1007
|
+
)
|
|
1008
|
+
}
|
|
1009
|
+
)
|
|
1010
|
+
] }),
|
|
1011
|
+
replyState === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: [
|
|
1012
|
+
rc.ui.sendFailed,
|
|
1013
|
+
" \xB7 ",
|
|
1014
|
+
rc.ui.retry
|
|
1015
|
+
] }) : null
|
|
1016
|
+
] });
|
|
1017
|
+
const renderInboxList = () => {
|
|
1018
|
+
if (inboxLoading && !inboxLoaded) {
|
|
1019
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted }, font], children: t.flows.reports.loading });
|
|
1020
|
+
}
|
|
1021
|
+
if (inboxReports.length === 0) {
|
|
1022
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted }, font], children: rc.ui.empty });
|
|
1023
|
+
}
|
|
1024
|
+
return inboxReports.map((r) => {
|
|
1025
|
+
const view = (0, import_reporter_ui.reporterStatus)(r, locale);
|
|
1026
|
+
const unread = (r.unread_count ?? 0) > 0;
|
|
1027
|
+
const title = (r.title ?? r.summary ?? r.description ?? "Report").slice(0, 80);
|
|
1028
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1029
|
+
import_react_native2.TouchableOpacity,
|
|
1030
|
+
{
|
|
1031
|
+
style: [s.inboxRow, { borderColor: colors.border }],
|
|
1032
|
+
onPress: () => void openThread(r.id),
|
|
1033
|
+
accessibilityRole: "button",
|
|
1034
|
+
accessibilityLabel: `${view.label}. ${title}${unread ? `. ${r.unread_count} new` : ""}`,
|
|
1035
|
+
children: [
|
|
1036
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontWeight: unread ? "700" : "600" }, font], numberOfLines: 1, children: title }),
|
|
1037
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: [{ color: unread ? colors.fg : colors.muted, fontSize: 12, marginTop: 2 }, font], numberOfLines: 1, children: [
|
|
1038
|
+
view.label,
|
|
1039
|
+
view.othersNote ? ` \xB7 ${view.othersNote}` : ""
|
|
1040
|
+
] }),
|
|
1041
|
+
unread && r.last_event_preview ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontSize: 12, fontWeight: "700", marginTop: 2 }, font], numberOfLines: 1, children: r.last_event_preview }) : null
|
|
1042
|
+
]
|
|
1043
|
+
},
|
|
1044
|
+
r.id
|
|
1045
|
+
);
|
|
1046
|
+
});
|
|
1047
|
+
};
|
|
1048
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: handleClose, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
736
1049
|
import_react_native2.KeyboardAvoidingView,
|
|
737
1050
|
{
|
|
738
1051
|
style: s.flex,
|
|
@@ -743,44 +1056,69 @@ var MushiBottomSheet = ({
|
|
|
743
1056
|
import_react_native2.Animated.View,
|
|
744
1057
|
{
|
|
745
1058
|
style: [s.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
|
|
746
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1059
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1060
|
+
import_react_native2.TouchableOpacity,
|
|
1061
|
+
{
|
|
1062
|
+
style: s.flex,
|
|
1063
|
+
activeOpacity: 1,
|
|
1064
|
+
onPress: handleClose,
|
|
1065
|
+
accessibilityRole: "button",
|
|
1066
|
+
accessibilityLabel: t.widget.close
|
|
1067
|
+
}
|
|
1068
|
+
)
|
|
747
1069
|
}
|
|
748
1070
|
),
|
|
749
1071
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
750
1072
|
import_react_native2.Animated.View,
|
|
751
1073
|
{
|
|
1074
|
+
accessibilityViewIsModal: true,
|
|
1075
|
+
onAccessibilityEscape: handleClose,
|
|
752
1076
|
style: [
|
|
753
1077
|
s.sheet,
|
|
754
1078
|
{ backgroundColor: colors.bg, transform: [{ translateY }] }
|
|
755
1079
|
],
|
|
756
1080
|
children: [
|
|
757
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1082
|
+
import_react_native2.View,
|
|
1083
|
+
{
|
|
1084
|
+
...panResponder.panHandlers,
|
|
1085
|
+
style: s.handleArea,
|
|
1086
|
+
accessible: true,
|
|
1087
|
+
accessibilityRole: "adjustable",
|
|
1088
|
+
accessibilityLabel: t.widget.close,
|
|
1089
|
+
accessibilityActions: [{ name: "decrement", label: t.widget.close }, { name: "escape" }],
|
|
1090
|
+
onAccessibilityAction: (e) => {
|
|
1091
|
+
if (e.nativeEvent.actionName === "decrement" || e.nativeEvent.actionName === "escape") handleClose();
|
|
1092
|
+
},
|
|
1093
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.handle, { backgroundColor: colors.muted }] })
|
|
1094
|
+
}
|
|
1095
|
+
),
|
|
1096
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.header, { borderBottomColor: colors.border }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { accessibilityRole: "header", style: [s.headerTitle, { color: colors.fg }, font], children: headerTitle }) }),
|
|
1097
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.tabRow, { borderBottomColor: colors.border }], accessibilityRole: "tablist", children: sheetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
763
1098
|
import_react_native2.TouchableOpacity,
|
|
764
1099
|
{
|
|
765
1100
|
onPress: () => {
|
|
766
1101
|
setSheetTab(tab);
|
|
767
|
-
if (tab === "inbox") void loadInbox();
|
|
768
|
-
else
|
|
1102
|
+
if (tab === "inbox") void loadInbox({ silent: inboxLoaded });
|
|
1103
|
+
else closeThread();
|
|
769
1104
|
},
|
|
770
1105
|
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
771
|
-
|
|
1106
|
+
accessibilityRole: "tab",
|
|
1107
|
+
accessibilityState: { selected: sheetTab === tab },
|
|
1108
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.fg : colors.muted }, font], children: tab === "report" ? t.widget.trigger : tab === "inbox" ? t.flows.reports.title : assistantLabel })
|
|
772
1109
|
},
|
|
773
1110
|
tab
|
|
774
1111
|
)) }),
|
|
775
1112
|
sheetTab === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
|
|
776
1113
|
assistantTurns.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
777
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.
|
|
1114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, marginBottom: 12, lineHeight: 20 }, font], children: greeting }),
|
|
778
1115
|
assistantSuggestions.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
779
1116
|
import_react_native2.TouchableOpacity,
|
|
780
1117
|
{
|
|
781
|
-
style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.
|
|
1118
|
+
style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.surface }],
|
|
782
1119
|
onPress: () => void sendAssistant(chip),
|
|
783
|
-
|
|
1120
|
+
accessibilityRole: "button",
|
|
1121
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.fg, fontSize: 13 }, font], children: chip })
|
|
784
1122
|
},
|
|
785
1123
|
chip
|
|
786
1124
|
))
|
|
@@ -791,25 +1129,34 @@ var MushiBottomSheet = ({
|
|
|
791
1129
|
s.assistantBubble,
|
|
792
1130
|
{
|
|
793
1131
|
alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
|
|
794
|
-
backgroundColor: turn.role === "user" ? colors.accent : colors.
|
|
1132
|
+
backgroundColor: turn.role === "user" ? colors.accent : colors.surface
|
|
795
1133
|
}
|
|
796
1134
|
],
|
|
797
1135
|
children: [
|
|
798
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: turn.role === "user" ? colors.
|
|
799
|
-
turn.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1136
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: turn.role === "user" ? colors.accentFg : colors.fg }, font], children: turn.text }),
|
|
1137
|
+
turn.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1138
|
+
import_react_native2.TouchableOpacity,
|
|
1139
|
+
{
|
|
1140
|
+
onPress: () => void sendAssistant(opt),
|
|
1141
|
+
style: { marginTop: 8 },
|
|
1142
|
+
accessibilityRole: "button",
|
|
1143
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.accent, fontSize: 12 }, font], children: opt })
|
|
1144
|
+
},
|
|
1145
|
+
opt
|
|
1146
|
+
))
|
|
800
1147
|
]
|
|
801
1148
|
},
|
|
802
1149
|
`${turn.role}-${idx}`
|
|
803
1150
|
)),
|
|
804
|
-
assistantSending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.
|
|
805
|
-
assistantError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color:
|
|
1151
|
+
assistantSending ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, marginTop: 8 }, font], accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
|
|
1152
|
+
assistantError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.error, marginTop: 8 }, font], accessibilityRole: "alert", children: assistantError }) : null,
|
|
806
1153
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.assistantComposer, children: [
|
|
807
1154
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
808
1155
|
import_react_native2.TextInput,
|
|
809
1156
|
{
|
|
810
|
-
style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.
|
|
1157
|
+
style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius }, font],
|
|
811
1158
|
placeholder: t.assistant.inputPlaceholder,
|
|
812
|
-
placeholderTextColor: colors.
|
|
1159
|
+
placeholderTextColor: colors.muted,
|
|
813
1160
|
value: assistantInput,
|
|
814
1161
|
onChangeText: setAssistantInput,
|
|
815
1162
|
editable: !assistantSending
|
|
@@ -818,45 +1165,19 @@ var MushiBottomSheet = ({
|
|
|
818
1165
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
819
1166
|
import_react_native2.TouchableOpacity,
|
|
820
1167
|
{
|
|
821
|
-
style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16 }],
|
|
1168
|
+
style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16, borderRadius: radius }],
|
|
822
1169
|
onPress: () => void sendAssistant(assistantInput),
|
|
823
1170
|
disabled: !assistantInput.trim() || assistantSending,
|
|
824
|
-
|
|
1171
|
+
accessibilityRole: "button",
|
|
1172
|
+
accessibilityLabel: t.flows.thread.send,
|
|
1173
|
+
accessibilityState: { disabled: !assistantInput.trim() || assistantSending, busy: assistantSending },
|
|
1174
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentFg }], children: "\u2191" })
|
|
825
1175
|
}
|
|
826
1176
|
)
|
|
827
1177
|
] })
|
|
828
|
-
] }) : sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled",
|
|
829
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.Text, { style: { color: colors.accent, marginBottom: 8 }, children: [
|
|
830
|
-
"\u2190 ",
|
|
831
|
-
t.widget.back
|
|
832
|
-
] }) }),
|
|
833
|
-
threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
|
|
834
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
|
|
835
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text }, children: c.body })
|
|
836
|
-
] }, c.id)),
|
|
837
|
-
VERIFIABLE_STATUSES.has(
|
|
838
|
-
inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
|
|
839
|
-
) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
|
|
840
|
-
/* @__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, { color: colors.accentInk }], children: t.flows.thread.confirmFixed }) }),
|
|
841
|
-
/* @__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: t.flows.thread.notFixed }) })
|
|
842
|
-
] }),
|
|
843
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
844
|
-
import_react_native2.TextInput,
|
|
845
|
-
{
|
|
846
|
-
style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
|
|
847
|
-
placeholder: t.flows.thread.replyPlaceholder,
|
|
848
|
-
placeholderTextColor: colors.sub,
|
|
849
|
-
value: replyText,
|
|
850
|
-
onChangeText: setReplyText
|
|
851
|
-
}
|
|
852
|
-
),
|
|
853
|
-
/* @__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, { color: colors.accentInk }], children: t.flows.thread.send }) })
|
|
854
|
-
] }) : inboxReports.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
855
|
-
/* @__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) }),
|
|
856
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: reporterStatusShort(r.status) })
|
|
857
|
-
] }, r.id)) }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
|
|
1178
|
+
] }) : sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: selectedReportId ? renderThread() : renderInboxList() }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, accessibilityLiveRegion: "polite", children: [
|
|
858
1179
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
859
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.
|
|
1180
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.fg }, font], children: t.widget.submitted })
|
|
860
1181
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
861
1182
|
import_react_native2.ScrollView,
|
|
862
1183
|
{
|
|
@@ -864,29 +1185,34 @@ var MushiBottomSheet = ({
|
|
|
864
1185
|
keyboardShouldPersistTaps: "handled",
|
|
865
1186
|
contentContainerStyle: { paddingBottom: 24 },
|
|
866
1187
|
children: [
|
|
867
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.stepLabel, { color: colors.
|
|
868
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORY_KEYS.map((key) => {
|
|
1188
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.stepLabel, { color: colors.muted }, font], children: t.step1.heading }),
|
|
1189
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, accessibilityRole: "radiogroup", children: CATEGORY_KEYS.map((key) => {
|
|
869
1190
|
const active = category === key;
|
|
870
1191
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
871
1192
|
import_react_native2.TouchableOpacity,
|
|
872
1193
|
{
|
|
873
1194
|
onPress: () => setCategory(key),
|
|
874
1195
|
activeOpacity: 0.7,
|
|
1196
|
+
accessibilityRole: "radio",
|
|
1197
|
+
accessibilityState: { selected: active, checked: active },
|
|
1198
|
+
accessibilityLabel: t.step1.categories[key],
|
|
875
1199
|
style: [
|
|
876
1200
|
s.catBtn,
|
|
877
1201
|
{
|
|
878
|
-
backgroundColor: active ? colors.accent : colors.
|
|
1202
|
+
backgroundColor: active ? colors.accent : colors.surface,
|
|
879
1203
|
borderColor: active ? colors.accent : colors.border
|
|
880
1204
|
}
|
|
881
1205
|
],
|
|
882
1206
|
children: [
|
|
883
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, children: CATEGORY_EMOJI[key] }),
|
|
1207
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, importantForAccessibility: "no", children: CATEGORY_EMOJI[key] }),
|
|
884
1208
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
885
1209
|
import_react_native2.Text,
|
|
886
1210
|
{
|
|
1211
|
+
numberOfLines: 1,
|
|
887
1212
|
style: [
|
|
888
1213
|
s.catLabel,
|
|
889
|
-
{ color: active ? colors.
|
|
1214
|
+
{ color: active ? colors.accentFg : colors.fg },
|
|
1215
|
+
font
|
|
890
1216
|
],
|
|
891
1217
|
children: t.step1.categories[key]
|
|
892
1218
|
}
|
|
@@ -902,13 +1228,16 @@ var MushiBottomSheet = ({
|
|
|
902
1228
|
style: [
|
|
903
1229
|
s.input,
|
|
904
1230
|
{
|
|
905
|
-
backgroundColor: colors.
|
|
906
|
-
color: colors.
|
|
907
|
-
borderColor: colors.border
|
|
908
|
-
|
|
1231
|
+
backgroundColor: colors.surface,
|
|
1232
|
+
color: colors.fg,
|
|
1233
|
+
borderColor: colors.border,
|
|
1234
|
+
borderRadius: radius
|
|
1235
|
+
},
|
|
1236
|
+
font
|
|
909
1237
|
],
|
|
910
1238
|
placeholder: t.step3.descriptionPlaceholder,
|
|
911
|
-
placeholderTextColor: colors.
|
|
1239
|
+
placeholderTextColor: colors.muted,
|
|
1240
|
+
accessibilityLabel: t.step3.descriptionPlaceholder,
|
|
912
1241
|
multiline: true,
|
|
913
1242
|
textAlignVertical: "top",
|
|
914
1243
|
value: description,
|
|
@@ -921,19 +1250,19 @@ var MushiBottomSheet = ({
|
|
|
921
1250
|
maxLength: 4e3
|
|
922
1251
|
}
|
|
923
1252
|
),
|
|
924
|
-
submitError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.
|
|
1253
|
+
submitError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: submitError }) : null,
|
|
925
1254
|
activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
|
|
926
1255
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
927
1256
|
import_react_native2.Image,
|
|
928
1257
|
{
|
|
929
1258
|
source: { uri: activeScreenshot },
|
|
930
|
-
style: [s.screenshotThumb, { backgroundColor: colors.
|
|
1259
|
+
style: [s.screenshotThumb, { backgroundColor: colors.fg }],
|
|
931
1260
|
accessibilityLabel: t.step3.screenshotPreviewAlt
|
|
932
1261
|
}
|
|
933
1262
|
),
|
|
934
1263
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
|
|
935
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.
|
|
936
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.
|
|
1264
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.fg }, font], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
|
|
1265
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.muted }, font], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
|
|
937
1266
|
] }),
|
|
938
1267
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
939
1268
|
import_react_native2.TouchableOpacity,
|
|
@@ -943,22 +1272,28 @@ var MushiBottomSheet = ({
|
|
|
943
1272
|
onClearScreenshot?.();
|
|
944
1273
|
},
|
|
945
1274
|
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
1275
|
+
accessibilityRole: "button",
|
|
946
1276
|
accessibilityLabel: t.widget.close,
|
|
947
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.
|
|
1277
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.muted }], children: "\u2715" })
|
|
948
1278
|
}
|
|
949
1279
|
)
|
|
950
1280
|
] }) : null,
|
|
1281
|
+
submitHint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [{ color: colors.muted, fontSize: 12, marginBottom: 6 }, font], children: submitHint }) : null,
|
|
951
1282
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
952
1283
|
import_react_native2.TouchableOpacity,
|
|
953
1284
|
{
|
|
954
1285
|
onPress: handleSubmit,
|
|
955
1286
|
disabled: !canSubmit,
|
|
956
1287
|
activeOpacity: 0.8,
|
|
1288
|
+
accessibilityRole: "button",
|
|
1289
|
+
accessibilityLabel: phase === "sending" ? t.widget.submitting : t.widget.submit,
|
|
1290
|
+
accessibilityHint: submitHint,
|
|
1291
|
+
accessibilityState: { disabled: !canSubmit, busy: phase === "sending" },
|
|
957
1292
|
style: [
|
|
958
1293
|
s.submitBtn,
|
|
959
|
-
{ backgroundColor: canSubmit ? colors.accent : colors.disabled }
|
|
1294
|
+
{ backgroundColor: canSubmit ? colors.accent : colors.disabled, borderRadius: radius }
|
|
960
1295
|
],
|
|
961
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: canSubmit ? colors.
|
|
1296
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: canSubmit ? colors.accentFg : colors.disabledFg }, font], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
|
|
962
1297
|
}
|
|
963
1298
|
)
|
|
964
1299
|
]
|
|
@@ -988,10 +1323,10 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
988
1323
|
left: 0,
|
|
989
1324
|
right: 0,
|
|
990
1325
|
minHeight: SHEET_HEIGHT,
|
|
991
|
-
borderTopLeftRadius:
|
|
992
|
-
borderTopRightRadius:
|
|
993
|
-
paddingBottom: import_react_native2.Platform.OS === "ios" ? 34 :
|
|
994
|
-
shadowColor:
|
|
1326
|
+
borderTopLeftRadius: import_core4.MUSHI_RADIUS.sheet,
|
|
1327
|
+
borderTopRightRadius: import_core4.MUSHI_RADIUS.sheet,
|
|
1328
|
+
paddingBottom: import_react_native2.Platform.OS === "ios" ? 34 : import_core4.MUSHI_SPACING.roomy,
|
|
1329
|
+
shadowColor: import_core4.MUSHI_SHADOW_INK,
|
|
995
1330
|
shadowOffset: { width: 0, height: -3 },
|
|
996
1331
|
shadowOpacity: 0.12,
|
|
997
1332
|
shadowRadius: 8,
|
|
@@ -999,88 +1334,72 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
999
1334
|
},
|
|
1000
1335
|
handleArea: {
|
|
1001
1336
|
alignItems: "center",
|
|
1002
|
-
paddingVertical:
|
|
1337
|
+
paddingVertical: import_core4.MUSHI_SPACING.comfy
|
|
1003
1338
|
},
|
|
1004
1339
|
handle: {
|
|
1005
1340
|
width: 40,
|
|
1006
1341
|
height: 5,
|
|
1007
|
-
borderRadius:
|
|
1342
|
+
borderRadius: import_core4.MUSHI_RADIUS.control,
|
|
1008
1343
|
opacity: 0.5
|
|
1009
1344
|
},
|
|
1010
|
-
|
|
1011
|
-
paddingHorizontal:
|
|
1012
|
-
|
|
1013
|
-
paddingBottom: import_core3.MUSHI_SPACING.comfy,
|
|
1014
|
-
borderBottomWidth: 1.5
|
|
1345
|
+
header: {
|
|
1346
|
+
paddingHorizontal: import_core4.MUSHI_SPACING.lounge,
|
|
1347
|
+
paddingBottom: import_core4.MUSHI_SPACING.comfy
|
|
1015
1348
|
},
|
|
1016
|
-
|
|
1017
|
-
fontSize: import_core3.MUSHI_TYPE.sizeLabel,
|
|
1018
|
-
fontWeight: "700",
|
|
1019
|
-
letterSpacing: 1.2,
|
|
1020
|
-
marginBottom: 2,
|
|
1021
|
-
opacity: 0.7
|
|
1022
|
-
},
|
|
1023
|
-
brandTitle: {
|
|
1349
|
+
headerTitle: {
|
|
1024
1350
|
fontSize: 18,
|
|
1025
|
-
fontWeight: "
|
|
1351
|
+
fontWeight: "700",
|
|
1026
1352
|
letterSpacing: -0.2
|
|
1027
1353
|
},
|
|
1028
1354
|
body: {
|
|
1029
|
-
paddingHorizontal:
|
|
1030
|
-
paddingTop:
|
|
1031
|
-
},
|
|
1032
|
-
title: {
|
|
1033
|
-
fontSize: 20,
|
|
1034
|
-
fontWeight: "700",
|
|
1035
|
-
marginBottom: import_core3.MUSHI_SPACING.roomy
|
|
1355
|
+
paddingHorizontal: import_core4.MUSHI_SPACING.lounge,
|
|
1356
|
+
paddingTop: import_core4.MUSHI_SPACING.tight
|
|
1036
1357
|
},
|
|
1037
1358
|
stepLabel: {
|
|
1038
|
-
fontSize:
|
|
1359
|
+
fontSize: import_core4.MUSHI_TYPE.sizeLabel,
|
|
1039
1360
|
fontWeight: "600",
|
|
1040
1361
|
letterSpacing: 0.2,
|
|
1041
|
-
marginTop:
|
|
1042
|
-
marginBottom:
|
|
1362
|
+
marginTop: import_core4.MUSHI_SPACING.snug,
|
|
1363
|
+
marginBottom: import_core4.MUSHI_SPACING.comfy,
|
|
1043
1364
|
textTransform: "uppercase"
|
|
1044
1365
|
},
|
|
1045
1366
|
catRow: {
|
|
1046
1367
|
flexDirection: "row",
|
|
1047
|
-
|
|
1048
|
-
|
|
1368
|
+
flexWrap: "wrap",
|
|
1369
|
+
gap: 8,
|
|
1370
|
+
marginBottom: import_core4.MUSHI_SPACING.roomy
|
|
1049
1371
|
},
|
|
1050
1372
|
catBtn: {
|
|
1373
|
+
flexDirection: "row",
|
|
1051
1374
|
alignItems: "center",
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1375
|
+
gap: 6,
|
|
1376
|
+
paddingVertical: 8,
|
|
1377
|
+
paddingHorizontal: 12,
|
|
1378
|
+
borderRadius: 999,
|
|
1055
1379
|
borderWidth: 1,
|
|
1056
|
-
flex: 1,
|
|
1057
|
-
marginHorizontal: 3,
|
|
1058
1380
|
minHeight: 44
|
|
1059
1381
|
},
|
|
1060
1382
|
catEmoji: {
|
|
1061
|
-
fontSize:
|
|
1062
|
-
marginBottom: import_core3.MUSHI_SPACING.tight
|
|
1383
|
+
fontSize: 16
|
|
1063
1384
|
},
|
|
1064
1385
|
catLabel: {
|
|
1065
|
-
fontSize:
|
|
1386
|
+
fontSize: 14,
|
|
1066
1387
|
fontWeight: "600"
|
|
1067
1388
|
},
|
|
1068
1389
|
input: {
|
|
1069
1390
|
borderWidth: 1,
|
|
1070
|
-
borderRadius: import_core3.MUSHI_RADIUS.card,
|
|
1071
1391
|
padding: 14,
|
|
1072
1392
|
fontSize: 15,
|
|
1073
1393
|
minHeight: 100,
|
|
1074
|
-
marginBottom:
|
|
1394
|
+
marginBottom: import_core4.MUSHI_SPACING.roomy
|
|
1075
1395
|
},
|
|
1076
1396
|
submitBtn: {
|
|
1077
|
-
borderRadius: import_core3.MUSHI_RADIUS.card,
|
|
1078
1397
|
paddingVertical: 14,
|
|
1079
1398
|
alignItems: "center",
|
|
1080
1399
|
minHeight: 44
|
|
1081
1400
|
},
|
|
1082
1401
|
submitText: {
|
|
1083
|
-
color:
|
|
1402
|
+
color: import_core4.MUSHI_INVERSE,
|
|
1084
1403
|
fontSize: 16,
|
|
1085
1404
|
fontWeight: "700"
|
|
1086
1405
|
},
|
|
@@ -1139,7 +1458,8 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
1139
1458
|
alignItems: "center",
|
|
1140
1459
|
paddingVertical: 10,
|
|
1141
1460
|
borderBottomWidth: 2,
|
|
1142
|
-
borderBottomColor: "transparent"
|
|
1461
|
+
borderBottomColor: "transparent",
|
|
1462
|
+
minHeight: 44
|
|
1143
1463
|
},
|
|
1144
1464
|
tabLabel: {
|
|
1145
1465
|
fontSize: 14,
|
|
@@ -1147,12 +1467,47 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
1147
1467
|
},
|
|
1148
1468
|
inboxRow: {
|
|
1149
1469
|
paddingVertical: 12,
|
|
1470
|
+
minHeight: 56,
|
|
1150
1471
|
borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth
|
|
1151
1472
|
},
|
|
1473
|
+
headerCard: {
|
|
1474
|
+
borderWidth: import_react_native2.StyleSheet.hairlineWidth,
|
|
1475
|
+
padding: 12,
|
|
1476
|
+
marginBottom: 12
|
|
1477
|
+
},
|
|
1478
|
+
pill: {
|
|
1479
|
+
alignSelf: "flex-start",
|
|
1480
|
+
borderWidth: 1,
|
|
1481
|
+
borderRadius: 999,
|
|
1482
|
+
paddingHorizontal: 8,
|
|
1483
|
+
paddingVertical: 2,
|
|
1484
|
+
fontSize: 12,
|
|
1485
|
+
fontWeight: "600",
|
|
1486
|
+
overflow: "hidden"
|
|
1487
|
+
},
|
|
1488
|
+
skeleton: {
|
|
1489
|
+
height: 14,
|
|
1490
|
+
borderRadius: 6,
|
|
1491
|
+
marginBottom: 10
|
|
1492
|
+
},
|
|
1493
|
+
errorRow: {
|
|
1494
|
+
flexDirection: "row",
|
|
1495
|
+
alignItems: "center",
|
|
1496
|
+
gap: 8,
|
|
1497
|
+
marginBottom: 12
|
|
1498
|
+
},
|
|
1499
|
+
retryBtn: {
|
|
1500
|
+
borderWidth: 1,
|
|
1501
|
+
paddingHorizontal: 12,
|
|
1502
|
+
paddingVertical: 8,
|
|
1503
|
+
minHeight: 44,
|
|
1504
|
+
justifyContent: "center"
|
|
1505
|
+
},
|
|
1152
1506
|
threadBubble: {
|
|
1153
1507
|
borderRadius: 10,
|
|
1154
1508
|
padding: 10,
|
|
1155
|
-
marginBottom: 8
|
|
1509
|
+
marginBottom: 8,
|
|
1510
|
+
maxWidth: "88%"
|
|
1156
1511
|
},
|
|
1157
1512
|
verifyRow: {
|
|
1158
1513
|
flexDirection: "row",
|
|
@@ -1161,9 +1516,26 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
1161
1516
|
},
|
|
1162
1517
|
verifyBtn: {
|
|
1163
1518
|
flex: 1,
|
|
1164
|
-
borderRadius: 10,
|
|
1165
1519
|
paddingVertical: 10,
|
|
1166
|
-
alignItems: "center"
|
|
1520
|
+
alignItems: "center",
|
|
1521
|
+
minHeight: 44
|
|
1522
|
+
},
|
|
1523
|
+
composer: {
|
|
1524
|
+
flexDirection: "row",
|
|
1525
|
+
alignItems: "flex-end",
|
|
1526
|
+
gap: 8
|
|
1527
|
+
},
|
|
1528
|
+
composerInput: {
|
|
1529
|
+
flex: 1,
|
|
1530
|
+
minHeight: 44,
|
|
1531
|
+
maxHeight: 120,
|
|
1532
|
+
marginBottom: 0
|
|
1533
|
+
},
|
|
1534
|
+
sendBtn: {
|
|
1535
|
+
paddingHorizontal: 16,
|
|
1536
|
+
paddingVertical: 12,
|
|
1537
|
+
minHeight: 44,
|
|
1538
|
+
justifyContent: "center"
|
|
1167
1539
|
},
|
|
1168
1540
|
assistantBubble: {
|
|
1169
1541
|
maxWidth: "88%",
|
|
@@ -1191,9 +1563,9 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
1191
1563
|
// src/components/MushiFloatingButton.tsx
|
|
1192
1564
|
var import_react2 = require("react");
|
|
1193
1565
|
var import_react_native3 = require("react-native");
|
|
1194
|
-
var
|
|
1566
|
+
var import_core5 = require("@mushi-mushi/core");
|
|
1195
1567
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1196
|
-
var FAB_SIZE =
|
|
1568
|
+
var FAB_SIZE = import_core5.MUSHI_GEOMETRY.fabSize;
|
|
1197
1569
|
var EDGE_PADDING = 12;
|
|
1198
1570
|
function getScreenBounds() {
|
|
1199
1571
|
const { width, height } = import_react_native3.Dimensions.get("window");
|
|
@@ -1243,7 +1615,7 @@ var MushiFloatingButton = ({
|
|
|
1243
1615
|
const currentX = (0, import_react2.useRef)(baseX);
|
|
1244
1616
|
const currentY = (0, import_react2.useRef)(0);
|
|
1245
1617
|
const isDragging = (0, import_react2.useRef)(false);
|
|
1246
|
-
const pal = (0,
|
|
1618
|
+
const pal = (0, import_core5.mushiPalette)(dark ? "dark" : "light");
|
|
1247
1619
|
const accentColor = accent ?? pal.accent;
|
|
1248
1620
|
(0, import_react2.useEffect)(() => {
|
|
1249
1621
|
if (!persist) return;
|
|
@@ -1354,7 +1726,7 @@ var MushiFloatingButton = ({
|
|
|
1354
1726
|
onStartShouldSetResponder: () => !draggable,
|
|
1355
1727
|
onResponderGrant: handlePressIn,
|
|
1356
1728
|
onResponderRelease: handlePressOut,
|
|
1357
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children:
|
|
1729
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: import_core5.MUSHI_COPY.triggerText })
|
|
1358
1730
|
}
|
|
1359
1731
|
);
|
|
1360
1732
|
};
|
|
@@ -1380,14 +1752,14 @@ var s2 = import_react_native3.StyleSheet.create({
|
|
|
1380
1752
|
// src/components/MushiBanner.tsx
|
|
1381
1753
|
var import_react3 = require("react");
|
|
1382
1754
|
var import_react_native4 = require("react-native");
|
|
1383
|
-
var
|
|
1755
|
+
var import_core6 = require("@mushi-mushi/core");
|
|
1384
1756
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1385
|
-
var MUSHI_BANNER_DEFAULT_HEIGHT =
|
|
1757
|
+
var MUSHI_BANNER_DEFAULT_HEIGHT = import_core6.MUSHI_GEOMETRY.bannerHeight;
|
|
1386
1758
|
var MushiBanner = ({
|
|
1387
1759
|
onPress,
|
|
1388
1760
|
onDismiss,
|
|
1389
|
-
message =
|
|
1390
|
-
label =
|
|
1761
|
+
message = import_core6.MUSHI_COPY.bannerMessage,
|
|
1762
|
+
label = import_core6.MUSHI_COPY.bannerLabel,
|
|
1391
1763
|
onLayoutHeight
|
|
1392
1764
|
}) => {
|
|
1393
1765
|
const handleLayout = (0, import_react3.useCallback)(
|
|
@@ -1407,7 +1779,7 @@ var MushiBanner = ({
|
|
|
1407
1779
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.TouchableOpacity, { style: s3.main, onPress, activeOpacity: 0.85, children: [
|
|
1408
1780
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.eyebrow, children: label.toUpperCase() }),
|
|
1409
1781
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.message, numberOfLines: 2, children: message }),
|
|
1410
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.cta, children:
|
|
1782
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.Text, { style: s3.cta, children: import_core6.MUSHI_COPY.bugCta })
|
|
1411
1783
|
] }),
|
|
1412
1784
|
onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1413
1785
|
import_react_native4.TouchableOpacity,
|
|
@@ -1430,13 +1802,13 @@ var s3 = import_react_native4.StyleSheet.create({
|
|
|
1430
1802
|
left: 0,
|
|
1431
1803
|
right: 0,
|
|
1432
1804
|
bottom: 0,
|
|
1433
|
-
minHeight:
|
|
1805
|
+
minHeight: import_core6.MUSHI_GEOMETRY.bannerHeight,
|
|
1434
1806
|
flexDirection: "row",
|
|
1435
1807
|
alignItems: "stretch",
|
|
1436
|
-
backgroundColor:
|
|
1808
|
+
backgroundColor: import_core6.MUSHI_BANNER_NEON.bg,
|
|
1437
1809
|
borderTopWidth: 1,
|
|
1438
|
-
borderTopColor:
|
|
1439
|
-
zIndex:
|
|
1810
|
+
borderTopColor: import_core6.MUSHI_BANNER_NEON.border,
|
|
1811
|
+
zIndex: import_core6.MUSHI_Z.banner,
|
|
1440
1812
|
elevation: 8,
|
|
1441
1813
|
paddingBottom: import_react_native4.Platform.OS === "ios" ? 0 : 0
|
|
1442
1814
|
},
|
|
@@ -1444,27 +1816,27 @@ var s3 = import_react_native4.StyleSheet.create({
|
|
|
1444
1816
|
flex: 1,
|
|
1445
1817
|
flexDirection: "row",
|
|
1446
1818
|
alignItems: "center",
|
|
1447
|
-
gap:
|
|
1448
|
-
paddingHorizontal:
|
|
1449
|
-
paddingVertical:
|
|
1819
|
+
gap: import_core6.MUSHI_SPACING.snug,
|
|
1820
|
+
paddingHorizontal: import_core6.MUSHI_SPACING.comfy,
|
|
1821
|
+
paddingVertical: import_core6.MUSHI_SPACING.snug,
|
|
1450
1822
|
minHeight: 44
|
|
1451
1823
|
},
|
|
1452
1824
|
eyebrow: {
|
|
1453
|
-
fontSize:
|
|
1825
|
+
fontSize: import_core6.MUSHI_TYPE.sizeLabel,
|
|
1454
1826
|
fontWeight: "700",
|
|
1455
1827
|
letterSpacing: 1.2,
|
|
1456
|
-
color:
|
|
1828
|
+
color: import_core6.MUSHI_BANNER_NEON.fg
|
|
1457
1829
|
},
|
|
1458
1830
|
message: {
|
|
1459
1831
|
flex: 1,
|
|
1460
1832
|
fontSize: 12,
|
|
1461
1833
|
fontWeight: "500",
|
|
1462
|
-
color:
|
|
1834
|
+
color: import_core6.MUSHI_BANNER_NEON.fg
|
|
1463
1835
|
},
|
|
1464
1836
|
cta: {
|
|
1465
1837
|
fontSize: 12,
|
|
1466
1838
|
fontWeight: "700",
|
|
1467
|
-
color:
|
|
1839
|
+
color: import_core6.MUSHI_BANNER_NEON.fg,
|
|
1468
1840
|
textDecorationLine: "underline"
|
|
1469
1841
|
},
|
|
1470
1842
|
dismiss: {
|
|
@@ -1474,18 +1846,341 @@ var s3 = import_react_native4.StyleSheet.create({
|
|
|
1474
1846
|
dismissText: {
|
|
1475
1847
|
fontSize: 20,
|
|
1476
1848
|
lineHeight: 22,
|
|
1477
|
-
color:
|
|
1849
|
+
color: import_core6.MUSHI_BANNER_NEON.fg,
|
|
1478
1850
|
opacity: 0.7
|
|
1479
1851
|
}
|
|
1480
1852
|
});
|
|
1481
1853
|
|
|
1854
|
+
// src/analytics/event-tracker.ts
|
|
1855
|
+
var import_core7 = require("@mushi-mushi/core");
|
|
1856
|
+
var CONSENT_BUFFER_MAX = 50;
|
|
1857
|
+
var SPILL_MAX = 200;
|
|
1858
|
+
var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1859
|
+
var SURFACE = "mobile";
|
|
1860
|
+
async function defaultStorage() {
|
|
1861
|
+
try {
|
|
1862
|
+
const mod = await import("@react-native-async-storage/async-storage");
|
|
1863
|
+
return mod.default;
|
|
1864
|
+
} catch {
|
|
1865
|
+
return null;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
function createRNEventTracker(opts) {
|
|
1869
|
+
const cfg = opts.config ?? {};
|
|
1870
|
+
const enabled = cfg.enabled !== false;
|
|
1871
|
+
const consentMode = cfg.consent ?? "implied";
|
|
1872
|
+
const allowlist = cfg.propertyAllowlist ?? [];
|
|
1873
|
+
const spillKey = `@mushi:events_spill_${opts.projectId}`;
|
|
1874
|
+
const consentKey = `@mushi:analytics_consent_${opts.projectId}`;
|
|
1875
|
+
let buffer = [];
|
|
1876
|
+
let consentBuffer = [];
|
|
1877
|
+
let consent = consentMode === "required" ? "pending" : "granted";
|
|
1878
|
+
let userId = null;
|
|
1879
|
+
let userTraits = null;
|
|
1880
|
+
let pendingIdentify = false;
|
|
1881
|
+
let flushing = false;
|
|
1882
|
+
let destroyed = false;
|
|
1883
|
+
let timer = null;
|
|
1884
|
+
let storage = null;
|
|
1885
|
+
const consentListeners = /* @__PURE__ */ new Set();
|
|
1886
|
+
const loadStorage = opts.storage ?? defaultStorage;
|
|
1887
|
+
const resolveClient = () => typeof opts.client === "function" ? opts.client() : opts.client;
|
|
1888
|
+
async function readSpill() {
|
|
1889
|
+
if (!storage) return [];
|
|
1890
|
+
try {
|
|
1891
|
+
const raw = await storage.getItem(spillKey);
|
|
1892
|
+
if (!raw) return [];
|
|
1893
|
+
const parsed = JSON.parse(raw);
|
|
1894
|
+
if (!parsed || Date.now() - parsed.at > SPILL_TTL_MS) {
|
|
1895
|
+
await storage.removeItem(spillKey);
|
|
1896
|
+
return [];
|
|
1897
|
+
}
|
|
1898
|
+
return Array.isArray(parsed.events) ? parsed.events.slice(0, SPILL_MAX) : [];
|
|
1899
|
+
} catch {
|
|
1900
|
+
return [];
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
async function writeSpill(events) {
|
|
1904
|
+
if (!storage) return;
|
|
1905
|
+
try {
|
|
1906
|
+
if (events.length === 0) await storage.removeItem(spillKey);
|
|
1907
|
+
else await storage.setItem(spillKey, JSON.stringify({ at: Date.now(), events: events.slice(-SPILL_MAX) }));
|
|
1908
|
+
} catch {
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
async function flushNow() {
|
|
1912
|
+
if (destroyed && buffer.length === 0) return;
|
|
1913
|
+
await ready;
|
|
1914
|
+
if (flushing || consent !== "granted") return;
|
|
1915
|
+
if (buffer.length === 0 && !pendingIdentify) return;
|
|
1916
|
+
const client = resolveClient();
|
|
1917
|
+
if (!client) return;
|
|
1918
|
+
flushing = true;
|
|
1919
|
+
const batch = buffer.splice(0, import_core7.EVENT_PROPERTY_LIMITS.maxServerBatch);
|
|
1920
|
+
if (pendingIdentify) {
|
|
1921
|
+
batch.unshift({ name: "identify", ts: (/* @__PURE__ */ new Date()).toISOString(), properties: {} });
|
|
1922
|
+
pendingIdentify = false;
|
|
1923
|
+
}
|
|
1924
|
+
try {
|
|
1925
|
+
const anonId = await Promise.resolve(opts.getAnonId());
|
|
1926
|
+
const payload = {
|
|
1927
|
+
anon_id: anonId,
|
|
1928
|
+
user_id: userId ?? opts.getUserId?.() ?? null,
|
|
1929
|
+
user_traits: userTraits ?? opts.getUserTraits?.() ?? null,
|
|
1930
|
+
session_id: null,
|
|
1931
|
+
sdk_version: opts.sdkVersion ?? null,
|
|
1932
|
+
surface: SURFACE,
|
|
1933
|
+
events: batch
|
|
1934
|
+
};
|
|
1935
|
+
const res = await client.postProductEvents(payload);
|
|
1936
|
+
if (!res.ok) await writeSpill([...await readSpill(), ...batch.filter((e) => e.name !== "identify")]);
|
|
1937
|
+
else await writeSpill([]);
|
|
1938
|
+
} catch {
|
|
1939
|
+
await writeSpill([...await readSpill(), ...batch.filter((e) => e.name !== "identify")]);
|
|
1940
|
+
} finally {
|
|
1941
|
+
flushing = false;
|
|
1942
|
+
}
|
|
1943
|
+
if (buffer.length >= import_core7.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
|
|
1944
|
+
}
|
|
1945
|
+
function enqueue(ev) {
|
|
1946
|
+
if (consent === "denied") return;
|
|
1947
|
+
if (consent === "pending") {
|
|
1948
|
+
if (consentBuffer.length < CONSENT_BUFFER_MAX) consentBuffer.push(ev);
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
buffer.push(ev);
|
|
1952
|
+
if (buffer.length >= import_core7.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
|
|
1953
|
+
}
|
|
1954
|
+
const ready = enabled ? (async () => {
|
|
1955
|
+
storage = await loadStorage();
|
|
1956
|
+
if (destroyed) return;
|
|
1957
|
+
let stored = null;
|
|
1958
|
+
try {
|
|
1959
|
+
stored = storage ? await storage.getItem(consentKey) : null;
|
|
1960
|
+
} catch {
|
|
1961
|
+
stored = null;
|
|
1962
|
+
}
|
|
1963
|
+
if (stored === "denied") {
|
|
1964
|
+
consent = "denied";
|
|
1965
|
+
buffer = [];
|
|
1966
|
+
consentBuffer = [];
|
|
1967
|
+
pendingIdentify = false;
|
|
1968
|
+
await writeSpill([]);
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
if (stored === "granted" && consent === "pending") {
|
|
1972
|
+
consent = "granted";
|
|
1973
|
+
buffer.push(...consentBuffer);
|
|
1974
|
+
consentBuffer = [];
|
|
1975
|
+
}
|
|
1976
|
+
const spill = await readSpill();
|
|
1977
|
+
if (spill.length > 0) {
|
|
1978
|
+
buffer.unshift(...spill);
|
|
1979
|
+
await writeSpill([]);
|
|
1980
|
+
}
|
|
1981
|
+
})().catch(() => void 0) : Promise.resolve();
|
|
1982
|
+
if (enabled) {
|
|
1983
|
+
const interval = Math.max(1e3, cfg.flushIntervalMs ?? 5e3);
|
|
1984
|
+
timer = setInterval(() => {
|
|
1985
|
+
void flushNow();
|
|
1986
|
+
}, interval);
|
|
1987
|
+
}
|
|
1988
|
+
return {
|
|
1989
|
+
ready,
|
|
1990
|
+
track(name, properties, trackOpts) {
|
|
1991
|
+
if (!enabled || destroyed) return false;
|
|
1992
|
+
if (!(0, import_core7.isValidEventName)(name)) return false;
|
|
1993
|
+
const { properties: props } = (0, import_core7.sanitizeEventProperties)(properties, { allowlist, scrub: opts.scrub });
|
|
1994
|
+
let merged = props;
|
|
1995
|
+
if (trackOpts?.reserved) {
|
|
1996
|
+
const { properties: res } = (0, import_core7.sanitizeEventProperties)(trackOpts.reserved, { allowReserved: true, scrub: opts.scrub });
|
|
1997
|
+
merged = { ...props, ...res };
|
|
1998
|
+
}
|
|
1999
|
+
merged.$surface = SURFACE;
|
|
2000
|
+
const route = opts.getRoute?.();
|
|
2001
|
+
if (route && merged.$route === void 0) merged.$route = route;
|
|
2002
|
+
if (!(0, import_core7.propertiesWithinByteLimit)(merged)) return false;
|
|
2003
|
+
enqueue({
|
|
2004
|
+
name,
|
|
2005
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2006
|
+
properties: merged,
|
|
2007
|
+
...trackOpts?.dedupKey ? { dedup_key: trackOpts.dedupKey } : {}
|
|
2008
|
+
});
|
|
2009
|
+
return true;
|
|
2010
|
+
},
|
|
2011
|
+
setConsent(state) {
|
|
2012
|
+
void ready.then(() => storage?.setItem(consentKey, state)).catch(() => void 0);
|
|
2013
|
+
if (state === "denied") {
|
|
2014
|
+
consent = "denied";
|
|
2015
|
+
buffer = [];
|
|
2016
|
+
consentBuffer = [];
|
|
2017
|
+
pendingIdentify = false;
|
|
2018
|
+
void writeSpill([]);
|
|
2019
|
+
} else {
|
|
2020
|
+
const wasPending = consent === "pending";
|
|
2021
|
+
consent = "granted";
|
|
2022
|
+
if (wasPending && consentBuffer.length > 0) {
|
|
2023
|
+
buffer.push(...consentBuffer);
|
|
2024
|
+
consentBuffer = [];
|
|
2025
|
+
}
|
|
2026
|
+
if (buffer.length > 0 || pendingIdentify) void flushNow();
|
|
2027
|
+
}
|
|
2028
|
+
for (const listener of [...consentListeners]) {
|
|
2029
|
+
try {
|
|
2030
|
+
listener(state);
|
|
2031
|
+
} catch {
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
},
|
|
2035
|
+
setIdentity(id, traits) {
|
|
2036
|
+
userId = id;
|
|
2037
|
+
userTraits = traits ?? null;
|
|
2038
|
+
if (id && enabled && consent !== "denied") {
|
|
2039
|
+
pendingIdentify = true;
|
|
2040
|
+
void flushNow();
|
|
2041
|
+
}
|
|
2042
|
+
},
|
|
2043
|
+
consentState() {
|
|
2044
|
+
return enabled ? consent : "denied";
|
|
2045
|
+
},
|
|
2046
|
+
onConsentChange(listener) {
|
|
2047
|
+
consentListeners.add(listener);
|
|
2048
|
+
return () => {
|
|
2049
|
+
consentListeners.delete(listener);
|
|
2050
|
+
};
|
|
2051
|
+
},
|
|
2052
|
+
async flush() {
|
|
2053
|
+
await ready;
|
|
2054
|
+
await flushNow();
|
|
2055
|
+
},
|
|
2056
|
+
async destroy() {
|
|
2057
|
+
if (timer != null) {
|
|
2058
|
+
clearInterval(timer);
|
|
2059
|
+
timer = null;
|
|
2060
|
+
}
|
|
2061
|
+
await flushNow().catch(() => void 0);
|
|
2062
|
+
destroyed = true;
|
|
2063
|
+
buffer = [];
|
|
2064
|
+
consentBuffer = [];
|
|
2065
|
+
pendingIdentify = false;
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/analytics/session-tracker.ts
|
|
2071
|
+
var import_core8 = require("@mushi-mushi/core");
|
|
2072
|
+
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
2073
|
+
var RESUME_WINDOW_MS = 30 * 6e4;
|
|
2074
|
+
function createRNSessionTracker(opts) {
|
|
2075
|
+
const clock = opts.now ?? Date.now;
|
|
2076
|
+
const resolveClient = () => typeof opts.client === "function" ? opts.client() : opts.client;
|
|
2077
|
+
let sessionId = opts.sessionId ?? `sess_${(0, import_core8.newUuid)()}`;
|
|
2078
|
+
let pageViewCount = 0;
|
|
2079
|
+
let started = false;
|
|
2080
|
+
let allowed = false;
|
|
2081
|
+
let foreground = true;
|
|
2082
|
+
let destroyed = false;
|
|
2083
|
+
let backgroundedAt = null;
|
|
2084
|
+
let heartbeat = null;
|
|
2085
|
+
function route() {
|
|
2086
|
+
return opts.getRoute?.() ?? null;
|
|
2087
|
+
}
|
|
2088
|
+
function send(kind, extra) {
|
|
2089
|
+
const client = resolveClient();
|
|
2090
|
+
if (!client || !allowed || destroyed) return;
|
|
2091
|
+
const base = {
|
|
2092
|
+
kind,
|
|
2093
|
+
session_id: sessionId,
|
|
2094
|
+
ts: new Date(clock()).toISOString(),
|
|
2095
|
+
page_view_count: pageViewCount,
|
|
2096
|
+
user_agent: opts.userAgent ?? null,
|
|
2097
|
+
sdk_version: opts.sdkVersion
|
|
2098
|
+
};
|
|
2099
|
+
void Promise.resolve(opts.getReporterToken()).then((token) => client.postSessionEvent({ ...base, reporter_token_hash: token, ...extra })).catch(() => void 0);
|
|
2100
|
+
}
|
|
2101
|
+
function stopHeartbeat() {
|
|
2102
|
+
if (heartbeat != null) {
|
|
2103
|
+
clearInterval(heartbeat);
|
|
2104
|
+
heartbeat = null;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
function startHeartbeat() {
|
|
2108
|
+
stopHeartbeat();
|
|
2109
|
+
heartbeat = setInterval(() => send("session_heartbeat", { route: route() }), HEARTBEAT_INTERVAL_MS);
|
|
2110
|
+
}
|
|
2111
|
+
function startSession() {
|
|
2112
|
+
started = true;
|
|
2113
|
+
pageViewCount = 1;
|
|
2114
|
+
send("session_start", { route: route() });
|
|
2115
|
+
startHeartbeat();
|
|
2116
|
+
}
|
|
2117
|
+
function activate() {
|
|
2118
|
+
if (allowed || destroyed) return;
|
|
2119
|
+
allowed = true;
|
|
2120
|
+
if (!foreground) return;
|
|
2121
|
+
if (!started) startSession();
|
|
2122
|
+
else startHeartbeat();
|
|
2123
|
+
}
|
|
2124
|
+
function deactivate() {
|
|
2125
|
+
allowed = false;
|
|
2126
|
+
stopHeartbeat();
|
|
2127
|
+
}
|
|
2128
|
+
const unsubscribe = opts.consent.onConsentChange(() => {
|
|
2129
|
+
if (opts.consent.consentState() === "granted") activate();
|
|
2130
|
+
else deactivate();
|
|
2131
|
+
});
|
|
2132
|
+
void opts.consent.ready.then(() => {
|
|
2133
|
+
if (opts.consent.consentState() === "granted") activate();
|
|
2134
|
+
}).catch(() => void 0);
|
|
2135
|
+
return {
|
|
2136
|
+
onAppStateChange(state) {
|
|
2137
|
+
if (destroyed) return;
|
|
2138
|
+
if (state === "active") {
|
|
2139
|
+
if (foreground) return;
|
|
2140
|
+
foreground = true;
|
|
2141
|
+
const away = backgroundedAt == null ? 0 : clock() - backgroundedAt;
|
|
2142
|
+
backgroundedAt = null;
|
|
2143
|
+
if (!allowed) return;
|
|
2144
|
+
if (!started || away >= RESUME_WINDOW_MS) {
|
|
2145
|
+
sessionId = `sess_${(0, import_core8.newUuid)()}`;
|
|
2146
|
+
startSession();
|
|
2147
|
+
} else {
|
|
2148
|
+
send("session_heartbeat", { route: route() });
|
|
2149
|
+
startHeartbeat();
|
|
2150
|
+
}
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (state !== "background" || !foreground) return;
|
|
2154
|
+
foreground = false;
|
|
2155
|
+
backgroundedAt = clock();
|
|
2156
|
+
stopHeartbeat();
|
|
2157
|
+
if (started) send("session_end", { route: route() });
|
|
2158
|
+
},
|
|
2159
|
+
pageView(next) {
|
|
2160
|
+
if (!allowed || !started || !foreground) return;
|
|
2161
|
+
pageViewCount += 1;
|
|
2162
|
+
send("page_view", { route: next });
|
|
2163
|
+
},
|
|
2164
|
+
destroy() {
|
|
2165
|
+
destroyed = true;
|
|
2166
|
+
deactivate();
|
|
2167
|
+
unsubscribe();
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
|
|
1482
2172
|
// src/version.ts
|
|
1483
2173
|
var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
|
|
1484
|
-
var MUSHI_SDK_VERSION = "0.
|
|
2174
|
+
var MUSHI_SDK_VERSION = "0.23.0" ? "0.23.0" : "0.17.0";
|
|
1485
2175
|
|
|
1486
2176
|
// src/provider.tsx
|
|
1487
2177
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1488
2178
|
var MushiContext = (0, import_react4.createContext)(null);
|
|
2179
|
+
function nativeUserAgent() {
|
|
2180
|
+
const os = import_react_native5.Platform?.OS ?? "native";
|
|
2181
|
+
const version = import_react_native5.Platform?.Version;
|
|
2182
|
+
return `${MUSHI_SDK_PACKAGE}/${MUSHI_SDK_VERSION} (${version != null ? `${os} ${String(version)}` : os})`;
|
|
2183
|
+
}
|
|
1489
2184
|
var DEFAULT_SCREENSHOT_HINT = "Check the preview \u2014 remove it if any private info (balances, personal details) is visible.";
|
|
1490
2185
|
function resolveScreenshotHint(v) {
|
|
1491
2186
|
if (v === false) return null;
|
|
@@ -1528,9 +2223,9 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1528
2223
|
const networkRef = (0, import_react4.useRef)(null);
|
|
1529
2224
|
const queueRef = (0, import_react4.useRef)(null);
|
|
1530
2225
|
const apiClientRef = (0, import_react4.useRef)(null);
|
|
1531
|
-
const sessionIdRef = (0, import_react4.useRef)(`sess_${(0,
|
|
2226
|
+
const sessionIdRef = (0, import_react4.useRef)(`sess_${(0, import_core9.newUuid)()}`);
|
|
1532
2227
|
const breadcrumbsRef = (0, import_react4.useRef)(
|
|
1533
|
-
(0,
|
|
2228
|
+
(0, import_core9.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
|
|
1534
2229
|
);
|
|
1535
2230
|
const [sheetVisible, setSheetVisible] = (0, import_react4.useState)(false);
|
|
1536
2231
|
const [sheetPreferredTab, setSheetPreferredTab] = (0, import_react4.useState)("report");
|
|
@@ -1555,7 +2250,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1555
2250
|
if (config.endpoint && !config.apiEndpoint && __DEV__) {
|
|
1556
2251
|
console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
|
|
1557
2252
|
}
|
|
1558
|
-
const apiEndpoint = rawEndpoint ??
|
|
2253
|
+
const apiEndpoint = rawEndpoint ?? import_core9.DEFAULT_API_ENDPOINT;
|
|
1559
2254
|
(0, import_react4.useEffect)(() => {
|
|
1560
2255
|
if (config.capture?.console !== false) {
|
|
1561
2256
|
consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
|
|
@@ -1563,7 +2258,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1563
2258
|
if (config.capture?.network !== false) {
|
|
1564
2259
|
networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
|
|
1565
2260
|
}
|
|
1566
|
-
apiClientRef.current = (0,
|
|
2261
|
+
apiClientRef.current = (0, import_core9.createApiClient)({
|
|
1567
2262
|
projectId: config.projectId,
|
|
1568
2263
|
apiKey: config.apiKey,
|
|
1569
2264
|
apiEndpoint,
|
|
@@ -1600,14 +2295,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1600
2295
|
if (existing && existing.startsWith("mushi_")) {
|
|
1601
2296
|
reporterTokenRef.current = existing;
|
|
1602
2297
|
} else {
|
|
1603
|
-
const fresh = `mushi_${(0,
|
|
2298
|
+
const fresh = `mushi_${(0, import_core9.newUuid)()}`;
|
|
1604
2299
|
reporterTokenRef.current = fresh;
|
|
1605
2300
|
await saveReporterToken(fresh, secureStorage);
|
|
1606
2301
|
}
|
|
1607
2302
|
resolveReporterTokenReady();
|
|
1608
2303
|
} catch {
|
|
1609
2304
|
if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
|
|
1610
|
-
reporterTokenRef.current = `mushi_${(0,
|
|
2305
|
+
reporterTokenRef.current = `mushi_${(0, import_core9.newUuid)()}`;
|
|
1611
2306
|
}
|
|
1612
2307
|
resolveReporterTokenReady();
|
|
1613
2308
|
}
|
|
@@ -1727,7 +2422,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1727
2422
|
const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
|
|
1728
2423
|
const breadcrumbs = breadcrumbsRef.current.getAll().map((c) => ({
|
|
1729
2424
|
...c,
|
|
1730
|
-
message: (0,
|
|
2425
|
+
message: (0, import_core9.scrubPii)(c.message)
|
|
1731
2426
|
}));
|
|
1732
2427
|
const timeline = breadcrumbs.map((c) => ({
|
|
1733
2428
|
ts: c.timestamp,
|
|
@@ -1736,10 +2431,10 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1736
2431
|
}));
|
|
1737
2432
|
const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
|
|
1738
2433
|
const report = {
|
|
1739
|
-
id: (0,
|
|
2434
|
+
id: (0, import_core9.newUuid)(),
|
|
1740
2435
|
projectId: config.projectId,
|
|
1741
2436
|
category: data.category,
|
|
1742
|
-
description: (0,
|
|
2437
|
+
description: (0, import_core9.scrubPii)(data.description),
|
|
1743
2438
|
environment: {
|
|
1744
2439
|
userAgent: deviceInfo.systemName ?? "ReactNative",
|
|
1745
2440
|
platform: deviceInfo.platform ?? "mobile",
|
|
@@ -1752,7 +2447,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1752
2447
|
},
|
|
1753
2448
|
consoleLogs: (consoleRef.current?.getEntries() ?? []).map((entry) => ({
|
|
1754
2449
|
...entry,
|
|
1755
|
-
message: (0,
|
|
2450
|
+
message: (0, import_core9.scrubPii)(entry.message)
|
|
1756
2451
|
})),
|
|
1757
2452
|
networkLogs: networkRef.current?.getEntries().map((entry) => ({
|
|
1758
2453
|
...entry,
|
|
@@ -1782,7 +2477,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1782
2477
|
const code = result.error?.code ?? "";
|
|
1783
2478
|
const isCredentialError = code === "HTTP_401" || code === "HTTP_403" || code.includes("UNAUTHORIZED") || code.includes("FORBIDDEN") || code.includes("INVALID_API_KEY");
|
|
1784
2479
|
if (isCredentialError) {
|
|
1785
|
-
const where = apiEndpoint.replace(/\/$/, "") ===
|
|
2480
|
+
const where = apiEndpoint.replace(/\/$/, "") === import_core9.DEFAULT_API_ENDPOINT ? "Get the correct values at: https://kensaur.us/mushi-mushi/admin/projects" : "Get the correct values from your Mushi console's Projects page.";
|
|
1786
2481
|
console.error(
|
|
1787
2482
|
'[Mushi] Credentials rejected. Check your projectId and apiKey (must have "report:write" scope). ' + where
|
|
1788
2483
|
);
|
|
@@ -1828,6 +2523,63 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1828
2523
|
if (rewardsFlushRef.current) clearInterval(rewardsFlushRef.current);
|
|
1829
2524
|
};
|
|
1830
2525
|
}, [config.rewards?.enabled, config.rewards?.trackActivity, config.rewards?.flushIntervalMs]);
|
|
2526
|
+
const eventTrackerRef = (0, import_react4.useRef)(null);
|
|
2527
|
+
const sessionTrackerRef = (0, import_react4.useRef)(null);
|
|
2528
|
+
(0, import_react4.useEffect)(() => {
|
|
2529
|
+
const getRoute = () => screenRef.current?.route ?? screenRef.current?.name ?? null;
|
|
2530
|
+
const tracker = createRNEventTracker({
|
|
2531
|
+
projectId: config.projectId,
|
|
2532
|
+
client: () => apiClientRef.current,
|
|
2533
|
+
getAnonId: async () => {
|
|
2534
|
+
await reporterTokenReadyRef.current;
|
|
2535
|
+
return reporterTokenRef.current;
|
|
2536
|
+
},
|
|
2537
|
+
getUserId: () => userRef.current?.id ?? null,
|
|
2538
|
+
getRoute,
|
|
2539
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2540
|
+
config: config.analytics,
|
|
2541
|
+
scrub: import_core9.scrubPii
|
|
2542
|
+
});
|
|
2543
|
+
eventTrackerRef.current = tracker;
|
|
2544
|
+
const sessions = config.trackSessions === false ? null : createRNSessionTracker({
|
|
2545
|
+
client: () => apiClientRef.current,
|
|
2546
|
+
getReporterToken: async () => {
|
|
2547
|
+
await reporterTokenReadyRef.current;
|
|
2548
|
+
return reporterTokenRef.current;
|
|
2549
|
+
},
|
|
2550
|
+
getRoute,
|
|
2551
|
+
consent: tracker,
|
|
2552
|
+
sessionId: sessionIdRef.current,
|
|
2553
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2554
|
+
userAgent: nativeUserAgent()
|
|
2555
|
+
});
|
|
2556
|
+
sessionTrackerRef.current = sessions;
|
|
2557
|
+
let sub;
|
|
2558
|
+
try {
|
|
2559
|
+
if (typeof import_react_native5.AppState?.addEventListener === "function") {
|
|
2560
|
+
sub = import_react_native5.AppState.addEventListener("change", (state) => {
|
|
2561
|
+
sessions?.onAppStateChange(state);
|
|
2562
|
+
if (state !== "active") tracker.flush().catch(() => {
|
|
2563
|
+
});
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
} catch {
|
|
2567
|
+
}
|
|
2568
|
+
return () => {
|
|
2569
|
+
sub?.remove();
|
|
2570
|
+
sessions?.destroy();
|
|
2571
|
+
sessionTrackerRef.current = null;
|
|
2572
|
+
eventTrackerRef.current = null;
|
|
2573
|
+
tracker.destroy().catch(() => {
|
|
2574
|
+
});
|
|
2575
|
+
};
|
|
2576
|
+
}, [
|
|
2577
|
+
config.projectId,
|
|
2578
|
+
config.analytics?.enabled,
|
|
2579
|
+
config.analytics?.consent,
|
|
2580
|
+
config.analytics?.flushIntervalMs,
|
|
2581
|
+
config.trackSessions
|
|
2582
|
+
]);
|
|
1831
2583
|
const instance = (0, import_react4.useMemo)(
|
|
1832
2584
|
() => ({
|
|
1833
2585
|
open,
|
|
@@ -1845,6 +2597,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1845
2597
|
name: traits?.name,
|
|
1846
2598
|
provider: traits?.provider
|
|
1847
2599
|
};
|
|
2600
|
+
eventTrackerRef.current?.setIdentity(userId, traits ?? null);
|
|
1848
2601
|
},
|
|
1849
2602
|
setUser(user) {
|
|
1850
2603
|
userRef.current = {
|
|
@@ -1853,18 +2606,22 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1853
2606
|
name: user.name,
|
|
1854
2607
|
provider: user.provider
|
|
1855
2608
|
};
|
|
2609
|
+
eventTrackerRef.current?.setIdentity(user.id, { email: user.email, name: user.name, provider: user.provider });
|
|
1856
2610
|
},
|
|
1857
2611
|
identifyWithToken(token) {
|
|
1858
2612
|
userTokenRef.current = token && typeof token === "string" ? token : null;
|
|
1859
2613
|
if (userTokenRef.current) {
|
|
1860
|
-
const claims = (0,
|
|
2614
|
+
const claims = (0, import_core9.parseIdentityToken)(userTokenRef.current);
|
|
1861
2615
|
if (claims?.sub) {
|
|
1862
2616
|
userRef.current = {
|
|
1863
2617
|
id: claims.sub,
|
|
1864
2618
|
...claims.email ? { email: claims.email } : {},
|
|
1865
2619
|
...claims.name ? { name: claims.name } : {}
|
|
1866
2620
|
};
|
|
2621
|
+
eventTrackerRef.current?.setIdentity(claims.sub, null);
|
|
1867
2622
|
}
|
|
2623
|
+
} else {
|
|
2624
|
+
eventTrackerRef.current?.setIdentity(null, null);
|
|
1868
2625
|
}
|
|
1869
2626
|
},
|
|
1870
2627
|
publishPageContext(context) {
|
|
@@ -1877,6 +2634,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1877
2634
|
const prev = screenRef.current?.name;
|
|
1878
2635
|
screenRef.current = screen;
|
|
1879
2636
|
if (screen.name && screen.name !== prev) {
|
|
2637
|
+
sessionTrackerRef.current?.pageView(screen.route ?? screen.name);
|
|
1880
2638
|
breadcrumbsRef.current.add({
|
|
1881
2639
|
category: "navigation",
|
|
1882
2640
|
level: "info",
|
|
@@ -1908,6 +2666,23 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1908
2666
|
const res = await client.listReporterComments(reportId, reporterTokenRef.current);
|
|
1909
2667
|
return res.ok ? res.data.comments ?? [] : [];
|
|
1910
2668
|
},
|
|
2669
|
+
async loadMyThread(reportId) {
|
|
2670
|
+
const client = apiClientRef.current;
|
|
2671
|
+
if (!client) return null;
|
|
2672
|
+
try {
|
|
2673
|
+
await reporterTokenReadyRef.current;
|
|
2674
|
+
const res = await client.listReporterComments(reportId, reporterTokenRef.current);
|
|
2675
|
+
return res.ok ? res.data?.comments ?? [] : null;
|
|
2676
|
+
} catch {
|
|
2677
|
+
return null;
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2680
|
+
async markReportRead(reportId) {
|
|
2681
|
+
const client = apiClientRef.current;
|
|
2682
|
+
if (!client) return 0;
|
|
2683
|
+
await reporterTokenReadyRef.current;
|
|
2684
|
+
return markReporterReportRead(client, reporterTokenRef.current, reportId);
|
|
2685
|
+
},
|
|
1911
2686
|
async replyToReport(reportId, body) {
|
|
1912
2687
|
const client = apiClientRef.current;
|
|
1913
2688
|
if (!client) return null;
|
|
@@ -1938,6 +2713,13 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1938
2713
|
if (!config.rewards?.enabled) return;
|
|
1939
2714
|
activityQueueRef.current.push({ action, metadata });
|
|
1940
2715
|
},
|
|
2716
|
+
// Product analytics (Users & Funnels)
|
|
2717
|
+
track(event, properties) {
|
|
2718
|
+
return eventTrackerRef.current?.track(event, properties) ?? false;
|
|
2719
|
+
},
|
|
2720
|
+
setConsent(state) {
|
|
2721
|
+
eventTrackerRef.current?.setConsent(state);
|
|
2722
|
+
},
|
|
1941
2723
|
async getTier() {
|
|
1942
2724
|
const userId = userRef.current?.id;
|
|
1943
2725
|
const client = apiClientRef.current;
|
|
@@ -2008,7 +2790,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
2008
2790
|
assistantLabel: config.assistant?.label,
|
|
2009
2791
|
assistantGreeting: config.assistant?.greeting,
|
|
2010
2792
|
assistantSuggestions: config.assistant?.suggestions,
|
|
2011
|
-
inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0
|
|
2793
|
+
inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0,
|
|
2794
|
+
theme: config.widget?.theme
|
|
2012
2795
|
}
|
|
2013
2796
|
)
|
|
2014
2797
|
] });
|