@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.js
CHANGED
|
@@ -15,11 +15,12 @@ import {
|
|
|
15
15
|
useCallback as useCallback4,
|
|
16
16
|
useMemo
|
|
17
17
|
} from "react";
|
|
18
|
+
import { AppState, Platform as Platform5 } from "react-native";
|
|
18
19
|
import {
|
|
19
20
|
createApiClient,
|
|
20
21
|
createBreadcrumbBuffer,
|
|
21
22
|
DEFAULT_API_ENDPOINT,
|
|
22
|
-
newUuid,
|
|
23
|
+
newUuid as newUuid2,
|
|
23
24
|
parseIdentityToken,
|
|
24
25
|
scrubPii as scrubPii3
|
|
25
26
|
} from "@mushi-mushi/core";
|
|
@@ -475,41 +476,105 @@ import {
|
|
|
475
476
|
useColorScheme
|
|
476
477
|
} from "react-native";
|
|
477
478
|
import {
|
|
478
|
-
mushiPalette,
|
|
479
|
-
MUSHI_BANNER_NEON,
|
|
480
479
|
MUSHI_COPY,
|
|
481
|
-
MUSHI_CONTROL_DISABLED,
|
|
482
480
|
MUSHI_INVERSE,
|
|
483
|
-
MUSHI_RADIUS,
|
|
481
|
+
MUSHI_RADIUS as MUSHI_RADIUS2,
|
|
484
482
|
MUSHI_SHADOW_INK,
|
|
485
483
|
MUSHI_SPACING,
|
|
486
484
|
MUSHI_TYPE
|
|
487
485
|
} from "@mushi-mushi/core";
|
|
486
|
+
import { reporterCopy, reporterStatus, resolveReporterLocale } from "@mushi-mushi/core/reporter-ui";
|
|
488
487
|
import { getLocale } from "@mushi-mushi/web/i18n";
|
|
489
488
|
|
|
490
|
-
// src/
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
489
|
+
// src/theme.ts
|
|
490
|
+
import { mushiPalette, MUSHI_CONTROL_DISABLED, MUSHI_RADIUS } from "@mushi-mushi/core";
|
|
491
|
+
function hexToRgb(hex) {
|
|
492
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})([0-9a-f]{2})?$/i.exec(hex.trim());
|
|
493
|
+
if (!m) return null;
|
|
494
|
+
const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
|
|
495
|
+
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
496
|
+
}
|
|
497
|
+
function luminance([r, g, b]) {
|
|
498
|
+
const lin = (c) => {
|
|
499
|
+
const s4 = c / 255;
|
|
500
|
+
return s4 <= 0.03928 ? s4 / 12.92 : ((s4 + 0.055) / 1.055) ** 2.4;
|
|
501
|
+
};
|
|
502
|
+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
503
|
+
}
|
|
504
|
+
function contrastingText(bg, fallback) {
|
|
505
|
+
const rgb = hexToRgb(bg);
|
|
506
|
+
if (!rgb) return fallback;
|
|
507
|
+
const l = luminance(rgb);
|
|
508
|
+
return 1.05 / (l + 0.05) >= (l + 0.05) / 0.05 ? "#FFFFFF" : "#000000";
|
|
509
|
+
}
|
|
510
|
+
function resolveRNTheme(dark, override = {}) {
|
|
511
|
+
const pal = mushiPalette(dark ? "dark" : "light");
|
|
512
|
+
const base = {
|
|
513
|
+
bg: dark ? pal.paper : pal.paperRaised,
|
|
514
|
+
fg: pal.ink,
|
|
515
|
+
muted: pal.inkMuted,
|
|
516
|
+
surface: dark ? pal.paperRaised : pal.paper,
|
|
517
|
+
border: pal.ruleStrong,
|
|
518
|
+
accent: pal.ink,
|
|
519
|
+
accentFg: dark ? pal.paper : pal.paperRaised,
|
|
520
|
+
success: pal.ok,
|
|
521
|
+
error: pal.danger,
|
|
522
|
+
fontFamily: void 0,
|
|
523
|
+
radius: MUSHI_RADIUS.card
|
|
524
|
+
};
|
|
525
|
+
const defined = Object.fromEntries(
|
|
526
|
+
Object.entries(override).filter(([, v]) => v !== void 0 && v !== null && v !== "")
|
|
527
|
+
);
|
|
528
|
+
const merged = { ...base, ...defined };
|
|
529
|
+
if (defined.accent && !defined.accentFg) merged.accentFg = contrastingText(defined.accent, base.accentFg);
|
|
530
|
+
return {
|
|
531
|
+
...merged,
|
|
532
|
+
backdrop: dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.35)",
|
|
533
|
+
disabled: dark ? MUSHI_CONTROL_DISABLED.dark : MUSHI_CONTROL_DISABLED.light,
|
|
534
|
+
disabledFg: pal.inkFaint
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/reporter-thread.ts
|
|
539
|
+
var THREAD_LOAD_TIMEOUT_MS = 12e3;
|
|
540
|
+
function settleWithin(p, ms) {
|
|
541
|
+
return new Promise((resolve) => {
|
|
542
|
+
const timer = setTimeout(() => resolve(null), ms);
|
|
543
|
+
p.then(
|
|
544
|
+
(value) => {
|
|
545
|
+
clearTimeout(timer);
|
|
546
|
+
resolve(value);
|
|
547
|
+
},
|
|
548
|
+
() => {
|
|
549
|
+
clearTimeout(timer);
|
|
550
|
+
resolve(null);
|
|
551
|
+
}
|
|
552
|
+
);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
function loadReporterThread(load, reportId, timeoutMs = THREAD_LOAD_TIMEOUT_MS) {
|
|
556
|
+
let started;
|
|
557
|
+
try {
|
|
558
|
+
started = load(reportId);
|
|
559
|
+
} catch {
|
|
560
|
+
return Promise.resolve(null);
|
|
561
|
+
}
|
|
562
|
+
return settleWithin(started, timeoutMs);
|
|
563
|
+
}
|
|
564
|
+
async function markReporterReportRead(client, reporterToken, reportId) {
|
|
565
|
+
try {
|
|
566
|
+
const res = await client.listNotifications(reporterToken, { limit: 100 });
|
|
567
|
+
if (!res.ok) return 0;
|
|
568
|
+
const rows = res.data?.notifications ?? [];
|
|
569
|
+
const ids = rows.filter((n) => !n.read_at && n.payload?.reportId === reportId).map((n) => String(n.id));
|
|
570
|
+
let marked = 0;
|
|
571
|
+
for (const id of ids) {
|
|
572
|
+
const r = await client.markNotificationRead(id, reporterToken);
|
|
573
|
+
if (r.ok) marked++;
|
|
574
|
+
}
|
|
575
|
+
return marked;
|
|
576
|
+
} catch {
|
|
577
|
+
return 0;
|
|
513
578
|
}
|
|
514
579
|
}
|
|
515
580
|
|
|
@@ -518,6 +583,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
518
583
|
var { height: SCREEN_HEIGHT } = Dimensions2.get("window");
|
|
519
584
|
var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
|
|
520
585
|
var DISMISS_THRESHOLD = 80;
|
|
586
|
+
var MIN_DESCRIPTION = 20;
|
|
521
587
|
var CATEGORY_KEYS = ["bug", "slow", "visual", "confusing", "other"];
|
|
522
588
|
var CATEGORY_EMOJI = {
|
|
523
589
|
bug: "\u{1F41B}",
|
|
@@ -526,7 +592,6 @@ var CATEGORY_EMOJI = {
|
|
|
526
592
|
confusing: "\u{1F615}",
|
|
527
593
|
other: "\u{1F4AC}"
|
|
528
594
|
};
|
|
529
|
-
var VERIFIABLE_STATUSES = /* @__PURE__ */ new Set(["fixed", "resolved", "verified"]);
|
|
530
595
|
var MushiBottomSheet = ({
|
|
531
596
|
visible,
|
|
532
597
|
onClose,
|
|
@@ -538,10 +603,15 @@ var MushiBottomSheet = ({
|
|
|
538
603
|
assistantLabel = MUSHI_COPY.assistantTab,
|
|
539
604
|
assistantGreeting,
|
|
540
605
|
assistantSuggestions = [],
|
|
541
|
-
inboxPollIntervalMs = 0
|
|
606
|
+
inboxPollIntervalMs = 0,
|
|
607
|
+
theme
|
|
542
608
|
}) => {
|
|
543
609
|
const mushi = useMushiContext();
|
|
544
610
|
const t = getLocale();
|
|
611
|
+
const locale = resolveReporterLocale(
|
|
612
|
+
typeof navigator !== "undefined" ? navigator.language : void 0
|
|
613
|
+
);
|
|
614
|
+
const rc = reporterCopy(locale);
|
|
545
615
|
const greeting = assistantGreeting ?? t.assistant.defaultGreeting;
|
|
546
616
|
const scheme = useColorScheme();
|
|
547
617
|
const dark = scheme === "dark";
|
|
@@ -553,26 +623,37 @@ var MushiBottomSheet = ({
|
|
|
553
623
|
const [submitError, setSubmitError] = useState(null);
|
|
554
624
|
const [sheetTab, setSheetTab] = useState("report");
|
|
555
625
|
const [inboxReports, setInboxReports] = useState([]);
|
|
626
|
+
const [inboxLoaded, setInboxLoaded] = useState(false);
|
|
556
627
|
const [inboxLoading, setInboxLoading] = useState(false);
|
|
557
628
|
const [selectedReportId, setSelectedReportId] = useState(null);
|
|
629
|
+
const selectedRef = useRef(null);
|
|
558
630
|
const [threadComments, setThreadComments] = useState([]);
|
|
631
|
+
const [threadStatus, setThreadStatus] = useState("idle");
|
|
559
632
|
const [replyText, setReplyText] = useState("");
|
|
633
|
+
const [replyState, setReplyState] = useState("idle");
|
|
634
|
+
const [feedbackSending, setFeedbackSending] = useState(false);
|
|
635
|
+
const [feedbackError, setFeedbackError] = useState(false);
|
|
560
636
|
const [assistantInput, setAssistantInput] = useState("");
|
|
561
637
|
const [assistantThreadId, setAssistantThreadId] = useState(null);
|
|
562
638
|
const [assistantSending, setAssistantSending] = useState(false);
|
|
563
639
|
const [assistantError, setAssistantError] = useState(null);
|
|
564
640
|
const [assistantTurns, setAssistantTurns] = useState([]);
|
|
565
641
|
const [screenshotAttached, setScreenshotAttached] = useState(true);
|
|
566
|
-
const loadInbox = useCallback(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
642
|
+
const loadInbox = useCallback(
|
|
643
|
+
async (opts = {}) => {
|
|
644
|
+
if (!mushi?.listMyReports) return;
|
|
645
|
+
if (!opts.silent) setInboxLoading(true);
|
|
646
|
+
try {
|
|
647
|
+
const rows = await mushi.listMyReports();
|
|
648
|
+
setInboxReports(rows.filter((r) => !reporterStatus(r, locale).hidden));
|
|
649
|
+
setInboxLoaded(true);
|
|
650
|
+
} catch {
|
|
651
|
+
} finally {
|
|
652
|
+
if (!opts.silent) setInboxLoading(false);
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
[mushi, locale]
|
|
656
|
+
);
|
|
576
657
|
useEffect(() => {
|
|
577
658
|
if (visible) {
|
|
578
659
|
setScreenshotAttached(true);
|
|
@@ -583,28 +664,87 @@ var MushiBottomSheet = ({
|
|
|
583
664
|
useEffect(() => {
|
|
584
665
|
if (!visible || sheetTab !== "inbox" || inboxPollIntervalMs <= 0) return;
|
|
585
666
|
const timer = setInterval(() => {
|
|
586
|
-
void loadInbox();
|
|
667
|
+
void loadInbox({ silent: true });
|
|
587
668
|
}, inboxPollIntervalMs);
|
|
588
669
|
return () => clearInterval(timer);
|
|
589
670
|
}, [visible, sheetTab, inboxPollIntervalMs, loadInbox]);
|
|
590
|
-
const openThread = useCallback(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
671
|
+
const openThread = useCallback(
|
|
672
|
+
async (reportId, opts = {}) => {
|
|
673
|
+
selectedRef.current = reportId;
|
|
674
|
+
setSelectedReportId(reportId);
|
|
675
|
+
if (!opts.silent) {
|
|
676
|
+
setThreadComments([]);
|
|
677
|
+
setThreadStatus("loading");
|
|
678
|
+
setReplyState("idle");
|
|
679
|
+
setFeedbackError(false);
|
|
680
|
+
}
|
|
681
|
+
const load = mushi?.loadMyThread ?? mushi?.listMyComments;
|
|
682
|
+
const comments = load ? await loadReporterThread(load, reportId) : null;
|
|
683
|
+
if (selectedRef.current !== reportId) return;
|
|
684
|
+
if (comments === null) {
|
|
685
|
+
setThreadStatus((prev) => opts.silent && prev === "ready" ? "ready" : "error");
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
setThreadComments(comments);
|
|
689
|
+
setThreadStatus("ready");
|
|
690
|
+
if (!opts.silent && mushi?.markReportRead) {
|
|
691
|
+
mushi.markReportRead(reportId).then((marked) => {
|
|
692
|
+
if (marked > 0) {
|
|
693
|
+
setInboxReports((rows) => rows.map((r) => r.id === reportId ? { ...r, unread_count: 0 } : r));
|
|
694
|
+
}
|
|
695
|
+
}).catch(() => void 0);
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
[mushi]
|
|
699
|
+
);
|
|
700
|
+
const closeThread = useCallback(() => {
|
|
701
|
+
selectedRef.current = null;
|
|
702
|
+
setSelectedReportId(null);
|
|
703
|
+
setThreadStatus("idle");
|
|
704
|
+
}, []);
|
|
596
705
|
const sendReply = useCallback(async () => {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
706
|
+
const body = replyText.trim();
|
|
707
|
+
const reportId = selectedReportId;
|
|
708
|
+
if (!mushi?.replyToReport || !reportId || !body || replyState === "sending") return;
|
|
709
|
+
setReplyState("sending");
|
|
710
|
+
try {
|
|
711
|
+
const comment = await settleWithin(mushi.replyToReport(reportId, body), THREAD_LOAD_TIMEOUT_MS);
|
|
712
|
+
if (!comment) {
|
|
713
|
+
setReplyState("failed");
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
setReplyText("");
|
|
717
|
+
setReplyState("idle");
|
|
718
|
+
if (selectedRef.current === reportId) {
|
|
719
|
+
setThreadComments((prev) => [...prev, comment]);
|
|
720
|
+
void openThread(reportId, { silent: true });
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
setReplyState("failed");
|
|
724
|
+
}
|
|
725
|
+
}, [mushi, selectedReportId, replyText, replyState, openThread]);
|
|
726
|
+
const submitFeedback = useCallback(
|
|
727
|
+
async (signal) => {
|
|
728
|
+
const reportId = selectedReportId;
|
|
729
|
+
if (!mushi?.submitFeedbackSignal || !reportId || feedbackSending) return;
|
|
730
|
+
setFeedbackSending(true);
|
|
731
|
+
setFeedbackError(false);
|
|
732
|
+
try {
|
|
733
|
+
const outcome = await mushi.submitFeedbackSignal(reportId, signal);
|
|
734
|
+
if (!outcome) {
|
|
735
|
+
setFeedbackError(true);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
await loadInbox({ silent: true });
|
|
739
|
+
await openThread(reportId, { silent: true });
|
|
740
|
+
} catch {
|
|
741
|
+
setFeedbackError(true);
|
|
742
|
+
} finally {
|
|
743
|
+
setFeedbackSending(false);
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
[mushi, selectedReportId, feedbackSending, loadInbox, openThread]
|
|
747
|
+
);
|
|
608
748
|
const sendAssistant = useCallback(async (message) => {
|
|
609
749
|
if (!mushi?.askAssistant || !message.trim() || assistantSending) return;
|
|
610
750
|
const trimmed = message.trim();
|
|
@@ -700,7 +840,7 @@ var MushiBottomSheet = ({
|
|
|
700
840
|
const handleSubmit = async () => {
|
|
701
841
|
if (!category || !description.trim() || !mushi) return;
|
|
702
842
|
const trimmed = description.trim();
|
|
703
|
-
if (trimmed.length <
|
|
843
|
+
if (trimmed.length < MIN_DESCRIPTION) {
|
|
704
844
|
setSubmitError("Please write at least 20 characters so we can understand the issue.");
|
|
705
845
|
return;
|
|
706
846
|
}
|
|
@@ -735,11 +875,181 @@ var MushiBottomSheet = ({
|
|
|
735
875
|
}
|
|
736
876
|
};
|
|
737
877
|
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
738
|
-
const
|
|
739
|
-
const
|
|
878
|
+
const colors = resolveRNTheme(dark, theme);
|
|
879
|
+
const font = colors.fontFamily ? { fontFamily: colors.fontFamily } : null;
|
|
880
|
+
const radius = colors.radius;
|
|
740
881
|
const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
|
|
741
|
-
const canSubmit = !!category && description.trim().length >=
|
|
742
|
-
|
|
882
|
+
const canSubmit = !!category && description.trim().length >= MIN_DESCRIPTION && (phase === "form" || phase === "error");
|
|
883
|
+
const submitHint = canSubmit || phase === "sending" ? void 0 : !category ? t.step1.heading : rc.ui.addWords;
|
|
884
|
+
const selectedReport = selectedReportId ? inboxReports.find((r) => r.id === selectedReportId) ?? null : null;
|
|
885
|
+
const selectedView = selectedReport ? reporterStatus(selectedReport, locale) : null;
|
|
886
|
+
const headerTitle = sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title;
|
|
887
|
+
const renderThread = () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
888
|
+
/* @__PURE__ */ jsx(
|
|
889
|
+
TouchableOpacity,
|
|
890
|
+
{
|
|
891
|
+
onPress: closeThread,
|
|
892
|
+
accessibilityRole: "button",
|
|
893
|
+
accessibilityLabel: t.widget.back,
|
|
894
|
+
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
895
|
+
children: /* @__PURE__ */ jsxs(Text, { style: [{ color: colors.accent, marginBottom: 8 }, font], children: [
|
|
896
|
+
"\u2190 ",
|
|
897
|
+
t.widget.back
|
|
898
|
+
] })
|
|
899
|
+
}
|
|
900
|
+
),
|
|
901
|
+
selectedReport && selectedView ? /* @__PURE__ */ jsxs(View, { style: [s.headerCard, { backgroundColor: colors.surface, borderRadius: radius, borderColor: colors.border }], children: [
|
|
902
|
+
/* @__PURE__ */ jsx(Text, { style: [s.pill, { color: colors.fg, borderColor: colors.border }, font], children: selectedView.label }),
|
|
903
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 12, marginTop: 4 }, font], children: selectedView.detail }),
|
|
904
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, marginTop: 8 }, font], numberOfLines: 4, children: selectedReport.title ?? selectedReport.summary ?? selectedReport.description ?? "" })
|
|
905
|
+
] }) : null,
|
|
906
|
+
threadStatus === "loading" ? /* @__PURE__ */ jsx(View, { accessibilityLabel: t.flows.reports.loading, accessibilityLiveRegion: "polite", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(View, { style: [s.skeleton, { backgroundColor: colors.surface, width: `${90 - i * 18}%` }] }, i)) }) : threadStatus === "error" ? /* @__PURE__ */ jsxs(View, { style: s.errorRow, accessibilityLiveRegion: "polite", children: [
|
|
907
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, flex: 1 }, font], accessibilityRole: "alert", children: rc.ui.loadError }),
|
|
908
|
+
/* @__PURE__ */ jsx(
|
|
909
|
+
TouchableOpacity,
|
|
910
|
+
{
|
|
911
|
+
onPress: () => selectedReportId && void openThread(selectedReportId),
|
|
912
|
+
accessibilityRole: "button",
|
|
913
|
+
style: [s.retryBtn, { borderColor: colors.border, borderRadius: radius }],
|
|
914
|
+
children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontWeight: "600" }, font], children: rc.ui.retry })
|
|
915
|
+
}
|
|
916
|
+
)
|
|
917
|
+
] }) : threadComments.length === 0 ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginBottom: 8 }, font], children: rc.ui.noReplies }) : threadComments.map((c) => /* @__PURE__ */ jsxs(
|
|
918
|
+
View,
|
|
919
|
+
{
|
|
920
|
+
style: [
|
|
921
|
+
s.threadBubble,
|
|
922
|
+
{
|
|
923
|
+
backgroundColor: colors.surface,
|
|
924
|
+
alignSelf: c.author_kind === "reporter" ? "flex-end" : "flex-start"
|
|
925
|
+
}
|
|
926
|
+
],
|
|
927
|
+
children: [
|
|
928
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: c.author_kind === "reporter" ? rc.ui.you : rc.ui.developer }),
|
|
929
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg }, font], children: c.body })
|
|
930
|
+
]
|
|
931
|
+
},
|
|
932
|
+
c.id
|
|
933
|
+
)),
|
|
934
|
+
replyState === "sending" ? /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.surface, alignSelf: "flex-end", opacity: 0.7 }], children: [
|
|
935
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 11 }, font], children: rc.ui.sending }),
|
|
936
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg }, font], children: replyText.trim() })
|
|
937
|
+
] }) : null,
|
|
938
|
+
selectedView?.canVerify ? /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
|
|
939
|
+
/* @__PURE__ */ jsx(
|
|
940
|
+
TouchableOpacity,
|
|
941
|
+
{
|
|
942
|
+
style: [s.verifyBtn, { backgroundColor: colors.accent, borderRadius: radius }],
|
|
943
|
+
onPress: () => void submitFeedback("confirms"),
|
|
944
|
+
disabled: feedbackSending,
|
|
945
|
+
accessibilityRole: "button",
|
|
946
|
+
accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
|
|
947
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentFg }, font], children: rc.ui.yes })
|
|
948
|
+
}
|
|
949
|
+
),
|
|
950
|
+
/* @__PURE__ */ jsx(
|
|
951
|
+
TouchableOpacity,
|
|
952
|
+
{
|
|
953
|
+
style: [s.verifyBtn, { backgroundColor: colors.border, borderRadius: radius }],
|
|
954
|
+
onPress: () => void submitFeedback("not_fixed"),
|
|
955
|
+
disabled: feedbackSending,
|
|
956
|
+
accessibilityRole: "button",
|
|
957
|
+
accessibilityState: { disabled: feedbackSending, busy: feedbackSending },
|
|
958
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.fg }, font], children: rc.ui.notYet })
|
|
959
|
+
}
|
|
960
|
+
)
|
|
961
|
+
] }) : null,
|
|
962
|
+
feedbackError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginBottom: 8 }, font], accessibilityRole: "alert", children: rc.ui.sendFailed }) : null,
|
|
963
|
+
/* @__PURE__ */ jsxs(View, { style: s.composer, children: [
|
|
964
|
+
/* @__PURE__ */ jsx(
|
|
965
|
+
TextInput,
|
|
966
|
+
{
|
|
967
|
+
style: [
|
|
968
|
+
s.input,
|
|
969
|
+
s.composerInput,
|
|
970
|
+
{ backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius },
|
|
971
|
+
font
|
|
972
|
+
],
|
|
973
|
+
placeholder: t.flows.thread.replyPlaceholder,
|
|
974
|
+
placeholderTextColor: colors.muted,
|
|
975
|
+
value: replyText,
|
|
976
|
+
onChangeText: (text) => {
|
|
977
|
+
setReplyText(text.slice(0, 2e3));
|
|
978
|
+
if (replyState === "failed") setReplyState("idle");
|
|
979
|
+
},
|
|
980
|
+
multiline: true,
|
|
981
|
+
maxLength: 2e3,
|
|
982
|
+
editable: replyState !== "sending",
|
|
983
|
+
accessibilityLabel: t.flows.thread.replyPlaceholder
|
|
984
|
+
}
|
|
985
|
+
),
|
|
986
|
+
/* @__PURE__ */ jsx(
|
|
987
|
+
TouchableOpacity,
|
|
988
|
+
{
|
|
989
|
+
style: [
|
|
990
|
+
s.sendBtn,
|
|
991
|
+
{
|
|
992
|
+
backgroundColor: replyText.trim() && replyState !== "sending" ? colors.accent : colors.disabled,
|
|
993
|
+
borderRadius: radius
|
|
994
|
+
}
|
|
995
|
+
],
|
|
996
|
+
onPress: () => void sendReply(),
|
|
997
|
+
disabled: !replyText.trim() || replyState === "sending",
|
|
998
|
+
accessibilityRole: "button",
|
|
999
|
+
accessibilityLabel: replyState === "failed" ? rc.ui.retry : t.flows.thread.send,
|
|
1000
|
+
accessibilityState: { disabled: !replyText.trim() || replyState === "sending", busy: replyState === "sending" },
|
|
1001
|
+
children: /* @__PURE__ */ jsx(
|
|
1002
|
+
Text,
|
|
1003
|
+
{
|
|
1004
|
+
style: [
|
|
1005
|
+
s.submitText,
|
|
1006
|
+
{ color: replyText.trim() && replyState !== "sending" ? colors.accentFg : colors.disabledFg },
|
|
1007
|
+
font
|
|
1008
|
+
],
|
|
1009
|
+
children: replyState === "failed" ? rc.ui.retry : t.flows.thread.send
|
|
1010
|
+
}
|
|
1011
|
+
)
|
|
1012
|
+
}
|
|
1013
|
+
)
|
|
1014
|
+
] }),
|
|
1015
|
+
replyState === "failed" ? /* @__PURE__ */ jsxs(Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: [
|
|
1016
|
+
rc.ui.sendFailed,
|
|
1017
|
+
" \xB7 ",
|
|
1018
|
+
rc.ui.retry
|
|
1019
|
+
] }) : null
|
|
1020
|
+
] });
|
|
1021
|
+
const renderInboxList = () => {
|
|
1022
|
+
if (inboxLoading && !inboxLoaded) {
|
|
1023
|
+
return /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted }, font], children: t.flows.reports.loading });
|
|
1024
|
+
}
|
|
1025
|
+
if (inboxReports.length === 0) {
|
|
1026
|
+
return /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted }, font], children: rc.ui.empty });
|
|
1027
|
+
}
|
|
1028
|
+
return inboxReports.map((r) => {
|
|
1029
|
+
const view = reporterStatus(r, locale);
|
|
1030
|
+
const unread = (r.unread_count ?? 0) > 0;
|
|
1031
|
+
const title = (r.title ?? r.summary ?? r.description ?? "Report").slice(0, 80);
|
|
1032
|
+
return /* @__PURE__ */ jsxs(
|
|
1033
|
+
TouchableOpacity,
|
|
1034
|
+
{
|
|
1035
|
+
style: [s.inboxRow, { borderColor: colors.border }],
|
|
1036
|
+
onPress: () => void openThread(r.id),
|
|
1037
|
+
accessibilityRole: "button",
|
|
1038
|
+
accessibilityLabel: `${view.label}. ${title}${unread ? `. ${r.unread_count} new` : ""}`,
|
|
1039
|
+
children: [
|
|
1040
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontWeight: unread ? "700" : "600" }, font], numberOfLines: 1, children: title }),
|
|
1041
|
+
/* @__PURE__ */ jsxs(Text, { style: [{ color: unread ? colors.fg : colors.muted, fontSize: 12, marginTop: 2 }, font], numberOfLines: 1, children: [
|
|
1042
|
+
view.label,
|
|
1043
|
+
view.othersNote ? ` \xB7 ${view.othersNote}` : ""
|
|
1044
|
+
] }),
|
|
1045
|
+
unread && r.last_event_preview ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontSize: 12, fontWeight: "700", marginTop: 2 }, font], numberOfLines: 1, children: r.last_event_preview }) : null
|
|
1046
|
+
]
|
|
1047
|
+
},
|
|
1048
|
+
r.id
|
|
1049
|
+
);
|
|
1050
|
+
});
|
|
1051
|
+
};
|
|
1052
|
+
return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: handleClose, children: /* @__PURE__ */ jsxs(
|
|
743
1053
|
KeyboardAvoidingView,
|
|
744
1054
|
{
|
|
745
1055
|
style: s.flex,
|
|
@@ -750,44 +1060,69 @@ var MushiBottomSheet = ({
|
|
|
750
1060
|
Animated.View,
|
|
751
1061
|
{
|
|
752
1062
|
style: [s.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
|
|
753
|
-
children: /* @__PURE__ */ jsx(
|
|
1063
|
+
children: /* @__PURE__ */ jsx(
|
|
1064
|
+
TouchableOpacity,
|
|
1065
|
+
{
|
|
1066
|
+
style: s.flex,
|
|
1067
|
+
activeOpacity: 1,
|
|
1068
|
+
onPress: handleClose,
|
|
1069
|
+
accessibilityRole: "button",
|
|
1070
|
+
accessibilityLabel: t.widget.close
|
|
1071
|
+
}
|
|
1072
|
+
)
|
|
754
1073
|
}
|
|
755
1074
|
),
|
|
756
1075
|
/* @__PURE__ */ jsxs(
|
|
757
1076
|
Animated.View,
|
|
758
1077
|
{
|
|
1078
|
+
accessibilityViewIsModal: true,
|
|
1079
|
+
onAccessibilityEscape: handleClose,
|
|
759
1080
|
style: [
|
|
760
1081
|
s.sheet,
|
|
761
1082
|
{ backgroundColor: colors.bg, transform: [{ translateY }] }
|
|
762
1083
|
],
|
|
763
1084
|
children: [
|
|
764
|
-
/* @__PURE__ */ jsx(
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1085
|
+
/* @__PURE__ */ jsx(
|
|
1086
|
+
View,
|
|
1087
|
+
{
|
|
1088
|
+
...panResponder.panHandlers,
|
|
1089
|
+
style: s.handleArea,
|
|
1090
|
+
accessible: true,
|
|
1091
|
+
accessibilityRole: "adjustable",
|
|
1092
|
+
accessibilityLabel: t.widget.close,
|
|
1093
|
+
accessibilityActions: [{ name: "decrement", label: t.widget.close }, { name: "escape" }],
|
|
1094
|
+
onAccessibilityAction: (e) => {
|
|
1095
|
+
if (e.nativeEvent.actionName === "decrement" || e.nativeEvent.actionName === "escape") handleClose();
|
|
1096
|
+
},
|
|
1097
|
+
children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.muted }] })
|
|
1098
|
+
}
|
|
1099
|
+
),
|
|
1100
|
+
/* @__PURE__ */ jsx(View, { style: [s.header, { borderBottomColor: colors.border }], children: /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: [s.headerTitle, { color: colors.fg }, font], children: headerTitle }) }),
|
|
1101
|
+
/* @__PURE__ */ jsx(View, { style: [s.tabRow, { borderBottomColor: colors.border }], accessibilityRole: "tablist", children: sheetTabs.map((tab) => /* @__PURE__ */ jsx(
|
|
770
1102
|
TouchableOpacity,
|
|
771
1103
|
{
|
|
772
1104
|
onPress: () => {
|
|
773
1105
|
setSheetTab(tab);
|
|
774
|
-
if (tab === "inbox") void loadInbox();
|
|
775
|
-
else
|
|
1106
|
+
if (tab === "inbox") void loadInbox({ silent: inboxLoaded });
|
|
1107
|
+
else closeThread();
|
|
776
1108
|
},
|
|
777
1109
|
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
778
|
-
|
|
1110
|
+
accessibilityRole: "tab",
|
|
1111
|
+
accessibilityState: { selected: sheetTab === tab },
|
|
1112
|
+
children: /* @__PURE__ */ jsx(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 })
|
|
779
1113
|
},
|
|
780
1114
|
tab
|
|
781
1115
|
)) }),
|
|
782
1116
|
sheetTab === "assistant" ? /* @__PURE__ */ jsxs(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
|
|
783
1117
|
assistantTurns.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
784
|
-
/* @__PURE__ */ jsx(Text, { style: { color: colors.
|
|
1118
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginBottom: 12, lineHeight: 20 }, font], children: greeting }),
|
|
785
1119
|
assistantSuggestions.map((chip) => /* @__PURE__ */ jsx(
|
|
786
1120
|
TouchableOpacity,
|
|
787
1121
|
{
|
|
788
|
-
style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.
|
|
1122
|
+
style: [s.assistantChip, { borderColor: colors.border, backgroundColor: colors.surface }],
|
|
789
1123
|
onPress: () => void sendAssistant(chip),
|
|
790
|
-
|
|
1124
|
+
accessibilityRole: "button",
|
|
1125
|
+
children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.fg, fontSize: 13 }, font], children: chip })
|
|
791
1126
|
},
|
|
792
1127
|
chip
|
|
793
1128
|
))
|
|
@@ -798,25 +1133,34 @@ var MushiBottomSheet = ({
|
|
|
798
1133
|
s.assistantBubble,
|
|
799
1134
|
{
|
|
800
1135
|
alignSelf: turn.role === "user" ? "flex-end" : "flex-start",
|
|
801
|
-
backgroundColor: turn.role === "user" ? colors.accent : colors.
|
|
1136
|
+
backgroundColor: turn.role === "user" ? colors.accent : colors.surface
|
|
802
1137
|
}
|
|
803
1138
|
],
|
|
804
1139
|
children: [
|
|
805
|
-
/* @__PURE__ */ jsx(Text, { style: { color: turn.role === "user" ? colors.
|
|
806
|
-
turn.options?.map((opt) => /* @__PURE__ */ jsx(
|
|
1140
|
+
/* @__PURE__ */ jsx(Text, { style: [{ color: turn.role === "user" ? colors.accentFg : colors.fg }, font], children: turn.text }),
|
|
1141
|
+
turn.options?.map((opt) => /* @__PURE__ */ jsx(
|
|
1142
|
+
TouchableOpacity,
|
|
1143
|
+
{
|
|
1144
|
+
onPress: () => void sendAssistant(opt),
|
|
1145
|
+
style: { marginTop: 8 },
|
|
1146
|
+
accessibilityRole: "button",
|
|
1147
|
+
children: /* @__PURE__ */ jsx(Text, { style: [{ color: colors.accent, fontSize: 12 }, font], children: opt })
|
|
1148
|
+
},
|
|
1149
|
+
opt
|
|
1150
|
+
))
|
|
807
1151
|
]
|
|
808
1152
|
},
|
|
809
1153
|
`${turn.role}-${idx}`
|
|
810
1154
|
)),
|
|
811
|
-
assistantSending ? /* @__PURE__ */ jsx(Text, { style: { color: colors.
|
|
812
|
-
assistantError ? /* @__PURE__ */ jsx(Text, { style: { color:
|
|
1155
|
+
assistantSending ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, marginTop: 8 }, font], accessibilityLiveRegion: "polite", children: t.assistant.thinking }) : null,
|
|
1156
|
+
assistantError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginTop: 8 }, font], accessibilityRole: "alert", children: assistantError }) : null,
|
|
813
1157
|
/* @__PURE__ */ jsxs(View, { style: s.assistantComposer, children: [
|
|
814
1158
|
/* @__PURE__ */ jsx(
|
|
815
1159
|
TextInput,
|
|
816
1160
|
{
|
|
817
|
-
style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.
|
|
1161
|
+
style: [s.input, { flex: 1, minHeight: 44, backgroundColor: colors.surface, color: colors.fg, borderColor: colors.border, borderRadius: radius }, font],
|
|
818
1162
|
placeholder: t.assistant.inputPlaceholder,
|
|
819
|
-
placeholderTextColor: colors.
|
|
1163
|
+
placeholderTextColor: colors.muted,
|
|
820
1164
|
value: assistantInput,
|
|
821
1165
|
onChangeText: setAssistantInput,
|
|
822
1166
|
editable: !assistantSending
|
|
@@ -825,45 +1169,19 @@ var MushiBottomSheet = ({
|
|
|
825
1169
|
/* @__PURE__ */ jsx(
|
|
826
1170
|
TouchableOpacity,
|
|
827
1171
|
{
|
|
828
|
-
style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16 }],
|
|
1172
|
+
style: [s.submitBtn, { backgroundColor: colors.accent, paddingHorizontal: 16, borderRadius: radius }],
|
|
829
1173
|
onPress: () => void sendAssistant(assistantInput),
|
|
830
1174
|
disabled: !assistantInput.trim() || assistantSending,
|
|
831
|
-
|
|
1175
|
+
accessibilityRole: "button",
|
|
1176
|
+
accessibilityLabel: t.flows.thread.send,
|
|
1177
|
+
accessibilityState: { disabled: !assistantInput.trim() || assistantSending, busy: assistantSending },
|
|
1178
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentFg }], children: "\u2191" })
|
|
832
1179
|
}
|
|
833
1180
|
)
|
|
834
1181
|
] })
|
|
835
|
-
] }) : sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled",
|
|
836
|
-
/* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ jsxs(Text, { style: { color: colors.accent, marginBottom: 8 }, children: [
|
|
837
|
-
"\u2190 ",
|
|
838
|
-
t.widget.back
|
|
839
|
-
] }) }),
|
|
840
|
-
threadComments.map((c) => /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
|
|
841
|
-
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
|
|
842
|
-
/* @__PURE__ */ jsx(Text, { style: { color: colors.text }, children: c.body })
|
|
843
|
-
] }, c.id)),
|
|
844
|
-
VERIFIABLE_STATUSES.has(
|
|
845
|
-
inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
|
|
846
|
-
) && /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
|
|
847
|
-
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.confirmFixed }) }),
|
|
848
|
-
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.text }], children: t.flows.thread.notFixed }) })
|
|
849
|
-
] }),
|
|
850
|
-
/* @__PURE__ */ jsx(
|
|
851
|
-
TextInput,
|
|
852
|
-
{
|
|
853
|
-
style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
|
|
854
|
-
placeholder: t.flows.thread.replyPlaceholder,
|
|
855
|
-
placeholderTextColor: colors.sub,
|
|
856
|
-
value: replyText,
|
|
857
|
-
onChangeText: setReplyText
|
|
858
|
-
}
|
|
859
|
-
),
|
|
860
|
-
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: t.flows.thread.send }) })
|
|
861
|
-
] }) : inboxReports.map((r) => /* @__PURE__ */ jsxs(TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
862
|
-
/* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
|
|
863
|
-
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: reporterStatusShort(r.status) })
|
|
864
|
-
] }, r.id)) }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
|
|
1182
|
+
] }) : sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", contentContainerStyle: { paddingBottom: 24 }, children: selectedReportId ? renderThread() : renderInboxList() }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, accessibilityLiveRegion: "polite", children: [
|
|
865
1183
|
/* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
866
|
-
/* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.
|
|
1184
|
+
/* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.fg }, font], children: t.widget.submitted })
|
|
867
1185
|
] }) : /* @__PURE__ */ jsxs(
|
|
868
1186
|
ScrollView,
|
|
869
1187
|
{
|
|
@@ -871,29 +1189,34 @@ var MushiBottomSheet = ({
|
|
|
871
1189
|
keyboardShouldPersistTaps: "handled",
|
|
872
1190
|
contentContainerStyle: { paddingBottom: 24 },
|
|
873
1191
|
children: [
|
|
874
|
-
/* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.
|
|
875
|
-
/* @__PURE__ */ jsx(View, { style: s.catRow, children: CATEGORY_KEYS.map((key) => {
|
|
1192
|
+
/* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.muted }, font], children: t.step1.heading }),
|
|
1193
|
+
/* @__PURE__ */ jsx(View, { style: s.catRow, accessibilityRole: "radiogroup", children: CATEGORY_KEYS.map((key) => {
|
|
876
1194
|
const active = category === key;
|
|
877
1195
|
return /* @__PURE__ */ jsxs(
|
|
878
1196
|
TouchableOpacity,
|
|
879
1197
|
{
|
|
880
1198
|
onPress: () => setCategory(key),
|
|
881
1199
|
activeOpacity: 0.7,
|
|
1200
|
+
accessibilityRole: "radio",
|
|
1201
|
+
accessibilityState: { selected: active, checked: active },
|
|
1202
|
+
accessibilityLabel: t.step1.categories[key],
|
|
882
1203
|
style: [
|
|
883
1204
|
s.catBtn,
|
|
884
1205
|
{
|
|
885
|
-
backgroundColor: active ? colors.accent : colors.
|
|
1206
|
+
backgroundColor: active ? colors.accent : colors.surface,
|
|
886
1207
|
borderColor: active ? colors.accent : colors.border
|
|
887
1208
|
}
|
|
888
1209
|
],
|
|
889
1210
|
children: [
|
|
890
|
-
/* @__PURE__ */ jsx(Text, { style: s.catEmoji, children: CATEGORY_EMOJI[key] }),
|
|
1211
|
+
/* @__PURE__ */ jsx(Text, { style: s.catEmoji, importantForAccessibility: "no", children: CATEGORY_EMOJI[key] }),
|
|
891
1212
|
/* @__PURE__ */ jsx(
|
|
892
1213
|
Text,
|
|
893
1214
|
{
|
|
1215
|
+
numberOfLines: 1,
|
|
894
1216
|
style: [
|
|
895
1217
|
s.catLabel,
|
|
896
|
-
{ color: active ? colors.
|
|
1218
|
+
{ color: active ? colors.accentFg : colors.fg },
|
|
1219
|
+
font
|
|
897
1220
|
],
|
|
898
1221
|
children: t.step1.categories[key]
|
|
899
1222
|
}
|
|
@@ -909,13 +1232,16 @@ var MushiBottomSheet = ({
|
|
|
909
1232
|
style: [
|
|
910
1233
|
s.input,
|
|
911
1234
|
{
|
|
912
|
-
backgroundColor: colors.
|
|
913
|
-
color: colors.
|
|
914
|
-
borderColor: colors.border
|
|
915
|
-
|
|
1235
|
+
backgroundColor: colors.surface,
|
|
1236
|
+
color: colors.fg,
|
|
1237
|
+
borderColor: colors.border,
|
|
1238
|
+
borderRadius: radius
|
|
1239
|
+
},
|
|
1240
|
+
font
|
|
916
1241
|
],
|
|
917
1242
|
placeholder: t.step3.descriptionPlaceholder,
|
|
918
|
-
placeholderTextColor: colors.
|
|
1243
|
+
placeholderTextColor: colors.muted,
|
|
1244
|
+
accessibilityLabel: t.step3.descriptionPlaceholder,
|
|
919
1245
|
multiline: true,
|
|
920
1246
|
textAlignVertical: "top",
|
|
921
1247
|
value: description,
|
|
@@ -928,19 +1254,19 @@ var MushiBottomSheet = ({
|
|
|
928
1254
|
maxLength: 4e3
|
|
929
1255
|
}
|
|
930
1256
|
),
|
|
931
|
-
submitError ? /* @__PURE__ */ jsx(Text, { style: { color: colors.
|
|
1257
|
+
submitError ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.error, marginTop: 6, fontSize: 12 }, font], accessibilityRole: "alert", children: submitError }) : null,
|
|
932
1258
|
activeScreenshot ? /* @__PURE__ */ jsxs(View, { style: s.screenshotRow, children: [
|
|
933
1259
|
/* @__PURE__ */ jsx(
|
|
934
1260
|
Image,
|
|
935
1261
|
{
|
|
936
1262
|
source: { uri: activeScreenshot },
|
|
937
|
-
style: [s.screenshotThumb, { backgroundColor: colors.
|
|
1263
|
+
style: [s.screenshotThumb, { backgroundColor: colors.fg }],
|
|
938
1264
|
accessibilityLabel: t.step3.screenshotPreviewAlt
|
|
939
1265
|
}
|
|
940
1266
|
),
|
|
941
1267
|
/* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
|
|
942
|
-
/* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.
|
|
943
|
-
/* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.
|
|
1268
|
+
/* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.fg }, font], children: t.step3.screenshotAttached.replace(" \u2713", "") }),
|
|
1269
|
+
/* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.muted }, font], children: screenshotSensitiveHint && screenshotSensitiveHint.trim() ? `\u26A0 ${screenshotSensitiveHint}` : t.step3.screenshotSensitiveHint.split("\u2014")[0]?.trim() ?? t.step3.screenshotSensitiveHint })
|
|
944
1270
|
] }),
|
|
945
1271
|
/* @__PURE__ */ jsx(
|
|
946
1272
|
TouchableOpacity,
|
|
@@ -950,22 +1276,28 @@ var MushiBottomSheet = ({
|
|
|
950
1276
|
onClearScreenshot?.();
|
|
951
1277
|
},
|
|
952
1278
|
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
1279
|
+
accessibilityRole: "button",
|
|
953
1280
|
accessibilityLabel: t.widget.close,
|
|
954
|
-
children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.
|
|
1281
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.muted }], children: "\u2715" })
|
|
955
1282
|
}
|
|
956
1283
|
)
|
|
957
1284
|
] }) : null,
|
|
1285
|
+
submitHint ? /* @__PURE__ */ jsx(Text, { style: [{ color: colors.muted, fontSize: 12, marginBottom: 6 }, font], children: submitHint }) : null,
|
|
958
1286
|
/* @__PURE__ */ jsx(
|
|
959
1287
|
TouchableOpacity,
|
|
960
1288
|
{
|
|
961
1289
|
onPress: handleSubmit,
|
|
962
1290
|
disabled: !canSubmit,
|
|
963
1291
|
activeOpacity: 0.8,
|
|
1292
|
+
accessibilityRole: "button",
|
|
1293
|
+
accessibilityLabel: phase === "sending" ? t.widget.submitting : t.widget.submit,
|
|
1294
|
+
accessibilityHint: submitHint,
|
|
1295
|
+
accessibilityState: { disabled: !canSubmit, busy: phase === "sending" },
|
|
964
1296
|
style: [
|
|
965
1297
|
s.submitBtn,
|
|
966
|
-
{ backgroundColor: canSubmit ? colors.accent : colors.disabled }
|
|
1298
|
+
{ backgroundColor: canSubmit ? colors.accent : colors.disabled, borderRadius: radius }
|
|
967
1299
|
],
|
|
968
|
-
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.
|
|
1300
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.accentFg : colors.disabledFg }, font], children: phase === "sending" ? t.widget.submitting : t.widget.submit })
|
|
969
1301
|
}
|
|
970
1302
|
)
|
|
971
1303
|
]
|
|
@@ -995,8 +1327,8 @@ var s = StyleSheet.create({
|
|
|
995
1327
|
left: 0,
|
|
996
1328
|
right: 0,
|
|
997
1329
|
minHeight: SHEET_HEIGHT,
|
|
998
|
-
borderTopLeftRadius:
|
|
999
|
-
borderTopRightRadius:
|
|
1330
|
+
borderTopLeftRadius: MUSHI_RADIUS2.sheet,
|
|
1331
|
+
borderTopRightRadius: MUSHI_RADIUS2.sheet,
|
|
1000
1332
|
paddingBottom: Platform2.OS === "ios" ? 34 : MUSHI_SPACING.roomy,
|
|
1001
1333
|
shadowColor: MUSHI_SHADOW_INK,
|
|
1002
1334
|
shadowOffset: { width: 0, height: -3 },
|
|
@@ -1011,36 +1343,22 @@ var s = StyleSheet.create({
|
|
|
1011
1343
|
handle: {
|
|
1012
1344
|
width: 40,
|
|
1013
1345
|
height: 5,
|
|
1014
|
-
borderRadius:
|
|
1346
|
+
borderRadius: MUSHI_RADIUS2.control,
|
|
1015
1347
|
opacity: 0.5
|
|
1016
1348
|
},
|
|
1017
|
-
|
|
1349
|
+
header: {
|
|
1018
1350
|
paddingHorizontal: MUSHI_SPACING.lounge,
|
|
1019
|
-
|
|
1020
|
-
paddingBottom: MUSHI_SPACING.comfy,
|
|
1021
|
-
borderBottomWidth: 1.5
|
|
1351
|
+
paddingBottom: MUSHI_SPACING.comfy
|
|
1022
1352
|
},
|
|
1023
|
-
|
|
1024
|
-
fontSize: MUSHI_TYPE.sizeLabel,
|
|
1025
|
-
fontWeight: "700",
|
|
1026
|
-
letterSpacing: 1.2,
|
|
1027
|
-
marginBottom: 2,
|
|
1028
|
-
opacity: 0.7
|
|
1029
|
-
},
|
|
1030
|
-
brandTitle: {
|
|
1353
|
+
headerTitle: {
|
|
1031
1354
|
fontSize: 18,
|
|
1032
|
-
fontWeight: "
|
|
1355
|
+
fontWeight: "700",
|
|
1033
1356
|
letterSpacing: -0.2
|
|
1034
1357
|
},
|
|
1035
1358
|
body: {
|
|
1036
1359
|
paddingHorizontal: MUSHI_SPACING.lounge,
|
|
1037
1360
|
paddingTop: MUSHI_SPACING.tight
|
|
1038
1361
|
},
|
|
1039
|
-
title: {
|
|
1040
|
-
fontSize: 20,
|
|
1041
|
-
fontWeight: "700",
|
|
1042
|
-
marginBottom: MUSHI_SPACING.roomy
|
|
1043
|
-
},
|
|
1044
1362
|
stepLabel: {
|
|
1045
1363
|
fontSize: MUSHI_TYPE.sizeLabel,
|
|
1046
1364
|
fontWeight: "600",
|
|
@@ -1051,37 +1369,35 @@ var s = StyleSheet.create({
|
|
|
1051
1369
|
},
|
|
1052
1370
|
catRow: {
|
|
1053
1371
|
flexDirection: "row",
|
|
1054
|
-
|
|
1372
|
+
flexWrap: "wrap",
|
|
1373
|
+
gap: 8,
|
|
1055
1374
|
marginBottom: MUSHI_SPACING.roomy
|
|
1056
1375
|
},
|
|
1057
1376
|
catBtn: {
|
|
1377
|
+
flexDirection: "row",
|
|
1058
1378
|
alignItems: "center",
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1379
|
+
gap: 6,
|
|
1380
|
+
paddingVertical: 8,
|
|
1381
|
+
paddingHorizontal: 12,
|
|
1382
|
+
borderRadius: 999,
|
|
1062
1383
|
borderWidth: 1,
|
|
1063
|
-
flex: 1,
|
|
1064
|
-
marginHorizontal: 3,
|
|
1065
1384
|
minHeight: 44
|
|
1066
1385
|
},
|
|
1067
1386
|
catEmoji: {
|
|
1068
|
-
fontSize:
|
|
1069
|
-
marginBottom: MUSHI_SPACING.tight
|
|
1387
|
+
fontSize: 16
|
|
1070
1388
|
},
|
|
1071
1389
|
catLabel: {
|
|
1072
|
-
fontSize:
|
|
1390
|
+
fontSize: 14,
|
|
1073
1391
|
fontWeight: "600"
|
|
1074
1392
|
},
|
|
1075
1393
|
input: {
|
|
1076
1394
|
borderWidth: 1,
|
|
1077
|
-
borderRadius: MUSHI_RADIUS.card,
|
|
1078
1395
|
padding: 14,
|
|
1079
1396
|
fontSize: 15,
|
|
1080
1397
|
minHeight: 100,
|
|
1081
1398
|
marginBottom: MUSHI_SPACING.roomy
|
|
1082
1399
|
},
|
|
1083
1400
|
submitBtn: {
|
|
1084
|
-
borderRadius: MUSHI_RADIUS.card,
|
|
1085
1401
|
paddingVertical: 14,
|
|
1086
1402
|
alignItems: "center",
|
|
1087
1403
|
minHeight: 44
|
|
@@ -1146,7 +1462,8 @@ var s = StyleSheet.create({
|
|
|
1146
1462
|
alignItems: "center",
|
|
1147
1463
|
paddingVertical: 10,
|
|
1148
1464
|
borderBottomWidth: 2,
|
|
1149
|
-
borderBottomColor: "transparent"
|
|
1465
|
+
borderBottomColor: "transparent",
|
|
1466
|
+
minHeight: 44
|
|
1150
1467
|
},
|
|
1151
1468
|
tabLabel: {
|
|
1152
1469
|
fontSize: 14,
|
|
@@ -1154,12 +1471,47 @@ var s = StyleSheet.create({
|
|
|
1154
1471
|
},
|
|
1155
1472
|
inboxRow: {
|
|
1156
1473
|
paddingVertical: 12,
|
|
1474
|
+
minHeight: 56,
|
|
1157
1475
|
borderBottomWidth: StyleSheet.hairlineWidth
|
|
1158
1476
|
},
|
|
1477
|
+
headerCard: {
|
|
1478
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
1479
|
+
padding: 12,
|
|
1480
|
+
marginBottom: 12
|
|
1481
|
+
},
|
|
1482
|
+
pill: {
|
|
1483
|
+
alignSelf: "flex-start",
|
|
1484
|
+
borderWidth: 1,
|
|
1485
|
+
borderRadius: 999,
|
|
1486
|
+
paddingHorizontal: 8,
|
|
1487
|
+
paddingVertical: 2,
|
|
1488
|
+
fontSize: 12,
|
|
1489
|
+
fontWeight: "600",
|
|
1490
|
+
overflow: "hidden"
|
|
1491
|
+
},
|
|
1492
|
+
skeleton: {
|
|
1493
|
+
height: 14,
|
|
1494
|
+
borderRadius: 6,
|
|
1495
|
+
marginBottom: 10
|
|
1496
|
+
},
|
|
1497
|
+
errorRow: {
|
|
1498
|
+
flexDirection: "row",
|
|
1499
|
+
alignItems: "center",
|
|
1500
|
+
gap: 8,
|
|
1501
|
+
marginBottom: 12
|
|
1502
|
+
},
|
|
1503
|
+
retryBtn: {
|
|
1504
|
+
borderWidth: 1,
|
|
1505
|
+
paddingHorizontal: 12,
|
|
1506
|
+
paddingVertical: 8,
|
|
1507
|
+
minHeight: 44,
|
|
1508
|
+
justifyContent: "center"
|
|
1509
|
+
},
|
|
1159
1510
|
threadBubble: {
|
|
1160
1511
|
borderRadius: 10,
|
|
1161
1512
|
padding: 10,
|
|
1162
|
-
marginBottom: 8
|
|
1513
|
+
marginBottom: 8,
|
|
1514
|
+
maxWidth: "88%"
|
|
1163
1515
|
},
|
|
1164
1516
|
verifyRow: {
|
|
1165
1517
|
flexDirection: "row",
|
|
@@ -1168,9 +1520,26 @@ var s = StyleSheet.create({
|
|
|
1168
1520
|
},
|
|
1169
1521
|
verifyBtn: {
|
|
1170
1522
|
flex: 1,
|
|
1171
|
-
borderRadius: 10,
|
|
1172
1523
|
paddingVertical: 10,
|
|
1173
|
-
alignItems: "center"
|
|
1524
|
+
alignItems: "center",
|
|
1525
|
+
minHeight: 44
|
|
1526
|
+
},
|
|
1527
|
+
composer: {
|
|
1528
|
+
flexDirection: "row",
|
|
1529
|
+
alignItems: "flex-end",
|
|
1530
|
+
gap: 8
|
|
1531
|
+
},
|
|
1532
|
+
composerInput: {
|
|
1533
|
+
flex: 1,
|
|
1534
|
+
minHeight: 44,
|
|
1535
|
+
maxHeight: 120,
|
|
1536
|
+
marginBottom: 0
|
|
1537
|
+
},
|
|
1538
|
+
sendBtn: {
|
|
1539
|
+
paddingHorizontal: 16,
|
|
1540
|
+
paddingVertical: 12,
|
|
1541
|
+
minHeight: 44,
|
|
1542
|
+
justifyContent: "center"
|
|
1174
1543
|
},
|
|
1175
1544
|
assistantBubble: {
|
|
1176
1545
|
maxWidth: "88%",
|
|
@@ -1400,7 +1769,7 @@ import {
|
|
|
1400
1769
|
StyleSheet as StyleSheet3,
|
|
1401
1770
|
Platform as Platform4
|
|
1402
1771
|
} from "react-native";
|
|
1403
|
-
import { MUSHI_BANNER_NEON
|
|
1772
|
+
import { MUSHI_BANNER_NEON, MUSHI_COPY as MUSHI_COPY3, MUSHI_GEOMETRY as MUSHI_GEOMETRY2, MUSHI_SPACING as MUSHI_SPACING2, MUSHI_TYPE as MUSHI_TYPE2, MUSHI_Z } from "@mushi-mushi/core";
|
|
1404
1773
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1405
1774
|
var MUSHI_BANNER_DEFAULT_HEIGHT = MUSHI_GEOMETRY2.bannerHeight;
|
|
1406
1775
|
var MushiBanner = ({
|
|
@@ -1453,9 +1822,9 @@ var s3 = StyleSheet3.create({
|
|
|
1453
1822
|
minHeight: MUSHI_GEOMETRY2.bannerHeight,
|
|
1454
1823
|
flexDirection: "row",
|
|
1455
1824
|
alignItems: "stretch",
|
|
1456
|
-
backgroundColor:
|
|
1825
|
+
backgroundColor: MUSHI_BANNER_NEON.bg,
|
|
1457
1826
|
borderTopWidth: 1,
|
|
1458
|
-
borderTopColor:
|
|
1827
|
+
borderTopColor: MUSHI_BANNER_NEON.border,
|
|
1459
1828
|
zIndex: MUSHI_Z.banner,
|
|
1460
1829
|
elevation: 8,
|
|
1461
1830
|
paddingBottom: Platform4.OS === "ios" ? 0 : 0
|
|
@@ -1473,18 +1842,18 @@ var s3 = StyleSheet3.create({
|
|
|
1473
1842
|
fontSize: MUSHI_TYPE2.sizeLabel,
|
|
1474
1843
|
fontWeight: "700",
|
|
1475
1844
|
letterSpacing: 1.2,
|
|
1476
|
-
color:
|
|
1845
|
+
color: MUSHI_BANNER_NEON.fg
|
|
1477
1846
|
},
|
|
1478
1847
|
message: {
|
|
1479
1848
|
flex: 1,
|
|
1480
1849
|
fontSize: 12,
|
|
1481
1850
|
fontWeight: "500",
|
|
1482
|
-
color:
|
|
1851
|
+
color: MUSHI_BANNER_NEON.fg
|
|
1483
1852
|
},
|
|
1484
1853
|
cta: {
|
|
1485
1854
|
fontSize: 12,
|
|
1486
1855
|
fontWeight: "700",
|
|
1487
|
-
color:
|
|
1856
|
+
color: MUSHI_BANNER_NEON.fg,
|
|
1488
1857
|
textDecorationLine: "underline"
|
|
1489
1858
|
},
|
|
1490
1859
|
dismiss: {
|
|
@@ -1494,18 +1863,346 @@ var s3 = StyleSheet3.create({
|
|
|
1494
1863
|
dismissText: {
|
|
1495
1864
|
fontSize: 20,
|
|
1496
1865
|
lineHeight: 22,
|
|
1497
|
-
color:
|
|
1866
|
+
color: MUSHI_BANNER_NEON.fg,
|
|
1498
1867
|
opacity: 0.7
|
|
1499
1868
|
}
|
|
1500
1869
|
});
|
|
1501
1870
|
|
|
1871
|
+
// src/analytics/event-tracker.ts
|
|
1872
|
+
import {
|
|
1873
|
+
EVENT_PROPERTY_LIMITS,
|
|
1874
|
+
isValidEventName,
|
|
1875
|
+
propertiesWithinByteLimit,
|
|
1876
|
+
sanitizeEventProperties
|
|
1877
|
+
} from "@mushi-mushi/core";
|
|
1878
|
+
var CONSENT_BUFFER_MAX = 50;
|
|
1879
|
+
var SPILL_MAX = 200;
|
|
1880
|
+
var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1881
|
+
var SURFACE = "mobile";
|
|
1882
|
+
async function defaultStorage() {
|
|
1883
|
+
try {
|
|
1884
|
+
const mod = await import("@react-native-async-storage/async-storage");
|
|
1885
|
+
return mod.default;
|
|
1886
|
+
} catch {
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
function createRNEventTracker(opts) {
|
|
1891
|
+
const cfg = opts.config ?? {};
|
|
1892
|
+
const enabled = cfg.enabled !== false;
|
|
1893
|
+
const consentMode = cfg.consent ?? "implied";
|
|
1894
|
+
const allowlist = cfg.propertyAllowlist ?? [];
|
|
1895
|
+
const spillKey = `@mushi:events_spill_${opts.projectId}`;
|
|
1896
|
+
const consentKey = `@mushi:analytics_consent_${opts.projectId}`;
|
|
1897
|
+
let buffer = [];
|
|
1898
|
+
let consentBuffer = [];
|
|
1899
|
+
let consent = consentMode === "required" ? "pending" : "granted";
|
|
1900
|
+
let userId = null;
|
|
1901
|
+
let userTraits = null;
|
|
1902
|
+
let pendingIdentify = false;
|
|
1903
|
+
let flushing = false;
|
|
1904
|
+
let destroyed = false;
|
|
1905
|
+
let timer = null;
|
|
1906
|
+
let storage = null;
|
|
1907
|
+
const consentListeners = /* @__PURE__ */ new Set();
|
|
1908
|
+
const loadStorage = opts.storage ?? defaultStorage;
|
|
1909
|
+
const resolveClient = () => typeof opts.client === "function" ? opts.client() : opts.client;
|
|
1910
|
+
async function readSpill() {
|
|
1911
|
+
if (!storage) return [];
|
|
1912
|
+
try {
|
|
1913
|
+
const raw = await storage.getItem(spillKey);
|
|
1914
|
+
if (!raw) return [];
|
|
1915
|
+
const parsed = JSON.parse(raw);
|
|
1916
|
+
if (!parsed || Date.now() - parsed.at > SPILL_TTL_MS) {
|
|
1917
|
+
await storage.removeItem(spillKey);
|
|
1918
|
+
return [];
|
|
1919
|
+
}
|
|
1920
|
+
return Array.isArray(parsed.events) ? parsed.events.slice(0, SPILL_MAX) : [];
|
|
1921
|
+
} catch {
|
|
1922
|
+
return [];
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
async function writeSpill(events) {
|
|
1926
|
+
if (!storage) return;
|
|
1927
|
+
try {
|
|
1928
|
+
if (events.length === 0) await storage.removeItem(spillKey);
|
|
1929
|
+
else await storage.setItem(spillKey, JSON.stringify({ at: Date.now(), events: events.slice(-SPILL_MAX) }));
|
|
1930
|
+
} catch {
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
async function flushNow() {
|
|
1934
|
+
if (destroyed && buffer.length === 0) return;
|
|
1935
|
+
await ready;
|
|
1936
|
+
if (flushing || consent !== "granted") return;
|
|
1937
|
+
if (buffer.length === 0 && !pendingIdentify) return;
|
|
1938
|
+
const client = resolveClient();
|
|
1939
|
+
if (!client) return;
|
|
1940
|
+
flushing = true;
|
|
1941
|
+
const batch = buffer.splice(0, EVENT_PROPERTY_LIMITS.maxServerBatch);
|
|
1942
|
+
if (pendingIdentify) {
|
|
1943
|
+
batch.unshift({ name: "identify", ts: (/* @__PURE__ */ new Date()).toISOString(), properties: {} });
|
|
1944
|
+
pendingIdentify = false;
|
|
1945
|
+
}
|
|
1946
|
+
try {
|
|
1947
|
+
const anonId = await Promise.resolve(opts.getAnonId());
|
|
1948
|
+
const payload = {
|
|
1949
|
+
anon_id: anonId,
|
|
1950
|
+
user_id: userId ?? opts.getUserId?.() ?? null,
|
|
1951
|
+
user_traits: userTraits ?? opts.getUserTraits?.() ?? null,
|
|
1952
|
+
session_id: null,
|
|
1953
|
+
sdk_version: opts.sdkVersion ?? null,
|
|
1954
|
+
surface: SURFACE,
|
|
1955
|
+
events: batch
|
|
1956
|
+
};
|
|
1957
|
+
const res = await client.postProductEvents(payload);
|
|
1958
|
+
if (!res.ok) await writeSpill([...await readSpill(), ...batch.filter((e) => e.name !== "identify")]);
|
|
1959
|
+
else await writeSpill([]);
|
|
1960
|
+
} catch {
|
|
1961
|
+
await writeSpill([...await readSpill(), ...batch.filter((e) => e.name !== "identify")]);
|
|
1962
|
+
} finally {
|
|
1963
|
+
flushing = false;
|
|
1964
|
+
}
|
|
1965
|
+
if (buffer.length >= EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
|
|
1966
|
+
}
|
|
1967
|
+
function enqueue(ev) {
|
|
1968
|
+
if (consent === "denied") return;
|
|
1969
|
+
if (consent === "pending") {
|
|
1970
|
+
if (consentBuffer.length < CONSENT_BUFFER_MAX) consentBuffer.push(ev);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
buffer.push(ev);
|
|
1974
|
+
if (buffer.length >= EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
|
|
1975
|
+
}
|
|
1976
|
+
const ready = enabled ? (async () => {
|
|
1977
|
+
storage = await loadStorage();
|
|
1978
|
+
if (destroyed) return;
|
|
1979
|
+
let stored = null;
|
|
1980
|
+
try {
|
|
1981
|
+
stored = storage ? await storage.getItem(consentKey) : null;
|
|
1982
|
+
} catch {
|
|
1983
|
+
stored = null;
|
|
1984
|
+
}
|
|
1985
|
+
if (stored === "denied") {
|
|
1986
|
+
consent = "denied";
|
|
1987
|
+
buffer = [];
|
|
1988
|
+
consentBuffer = [];
|
|
1989
|
+
pendingIdentify = false;
|
|
1990
|
+
await writeSpill([]);
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
if (stored === "granted" && consent === "pending") {
|
|
1994
|
+
consent = "granted";
|
|
1995
|
+
buffer.push(...consentBuffer);
|
|
1996
|
+
consentBuffer = [];
|
|
1997
|
+
}
|
|
1998
|
+
const spill = await readSpill();
|
|
1999
|
+
if (spill.length > 0) {
|
|
2000
|
+
buffer.unshift(...spill);
|
|
2001
|
+
await writeSpill([]);
|
|
2002
|
+
}
|
|
2003
|
+
})().catch(() => void 0) : Promise.resolve();
|
|
2004
|
+
if (enabled) {
|
|
2005
|
+
const interval = Math.max(1e3, cfg.flushIntervalMs ?? 5e3);
|
|
2006
|
+
timer = setInterval(() => {
|
|
2007
|
+
void flushNow();
|
|
2008
|
+
}, interval);
|
|
2009
|
+
}
|
|
2010
|
+
return {
|
|
2011
|
+
ready,
|
|
2012
|
+
track(name, properties, trackOpts) {
|
|
2013
|
+
if (!enabled || destroyed) return false;
|
|
2014
|
+
if (!isValidEventName(name)) return false;
|
|
2015
|
+
const { properties: props } = sanitizeEventProperties(properties, { allowlist, scrub: opts.scrub });
|
|
2016
|
+
let merged = props;
|
|
2017
|
+
if (trackOpts?.reserved) {
|
|
2018
|
+
const { properties: res } = sanitizeEventProperties(trackOpts.reserved, { allowReserved: true, scrub: opts.scrub });
|
|
2019
|
+
merged = { ...props, ...res };
|
|
2020
|
+
}
|
|
2021
|
+
merged.$surface = SURFACE;
|
|
2022
|
+
const route = opts.getRoute?.();
|
|
2023
|
+
if (route && merged.$route === void 0) merged.$route = route;
|
|
2024
|
+
if (!propertiesWithinByteLimit(merged)) return false;
|
|
2025
|
+
enqueue({
|
|
2026
|
+
name,
|
|
2027
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2028
|
+
properties: merged,
|
|
2029
|
+
...trackOpts?.dedupKey ? { dedup_key: trackOpts.dedupKey } : {}
|
|
2030
|
+
});
|
|
2031
|
+
return true;
|
|
2032
|
+
},
|
|
2033
|
+
setConsent(state) {
|
|
2034
|
+
void ready.then(() => storage?.setItem(consentKey, state)).catch(() => void 0);
|
|
2035
|
+
if (state === "denied") {
|
|
2036
|
+
consent = "denied";
|
|
2037
|
+
buffer = [];
|
|
2038
|
+
consentBuffer = [];
|
|
2039
|
+
pendingIdentify = false;
|
|
2040
|
+
void writeSpill([]);
|
|
2041
|
+
} else {
|
|
2042
|
+
const wasPending = consent === "pending";
|
|
2043
|
+
consent = "granted";
|
|
2044
|
+
if (wasPending && consentBuffer.length > 0) {
|
|
2045
|
+
buffer.push(...consentBuffer);
|
|
2046
|
+
consentBuffer = [];
|
|
2047
|
+
}
|
|
2048
|
+
if (buffer.length > 0 || pendingIdentify) void flushNow();
|
|
2049
|
+
}
|
|
2050
|
+
for (const listener of [...consentListeners]) {
|
|
2051
|
+
try {
|
|
2052
|
+
listener(state);
|
|
2053
|
+
} catch {
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
},
|
|
2057
|
+
setIdentity(id, traits) {
|
|
2058
|
+
userId = id;
|
|
2059
|
+
userTraits = traits ?? null;
|
|
2060
|
+
if (id && enabled && consent !== "denied") {
|
|
2061
|
+
pendingIdentify = true;
|
|
2062
|
+
void flushNow();
|
|
2063
|
+
}
|
|
2064
|
+
},
|
|
2065
|
+
consentState() {
|
|
2066
|
+
return enabled ? consent : "denied";
|
|
2067
|
+
},
|
|
2068
|
+
onConsentChange(listener) {
|
|
2069
|
+
consentListeners.add(listener);
|
|
2070
|
+
return () => {
|
|
2071
|
+
consentListeners.delete(listener);
|
|
2072
|
+
};
|
|
2073
|
+
},
|
|
2074
|
+
async flush() {
|
|
2075
|
+
await ready;
|
|
2076
|
+
await flushNow();
|
|
2077
|
+
},
|
|
2078
|
+
async destroy() {
|
|
2079
|
+
if (timer != null) {
|
|
2080
|
+
clearInterval(timer);
|
|
2081
|
+
timer = null;
|
|
2082
|
+
}
|
|
2083
|
+
await flushNow().catch(() => void 0);
|
|
2084
|
+
destroyed = true;
|
|
2085
|
+
buffer = [];
|
|
2086
|
+
consentBuffer = [];
|
|
2087
|
+
pendingIdentify = false;
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// src/analytics/session-tracker.ts
|
|
2093
|
+
import { newUuid } from "@mushi-mushi/core";
|
|
2094
|
+
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
2095
|
+
var RESUME_WINDOW_MS = 30 * 6e4;
|
|
2096
|
+
function createRNSessionTracker(opts) {
|
|
2097
|
+
const clock = opts.now ?? Date.now;
|
|
2098
|
+
const resolveClient = () => typeof opts.client === "function" ? opts.client() : opts.client;
|
|
2099
|
+
let sessionId = opts.sessionId ?? `sess_${newUuid()}`;
|
|
2100
|
+
let pageViewCount = 0;
|
|
2101
|
+
let started = false;
|
|
2102
|
+
let allowed = false;
|
|
2103
|
+
let foreground = true;
|
|
2104
|
+
let destroyed = false;
|
|
2105
|
+
let backgroundedAt = null;
|
|
2106
|
+
let heartbeat = null;
|
|
2107
|
+
function route() {
|
|
2108
|
+
return opts.getRoute?.() ?? null;
|
|
2109
|
+
}
|
|
2110
|
+
function send(kind, extra) {
|
|
2111
|
+
const client = resolveClient();
|
|
2112
|
+
if (!client || !allowed || destroyed) return;
|
|
2113
|
+
const base = {
|
|
2114
|
+
kind,
|
|
2115
|
+
session_id: sessionId,
|
|
2116
|
+
ts: new Date(clock()).toISOString(),
|
|
2117
|
+
page_view_count: pageViewCount,
|
|
2118
|
+
user_agent: opts.userAgent ?? null,
|
|
2119
|
+
sdk_version: opts.sdkVersion
|
|
2120
|
+
};
|
|
2121
|
+
void Promise.resolve(opts.getReporterToken()).then((token) => client.postSessionEvent({ ...base, reporter_token_hash: token, ...extra })).catch(() => void 0);
|
|
2122
|
+
}
|
|
2123
|
+
function stopHeartbeat() {
|
|
2124
|
+
if (heartbeat != null) {
|
|
2125
|
+
clearInterval(heartbeat);
|
|
2126
|
+
heartbeat = null;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
function startHeartbeat() {
|
|
2130
|
+
stopHeartbeat();
|
|
2131
|
+
heartbeat = setInterval(() => send("session_heartbeat", { route: route() }), HEARTBEAT_INTERVAL_MS);
|
|
2132
|
+
}
|
|
2133
|
+
function startSession() {
|
|
2134
|
+
started = true;
|
|
2135
|
+
pageViewCount = 1;
|
|
2136
|
+
send("session_start", { route: route() });
|
|
2137
|
+
startHeartbeat();
|
|
2138
|
+
}
|
|
2139
|
+
function activate() {
|
|
2140
|
+
if (allowed || destroyed) return;
|
|
2141
|
+
allowed = true;
|
|
2142
|
+
if (!foreground) return;
|
|
2143
|
+
if (!started) startSession();
|
|
2144
|
+
else startHeartbeat();
|
|
2145
|
+
}
|
|
2146
|
+
function deactivate() {
|
|
2147
|
+
allowed = false;
|
|
2148
|
+
stopHeartbeat();
|
|
2149
|
+
}
|
|
2150
|
+
const unsubscribe = opts.consent.onConsentChange(() => {
|
|
2151
|
+
if (opts.consent.consentState() === "granted") activate();
|
|
2152
|
+
else deactivate();
|
|
2153
|
+
});
|
|
2154
|
+
void opts.consent.ready.then(() => {
|
|
2155
|
+
if (opts.consent.consentState() === "granted") activate();
|
|
2156
|
+
}).catch(() => void 0);
|
|
2157
|
+
return {
|
|
2158
|
+
onAppStateChange(state) {
|
|
2159
|
+
if (destroyed) return;
|
|
2160
|
+
if (state === "active") {
|
|
2161
|
+
if (foreground) return;
|
|
2162
|
+
foreground = true;
|
|
2163
|
+
const away = backgroundedAt == null ? 0 : clock() - backgroundedAt;
|
|
2164
|
+
backgroundedAt = null;
|
|
2165
|
+
if (!allowed) return;
|
|
2166
|
+
if (!started || away >= RESUME_WINDOW_MS) {
|
|
2167
|
+
sessionId = `sess_${newUuid()}`;
|
|
2168
|
+
startSession();
|
|
2169
|
+
} else {
|
|
2170
|
+
send("session_heartbeat", { route: route() });
|
|
2171
|
+
startHeartbeat();
|
|
2172
|
+
}
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
if (state !== "background" || !foreground) return;
|
|
2176
|
+
foreground = false;
|
|
2177
|
+
backgroundedAt = clock();
|
|
2178
|
+
stopHeartbeat();
|
|
2179
|
+
if (started) send("session_end", { route: route() });
|
|
2180
|
+
},
|
|
2181
|
+
pageView(next) {
|
|
2182
|
+
if (!allowed || !started || !foreground) return;
|
|
2183
|
+
pageViewCount += 1;
|
|
2184
|
+
send("page_view", { route: next });
|
|
2185
|
+
},
|
|
2186
|
+
destroy() {
|
|
2187
|
+
destroyed = true;
|
|
2188
|
+
deactivate();
|
|
2189
|
+
unsubscribe();
|
|
2190
|
+
}
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
|
|
1502
2194
|
// src/version.ts
|
|
1503
2195
|
var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
|
|
1504
|
-
var MUSHI_SDK_VERSION = "0.
|
|
2196
|
+
var MUSHI_SDK_VERSION = "0.23.0" ? "0.23.0" : "0.17.0";
|
|
1505
2197
|
|
|
1506
2198
|
// src/provider.tsx
|
|
1507
2199
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1508
2200
|
var MushiContext = createContext(null);
|
|
2201
|
+
function nativeUserAgent() {
|
|
2202
|
+
const os = Platform5?.OS ?? "native";
|
|
2203
|
+
const version = Platform5?.Version;
|
|
2204
|
+
return `${MUSHI_SDK_PACKAGE}/${MUSHI_SDK_VERSION} (${version != null ? `${os} ${String(version)}` : os})`;
|
|
2205
|
+
}
|
|
1509
2206
|
var DEFAULT_SCREENSHOT_HINT = "Check the preview \u2014 remove it if any private info (balances, personal details) is visible.";
|
|
1510
2207
|
function resolveScreenshotHint(v) {
|
|
1511
2208
|
if (v === false) return null;
|
|
@@ -1548,7 +2245,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1548
2245
|
const networkRef = useRef3(null);
|
|
1549
2246
|
const queueRef = useRef3(null);
|
|
1550
2247
|
const apiClientRef = useRef3(null);
|
|
1551
|
-
const sessionIdRef = useRef3(`sess_${
|
|
2248
|
+
const sessionIdRef = useRef3(`sess_${newUuid2()}`);
|
|
1552
2249
|
const breadcrumbsRef = useRef3(
|
|
1553
2250
|
createBreadcrumbBuffer({ max: config.capture?.maxBreadcrumbs ?? 50 })
|
|
1554
2251
|
);
|
|
@@ -1620,14 +2317,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1620
2317
|
if (existing && existing.startsWith("mushi_")) {
|
|
1621
2318
|
reporterTokenRef.current = existing;
|
|
1622
2319
|
} else {
|
|
1623
|
-
const fresh = `mushi_${
|
|
2320
|
+
const fresh = `mushi_${newUuid2()}`;
|
|
1624
2321
|
reporterTokenRef.current = fresh;
|
|
1625
2322
|
await saveReporterToken(fresh, secureStorage);
|
|
1626
2323
|
}
|
|
1627
2324
|
resolveReporterTokenReady();
|
|
1628
2325
|
} catch {
|
|
1629
2326
|
if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
|
|
1630
|
-
reporterTokenRef.current = `mushi_${
|
|
2327
|
+
reporterTokenRef.current = `mushi_${newUuid2()}`;
|
|
1631
2328
|
}
|
|
1632
2329
|
resolveReporterTokenReady();
|
|
1633
2330
|
}
|
|
@@ -1756,7 +2453,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1756
2453
|
}));
|
|
1757
2454
|
const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
|
|
1758
2455
|
const report = {
|
|
1759
|
-
id:
|
|
2456
|
+
id: newUuid2(),
|
|
1760
2457
|
projectId: config.projectId,
|
|
1761
2458
|
category: data.category,
|
|
1762
2459
|
description: scrubPii3(data.description),
|
|
@@ -1848,6 +2545,63 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1848
2545
|
if (rewardsFlushRef.current) clearInterval(rewardsFlushRef.current);
|
|
1849
2546
|
};
|
|
1850
2547
|
}, [config.rewards?.enabled, config.rewards?.trackActivity, config.rewards?.flushIntervalMs]);
|
|
2548
|
+
const eventTrackerRef = useRef3(null);
|
|
2549
|
+
const sessionTrackerRef = useRef3(null);
|
|
2550
|
+
useEffect3(() => {
|
|
2551
|
+
const getRoute = () => screenRef.current?.route ?? screenRef.current?.name ?? null;
|
|
2552
|
+
const tracker = createRNEventTracker({
|
|
2553
|
+
projectId: config.projectId,
|
|
2554
|
+
client: () => apiClientRef.current,
|
|
2555
|
+
getAnonId: async () => {
|
|
2556
|
+
await reporterTokenReadyRef.current;
|
|
2557
|
+
return reporterTokenRef.current;
|
|
2558
|
+
},
|
|
2559
|
+
getUserId: () => userRef.current?.id ?? null,
|
|
2560
|
+
getRoute,
|
|
2561
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2562
|
+
config: config.analytics,
|
|
2563
|
+
scrub: scrubPii3
|
|
2564
|
+
});
|
|
2565
|
+
eventTrackerRef.current = tracker;
|
|
2566
|
+
const sessions = config.trackSessions === false ? null : createRNSessionTracker({
|
|
2567
|
+
client: () => apiClientRef.current,
|
|
2568
|
+
getReporterToken: async () => {
|
|
2569
|
+
await reporterTokenReadyRef.current;
|
|
2570
|
+
return reporterTokenRef.current;
|
|
2571
|
+
},
|
|
2572
|
+
getRoute,
|
|
2573
|
+
consent: tracker,
|
|
2574
|
+
sessionId: sessionIdRef.current,
|
|
2575
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2576
|
+
userAgent: nativeUserAgent()
|
|
2577
|
+
});
|
|
2578
|
+
sessionTrackerRef.current = sessions;
|
|
2579
|
+
let sub;
|
|
2580
|
+
try {
|
|
2581
|
+
if (typeof AppState?.addEventListener === "function") {
|
|
2582
|
+
sub = AppState.addEventListener("change", (state) => {
|
|
2583
|
+
sessions?.onAppStateChange(state);
|
|
2584
|
+
if (state !== "active") tracker.flush().catch(() => {
|
|
2585
|
+
});
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
} catch {
|
|
2589
|
+
}
|
|
2590
|
+
return () => {
|
|
2591
|
+
sub?.remove();
|
|
2592
|
+
sessions?.destroy();
|
|
2593
|
+
sessionTrackerRef.current = null;
|
|
2594
|
+
eventTrackerRef.current = null;
|
|
2595
|
+
tracker.destroy().catch(() => {
|
|
2596
|
+
});
|
|
2597
|
+
};
|
|
2598
|
+
}, [
|
|
2599
|
+
config.projectId,
|
|
2600
|
+
config.analytics?.enabled,
|
|
2601
|
+
config.analytics?.consent,
|
|
2602
|
+
config.analytics?.flushIntervalMs,
|
|
2603
|
+
config.trackSessions
|
|
2604
|
+
]);
|
|
1851
2605
|
const instance = useMemo(
|
|
1852
2606
|
() => ({
|
|
1853
2607
|
open,
|
|
@@ -1865,6 +2619,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1865
2619
|
name: traits?.name,
|
|
1866
2620
|
provider: traits?.provider
|
|
1867
2621
|
};
|
|
2622
|
+
eventTrackerRef.current?.setIdentity(userId, traits ?? null);
|
|
1868
2623
|
},
|
|
1869
2624
|
setUser(user) {
|
|
1870
2625
|
userRef.current = {
|
|
@@ -1873,6 +2628,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1873
2628
|
name: user.name,
|
|
1874
2629
|
provider: user.provider
|
|
1875
2630
|
};
|
|
2631
|
+
eventTrackerRef.current?.setIdentity(user.id, { email: user.email, name: user.name, provider: user.provider });
|
|
1876
2632
|
},
|
|
1877
2633
|
identifyWithToken(token) {
|
|
1878
2634
|
userTokenRef.current = token && typeof token === "string" ? token : null;
|
|
@@ -1884,7 +2640,10 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1884
2640
|
...claims.email ? { email: claims.email } : {},
|
|
1885
2641
|
...claims.name ? { name: claims.name } : {}
|
|
1886
2642
|
};
|
|
2643
|
+
eventTrackerRef.current?.setIdentity(claims.sub, null);
|
|
1887
2644
|
}
|
|
2645
|
+
} else {
|
|
2646
|
+
eventTrackerRef.current?.setIdentity(null, null);
|
|
1888
2647
|
}
|
|
1889
2648
|
},
|
|
1890
2649
|
publishPageContext(context) {
|
|
@@ -1897,6 +2656,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1897
2656
|
const prev = screenRef.current?.name;
|
|
1898
2657
|
screenRef.current = screen;
|
|
1899
2658
|
if (screen.name && screen.name !== prev) {
|
|
2659
|
+
sessionTrackerRef.current?.pageView(screen.route ?? screen.name);
|
|
1900
2660
|
breadcrumbsRef.current.add({
|
|
1901
2661
|
category: "navigation",
|
|
1902
2662
|
level: "info",
|
|
@@ -1928,6 +2688,23 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1928
2688
|
const res = await client.listReporterComments(reportId, reporterTokenRef.current);
|
|
1929
2689
|
return res.ok ? res.data.comments ?? [] : [];
|
|
1930
2690
|
},
|
|
2691
|
+
async loadMyThread(reportId) {
|
|
2692
|
+
const client = apiClientRef.current;
|
|
2693
|
+
if (!client) return null;
|
|
2694
|
+
try {
|
|
2695
|
+
await reporterTokenReadyRef.current;
|
|
2696
|
+
const res = await client.listReporterComments(reportId, reporterTokenRef.current);
|
|
2697
|
+
return res.ok ? res.data?.comments ?? [] : null;
|
|
2698
|
+
} catch {
|
|
2699
|
+
return null;
|
|
2700
|
+
}
|
|
2701
|
+
},
|
|
2702
|
+
async markReportRead(reportId) {
|
|
2703
|
+
const client = apiClientRef.current;
|
|
2704
|
+
if (!client) return 0;
|
|
2705
|
+
await reporterTokenReadyRef.current;
|
|
2706
|
+
return markReporterReportRead(client, reporterTokenRef.current, reportId);
|
|
2707
|
+
},
|
|
1931
2708
|
async replyToReport(reportId, body) {
|
|
1932
2709
|
const client = apiClientRef.current;
|
|
1933
2710
|
if (!client) return null;
|
|
@@ -1958,6 +2735,13 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1958
2735
|
if (!config.rewards?.enabled) return;
|
|
1959
2736
|
activityQueueRef.current.push({ action, metadata });
|
|
1960
2737
|
},
|
|
2738
|
+
// Product analytics (Users & Funnels)
|
|
2739
|
+
track(event, properties) {
|
|
2740
|
+
return eventTrackerRef.current?.track(event, properties) ?? false;
|
|
2741
|
+
},
|
|
2742
|
+
setConsent(state) {
|
|
2743
|
+
eventTrackerRef.current?.setConsent(state);
|
|
2744
|
+
},
|
|
1961
2745
|
async getTier() {
|
|
1962
2746
|
const userId = userRef.current?.id;
|
|
1963
2747
|
const client = apiClientRef.current;
|
|
@@ -2028,7 +2812,8 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
2028
2812
|
assistantLabel: config.assistant?.label,
|
|
2029
2813
|
assistantGreeting: config.assistant?.greeting,
|
|
2030
2814
|
assistantSuggestions: config.assistant?.suggestions,
|
|
2031
|
-
inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0
|
|
2815
|
+
inboxPollIntervalMs: config.widget?.inboxPollIntervalMs ?? 0,
|
|
2816
|
+
theme: config.widget?.theme
|
|
2032
2817
|
}
|
|
2033
2818
|
)
|
|
2034
2819
|
] });
|