@mrrocket/sdk 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/README.md +43 -3
  3. package/dist/api-client.d.ts +62 -1
  4. package/dist/api-client.js +28 -1
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/browser/mrrocket-flows.js +11 -2
  7. package/dist/browser/mrrocket-messenger.js +304 -70
  8. package/dist/browser/mrrocket.js +1 -1
  9. package/dist/browser.d.ts +1 -1
  10. package/dist/browser.js +2 -1
  11. package/dist/browser.js.map +1 -1
  12. package/dist/client.d.ts +12 -0
  13. package/dist/client.js +22 -0
  14. package/dist/client.js.map +1 -1
  15. package/dist/context.d.ts +3 -0
  16. package/dist/context.js.map +1 -1
  17. package/dist/core.d.ts +4 -1
  18. package/dist/core.js +1 -1
  19. package/dist/core.js.map +1 -1
  20. package/dist/flows-ui.d.ts +6 -4
  21. package/dist/flows-ui.js +147 -5
  22. package/dist/flows-ui.js.map +1 -1
  23. package/dist/flows.d.ts +51 -1
  24. package/dist/flows.js +62 -21
  25. package/dist/flows.js.map +1 -1
  26. package/dist/index.d.ts +2 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/messenger-ui.d.ts +37 -2
  30. package/dist/messenger-ui.js +597 -109
  31. package/dist/messenger-ui.js.map +1 -1
  32. package/dist/messenger.d.ts +11 -3
  33. package/dist/messenger.js +6 -3
  34. package/dist/messenger.js.map +1 -1
  35. package/dist/paywall.d.ts +123 -0
  36. package/dist/paywall.js +131 -0
  37. package/dist/paywall.js.map +1 -0
  38. package/dist/react-native/index.d.cts +235 -8
  39. package/dist/react-native/index.d.ts +235 -8
  40. package/dist/react-native/index.js +650 -184
  41. package/dist/react-native/index.js.map +1 -1
  42. package/dist/styles-css.d.ts +1 -1
  43. package/dist/styles-css.js +302 -68
  44. package/dist/styles-css.js.map +1 -1
  45. package/package.json +5 -1
@@ -41,6 +41,7 @@ __export(native_exports, {
41
41
  useMessenger: () => useMessenger,
42
42
  useNavigationTracking: () => useNavigationTracking,
43
43
  useOnboarding: () => useOnboarding,
44
+ usePaywall: () => usePaywall,
44
45
  usePush: () => usePush,
45
46
  useText: () => useText,
46
47
  useVariant: () => useVariant
@@ -49,7 +50,7 @@ module.exports = __toCommonJS(native_exports);
49
50
  var import_react3 = require("react");
50
51
 
51
52
  // src/core.ts
52
- var SDK_VERSION = "0.13.0";
53
+ var SDK_VERSION = "0.15.0";
53
54
  var propOf = (person, name) => {
54
55
  const want = name.trim().toLowerCase();
55
56
  for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;
@@ -172,7 +173,14 @@ var textChanges = (config, distinctId, preview) => {
172
173
  var newDistinctId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `ck_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
173
174
 
174
175
  // src/api-client.ts
175
- var replyLine = (t) => t === "minutes" ? "We usually reply in a few minutes" : t === "day" ? "We usually reply within a day" : "We usually reply in a few hours";
176
+ var CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;
177
+ var CHAT_FILES_PER_MESSAGE = 4;
178
+ var CORNERS = {
179
+ round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },
180
+ soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },
181
+ square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 }
182
+ };
183
+ var BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
176
184
  var inkOn = (hex) => {
177
185
  const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
178
186
  if (!m) return "#ffffff";
@@ -196,7 +204,8 @@ var call = async (url, init, signal) => {
196
204
  res = await fetch(url, {
197
205
  ...init,
198
206
  signal,
199
- headers: init?.body ? { "content-type": "application/json" } : void 0
207
+ // A form (a file upload) sets its own multipart type with the boundary.
208
+ headers: typeof init?.body === "string" ? { "content-type": "application/json" } : void 0
200
209
  });
201
210
  } catch (e) {
202
211
  if (e.name === "AbortError") throw e;
@@ -229,7 +238,19 @@ var createApi = (c) => {
229
238
  seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
230
239
  respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
231
240
  chat: (signal) => call(`${base}/messages?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
241
+ /** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */
232
242
  sendChat: (m) => post("/messages", m),
243
+ /** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
244
+ uploadChatFile: (file, signal) => {
245
+ const form = new FormData();
246
+ form.append("key", c.apiKey);
247
+ form.append("distinctId", c.distinctId);
248
+ if ("uri" in file) form.append("file", file);
249
+ else form.append("file", file, file.name || "file");
250
+ return call(`${base}/messages/attachments`, { method: "POST", body: form }, signal);
251
+ },
252
+ /** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
253
+ attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`,
233
254
  articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
234
255
  article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
235
256
  messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
@@ -342,6 +363,9 @@ var pageMatches = (pattern, path) => {
342
363
  return at === (p.replace(/\/+$/, "") || "/");
343
364
  };
344
365
  var stepsIn = (nodes) => nodes.flatMap((n) => n.kind === "condition" ? [...stepsIn(n.yes), ...stepsIn(n.no)] : n.kind === "wait" ? [] : [n]);
366
+ var monthsSince = (since, now = Date.now()) => since == null || since === "" ? NaN : Math.floor((now - new Date(since).getTime()) / 2630016e3);
367
+ var low = (x) => x?.trim().toLowerCase();
368
+ var ruleMatches = ({ when: w = {} }, c) => (w.answer == null || low(w.answer) === low(c.answer)) && (w.plan == null || [w.plan].flat().some((x) => low(x) === low(c.plan))) && !(c.months >= w.tenureUnder) && !(c.months < w.tenureOver) && (w.tenureUnder == null || c.months >= 0) && (w.tenureOver == null || c.months >= 0);
345
369
  var createFlows = (env) => {
346
370
  let config = null;
347
371
  let cancel = null;
@@ -509,18 +533,23 @@ var createFlows = (env) => {
509
533
  };
510
534
  const view = () => {
511
535
  if (cancel) {
512
- const offer = cancel.reason ? cancel.flow.offers[cancel.reason] : void 0;
536
+ const c = cancel;
537
+ const followUp = c.flow.followUps?.[c.reason];
538
+ const f = followUp ? 1 : 0;
539
+ const steps = c.plain ? 1 : 3 + f + (c.flow.second?.[c.reason] ? 1 : 0);
513
540
  return {
514
541
  type: "cancel",
515
- screen: cancel.screen,
516
- flow: cancel.flow,
517
- reason: cancel.reason,
518
- offer: cancel.screen === "offer" ? offer : void 0,
519
- price: cancel.options.price,
520
- busy: cancel.busy,
521
- error: cancel.error,
522
- step: cancel.plain ? 1 : cancel.screen === "survey" ? 1 : cancel.screen === "offer" ? 2 : 3,
523
- steps: cancel.plain ? 1 : 3
542
+ screen: c.screen,
543
+ flow: c.flow,
544
+ reason: c.reason,
545
+ ...c.screen === "followup" && followUp ? { followUp, answer: c.answer } : {},
546
+ offer: c.screen === "offer" ? c.offer : void 0,
547
+ ...c.screen === "offer" ? { round: c.round } : {},
548
+ price: c.options.price,
549
+ busy: c.busy,
550
+ error: c.error,
551
+ step: c.screen === "survey" ? 1 : c.screen === "followup" ? 2 : c.screen === "offer" ? 1 + f + c.round : steps,
552
+ steps
524
553
  };
525
554
  }
526
555
  const ob = onboardingView();
@@ -599,12 +628,22 @@ var createFlows = (env) => {
599
628
  });
600
629
  return loading;
601
630
  };
631
+ const offerAt = (c) => c.round > 1 ? { round: 2 } : c.rule >= 0 ? { rule: c.rule } : {};
632
+ const toOffer = (c) => {
633
+ const who = { answer: c.answer, plan: c.options.plan, months: monthsSince(c.options.customerSince, env.now?.()) };
634
+ const rules = c.flow.rules?.[c.reason] ?? [];
635
+ c.rule = rules.findIndex((r) => ruleMatches(r, who));
636
+ c.offer = c.rule < 0 ? c.flow.offers[c.reason] : rules[c.rule].offer ?? void 0;
637
+ c.round = 1;
638
+ c.screen = c.offer ? "offer" : "confirm";
639
+ };
602
640
  const result = (c, outcome, comment) => {
603
- const offer = c.reason ? c.flow.offers[c.reason] : void 0;
641
+ const offer = c.screen === "offer" ? c.offer : void 0;
604
642
  return {
605
643
  outcome,
606
644
  run: c.run,
607
645
  ...c.reason ? { reason: c.reason, reasonLabel: c.flow.reasons.find((r) => r.id === c.reason)?.label } : {},
646
+ ...c.answer ? { answer: c.answer } : {},
608
647
  ...outcome === "saved" && offer ? { offer } : {},
609
648
  ...comment?.trim() ? { comment: comment.trim().slice(0, 500) } : {}
610
649
  };
@@ -617,7 +656,8 @@ var createFlows = (env) => {
617
656
  outcome: r.outcome,
618
657
  customer: c.options.customer ?? "consumer",
619
658
  ...r.reason ? { reason: r.reason } : {},
620
- ...r.offer ? { offer: r.offer.kind } : {},
659
+ ...r.answer ? { answer: r.answer } : {},
660
+ ...r.offer ? { offer: r.offer.kind, ...offerAt(c) } : {},
621
661
  ...r.comment ? { comment: r.comment } : {}
622
662
  });
623
663
  if (cancel === c) cancel = null;
@@ -647,7 +687,7 @@ var createFlows = (env) => {
647
687
  let resolve = () => {
648
688
  };
649
689
  const promise = new Promise((r) => resolve = r);
650
- const c = { flow, options, plain, screen: plain ? "confirm" : "survey", run: runId(), busy: false, resolve, promise };
690
+ const c = { flow, options, plain, screen: plain ? "confirm" : "survey", round: 1, run: runId(), busy: false, resolve, promise };
651
691
  cancel = c;
652
692
  env.track(CANCEL_EVENT, { action: "shown", run: c.run, version: flow.version, customer: options.customer ?? "consumer", plain });
653
693
  emit();
@@ -699,29 +739,43 @@ var createFlows = (env) => {
699
739
  },
700
740
  cancel: {
701
741
  pick: (reason) => {
702
- if (!cancel || cancel.busy || !cancel.flow.reasons.some((r) => r.id === reason)) return;
742
+ if (!cancel || cancel.busy || cancel.screen !== "survey" || !cancel.flow.reasons.some((r) => r.id === reason)) return;
743
+ if (cancel.reason !== reason) cancel.answer = void 0;
703
744
  cancel.reason = reason;
704
745
  cancel.error = void 0;
705
746
  emit();
706
747
  },
748
+ answer: (text2) => {
749
+ const c = cancel;
750
+ if (!c || c.busy || c.screen !== "followup") return;
751
+ const t = text2.slice(0, 500);
752
+ c.answer = t.trim() ? t : void 0;
753
+ emit();
754
+ },
707
755
  next: () => {
708
756
  const c = cancel;
709
- if (!c || c.screen !== "survey" || !c.reason) return;
710
- env.track(CANCEL_EVENT, { action: "answered", run: c.run, version: c.flow.version, reason: c.reason });
711
- c.screen = c.flow.offers[c.reason] ? "offer" : "confirm";
757
+ if (!c || !c.reason) return;
758
+ if (c.screen === "survey") {
759
+ env.track(CANCEL_EVENT, { action: "answered", run: c.run, version: c.flow.version, reason: c.reason });
760
+ if (c.flow.followUps?.[c.reason]) c.screen = "followup";
761
+ else toOffer(c);
762
+ } else if (c.screen === "followup") {
763
+ c.answer = c.answer?.trim();
764
+ toOffer(c);
765
+ } else return;
712
766
  emit();
713
767
  },
714
768
  accept: (comment) => {
715
769
  const c = cancel;
716
770
  if (!c || c.screen !== "offer" || !c.reason) return;
717
- const offer = c.flow.offers[c.reason];
718
771
  if (!c.busy)
719
772
  env.track(CANCEL_EVENT, {
720
773
  action: "offer",
721
774
  run: c.run,
722
775
  version: c.flow.version,
723
776
  reason: c.reason,
724
- offer: offer?.kind,
777
+ offer: c.offer?.kind,
778
+ ...offerAt(c),
725
779
  outcome: "accepted"
726
780
  });
727
781
  settle(c, "saved", comment);
@@ -734,10 +788,16 @@ var createFlows = (env) => {
734
788
  run: c.run,
735
789
  version: c.flow.version,
736
790
  reason: c.reason,
737
- offer: c.reason ? c.flow.offers[c.reason]?.kind : void 0,
791
+ offer: c.offer?.kind,
792
+ ...offerAt(c),
738
793
  outcome: "declined"
739
794
  });
740
- c.screen = "confirm";
795
+ const second = c.round === 1 && c.reason ? c.flow.second?.[c.reason] : void 0;
796
+ if (second) {
797
+ c.offer = second;
798
+ c.rule = void 0;
799
+ c.round = 2;
800
+ } else c.screen = "confirm";
741
801
  c.error = void 0;
742
802
  emit();
743
803
  },
@@ -805,6 +865,131 @@ var createFlows = (env) => {
805
865
  return controller;
806
866
  };
807
867
 
868
+ // src/paywall.ts
869
+ var PAYWALL_EVENT = "$paywall";
870
+ var paywallPages = (config, w, distinctId) => {
871
+ const e = w.experiment ? config?.experiments.find((x) => x.key === w.experiment) : void 0;
872
+ const id = e ? assign(e, distinctId) : w.variants[0]?.id;
873
+ const v = w.variants.find((x) => x.id === id) ?? w.variants[0];
874
+ return v ? { variant: v.id, pages: v.pages, test: e?.status === "running" && !e.winner ? e.key : void 0 } : null;
875
+ };
876
+ var createPaywallHost = (env) => {
877
+ let run = null;
878
+ const listeners = /* @__PURE__ */ new Set();
879
+ const emit = () => {
880
+ for (const l of [...listeners]) {
881
+ try {
882
+ l();
883
+ } catch {
884
+ }
885
+ }
886
+ };
887
+ const base = () => run ? { paywall: run.w.id, placement: run.w.placement, variant: run.variant } : {};
888
+ const show = () => {
889
+ if (run && !run.seen.has(run.index)) {
890
+ run.seen.add(run.index);
891
+ env.track(PAYWALL_EVENT, { ...base(), page: run.pages[run.index].id, index: run.index, action: "page" });
892
+ }
893
+ emit();
894
+ };
895
+ const end = (r) => {
896
+ const c = run;
897
+ if (!c) return;
898
+ run = null;
899
+ if (r.outcome === "closed") c.options.onClose?.(r);
900
+ c.resolve(r);
901
+ emit();
902
+ };
903
+ const result = (outcome) => ({
904
+ outcome,
905
+ index: run?.index ?? 0,
906
+ ...run ? { paywall: run.w.id, variant: run.variant } : {},
907
+ ...outcome !== "closed" && run ? { plan: run.plans.find((p) => p.id === run.plan) } : {}
908
+ });
909
+ const buy = () => {
910
+ const c = run;
911
+ const plan = c?.plans.find((p) => p.id === c.plan);
912
+ if (!c || !plan || c.busy) return;
913
+ if (!c.options.onPurchase) return end(result("chosen"));
914
+ c.busy = true;
915
+ c.error = void 0;
916
+ emit();
917
+ Promise.resolve().then(() => c.options.onPurchase?.(plan)).then(
918
+ (ok) => {
919
+ c.busy = false;
920
+ if (run !== c) return;
921
+ if (ok === false) return emit();
922
+ env.track(PAYWALL_EVENT, { ...base(), page: c.pages[c.index].id, index: c.index, plan: plan.id, action: "purchased" });
923
+ end(result("purchased"));
924
+ },
925
+ () => {
926
+ c.busy = false;
927
+ c.error = "The purchase did not go through. Please try again.";
928
+ emit();
929
+ }
930
+ );
931
+ };
932
+ return {
933
+ open: (placement, options = {}) => {
934
+ if (run) return run.promise;
935
+ const config = env.config();
936
+ const w = config?.paywalls?.find((x) => x.placement === placement);
937
+ const picked = w ? paywallPages(config, w, env.distinctId()) : null;
938
+ if (!w || !picked?.pages.length) return Promise.resolve({ outcome: "closed", index: 0 });
939
+ const own = new Map((options.plans ?? []).map((p) => [p.id, p]));
940
+ const plans = w.plans.map((p) => own.get(p.id) ?? p);
941
+ let resolve = () => {
942
+ };
943
+ const promise = new Promise((r) => resolve = r);
944
+ const plan = plans.some((p) => p.id === w.highlight) ? w.highlight : plans[0]?.id;
945
+ run = { w, variant: picked.variant, pages: picked.pages, plans, index: 0, plan, busy: false, seen: /* @__PURE__ */ new Set(), options, resolve, promise };
946
+ if (picked.test) env.track("$exposure", { experiment: picked.test, variant: picked.variant });
947
+ show();
948
+ return promise;
949
+ },
950
+ view: () => run ? {
951
+ paywall: run.w,
952
+ variant: run.variant,
953
+ page: run.pages[run.index],
954
+ index: run.index,
955
+ total: run.pages.length,
956
+ last: run.index === run.pages.length - 1,
957
+ plans: run.plans,
958
+ plan: run.plan,
959
+ busy: run.busy,
960
+ error: run.error
961
+ } : null,
962
+ subscribe: (l) => {
963
+ listeners.add(l);
964
+ return () => listeners.delete(l);
965
+ },
966
+ next: () => {
967
+ if (!run || run.busy) return;
968
+ if (run.pages[run.index].kind === "plans" || run.index === run.pages.length - 1) return buy();
969
+ run.index++;
970
+ run.error = void 0;
971
+ show();
972
+ },
973
+ back: () => {
974
+ if (!run || run.busy || !run.index) return;
975
+ run.index--;
976
+ run.error = void 0;
977
+ emit();
978
+ },
979
+ pick: (id) => {
980
+ if (!run || run.busy || !run.plans.some((p) => p.id === id)) return;
981
+ run.plan = id;
982
+ run.error = void 0;
983
+ emit();
984
+ },
985
+ close: () => {
986
+ if (!run || run.busy) return;
987
+ env.track(PAYWALL_EVENT, { ...base(), index: run.index, action: "closed" });
988
+ end(result("closed"));
989
+ }
990
+ };
991
+ };
992
+
808
993
  // src/rage.ts
809
994
  var RageDetector = class {
810
995
  constructor(windowMs = 1e3, radius = 30, count = 3) {
@@ -1072,6 +1257,7 @@ var createClient = (options, deps) => {
1072
1257
  const errorQueue = [];
1073
1258
  const errorsSeen = /* @__PURE__ */ new Set();
1074
1259
  let errorsSent = 0;
1260
+ const paywall = createPaywallHost({ config: () => config, distinctId: () => distinctId(), track: (event, props) => record(event, props) });
1075
1261
  const flows = createFlows({
1076
1262
  track: (event, props) => record(event, props),
1077
1263
  personId: () => consent !== "off" ? identified : void 0,
@@ -1632,6 +1818,8 @@ var createClient = (options, deps) => {
1632
1818
  },
1633
1819
  openCancelFlow: (o) => flows.openCancelFlow(o),
1634
1820
  startOnboarding: (id) => flows.startOnboarding(id),
1821
+ openPaywall: (placement, o) => paywall.open(placement, o),
1822
+ paywall,
1635
1823
  registerForPush,
1636
1824
  unregisterForPush,
1637
1825
  flows,
@@ -1697,19 +1885,21 @@ var RN = require("react-native");
1697
1885
  var LIGHT = { bg: "#ffffff", fg: "#1c1d20", muted: "#5d6068", soft: "#f4f5f7", line: "#e4e5e8" };
1698
1886
  var DARK = { bg: "#17181b", fg: "#f1f2f4", muted: "#a9acb3", soft: "#232428", line: "#2e2f35" };
1699
1887
  var months = (n) => `${n} ${n === 1 ? "month" : "months"}`;
1700
- var MRRocketFlows = ({ flows, appearance = {} }) => {
1888
+ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
1701
1889
  const scheme = RN.useColorScheme?.();
1702
1890
  const dark = appearance.theme === "dark" || (appearance.theme ?? "auto") === "auto" && scheme === "dark";
1703
1891
  const p = { ...dark ? DARK : LIGHT, accent: appearance.accent ?? "#3f76ff", danger: "#d92d20" };
1704
1892
  const [, setTick] = (0, import_react.useState)(0);
1705
1893
  (0, import_react.useEffect)(() => flows.subscribe(() => setTick((t) => t + 1)), [flows]);
1894
+ (0, import_react.useEffect)(() => paywall?.subscribe(() => setTick((t) => t + 1)), [paywall]);
1895
+ const pw = paywall?.view() ?? null;
1706
1896
  const v = flows.view();
1707
1897
  const [comment, setComment] = (0, import_react.useState)("");
1708
1898
  const screen = v?.type === "cancel" ? v.screen : null;
1709
1899
  (0, import_react.useEffect)(() => {
1710
1900
  if (!screen) setComment("");
1711
1901
  }, [screen]);
1712
- if (!v) return null;
1902
+ if (!v && !pw) return null;
1713
1903
  const text2 = (children, style = {}) => (0, import_react.createElement)(RN.Text, { style: { color: p.fg, fontSize: 15, ...style } }, children);
1714
1904
  const muted = (children, style = {}) => text2(children, { color: p.muted, fontSize: 13, ...style });
1715
1905
  const title = (children) => (0, import_react.createElement)(RN.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 18, fontWeight: "700" } }, children);
@@ -1768,9 +1958,9 @@ var MRRocketFlows = ({ flows, appearance = {} }) => {
1768
1958
  card(children, { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, paddingBottom: 34 })
1769
1959
  )
1770
1960
  );
1771
- const input = (placeholder) => (0, import_react.createElement)(RN.TextInput, {
1772
- value: comment,
1773
- onChangeText: setComment,
1961
+ const input = (placeholder, value = comment, onChange = setComment) => (0, import_react.createElement)(RN.TextInput, {
1962
+ value,
1963
+ onChangeText: onChange,
1774
1964
  placeholder,
1775
1965
  placeholderTextColor: p.muted,
1776
1966
  multiline: true,
@@ -1778,6 +1968,71 @@ var MRRocketFlows = ({ flows, appearance = {} }) => {
1778
1968
  accessibilityLabel: placeholder,
1779
1969
  style: { minHeight: 72, borderWidth: 1, borderColor: p.line, borderRadius: 10, padding: 10, color: p.fg, textAlignVertical: "top" }
1780
1970
  });
1971
+ const paywallSheet = (w) => {
1972
+ const pg = w.page;
1973
+ const host = paywall;
1974
+ return sheet(
1975
+ [
1976
+ (0, import_react.createElement)(
1977
+ RN.View,
1978
+ { key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
1979
+ ...w.total > 1 ? Array.from(
1980
+ { length: w.total },
1981
+ (_, i) => (0, import_react.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= w.index ? p.accent : p.line } })
1982
+ ) : [],
1983
+ close("Close", host.close)
1984
+ ),
1985
+ title(pg.title),
1986
+ pg.body ? muted(pg.body, { fontSize: 15 }) : null,
1987
+ ...pg.kind === "benefits" ? (pg.items ?? []).map(
1988
+ (item, i) => (0, import_react.createElement)(
1989
+ RN.View,
1990
+ { key: `b${i}`, style: { flexDirection: "row", gap: 10, alignItems: "flex-start" } },
1991
+ text2("\u2713", { color: p.accent, fontWeight: "700" }),
1992
+ text2(item, { flex: 1 })
1993
+ )
1994
+ ) : [],
1995
+ ...pg.kind === "proof" ? (pg.quotes ?? []).map(
1996
+ (q, i) => (0, import_react.createElement)(RN.View, { key: `q${i}`, style: { backgroundColor: p.soft, borderRadius: 12, padding: 12, gap: 6 } }, text2(q.text), q.by ? muted(q.by) : null)
1997
+ ) : [],
1998
+ pg.kind === "plans" ? (0, import_react.createElement)(
1999
+ RN.View,
2000
+ { key: "plans", accessibilityRole: "radiogroup", style: { gap: 6 } },
2001
+ ...w.plans.map(
2002
+ (plan) => (0, import_react.createElement)(
2003
+ RN.Pressable,
2004
+ {
2005
+ key: plan.id,
2006
+ onPress: () => host.pick(plan.id),
2007
+ accessibilityRole: "radio",
2008
+ accessibilityLabel: `${plan.name}, ${plan.price}`,
2009
+ accessibilityState: { checked: w.plan === plan.id },
2010
+ style: {
2011
+ minHeight: 52,
2012
+ paddingHorizontal: 12,
2013
+ borderRadius: 10,
2014
+ borderWidth: 1,
2015
+ borderColor: w.plan === plan.id ? p.accent : p.line,
2016
+ backgroundColor: w.plan === plan.id ? p.soft : p.bg,
2017
+ flexDirection: "row",
2018
+ alignItems: "center",
2019
+ gap: 10
2020
+ }
2021
+ },
2022
+ (0, import_react.createElement)(RN.View, { style: { flex: 1 } }, text2(plan.name, { fontWeight: "600" }), plan.note ? muted(plan.note) : null),
2023
+ text2(plan.price, { fontWeight: "700" })
2024
+ )
2025
+ )
2026
+ ) : null,
2027
+ w.error ? text2(w.error, { color: p.danger, fontSize: 13 }) : null,
2028
+ btn(w.busy ? "One moment\u2026" : pg.button || "Continue", "primary", host.next, w.busy || pg.kind === "plans" && !w.plan),
2029
+ w.index ? btn("Back", "quiet", host.back, w.busy) : null
2030
+ ],
2031
+ host.close
2032
+ );
2033
+ };
2034
+ if (pw) return paywallSheet(pw);
2035
+ if (!v) return null;
1781
2036
  if (v.type === "cancel") {
1782
2037
  const top = (0, import_react.createElement)(
1783
2038
  RN.View,
@@ -1790,38 +2045,49 @@ var MRRocketFlows = ({ flows, appearance = {} }) => {
1790
2045
  );
1791
2046
  const error = v.error ? text2(v.error, { color: p.danger, fontSize: 13 }) : null;
1792
2047
  let body = [];
2048
+ const choices = (items) => (0, import_react.createElement)(
2049
+ RN.ScrollView,
2050
+ { style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
2051
+ ...items.map(
2052
+ (c) => (0, import_react.createElement)(
2053
+ RN.Pressable,
2054
+ {
2055
+ key: c.key,
2056
+ onPress: c.press,
2057
+ accessibilityRole: "radio",
2058
+ accessibilityState: { checked: c.on },
2059
+ style: {
2060
+ minHeight: 44,
2061
+ paddingHorizontal: 12,
2062
+ borderRadius: 10,
2063
+ borderWidth: 1,
2064
+ borderColor: c.on ? p.accent : p.line,
2065
+ backgroundColor: c.on ? p.soft : p.bg,
2066
+ justifyContent: "center"
2067
+ }
2068
+ },
2069
+ text2(c.label, { fontWeight: "500" })
2070
+ )
2071
+ )
2072
+ );
1793
2073
  if (v.screen === "survey")
1794
2074
  body = [
1795
2075
  title(v.flow.question || "Why are you leaving?"),
1796
- (0, import_react.createElement)(
1797
- RN.ScrollView,
1798
- { style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
1799
- ...v.flow.reasons.map(
1800
- (r) => (0, import_react.createElement)(
1801
- RN.Pressable,
1802
- {
1803
- key: r.id,
1804
- onPress: () => flows.cancel.pick(r.id),
1805
- accessibilityRole: "radio",
1806
- accessibilityState: { checked: v.reason === r.id },
1807
- style: {
1808
- minHeight: 44,
1809
- paddingHorizontal: 12,
1810
- borderRadius: 10,
1811
- borderWidth: 1,
1812
- borderColor: v.reason === r.id ? p.accent : p.line,
1813
- backgroundColor: v.reason === r.id ? p.soft : p.bg,
1814
- justifyContent: "center"
1815
- }
1816
- },
1817
- text2(r.label, { fontWeight: "500" })
1818
- )
1819
- )
1820
- ),
2076
+ choices(v.flow.reasons.map((r) => ({ key: r.id, label: r.label, on: v.reason === r.id, press: () => flows.cancel.pick(r.id) }))),
1821
2077
  btn("Continue", "primary", flows.cancel.next, !v.reason),
1822
2078
  btn("Never mind, keep my plan", "quiet", flows.cancel.close)
1823
2079
  ];
1824
- else if (v.screen === "offer" && v.offer) {
2080
+ else if (v.screen === "followup" && v.followUp) {
2081
+ const f = v.followUp;
2082
+ const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
2083
+ body = [
2084
+ label ? muted(`You said: ${label}`) : null,
2085
+ title(f.question),
2086
+ f.answers.length ? choices(f.answers.map((a, i) => ({ key: String(i), label: a, on: v.answer === a, press: () => flows.cancel.answer(a) }))) : input("Your answer (optional)", v.answer ?? "", flows.cancel.answer),
2087
+ btn("Continue", "primary", flows.cancel.next),
2088
+ btn("Never mind, keep my plan", "quiet", flows.cancel.close)
2089
+ ];
2090
+ } else if (v.screen === "offer" && v.offer) {
1825
2091
  const o = v.offer;
1826
2092
  const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
1827
2093
  const detail = o.kind === "discount" && o.percent ? v.price ? `${money(discounted(v.price.amount, o.percent), v.price.currency)} instead of ${money(v.price.amount, v.price.currency)}${o.months ? ` for ${months(o.months)}` : ""}` : `${o.percent}% off${o.months ? ` for ${months(o.months)}` : ""}` : o.kind === "pause" && o.months ? `Pause for up to ${months(o.months)}. Your data stays.` : o.kind === "downgrade" && o.planName ? o.planName : null;
@@ -2497,6 +2763,13 @@ var buzz = () => {
2497
2763
  } catch {
2498
2764
  }
2499
2765
  };
2766
+ var imagePicker = null;
2767
+ try {
2768
+ imagePicker = require("expo-image-picker");
2769
+ } catch {
2770
+ imagePicker = null;
2771
+ }
2772
+ var PHOTO_TYPES = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif" };
2500
2773
  var teamOf = (t) => (t?.messages ?? []).filter((m) => m.from !== "visitor");
2501
2774
  var svg = null;
2502
2775
  try {
@@ -2512,9 +2785,10 @@ var GLYPH = {
2512
2785
  up: "M8 13V3M3.5 7.5 8 3l4.5 4.5",
2513
2786
  next: "M6 3.5 10.5 8 6 12.5",
2514
2787
  search: "M7 11.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9zM10.5 10.5l3 3",
2515
- cross: "m4 4 8 8M12 4l-8 8"
2788
+ cross: "m4 4 8 8M12 4l-8 8",
2789
+ clip: "m14.3 7.4-6.1 6.1a4 4 0 0 1-5.7-5.7l5.7-5.7a2.7 2.7 0 0 1 3.8 3.8L6.3 11.6a1.3 1.3 0 0 1-1.9-1.9l5.7-5.6"
2516
2790
  };
2517
- var FALLBACK = { chat: "\u{1F4AC}", home: "\u2302", help: "?", send: "\u2192", up: "\u2191", next: "\u203A", search: "\u2315", cross: "\xD7" };
2791
+ var FALLBACK = { chat: "\u{1F4AC}", home: "\u2302", help: "?", send: "\u2192", up: "\u2191", next: "\u203A", search: "\u2315", cross: "\xD7", clip: "\u{1F4CE}" };
2518
2792
  var glyph = (name, color, size = 20) => {
2519
2793
  const Svg = svg?.Svg ?? svg?.default;
2520
2794
  if (!svg || !Svg) return (0, import_react2.createElement)(RN3.Text, { style: { color, fontSize: Math.round(size * 0.85), lineHeight: size + 2, textAlign: "center", minWidth: size } }, FALLBACK[name]);
@@ -2526,7 +2800,11 @@ var teamMark = (title, bg, fg, size = 36) => (0, import_react2.createElement)(
2526
2800
  { style: { width: size, height: size, borderRadius: size / 2, backgroundColor: bg, alignItems: "center", justifyContent: "center" } },
2527
2801
  (0, import_react2.createElement)(RN3.Text, { style: { color: fg, fontSize: Math.round(size * 0.42), fontWeight: "700" } }, (title.trim()[0] ?? "?").toUpperCase())
2528
2802
  );
2529
- var lift = { shadowColor: "#101218", shadowOpacity: 0.07, shadowRadius: 14, shadowOffset: { width: 0, height: 4 }, elevation: 2 };
2803
+ var HU = {
2804
+ light: { bg: "#ffffff", surface: "#f4f4f5", def: "#ececee", segment: "#ffffff", fg: "#18181b", muted: "#71717a" },
2805
+ dark: { bg: "#111113", surface: "#1c1c1f", def: "#26262a", segment: "#3a3a40", fg: "#fafafa", muted: "#a1a1aa" }
2806
+ };
2807
+ var hairline = { shadowColor: "#000", shadowOpacity: 0.07, shadowRadius: 3, shadowOffset: { width: 0, height: 1 }, elevation: 1 };
2530
2808
  var useChat = (client, o, open, view) => {
2531
2809
  const [thread, setThread] = (0, import_react2.useState)(null);
2532
2810
  const [loaded, setLoaded] = (0, import_react2.useState)(false);
@@ -2693,12 +2971,14 @@ var MessengerSheet = ({
2693
2971
  o,
2694
2972
  chat,
2695
2973
  view,
2696
- setView
2974
+ setView,
2975
+ brand
2697
2976
  }) => {
2698
2977
  const k = kit(p);
2699
2978
  const [body, setBody] = (0, import_react2.useState)("");
2700
2979
  const [email, setEmail] = (0, import_react2.useState)("");
2701
2980
  const [pending, setPending] = (0, import_react2.useState)(null);
2981
+ const [photos, setPhotos] = (0, import_react2.useState)([]);
2702
2982
  const [query, setQuery] = (0, import_react2.useState)("");
2703
2983
  const helpOn = o.help !== false;
2704
2984
  const arts = useArticles(client, helpOn, query);
@@ -2712,52 +2992,135 @@ var MessengerSheet = ({
2712
2992
  }, [count, view]);
2713
2993
  const needEmail = (o.askEmail ?? true) && !thread?.hasEmail && !client.getUser()?.email;
2714
2994
  const emailOk = !email.trim() || EMAIL.test(email.trim());
2995
+ const pickPhotos = async () => {
2996
+ const api = client.api();
2997
+ if (!api || !imagePicker?.launchImageLibraryAsync || pending) return;
2998
+ const room = CHAT_FILES_PER_MESSAGE - photos.length;
2999
+ if (room <= 0) return chat.setError(`Up to ${CHAT_FILES_PER_MESSAGE} files per message.`);
3000
+ let res;
3001
+ try {
3002
+ res = await imagePicker.launchImageLibraryAsync({ allowsMultipleSelection: room > 1, selectionLimit: room, quality: 0.8, exif: false });
3003
+ } catch (e) {
3004
+ return chat.setError(e.message || "The photos could not be opened.");
3005
+ }
3006
+ if (res.canceled || res.cancelled || !res.assets?.length) return;
3007
+ chat.setError("");
3008
+ const added = [];
3009
+ for (const a of res.assets.slice(0, room)) {
3010
+ const name = a.fileName || a.uri.split("/").pop() || "photo.jpg";
3011
+ const type = a.mimeType && Object.values(PHOTO_TYPES).includes(a.mimeType) ? a.mimeType : PHOTO_TYPES[name.split(".").pop()?.toLowerCase() ?? ""];
3012
+ if (!type) {
3013
+ chat.setError("Only pictures (PNG, JPEG, WebP, GIF) can be sent.");
3014
+ continue;
3015
+ }
3016
+ if (a.fileSize && a.fileSize > CHAT_FILE_MAX_BYTES) {
3017
+ chat.setError(`${name} is larger than 10 MB.`);
3018
+ continue;
3019
+ }
3020
+ const ph = { key: `local-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}`, uri: a.uri, name, type, size: a.fileSize ?? 0, up: Promise.resolve() };
3021
+ ph.up = api.uploadChatFile({ uri: a.uri, name, type }).then(
3022
+ (r2) => {
3023
+ ph.id = r2.id;
3024
+ setPhotos((list) => list.map((x) => x.key === ph.key ? { ...ph } : x));
3025
+ },
3026
+ (e) => {
3027
+ setPhotos((list) => list.filter((x) => x.key !== ph.key));
3028
+ chat.setError(`${name}: ${e.message}`);
3029
+ }
3030
+ );
3031
+ added.push(ph);
3032
+ }
3033
+ setPhotos((list) => [...list, ...added]);
3034
+ };
3035
+ const canSend = (!!body.trim() || photos.length > 0) && !pending && emailOk;
2715
3036
  const send = async () => {
2716
3037
  const text2 = body.trim();
2717
3038
  const api = client.api();
2718
- if (!text2 || pending || !emailOk || !api) return;
2719
- setPending({ id: "pending", from: "visitor", body: text2, at: (/* @__PURE__ */ new Date()).toISOString() });
3039
+ const files = photos;
3040
+ if (!text2 && !files.length || pending || !emailOk || !api) return;
3041
+ setPending({
3042
+ id: "pending",
3043
+ from: "visitor",
3044
+ body: text2,
3045
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3046
+ ...files.length ? { attachments: files.map((f) => ({ id: f.key, name: f.name, type: f.type, size: f.size })) } : {}
3047
+ });
2720
3048
  setBody("");
3049
+ setPhotos([]);
2721
3050
  chat.setError("");
2722
3051
  try {
2723
- chat.take(await api.sendChat({ body: text2, email: needEmail && email.trim() ? email.trim() : void 0 }));
3052
+ await Promise.all(files.map((f) => f.up));
3053
+ const ids = files.filter((f) => f.id).map((f) => f.id);
3054
+ if (!text2 && !ids.length) throw new Error("The photo could not be sent.");
3055
+ const win = RN3.Dimensions.get("screen");
3056
+ chat.take(
3057
+ await api.sendChat({
3058
+ body: text2,
3059
+ email: needEmail && email.trim() ? email.trim() : void 0,
3060
+ ...ids.length ? { attachments: ids } : {},
3061
+ // A little context for a bug report: the system and the screen size.
3062
+ context: { os: `${RN3.Platform.OS === "ios" ? "iOS" : RN3.Platform.OS === "android" ? "Android" : RN3.Platform.OS} ${RN3.Platform.Version ?? ""}`.trim(), browser: "App", viewport: `${Math.round(win.width)}\xD7${Math.round(win.height)}` }
3063
+ })
3064
+ );
2724
3065
  } catch (e) {
2725
3066
  setBody(text2);
3067
+ setPhotos(files.filter((f) => f.id));
2726
3068
  chat.setError(e.message);
2727
3069
  } finally {
2728
3070
  setPending(null);
2729
3071
  }
2730
3072
  };
3073
+ const localUris = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
3074
+ for (const f of photos) localUris.current.set(f.key, f.uri);
3075
+ const fileUrl = (id) => localUris.current.get(id) ?? client.api()?.attachmentUrl(id) ?? "";
2731
3076
  const ink = inkOn(p.accent);
2732
3077
  const dark = p.bg === DARK.bg;
2733
- const canvas = dark ? p.bg : p.soft;
2734
- const raised = dark ? p.soft : p.bg;
2735
- const title = o.title ?? "Chat with us";
2736
- const reply = replyLine(o.replyTime);
3078
+ const r = CORNERS[o.corners ?? "round"];
3079
+ const plain = o.header !== "color";
3080
+ const hu = dark ? HU.dark : HU.light;
3081
+ const surf = {};
3082
+ const segLift = dark ? {} : hairline;
3083
+ const canvas = hu.bg;
3084
+ const raised = hu.surface;
3085
+ const title = o.title?.trim() ?? "";
2737
3086
  const bubble = (m, isPending = false) => {
2738
3087
  const mine = m.from === "visitor";
2739
3088
  return (0, import_react2.createElement)(
2740
3089
  RN3.View,
2741
3090
  { key: m.id, style: { alignSelf: mine ? "flex-end" : "flex-start", maxWidth: "82%", gap: 4 } },
2742
- (0, import_react2.createElement)(
3091
+ ...(m.attachments ?? []).map(
3092
+ (f) => (0, import_react2.createElement)(
3093
+ RN3.Pressable,
3094
+ {
3095
+ key: f.id,
3096
+ onPress: () => void RN3.Linking.openURL(fileUrl(f.id)).catch(() => void 0),
3097
+ accessibilityRole: "imagebutton",
3098
+ accessibilityLabel: f.name,
3099
+ style: { alignSelf: mine ? "flex-end" : "flex-start", opacity: isPending ? 0.7 : 1 }
3100
+ },
3101
+ f.type.startsWith("image/") ? (0, import_react2.createElement)(RN3.Image, { source: { uri: fileUrl(f.id) }, resizeMode: "cover", style: { width: 200, height: 150, borderRadius: r.bubble, backgroundColor: p.soft } }) : (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } }, k.text(`\u{1F4C4} ${f.name}`, { fontSize: 13 }))
3102
+ )
3103
+ ),
3104
+ !m.body && m.attachments?.length ? null : (0, import_react2.createElement)(
2743
3105
  RN3.View,
2744
3106
  {
2745
3107
  style: {
2746
- backgroundColor: mine ? p.accent : dark ? p.soft : "#eeeff1",
2747
- borderRadius: 18,
2748
- borderBottomRightRadius: mine ? 6 : 18,
2749
- borderBottomLeftRadius: mine ? 18 : 6,
3108
+ backgroundColor: mine ? p.accent : hu.surface,
3109
+ ...mine ? {} : surf,
3110
+ borderRadius: r.bubble,
3111
+ borderBottomRightRadius: mine ? r.tail : r.bubble,
3112
+ borderBottomLeftRadius: mine ? r.bubble : r.tail,
2750
3113
  paddingHorizontal: 14,
2751
3114
  paddingVertical: 9,
2752
3115
  opacity: isPending ? 0.7 : 1
2753
3116
  }
2754
3117
  },
2755
- k.text(m.body, { color: mine ? ink : p.fg })
3118
+ k.text(m.body, { color: mine ? ink : hu.fg })
2756
3119
  ),
2757
3120
  ...(m.articles ?? []).map(
2758
3121
  (a) => (0, import_react2.createElement)(
2759
3122
  RN3.Pressable,
2760
- { key: a.id, onPress: () => setView({ article: a.id }), accessibilityRole: "link", style: { borderWidth: 1, borderColor: p.line, borderRadius: 12, paddingHorizontal: 10, paddingVertical: 7 } },
3123
+ { key: a.id, onPress: () => setView({ article: a.id }), accessibilityRole: "link", style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } },
2761
3124
  k.text(`\u{1F4C4} ${a.title}`, { fontSize: 13 })
2762
3125
  )
2763
3126
  ),
@@ -2770,60 +3133,80 @@ var MessengerSheet = ({
2770
3133
  k.text(a.title, { flex: 1 }),
2771
3134
  glyph("next", p.accent, 14)
2772
3135
  );
2773
- const card = (children, style = {}) => (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: raised, borderRadius: 16, padding: 16, ...dark ? { borderWidth: 1, borderColor: p.line } : lift, ...style } }, ...children);
3136
+ const card = (children, style = {}) => (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: raised, borderRadius: r.card, padding: 16, ...surf, ...style } }, ...children);
2774
3137
  const searchRow = (onPress) => (0, import_react2.createElement)(
2775
3138
  RN3.Pressable,
2776
- { key: "s", onPress, accessibilityRole: "search", style: { flexDirection: "row", alignItems: "center", height: 44, borderRadius: 12, paddingHorizontal: 14, backgroundColor: canvas } },
3139
+ { key: "s", onPress, accessibilityRole: "search", style: { flexDirection: "row", alignItems: "center", height: 44, borderRadius: r.field, paddingHorizontal: 14, backgroundColor: hu.def } },
2777
3140
  k.muted("Search for help", { flex: 1, fontSize: 15 }),
2778
3141
  glyph("search", p.accent, 16)
2779
3142
  );
2780
- const search = k.field({ value: query, onChange: setQuery, placeholder: "Search for help", max: 200, style: { borderWidth: 0, borderRadius: 12, backgroundColor: raised, ...dark ? {} : lift } });
3143
+ const search = k.field({ value: query, onChange: setQuery, placeholder: "Search for help", max: 200, style: { borderWidth: 0, borderRadius: r.field, backgroundColor: raised, ...surf } });
3144
+ const first = client.getUser()?.name?.trim().split(/\s+/)[0];
3145
+ const rest = greeting.replace(/^(hi|hello|hey|hallo)( there)?\s*[!,.]*\s*/i, "").trim() || "How can we help you today?";
3146
+ const hero = (fg, sub) => (0, import_react2.createElement)(
3147
+ RN3.Text,
3148
+ { key: "hero", accessibilityRole: "header", style: { color: fg, fontSize: 25, fontWeight: "600", letterSpacing: -0.6, lineHeight: 30 } },
3149
+ first ? `Hi, ${first} \u{1F44B}
3150
+ ` : "Hi there \u{1F44B}\n",
3151
+ (0, import_react2.createElement)(RN3.Text, { style: { color: sub, fontWeight: "500" } }, rest)
3152
+ );
2781
3153
  const last = thread?.messages.at(-1);
2782
3154
  const showHelp = helpOn && arts.any !== false;
2783
3155
  let content;
2784
- if (view === "home")
2785
- content = (0, import_react2.createElement)(
2786
- RN3.ScrollView,
2787
- { key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingBottom: 24 }, keyboardShouldPersistTaps: "handled" },
2788
- (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: p.accent, paddingHorizontal: 22, paddingTop: 4, paddingBottom: 64 } }, (0, import_react2.createElement)(RN3.Text, { style: { color: ink, fontSize: 28, fontWeight: "700", letterSpacing: -0.4, lineHeight: 34 } }, greeting)),
3156
+ if (view === "home") {
3157
+ const cards = [
3158
+ // "Ask a question": the words go into the chat's field and are sent as the chat opens.
2789
3159
  (0, import_react2.createElement)(
2790
3160
  RN3.View,
2791
- { style: { marginTop: -48, paddingHorizontal: 16, gap: 12 } },
2792
- last ? (0, import_react2.createElement)(
2793
- RN3.Pressable,
2794
- { key: "recent", onPress: () => setView("messages"), accessibilityRole: "button" },
2795
- card([
2796
- k.muted("Recent message", { fontSize: 12, fontWeight: "600", marginBottom: 10 }),
2797
- (0, import_react2.createElement)(
2798
- RN3.View,
2799
- { key: "r", style: { flexDirection: "row", alignItems: "center", gap: 12 } },
2800
- teamMark(title, p.accent, ink),
2801
- (0, import_react2.createElement)(RN3.View, { style: { flex: 1, gap: 2 } }, (0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { color: p.fg, fontSize: 15 } }, last.body), k.muted(`${last.from === "visitor" ? "You" : whoLabel[last.from]} \xB7 ${timeAgo(last.at)}`, { fontSize: 12 })),
2802
- chat.unseen.length ? (0, import_react2.createElement)(RN3.View, { style: { minWidth: 20, height: 20, borderRadius: 10, paddingHorizontal: 6, backgroundColor: p.danger, alignItems: "center", justifyContent: "center" } }, k.text(String(chat.unseen.length), { color: "#fff", fontSize: 12, fontWeight: "700" })) : glyph("next", p.muted, 14)
2803
- )
2804
- ])
2805
- ) : null,
3161
+ { key: "ask", style: { flexDirection: "row", alignItems: "center", gap: 8, height: 48, borderRadius: 24, paddingLeft: 18, paddingRight: 6, backgroundColor: hu.surface, ...surf } },
3162
+ k.field({ value: body, onChange: setBody, placeholder: "Ask a question", max: 4e3, style: { flex: 1, borderWidth: 0, paddingHorizontal: 0, backgroundColor: "transparent" } }),
2806
3163
  (0, import_react2.createElement)(
2807
3164
  RN3.Pressable,
2808
- { key: "send", onPress: () => setView("messages"), accessibilityRole: "button" },
2809
- card(
2810
- [
2811
- (0, import_react2.createElement)(RN3.View, { key: "t", style: { flex: 1, gap: 2 } }, k.text("Send us a message", { fontWeight: "700", fontSize: 16 }), k.muted(reply)),
2812
- (0, import_react2.createElement)(RN3.View, { key: "g", style: { width: 36, height: 36, borderRadius: 18, backgroundColor: alpha(p.accent, 0.12), alignItems: "center", justifyContent: "center" } }, glyph("send", p.accent, 18))
2813
- ],
2814
- { flexDirection: "row", alignItems: "center", gap: 12 }
3165
+ {
3166
+ onPress: () => {
3167
+ setView("messages");
3168
+ if (body.trim()) void send();
3169
+ },
3170
+ accessibilityRole: "button",
3171
+ accessibilityLabel: "Send",
3172
+ style: { width: 36, height: 36, borderRadius: 18, alignItems: "center", justifyContent: "center", backgroundColor: body.trim() ? p.accent : hu.def }
3173
+ },
3174
+ glyph("up", body.trim() ? ink : hu.muted, 18)
3175
+ )
3176
+ ),
3177
+ last ? (0, import_react2.createElement)(
3178
+ RN3.Pressable,
3179
+ { key: "recent", onPress: () => setView("messages"), accessibilityRole: "button" },
3180
+ card([
3181
+ (0, import_react2.createElement)(
3182
+ RN3.View,
3183
+ { key: "r", style: { flexDirection: "row", alignItems: "center", gap: 10 } },
3184
+ title ? teamMark(title, alpha(p.accent, 0.16), p.accent, 26) : null,
3185
+ (0, import_react2.createElement)(RN3.View, { style: { flex: 1, gap: 1 } }, (0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.fg, fontSize: 14, fontWeight: "500" } }, last.body || (last.attachments?.length ? `Sent ${last.attachments.length === 1 ? last.attachments[0].name : `${last.attachments.length} files`}` : "")), k.muted(`${last.from === "visitor" ? "You" : whoLabel[last.from]} \xB7 ${timeAgo(last.at)}`, { fontSize: 11.5 })),
3186
+ chat.unseen.length ? (0, import_react2.createElement)(RN3.View, { style: { width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : glyph("next", p.muted, 14)
2815
3187
  )
2816
- ),
2817
- showHelp ? card([searchRow(() => setView("help")), ...(arts.list ?? []).slice(0, 4).map(row)], { padding: 8, gap: 2 }) : null
2818
- )
3188
+ ], { paddingVertical: 10, paddingHorizontal: 12 })
3189
+ ) : null,
3190
+ showHelp ? card([searchRow(() => setView("help")), ...(arts.list ?? []).slice(0, 4).map(row)], { padding: 8, gap: 2 }) : null
3191
+ ];
3192
+ content = plain ? (0, import_react2.createElement)(
3193
+ RN3.ScrollView,
3194
+ { key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 24, gap: 12 }, keyboardShouldPersistTaps: "handled" },
3195
+ (0, import_react2.createElement)(RN3.View, { key: "top", style: { paddingHorizontal: 6, paddingBottom: 10 } }, hero(p.fg, p.muted)),
3196
+ ...cards
3197
+ ) : (0, import_react2.createElement)(
3198
+ RN3.ScrollView,
3199
+ { key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingBottom: 24 }, keyboardShouldPersistTaps: "handled" },
3200
+ (0, import_react2.createElement)(RN3.View, { style: { backgroundColor: p.accent, paddingHorizontal: 22, paddingTop: 4, paddingBottom: 64 } }, hero(ink, alpha(ink, 0.8))),
3201
+ (0, import_react2.createElement)(RN3.View, { style: { marginTop: -48, paddingHorizontal: 16, gap: 12 } }, ...cards)
2819
3202
  );
2820
- else if (view === "messages")
3203
+ } else if (view === "messages")
2821
3204
  content = (0, import_react2.createElement)(
2822
3205
  RN3.View,
2823
- { key: "messages", style: { flex: 1, backgroundColor: p.bg } },
3206
+ { key: "messages", style: { flex: 1, backgroundColor: hu.bg } },
2824
3207
  (0, import_react2.createElement)(
2825
3208
  RN3.ScrollView,
2826
- { ref: scroll, style: { flex: 1 }, contentContainerStyle: { padding: 16, gap: 12 } },
3209
+ { ref: scroll, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 } },
2827
3210
  bubble({ id: "greeting", from: "team", body: greeting, at: "" }),
2828
3211
  !chat.loaded ? k.muted("Loading\u2026") : null,
2829
3212
  ...(thread?.messages ?? []).map((m) => bubble(m)),
@@ -2832,23 +3215,57 @@ var MessengerSheet = ({
2832
3215
  ),
2833
3216
  (0, import_react2.createElement)(
2834
3217
  RN3.View,
2835
- { style: { paddingHorizontal: 12, paddingTop: 8, paddingBottom: 10, gap: 8 } },
3218
+ { style: { paddingHorizontal: 12, paddingTop: 8, paddingBottom: 6, gap: 8 } },
2836
3219
  chat.error ? k.text(chat.error, { color: p.danger, fontSize: 12 }) : null,
2837
- needEmail ? k.field({ value: email, onChange: setEmail, placeholder: "Your email (optional), so we can follow up", email: true, max: 200, style: { borderWidth: emailOk ? 0 : 1, borderColor: p.danger, borderRadius: 12, backgroundColor: canvas } }) : null,
3220
+ needEmail ? k.field({ value: email, onChange: setEmail, placeholder: "Your email (optional), so we can follow up", email: true, max: 200, style: { borderWidth: emailOk ? 0 : 1, borderColor: p.danger, borderRadius: r.field, paddingHorizontal: 16, backgroundColor: hu.surface, ...surf } }) : null,
3221
+ // Photos picked for the next message, each with a way to take it out again.
3222
+ photos.length ? (0, import_react2.createElement)(
3223
+ RN3.View,
3224
+ { style: { flexDirection: "row", flexWrap: "wrap", gap: 10, paddingTop: 6, paddingHorizontal: 2 } },
3225
+ ...photos.map(
3226
+ (f) => (0, import_react2.createElement)(
3227
+ RN3.View,
3228
+ { key: f.key, style: { width: 56, height: 56 } },
3229
+ (0, import_react2.createElement)(RN3.Image, { source: { uri: f.uri }, resizeMode: "cover", accessibilityLabel: f.id ? f.name : `${f.name}, uploading`, style: { width: 56, height: 56, borderRadius: Math.max(4, r.card - 4), backgroundColor: p.soft, opacity: f.id ? 1 : 0.5 } }),
3230
+ (0, import_react2.createElement)(
3231
+ RN3.Pressable,
3232
+ {
3233
+ onPress: () => setPhotos((list) => list.filter((x) => x.key !== f.key)),
3234
+ accessibilityRole: "button",
3235
+ accessibilityLabel: `Remove ${f.name}`,
3236
+ hitSlop: 8,
3237
+ style: { position: "absolute", top: -7, right: -7, width: 22, height: 22, borderRadius: 11, borderWidth: 2, borderColor: p.bg, backgroundColor: p.fg, alignItems: "center", justifyContent: "center" }
3238
+ },
3239
+ glyph("cross", p.bg, 10)
3240
+ )
3241
+ )
3242
+ )
3243
+ ) : null,
3244
+ // The message field on top, the paperclip (when the app has expo-image-picker) and the send button under it.
2838
3245
  (0, import_react2.createElement)(
2839
3246
  RN3.View,
2840
- { style: { flexDirection: "row", alignItems: "flex-end", gap: 6, borderWidth: 1, borderColor: p.line, borderRadius: 22, paddingVertical: 5, paddingRight: 5, paddingLeft: 16, backgroundColor: p.bg } },
2841
- k.field({ value: body, onChange: setBody, placeholder: "Write a message\u2026", multiline: true, max: 4e3, style: { flex: 1, minHeight: 34, maxHeight: 120, borderWidth: 0, paddingHorizontal: 0, paddingVertical: 7, backgroundColor: "transparent" } }),
3247
+ { style: { gap: 2, borderRadius: 20, paddingTop: 10, paddingBottom: 6, paddingRight: 6, paddingLeft: 16, backgroundColor: hu.surface, ...surf } },
3248
+ k.field({ value: body, onChange: setBody, placeholder: "Write a message\u2026", multiline: true, max: 4e3, style: { minHeight: 28, maxHeight: 120, borderWidth: 0, paddingHorizontal: 0, paddingVertical: 4, backgroundColor: "transparent" } }),
2842
3249
  (0, import_react2.createElement)(
2843
- RN3.Pressable,
2844
- {
2845
- onPress: () => void send(),
2846
- disabled: !body.trim() || !!pending || !emailOk,
2847
- accessibilityRole: "button",
2848
- accessibilityLabel: "Send",
2849
- style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center", backgroundColor: !body.trim() || !!pending || !emailOk ? p.line : p.accent }
2850
- },
2851
- glyph("up", !body.trim() || !!pending || !emailOk ? p.muted : ink, 18)
3250
+ RN3.View,
3251
+ { style: { flexDirection: "row", alignItems: "center", marginLeft: -8 } },
3252
+ imagePicker?.launchImageLibraryAsync ? (0, import_react2.createElement)(
3253
+ RN3.Pressable,
3254
+ { onPress: () => void pickPhotos(), disabled: !!pending, accessibilityRole: "button", accessibilityLabel: "Attach a photo or screenshot", hitSlop: 6, style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center" } },
3255
+ glyph("clip", hu.muted, 19)
3256
+ ) : null,
3257
+ (0, import_react2.createElement)(RN3.View, { style: { flex: 1 } }),
3258
+ (0, import_react2.createElement)(
3259
+ RN3.Pressable,
3260
+ {
3261
+ onPress: () => void send(),
3262
+ disabled: !canSend,
3263
+ accessibilityRole: "button",
3264
+ accessibilityLabel: "Send",
3265
+ style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center", backgroundColor: canSend ? p.accent : hu.def }
3266
+ },
3267
+ glyph("up", canSend ? ink : hu.muted, 18)
3268
+ )
2852
3269
  )
2853
3270
  )
2854
3271
  )
@@ -2858,7 +3275,7 @@ var MessengerSheet = ({
2858
3275
  for (const a of arts.list ?? []) groups.set(a.collection, [...groups.get(a.collection) ?? [], a]);
2859
3276
  content = (0, import_react2.createElement)(
2860
3277
  RN3.ScrollView,
2861
- { key: "help", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { padding: 16, gap: 12 }, keyboardShouldPersistTaps: "handled" },
3278
+ { key: "help", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 }, keyboardShouldPersistTaps: "handled" },
2862
3279
  search,
2863
3280
  arts.list === null ? k.muted("Loading\u2026") : null,
2864
3281
  arts.list?.length === 0 ? (0, import_react2.createElement)(RN3.View, { style: { gap: 10, alignItems: "center", paddingVertical: 24 } }, k.muted(query.trim() ? `Nothing found for \u201C${query.trim()}\u201D.` : "No help articles yet."), k.btn("Send us a message", "primary", () => setView("messages"))) : null,
@@ -2867,46 +3284,73 @@ var MessengerSheet = ({
2867
3284
  } else
2868
3285
  content = (0, import_react2.createElement)(
2869
3286
  RN3.ScrollView,
2870
- { key: `a-${view.article}`, style: { flex: 1 }, contentContainerStyle: { padding: 16, paddingBottom: 32 } },
3287
+ { key: `a-${view.article}`, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 32 } },
2871
3288
  (0, import_react2.createElement)(ArticleView, { client, p, id: view.article, onAsk: () => setView("messages") })
2872
3289
  );
2873
3290
  const current = typeof view === "string" ? view : "help";
2874
- const tab = (v, name, icon, badge = 0) => (0, import_react2.createElement)(
3291
+ const tab = (v, name, icon, badge = 0) => {
3292
+ const on = current === v;
3293
+ return (0, import_react2.createElement)(
3294
+ RN3.Pressable,
3295
+ {
3296
+ key: v,
3297
+ onPress: () => setView(v),
3298
+ accessibilityRole: "tab",
3299
+ accessibilityState: { selected: on },
3300
+ style: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 6, height: 36, borderRadius: 18, ...on ? { backgroundColor: hu.segment, ...segLift } : {} }
3301
+ },
3302
+ glyph(icon, on ? hu.fg : hu.muted, 17),
3303
+ k.text(name, { fontSize: 13.5, fontWeight: "500", color: on ? hu.fg : hu.muted }),
3304
+ badge ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: 7, right: 12, width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : null
3305
+ );
3306
+ };
3307
+ const onAccent = view === "home" && !plain;
3308
+ const ground = onAccent ? p.accent : hu.bg;
3309
+ const back = (to, label) => (0, import_react2.createElement)(
2875
3310
  RN3.Pressable,
2876
- { key: v, onPress: () => setView(v), accessibilityRole: "tab", accessibilityState: { selected: current === v }, style: { flex: 1, alignItems: "center", paddingTop: 8, paddingBottom: 6, gap: 3 } },
2877
- glyph(icon, current === v ? p.accent : p.muted, 22),
2878
- k.text(name, { fontSize: 12, fontWeight: "600", color: current === v ? p.accent : p.muted }),
2879
- badge ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: 4, right: "28%", minWidth: 18, height: 18, borderRadius: 9, backgroundColor: p.danger, alignItems: "center", justifyContent: "center" } }, k.text(String(Math.min(badge, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : null
3311
+ { key: "back", onPress: () => setView(to), accessibilityRole: "button", accessibilityLabel: label, hitSlop: 10, style: { width: 32, height: 32, alignItems: "center", justifyContent: "center", transform: [{ rotate: "180deg" }] } },
3312
+ glyph("next", hu.fg, 18)
2880
3313
  );
2881
- const onHome = view === "home";
2882
- const headFg = onHome ? ink : p.fg;
2883
3314
  const head = (0, import_react2.createElement)(
2884
3315
  RN3.View,
2885
- { key: "head", style: { flexDirection: "row", alignItems: "center", gap: 10, paddingLeft: 16, paddingRight: 12, paddingTop: 16, paddingBottom: 12, backgroundColor: onHome ? p.accent : p.bg, borderBottomWidth: onHome ? 0 : 1, borderBottomColor: p.line } },
2886
- typeof view !== "string" ? (0, import_react2.createElement)(RN3.Pressable, { key: "back", onPress: () => setView("help"), accessibilityRole: "button", hitSlop: 10 }, k.text("\u2039 All articles", { color: p.accent, fontWeight: "600" })) : view === "help" ? null : teamMark(title, onHome ? ink : p.accent, onHome ? p.accent : ink, onHome ? 28 : 36),
2887
- (0, import_react2.createElement)(
3316
+ { key: "head", style: { flexDirection: "row", alignItems: "center", gap: 8, paddingLeft: 12, paddingRight: 12, paddingTop: 12, paddingBottom: 6, backgroundColor: ground } },
3317
+ typeof view !== "string" ? back("help", "All articles") : view === "messages" ? back("home", "Back to home") : view === "home" && (o.logo || title) ? o.logo ? (0, import_react2.createElement)(RN3.Image, { key: "logo", source: { uri: o.logo }, accessibilityIgnoresInvertColors: true, style: { width: 28, height: 28, borderRadius: 8, marginLeft: 4 } }) : teamMark(title, alpha(p.accent, 0.16), p.accent, 28) : null,
3318
+ // In the chat: who answers, in the middle.
3319
+ view === "messages" && title ? (0, import_react2.createElement)(
2888
3320
  RN3.View,
2889
- { style: { flex: 1, minWidth: 0 } },
2890
- typeof view !== "string" ? null : (0, import_react2.createElement)(RN3.Text, { accessibilityRole: "header", numberOfLines: 1, style: { color: headFg, fontSize: 17, fontWeight: "700" } }, view === "help" ? "Help" : title),
2891
- view === "messages" ? k.muted(reply, { fontSize: 12 }) : null
2892
- ),
2893
- (0, import_react2.createElement)(RN3.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: "Close", hitSlop: 12, style: { width: 32, height: 32, borderRadius: 10, alignItems: "center", justifyContent: "center" } }, glyph("cross", onHome ? ink : p.muted, 18))
3321
+ { style: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8 } },
3322
+ teamMark(title, alpha(p.accent, 0.16), p.accent, 28),
3323
+ (0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { flexShrink: 1, color: hu.fg, fontSize: 15, fontWeight: "600" } }, title)
3324
+ ) : (0, import_react2.createElement)(RN3.View, { style: { flex: 1 } }),
3325
+ (0, import_react2.createElement)(RN3.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: "Close", hitSlop: 12, style: { width: 32, height: 32, borderRadius: 10, alignItems: "center", justifyContent: "center" } }, glyph("cross", onAccent ? ink : p.muted, 18))
2894
3326
  );
3327
+ const powered = brand ? (0, import_react2.createElement)(
3328
+ RN3.View,
3329
+ { key: "brand", style: { paddingTop: 2, paddingBottom: 6, alignItems: "center", backgroundColor: hu.bg } },
3330
+ (0, import_react2.createElement)(
3331
+ RN3.Text,
3332
+ { onPress: () => void RN3.Linking.openURL(BRAND_URL).catch(() => void 0), accessibilityRole: "link", hitSlop: 8, style: { color: p.muted, fontSize: 11.5 } },
3333
+ "Powered by ",
3334
+ (0, import_react2.createElement)(RN3.Text, { style: { fontWeight: "600" } }, "MRRocket")
3335
+ )
3336
+ ) : null;
2895
3337
  return (0, import_react2.createElement)(
2896
3338
  RN3.Modal,
2897
3339
  { visible: true, animationType: "slide", presentationStyle: "pageSheet", onRequestClose: close },
2898
3340
  (0, import_react2.createElement)(
2899
3341
  RN3.KeyboardAvoidingView,
2900
- { behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: p.bg } },
3342
+ { behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: hu.bg } },
2901
3343
  head,
2902
3344
  content,
2903
- (0, import_react2.createElement)(
3345
+ view === "messages" ? null : (0, import_react2.createElement)(
2904
3346
  RN3.View,
2905
- { key: "tabs", accessibilityRole: "tablist", style: { flexDirection: "row", borderTopWidth: 1, borderTopColor: p.line, paddingHorizontal: 8, paddingBottom: 22, backgroundColor: p.bg } },
3347
+ { key: "tabs", accessibilityRole: "tablist", style: { flexDirection: "row", marginHorizontal: 12, marginTop: 6, marginBottom: 6, padding: 4, borderRadius: 22, backgroundColor: hu.surface } },
2906
3348
  tab("home", "Home", "home"),
2907
- tab("messages", "Messages", "chat", view === "messages" ? 0 : chat.unseen.length),
3349
+ tab("messages", "Messages", "chat", chat.unseen.length),
2908
3350
  showHelp ? tab("help", "Help", "help") : null
2909
- )
3351
+ ),
3352
+ powered,
3353
+ (0, import_react2.createElement)(RN3.View, { style: { height: 16 } })
2910
3354
  )
2911
3355
  );
2912
3356
  };
@@ -2931,6 +3375,9 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
2931
3375
  preview: look.preview,
2932
3376
  askEmail: look.askEmail,
2933
3377
  haptics: look.sound,
3378
+ corners: look.corners,
3379
+ header: look.header,
3380
+ logo: look.logo,
2934
3381
  ...defined
2935
3382
  } : given;
2936
3383
  const p = usePalette({ theme: appearance?.theme ?? look?.theme, accent: appearance?.accent ?? look?.accent });
@@ -2959,37 +3406,42 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
2959
3406
  const unseen = chat.unseen.length;
2960
3407
  const ink = inkOn(p.accent);
2961
3408
  const label = options.label?.trim() ?? "";
2962
- const side = options.position === "bottom-left" ? { left: 20 } : { right: 20 };
3409
+ const r = CORNERS[options.corners ?? "round"];
3410
+ const [edge, side] = (options.position ?? "bottom-right").split("-");
3411
+ const place = {
3412
+ ...edge === "top" ? { top: 60 } : edge === "bottom" ? { bottom: 34 } : { top: 0, bottom: 0, justifyContent: "center" },
3413
+ ...side === "left" ? { left: 20 } : side === "right" ? { right: 20 } : { left: 0, right: 0, alignItems: "center" }
3414
+ };
2963
3415
  const launcher = options.launcher ? (0, import_react2.createElement)(
2964
- RN3.Pressable,
2965
- {
2966
- key: "launcher",
2967
- onPress: () => openAt(unseen ? "messages" : "home"),
2968
- accessibilityRole: "button",
2969
- accessibilityLabel: unseen ? `Open chat, ${unseen} new` : "Open chat",
2970
- style: {
2971
- position: "absolute",
2972
- ...side,
2973
- bottom: 34,
2974
- height: label ? 50 : 56,
2975
- minWidth: label ? 0 : 56,
2976
- paddingHorizontal: label ? 18 : 0,
2977
- borderRadius: 28,
2978
- flexDirection: "row",
2979
- gap: 8,
2980
- backgroundColor: p.accent,
2981
- alignItems: "center",
2982
- justifyContent: "center",
2983
- shadowColor: p.accent,
2984
- shadowOpacity: 0.4,
2985
- shadowRadius: 12,
2986
- shadowOffset: { width: 0, height: 6 },
2987
- elevation: 6
2988
- }
2989
- },
2990
- glyph("chat", ink, label ? 20 : 26),
2991
- label ? k.text(label, { color: ink, fontWeight: "700" }) : null,
2992
- unseen ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: p.danger, alignItems: "center", justifyContent: "center", borderWidth: 2, borderColor: p.bg } }, k.text(String(Math.min(unseen, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : null
3416
+ RN3.View,
3417
+ { key: "launcher", pointerEvents: "box-none", style: { position: "absolute", ...place } },
3418
+ (0, import_react2.createElement)(
3419
+ RN3.Pressable,
3420
+ {
3421
+ onPress: () => openAt(unseen ? "messages" : "home"),
3422
+ accessibilityRole: "button",
3423
+ accessibilityLabel: unseen ? `Open chat, ${unseen} new` : "Open chat",
3424
+ style: {
3425
+ height: label ? 50 : 56,
3426
+ minWidth: label ? 0 : 56,
3427
+ paddingHorizontal: label ? 18 : 0,
3428
+ borderRadius: 28,
3429
+ flexDirection: "row",
3430
+ gap: 8,
3431
+ backgroundColor: p.accent,
3432
+ alignItems: "center",
3433
+ justifyContent: "center",
3434
+ shadowColor: p.accent,
3435
+ shadowOpacity: 0.4,
3436
+ shadowRadius: 12,
3437
+ shadowOffset: { width: 0, height: 6 },
3438
+ elevation: 6
3439
+ }
3440
+ },
3441
+ glyph("chat", ink, label ? 20 : 26),
3442
+ label ? k.text(label, { color: ink, fontWeight: "700" }) : null,
3443
+ unseen ? (0, import_react2.createElement)(RN3.View, { style: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: p.danger, alignItems: "center", justifyContent: "center", borderWidth: 2, borderColor: p.bg } }, k.text(String(Math.min(unseen, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : null
3444
+ )
2993
3445
  ) : null;
2994
3446
  const toast = banner ? (0, import_react2.createElement)(
2995
3447
  RN3.Pressable,
@@ -2998,15 +3450,25 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
2998
3450
  onPress: () => openAt("messages"),
2999
3451
  accessibilityRole: "button",
3000
3452
  accessibilityLiveRegion: "polite",
3001
- style: { position: "absolute", top: 54, left: 16, right: 16, flexDirection: "row", alignItems: "flex-start", gap: 12, backgroundColor: p.bg, borderRadius: 18, padding: 14, borderWidth: 1, borderColor: p.line, shadowColor: "#000", shadowOpacity: 0.16, shadowRadius: 18, shadowOffset: { width: 0, height: 8 }, elevation: 8 }
3453
+ style: {
3454
+ position: "absolute",
3455
+ top: 54,
3456
+ left: 16,
3457
+ right: 16,
3458
+ gap: 3,
3459
+ backgroundColor: p.bg === DARK.bg ? p.soft : p.bg,
3460
+ borderRadius: r.card + 2,
3461
+ paddingHorizontal: 16,
3462
+ paddingVertical: 13,
3463
+ shadowColor: "#101218",
3464
+ shadowOpacity: 0.14,
3465
+ shadowRadius: 18,
3466
+ shadowOffset: { width: 0, height: 8 },
3467
+ elevation: 6
3468
+ }
3002
3469
  },
3003
- teamMark(options.title ?? "Chat with us", p.accent, ink),
3004
- (0, import_react2.createElement)(
3005
- RN3.View,
3006
- { style: { flex: 1, gap: 2 } },
3007
- k.muted(`${whoLabel[banner.from]}${unseen > 1 ? ` \xB7 ${unseen} new` : ""}`, { fontWeight: "600", fontSize: 12 }),
3008
- (0, import_react2.createElement)(RN3.Text, { numberOfLines: 3, style: { color: p.fg, fontSize: 15 } }, banner.body)
3009
- )
3470
+ k.muted(`${whoLabel[banner.from]}${unseen > 1 ? ` \xB7 ${unseen} new` : ""}`, { fontWeight: "600", fontSize: 12 }),
3471
+ (0, import_react2.createElement)(RN3.Text, { numberOfLines: 3, style: { color: p.fg, fontSize: 15, lineHeight: 20 } }, banner.body)
3010
3472
  ) : null;
3011
3473
  if (!launcher && !toast && !open) return null;
3012
3474
  return (0, import_react2.createElement)(
@@ -3014,7 +3476,7 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
3014
3476
  { pointerEvents: "box-none", style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 } },
3015
3477
  launcher,
3016
3478
  toast,
3017
- open ? (0, import_react2.createElement)(MessengerSheet, { client, p, o: options, chat, view, setView }) : null
3479
+ open ? (0, import_react2.createElement)(MessengerSheet, { client, p, o: options, chat, view, setView, brand: look?.branding !== false }) : null
3018
3480
  );
3019
3481
  };
3020
3482
 
@@ -3067,7 +3529,7 @@ var MRRocketProvider = ({ client: given, children, consent, flows = true, appear
3067
3529
  Ctx.Provider,
3068
3530
  { value: client },
3069
3531
  heatmaps ? (0, import_react3.createElement)(RN4.View, { style: { flex: 1 }, onTouchStartCapture: (e) => touch(e, false), onTouchEndCapture: (e) => touch(e, true) }, children) : children,
3070
- flows ? (0, import_react3.createElement)(MRRocketFlows, { flows: client.flows, appearance }) : null,
3532
+ flows ? (0, import_react3.createElement)(MRRocketFlows, { flows: client.flows, paywall: client.paywall, appearance }) : null,
3071
3533
  surveys ? (0, import_react3.createElement)(MRRocketSurveys, { client, appearance }) : null,
3072
3534
  feedback ? (0, import_react3.createElement)(MRRocketFeedback, { client, appearance, options: feedback }) : null,
3073
3535
  messenger ? (0, import_react3.createElement)(MRRocketMessenger, { client, appearance, options: messenger }) : null
@@ -3104,6 +3566,10 @@ var usePush = () => {
3104
3566
  const client = useClient();
3105
3567
  return { register: client.registerForPush, unregister: client.unregisterForPush };
3106
3568
  };
3569
+ var usePaywall = () => {
3570
+ const client = useClient();
3571
+ return { open: client.openPaywall };
3572
+ };
3107
3573
  var useOnboarding = () => {
3108
3574
  const client = useClient();
3109
3575
  return { start: client.startOnboarding };