@mrrocket/sdk 0.18.0 → 0.22.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/CHANGELOG.md +29 -0
- package/README.md +36 -1
- package/dist/api-client.d.ts +28 -4
- package/dist/api-client.js +30 -13
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +8 -3
- package/dist/browser/mrrocket-messenger.js +38 -5
- package/dist/browser/mrrocket.js +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +17 -1
- package/dist/client.js +25 -5
- package/dist/client.js.map +1 -1
- package/dist/core.d.ts +5 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/feedback.js +161 -119
- package/dist/feedback.js.map +1 -1
- package/dist/flows-ui.d.ts +1 -1
- package/dist/flows-ui.js +100 -72
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows-web.js +2 -1
- package/dist/flows-web.js.map +1 -1
- package/dist/flows.d.ts +38 -6
- package/dist/flows.js +51 -17
- package/dist/flows.js.map +1 -1
- package/dist/i18n.d.ts +22 -0
- package/dist/i18n.js +74 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +9 -3
- package/dist/messenger-ui.js +324 -152
- package/dist/messenger-ui.js.map +1 -1
- package/dist/messenger.d.ts +3 -1
- package/dist/messenger.js +3 -2
- package/dist/messenger.js.map +1 -1
- package/dist/onboarding-report.d.ts +19 -0
- package/dist/onboarding-report.js +23 -0
- package/dist/onboarding-report.js.map +1 -0
- package/dist/onboarding-state.d.ts +3 -0
- package/dist/onboarding-state.js +7 -1
- package/dist/onboarding-state.js.map +1 -1
- package/dist/paywall.js +2 -1
- package/dist/paywall.js.map +1 -1
- package/dist/react-native/index.d.cts +93 -8
- package/dist/react-native/index.d.ts +93 -8
- package/dist/react-native/index.js +819 -511
- package/dist/react-native/index.js.map +1 -1
- package/dist/server.d.ts +4 -2
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +35 -2
- package/dist/styles-css.js.map +1 -1
- package/dist/survey.js +18 -14
- package/dist/survey.js.map +1 -1
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +15 -12
- package/dist/ui.js.map +1 -1
- package/package.json +6 -3
|
@@ -31,7 +31,9 @@ __export(native_exports, {
|
|
|
31
31
|
SDK_VERSION: () => SDK_VERSION,
|
|
32
32
|
activeRouteName: () => activeRouteName,
|
|
33
33
|
createMRRocket: () => createMRRocket,
|
|
34
|
+
currentLocale: () => currentLocale,
|
|
34
35
|
getMRRocket: () => getMRRocket,
|
|
36
|
+
setLocale: () => setLocale,
|
|
35
37
|
useCancelFlow: () => useCancelFlow,
|
|
36
38
|
useExpoRouterTracking: () => useExpoRouterTracking,
|
|
37
39
|
useFeedback: () => useFeedback,
|
|
@@ -41,6 +43,7 @@ __export(native_exports, {
|
|
|
41
43
|
useMessenger: () => useMessenger,
|
|
42
44
|
useNavigationTracking: () => useNavigationTracking,
|
|
43
45
|
useOnboarding: () => useOnboarding,
|
|
46
|
+
useOnboardingReport: () => useOnboardingReport,
|
|
44
47
|
usePaywall: () => usePaywall,
|
|
45
48
|
usePush: () => usePush,
|
|
46
49
|
useReview: () => useReview,
|
|
@@ -50,8 +53,68 @@ __export(native_exports, {
|
|
|
50
53
|
module.exports = __toCommonJS(native_exports);
|
|
51
54
|
var import_react4 = require("react");
|
|
52
55
|
|
|
56
|
+
// src/i18n.ts
|
|
57
|
+
var g = globalThis;
|
|
58
|
+
var s = g.__mrrocketI18n ?? (g.__mrrocketI18n = { version: 0, onWords: /* @__PURE__ */ new Set(), onLocale: /* @__PURE__ */ new Set() });
|
|
59
|
+
var tell = (listeners) => {
|
|
60
|
+
for (const fn of [...listeners]) {
|
|
61
|
+
try {
|
|
62
|
+
fn();
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var t = (word) => s.words?.[word] || word;
|
|
68
|
+
var tf = (word, vars) => t(word).replace(/\{(\w+)\}/g, (all, k) => k in vars ? String(vars[k]) : all);
|
|
69
|
+
var setWords = (words, lang) => {
|
|
70
|
+
const same2 = s.lang === lang && JSON.stringify(s.words ?? {}) === JSON.stringify(words ?? {});
|
|
71
|
+
s.words = words;
|
|
72
|
+
s.lang = lang;
|
|
73
|
+
if (same2) return;
|
|
74
|
+
s.version++;
|
|
75
|
+
tell(s.onWords);
|
|
76
|
+
};
|
|
77
|
+
var tidy = (locale) => {
|
|
78
|
+
const tag = locale?.trim().replace(/_/g, "-");
|
|
79
|
+
if (!tag) return void 0;
|
|
80
|
+
try {
|
|
81
|
+
return Intl.getCanonicalLocales(tag)[0];
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return e instanceof RangeError ? void 0 : tag;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var setLocale = (locale) => {
|
|
87
|
+
const next = tidy(locale);
|
|
88
|
+
if (next === s.locale) return;
|
|
89
|
+
s.locale = next;
|
|
90
|
+
s.version++;
|
|
91
|
+
tell(s.onWords);
|
|
92
|
+
tell(s.onLocale);
|
|
93
|
+
};
|
|
94
|
+
var setDeviceLocale = (locale) => {
|
|
95
|
+
s.device = tidy(locale);
|
|
96
|
+
};
|
|
97
|
+
var currentLocale = () => s.locale ?? s.device ?? (typeof navigator !== "undefined" && navigator.language || void 0);
|
|
98
|
+
var langParam = () => {
|
|
99
|
+
const l = currentLocale();
|
|
100
|
+
return l ? `&lang=${encodeURIComponent(l)}` : "";
|
|
101
|
+
};
|
|
102
|
+
var wordsVersion = () => s.version;
|
|
103
|
+
var onWords = (fn) => {
|
|
104
|
+
s.onWords.add(fn);
|
|
105
|
+
return () => {
|
|
106
|
+
s.onWords.delete(fn);
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
var onLocale = (fn) => {
|
|
110
|
+
s.onLocale.add(fn);
|
|
111
|
+
return () => {
|
|
112
|
+
s.onLocale.delete(fn);
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
53
116
|
// src/core.ts
|
|
54
|
-
var SDK_VERSION = "0.
|
|
117
|
+
var SDK_VERSION = "0.22.0";
|
|
55
118
|
var propOf = (person, name) => {
|
|
56
119
|
const want = name.trim().toLowerCase();
|
|
57
120
|
for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;
|
|
@@ -77,7 +140,7 @@ var matchesCondition = (c, person) => {
|
|
|
77
140
|
var evaluateFlag = (flag, distinctId, person = {}) => {
|
|
78
141
|
if (!flag.on) return false;
|
|
79
142
|
const subject = { id: distinctId, ...person };
|
|
80
|
-
const set = flag.sets.length ? flag.sets.find((
|
|
143
|
+
const set = flag.sets.length ? flag.sets.find((s2) => s2.conditions.every((c) => matchesCondition(c, subject))) : { conditions: [], rollout: 100 };
|
|
81
144
|
if (!set) return false;
|
|
82
145
|
if (flagUnit(`${flag.key}:rollout:${distinctId}`) * 100 >= set.rollout) return false;
|
|
83
146
|
if (!flag.variants?.length) return true;
|
|
@@ -108,11 +171,11 @@ var parseQuery = (url) => {
|
|
|
108
171
|
const q = url.split("#")[0];
|
|
109
172
|
const at = q.indexOf("?");
|
|
110
173
|
if (at < 0) return out;
|
|
111
|
-
const dec = (
|
|
174
|
+
const dec = (s2) => {
|
|
112
175
|
try {
|
|
113
|
-
return decodeURIComponent(
|
|
176
|
+
return decodeURIComponent(s2.replace(/\+/g, " "));
|
|
114
177
|
} catch {
|
|
115
|
-
return
|
|
178
|
+
return s2;
|
|
116
179
|
}
|
|
117
180
|
};
|
|
118
181
|
for (const pair of q.slice(at + 1).split("&")) {
|
|
@@ -175,204 +238,6 @@ var newDistinctId = () => typeof crypto !== "undefined" && "randomUUID" in crypt
|
|
|
175
238
|
|
|
176
239
|
// src/onboarding-state.ts
|
|
177
240
|
var import_react = require("react");
|
|
178
|
-
var none = [];
|
|
179
|
-
var useOnboardingItem = (flows, id, start) => {
|
|
180
|
-
const [state, setState] = (0, import_react.useState)(() => flows && id ? flows.onboarding.item(id) : null);
|
|
181
|
-
(0, import_react.useEffect)(() => {
|
|
182
|
-
if (!flows || !id) {
|
|
183
|
-
setState(null);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const up = () => {
|
|
187
|
-
const s = flows.onboarding.item(id);
|
|
188
|
-
setState(s);
|
|
189
|
-
if (s && !s.done && !s.dismissed) flows.onboarding.seen(id);
|
|
190
|
-
};
|
|
191
|
-
up();
|
|
192
|
-
return flows.subscribe(up);
|
|
193
|
-
}, [flows, id]);
|
|
194
|
-
return (0, import_react.useMemo)(
|
|
195
|
-
() => ({
|
|
196
|
-
item: state?.item ?? null,
|
|
197
|
-
steps: state?.steps ?? none,
|
|
198
|
-
done: !!state?.done,
|
|
199
|
-
dismissed: !!state?.dismissed,
|
|
200
|
-
eligible: !!state?.eligible,
|
|
201
|
-
complete: (stepId) => {
|
|
202
|
-
if (flows && id) flows.onboarding.complete(id, stepId);
|
|
203
|
-
},
|
|
204
|
-
dismiss: () => {
|
|
205
|
-
if (flows && id) flows.onboarding.dismissItem(id);
|
|
206
|
-
},
|
|
207
|
-
answer: (question, value) => {
|
|
208
|
-
if (flows && id) flows.onboarding.answerItem(id, question, value);
|
|
209
|
-
},
|
|
210
|
-
start
|
|
211
|
-
}),
|
|
212
|
-
[state, flows, id, start]
|
|
213
|
-
);
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// src/identity.ts
|
|
217
|
-
var UNSIGNED_HELP = "401: sign the user id on your server with userHash(id) from @mrrocket/sdk/next/server and pass identify(id, props, { hash }).";
|
|
218
|
-
var warnUnsigned = (status, log = (m) => console.warn(m)) => {
|
|
219
|
-
const g = globalThis;
|
|
220
|
-
if (status !== 401 || g.__mrrocketUnsigned) return;
|
|
221
|
-
g.__mrrocketUnsigned = true;
|
|
222
|
-
log(`[MRRocket] ${UNSIGNED_HELP}`);
|
|
223
|
-
};
|
|
224
|
-
var nextSigned = (signed, id, hash, log = (m) => console.warn(m)) => {
|
|
225
|
-
const h3 = typeof hash === "string" ? hash.trim().toLowerCase() : "";
|
|
226
|
-
if (/^[0-9a-f]{64}$/.test(h3)) return { id, hash: h3 };
|
|
227
|
-
if (h3) log("[MRRocket] identify: that hash is no userHash(id); not sent.");
|
|
228
|
-
return signed && signed.id === id ? signed : null;
|
|
229
|
-
};
|
|
230
|
-
var hashFor = (signed, id) => signed && id && signed.id === id ? signed.hash : void 0;
|
|
231
|
-
var hashForAll = (signed, ids) => signed && ids.some((id) => id === signed.id) && ids.every((id) => !id || id === signed.id) ? signed.hash : void 0;
|
|
232
|
-
|
|
233
|
-
// src/api-client.ts
|
|
234
|
-
var CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
235
|
-
var CHAT_FILES_PER_MESSAGE = 4;
|
|
236
|
-
var CORNERS = {
|
|
237
|
-
round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },
|
|
238
|
-
soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },
|
|
239
|
-
square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 }
|
|
240
|
-
};
|
|
241
|
-
var BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
|
|
242
|
-
var inkOn = (hex) => {
|
|
243
|
-
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
244
|
-
if (!m) return "#ffffff";
|
|
245
|
-
const [r, g, b] = [m[1], m[2], m[3]].map((x) => {
|
|
246
|
-
const c = parseInt(x, 16) / 255;
|
|
247
|
-
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
248
|
-
});
|
|
249
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.45 ? "#111214" : "#ffffff";
|
|
250
|
-
};
|
|
251
|
-
var ApiError = class extends Error {
|
|
252
|
-
constructor(message, status) {
|
|
253
|
-
super(message);
|
|
254
|
-
__publicField(this, "status");
|
|
255
|
-
this.status = status;
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
var qs = (params) => Object.entries(params).filter((e) => !!e[1]).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
259
|
-
var call = async (url, init, signal) => {
|
|
260
|
-
let res;
|
|
261
|
-
try {
|
|
262
|
-
res = await fetch(url, {
|
|
263
|
-
...init,
|
|
264
|
-
signal,
|
|
265
|
-
// A form (a file upload) sets its own multipart type with the boundary.
|
|
266
|
-
headers: typeof init?.body === "string" ? { "content-type": "application/json" } : void 0
|
|
267
|
-
});
|
|
268
|
-
} catch (e) {
|
|
269
|
-
if (e.name === "AbortError") throw e;
|
|
270
|
-
throw new ApiError("Could not reach the server. Check your connection and try again.", 0);
|
|
271
|
-
}
|
|
272
|
-
if (!res.ok) {
|
|
273
|
-
warnUnsigned(res.status);
|
|
274
|
-
let detail = "";
|
|
275
|
-
try {
|
|
276
|
-
detail = (await res.json()).error ?? "";
|
|
277
|
-
} catch {
|
|
278
|
-
}
|
|
279
|
-
throw new ApiError(detail || (res.status === 429 ? "Too many requests. Try again in a minute." : "Something went wrong. Please try again."), res.status);
|
|
280
|
-
}
|
|
281
|
-
return await res.json();
|
|
282
|
-
};
|
|
283
|
-
var createApi = (c) => {
|
|
284
|
-
const base = `${c.host}/api/sdk`;
|
|
285
|
-
const who2 = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };
|
|
286
|
-
const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };
|
|
287
|
-
const post = (path, body, signal) => call(`${base}${path}`, { method: "POST", body: JSON.stringify({ ...who2, ...body }) }, signal);
|
|
288
|
-
return {
|
|
289
|
-
list: (o, signal) => call(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
|
|
290
|
-
post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
291
|
-
roadmap: (signal) => call(`${base}/feedback/roadmap?${qs(me)}`, void 0, signal),
|
|
292
|
-
changelog: (signal) => call(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
293
|
-
submit: (p) => post("/feedback", p),
|
|
294
|
-
vote: (id) => post(`/feedback/${encodeURIComponent(id)}/vote`, {}),
|
|
295
|
-
comments: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
296
|
-
comment: (id, body) => post(`/feedback/${encodeURIComponent(id)}/comments`, { body }),
|
|
297
|
-
survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
|
|
298
|
-
seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
|
|
299
|
-
respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
|
|
300
|
-
chat: (signal) => call(`${base}/messages?${qs(me)}`, void 0, signal),
|
|
301
|
-
/** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */
|
|
302
|
-
sendChat: (m) => post("/messages", m),
|
|
303
|
-
/** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
|
|
304
|
-
uploadChatFile: (file, signal) => {
|
|
305
|
-
const form = new FormData();
|
|
306
|
-
form.append("key", c.apiKey);
|
|
307
|
-
form.append("distinctId", c.distinctId);
|
|
308
|
-
if (c.userHash) form.append("userHash", c.userHash);
|
|
309
|
-
if ("uri" in file) form.append("file", file);
|
|
310
|
-
else form.append("file", file, file.name || "file");
|
|
311
|
-
return call(`${base}/messages/attachments`, { method: "POST", body: form }, signal);
|
|
312
|
-
},
|
|
313
|
-
/** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
|
|
314
|
-
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,
|
|
315
|
-
articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
|
|
316
|
-
article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
317
|
-
messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
318
|
-
rateArticle: (id, helpful) => post(`/articles/${encodeURIComponent(id)}/rate`, { helpful })
|
|
319
|
-
};
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
// src/batch.ts
|
|
323
|
-
var MAX_BATCH = 100;
|
|
324
|
-
var MAX_BYTES = 6e4;
|
|
325
|
-
var byteLength = (s) => {
|
|
326
|
-
let n = 0;
|
|
327
|
-
for (let i = 0; i < s.length; i++) {
|
|
328
|
-
const c = s.charCodeAt(i);
|
|
329
|
-
if (c < 128) n += 1;
|
|
330
|
-
else if (c < 2048) n += 2;
|
|
331
|
-
else if (c >= 55296 && c <= 56319 && i + 1 < s.length && (s.charCodeAt(i + 1) & 64512) === 56320) {
|
|
332
|
-
n += 4;
|
|
333
|
-
i++;
|
|
334
|
-
} else n += 3;
|
|
335
|
-
}
|
|
336
|
-
return n;
|
|
337
|
-
};
|
|
338
|
-
var batchRequests = (key, events, sdk, userHash) => {
|
|
339
|
-
const head = `{"key":${JSON.stringify(key)},${userHash ? `"userHash":${JSON.stringify(userHash)},` : ""}"sdk":${JSON.stringify(sdk)},"events":[`;
|
|
340
|
-
const empty = byteLength(head) + 2;
|
|
341
|
-
const out = [];
|
|
342
|
-
let chunk = [];
|
|
343
|
-
let items = [];
|
|
344
|
-
let size = empty;
|
|
345
|
-
const close = () => {
|
|
346
|
-
if (chunk.length) out.push({ body: `${head}${chunk.join(",")}]}`, events: items });
|
|
347
|
-
chunk = [];
|
|
348
|
-
items = [];
|
|
349
|
-
size = empty;
|
|
350
|
-
};
|
|
351
|
-
for (const event of events) {
|
|
352
|
-
const json = JSON.stringify(event);
|
|
353
|
-
const n = byteLength(json) + 1;
|
|
354
|
-
if (empty + n > MAX_BYTES) continue;
|
|
355
|
-
if (chunk.length === MAX_BATCH || size + n > MAX_BYTES) close();
|
|
356
|
-
chunk.push(json);
|
|
357
|
-
items.push(event);
|
|
358
|
-
size += n;
|
|
359
|
-
}
|
|
360
|
-
close();
|
|
361
|
-
return out;
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
// src/errors.ts
|
|
365
|
-
var MAX_STACK = 8 * 1024;
|
|
366
|
-
var clip = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
367
|
-
var describeError = (error) => {
|
|
368
|
-
if (error instanceof Error) return { type: error.name || "Error", message: clip(error.message || String(error), 1e3), stack: error.stack ? clip(error.stack, MAX_STACK) : void 0 };
|
|
369
|
-
if (typeof error === "string") return { type: "Error", message: clip(error, 1e3) };
|
|
370
|
-
try {
|
|
371
|
-
return { type: "Error", message: clip(JSON.stringify(error) ?? String(error), 1e3) };
|
|
372
|
-
} catch {
|
|
373
|
-
return { type: "Error", message: String(error) };
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
241
|
|
|
377
242
|
// src/flows.ts
|
|
378
243
|
var CANCEL_EVENT = "$cancel_flow";
|
|
@@ -384,18 +249,20 @@ var PLAIN_CANCEL = {
|
|
|
384
249
|
question: "",
|
|
385
250
|
reasons: [],
|
|
386
251
|
offers: {},
|
|
387
|
-
|
|
252
|
+
// Read when it is shown, so it follows the visitor's language.
|
|
253
|
+
get confirm() {
|
|
254
|
+
return { title: t("Cancel your subscription?"), body: t("You keep access until the end of the billing period."), button: t("Cancel subscription") };
|
|
255
|
+
},
|
|
388
256
|
offersFor: "business"
|
|
389
257
|
};
|
|
390
|
-
var FAILED = "Something went wrong. Please try again.";
|
|
391
258
|
var PROGRESS_KEY = "mrrocket:onboarding";
|
|
392
|
-
var eventSlug = (
|
|
259
|
+
var eventSlug = (s2) => s2.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
393
260
|
var SIGNUP = /* @__PURE__ */ new Set(["signed_up", "signup", "sign_up", "registered", "account_created", "user_signed_up"]);
|
|
394
261
|
var matchesTrigger = (trigger, event) => {
|
|
395
|
-
const
|
|
262
|
+
const t2 = eventSlug(trigger);
|
|
396
263
|
const e = eventSlug(event);
|
|
397
|
-
if (!
|
|
398
|
-
return
|
|
264
|
+
if (!t2 || !e) return false;
|
|
265
|
+
return t2 === e || SIGNUP.has(t2) && SIGNUP.has(e);
|
|
399
266
|
};
|
|
400
267
|
var startsOnLoad = (trigger) => /^(first (launch|visit|open|session|login|sign ?in|app open)|app (opened|launched)|on load|every visit|always)?$/i.test(trigger.trim());
|
|
401
268
|
var isPageTrigger = (trigger) => trigger.trim().startsWith("/");
|
|
@@ -404,16 +271,30 @@ var momentOf = (f) => f.when ?? f.trigger;
|
|
|
404
271
|
var manual = (trigger) => eventSlug(trigger) === "from_a_checklist" || eventSlug(trigger) === "from_your_code" || eventSlug(trigger) === eventSlug(NEEDS_NEWER);
|
|
405
272
|
var AFTER_PREFIX = "after:";
|
|
406
273
|
var afterOf = (trigger) => {
|
|
407
|
-
const
|
|
408
|
-
return
|
|
274
|
+
const t2 = (trigger ?? "").trim();
|
|
275
|
+
return t2.toLowerCase().startsWith(AFTER_PREFIX) ? t2.slice(AFTER_PREFIX.length).trim() || null : null;
|
|
409
276
|
};
|
|
410
277
|
var opensOf = (step) => step.opens ?? (step.startsTour ? { kind: "tour", id: step.startsTour } : void 0);
|
|
278
|
+
var OTHER_PREFIX = "other:";
|
|
279
|
+
var scaleOf = (q) => {
|
|
280
|
+
const min = q?.min ?? 0;
|
|
281
|
+
const max = q?.max ?? 10;
|
|
282
|
+
return Array.from({ length: Math.max(0, Math.min(max - min, 20) + 1) }, (_, i) => min + i);
|
|
283
|
+
};
|
|
411
284
|
var answerOk = (q, value) => {
|
|
412
285
|
if (!q || value == null) return false;
|
|
413
|
-
if (q.type === "choice")
|
|
286
|
+
if (q.type === "choice") {
|
|
287
|
+
if (q.other && value.startsWith(OTHER_PREFIX)) return value.slice(OTHER_PREFIX.length).trim().length > 0;
|
|
288
|
+
return (q.options ?? []).some((o) => o.id === value);
|
|
289
|
+
}
|
|
290
|
+
if (q.type === "scale") return scaleOf(q).some((n) => String(n) === value);
|
|
414
291
|
return value.trim().length > 0 && value.length <= 500;
|
|
415
292
|
};
|
|
416
|
-
var answerLabel = (q, value) =>
|
|
293
|
+
var answerLabel = (q, value) => {
|
|
294
|
+
if (q?.type !== "choice") return value.trim();
|
|
295
|
+
if (q.other && value.startsWith(OTHER_PREFIX)) return value.slice(OTHER_PREFIX.length).trim();
|
|
296
|
+
return q.options?.find((o) => o.id === value)?.label ?? value;
|
|
297
|
+
};
|
|
417
298
|
var videoEmbed = (url) => {
|
|
418
299
|
const u = (url ?? "").trim();
|
|
419
300
|
if (!/^https?:\/\//i.test(u)) return null;
|
|
@@ -435,12 +316,14 @@ var runId = () => `cf_${Math.random().toString(36).slice(2, 10)}${Date.now().toS
|
|
|
435
316
|
var discounted = (amount, percent) => Math.round(amount * (1 - percent / 100) * 100) / 100;
|
|
436
317
|
var money = (amount, currency) => {
|
|
437
318
|
try {
|
|
438
|
-
return new Intl.NumberFormat(
|
|
319
|
+
return new Intl.NumberFormat(currentLocale(), { style: "currency", currency }).format(amount);
|
|
439
320
|
} catch {
|
|
440
321
|
return `${amount.toFixed(2)} ${currency}`;
|
|
441
322
|
}
|
|
442
323
|
};
|
|
443
|
-
var offerButton = (o) => o.kind === "contact" ? "Send to the team" : o.kind === "pause" ? "Pause instead" : o.kind === "downgrade" ? "Switch plan" : "Accept offer";
|
|
324
|
+
var offerButton = (o) => o.kind === "contact" ? t("Send to the team") : o.kind === "pause" ? t("Pause instead") : o.kind === "downgrade" ? t("Switch plan") : t("Accept offer");
|
|
325
|
+
var monthsWord = (n) => n === 1 ? t("1 month") : tf("{n} months", { n });
|
|
326
|
+
var forMonths = (n) => tf("for {months}", { months: monthsWord(n) });
|
|
444
327
|
var safeUrl = (url) => {
|
|
445
328
|
const u = (url ?? "").trim();
|
|
446
329
|
return /^https?:\/\//i.test(u) || /^\/(?!\/)/.test(u) ? u : void 0;
|
|
@@ -587,13 +470,13 @@ var createFlows = (env) => {
|
|
|
587
470
|
}
|
|
588
471
|
};
|
|
589
472
|
const merge = (state) => {
|
|
590
|
-
for (const [id,
|
|
473
|
+
for (const [id, s2] of Object.entries(state ?? {})) {
|
|
591
474
|
const p = prog(id);
|
|
592
|
-
for (const d of
|
|
593
|
-
if (
|
|
594
|
-
for (const [k, v] of Object.entries(
|
|
595
|
-
for (const [k, v] of Object.entries(
|
|
596
|
-
for (const [k, v] of Object.entries(
|
|
475
|
+
for (const d of s2.done ?? []) p.done.add(d);
|
|
476
|
+
if (s2.dismissed) p.dismissed = true;
|
|
477
|
+
for (const [k, v] of Object.entries(s2.answers ?? {})) if (!p.answers.has(k)) p.answers.set(k, v === "yes");
|
|
478
|
+
for (const [k, v] of Object.entries(s2.at ?? {})) if (typeof v === "number" && !p.at.has(k)) p.at.set(k, v);
|
|
479
|
+
for (const [k, v] of Object.entries(s2.reached ?? {})) if (typeof v === "number" && (!p.reached.has(k) || v < p.reached.get(k))) p.reached.set(k, v);
|
|
597
480
|
}
|
|
598
481
|
};
|
|
599
482
|
const wakeAt = (until) => {
|
|
@@ -633,14 +516,14 @@ var createFlows = (env) => {
|
|
|
633
516
|
return {
|
|
634
517
|
type: "checklist",
|
|
635
518
|
flow,
|
|
636
|
-
items: items.map((
|
|
637
|
-
const o = opensOf(
|
|
638
|
-
const
|
|
639
|
-
const target =
|
|
519
|
+
items: items.map((s2) => {
|
|
520
|
+
const o = opensOf(s2);
|
|
521
|
+
const t2 = o && (o.kind === "tour" || o.kind === "modal") && o.id !== flow.id ? flowById(o.id) : void 0;
|
|
522
|
+
const target = t2 && t2.shownBy !== "code" && nodesOf(t2).length ? t2 : void 0;
|
|
640
523
|
const opens = o && (o.kind === "page" ? !!o.target.trim() : o.kind === "code" || !!target) ? o : void 0;
|
|
641
524
|
return {
|
|
642
|
-
step:
|
|
643
|
-
done: p.done.has(
|
|
525
|
+
step: s2,
|
|
526
|
+
done: p.done.has(s2.id),
|
|
644
527
|
...target && o?.kind === "tour" ? { tour: target } : {},
|
|
645
528
|
...opens ? { opens } : {},
|
|
646
529
|
...target ? { target } : {}
|
|
@@ -696,7 +579,7 @@ var createFlows = (env) => {
|
|
|
696
579
|
active = null;
|
|
697
580
|
const list = back ? flowById(back.flow) : void 0;
|
|
698
581
|
if (back && list) {
|
|
699
|
-
const item = stepsIn(nodesOf(list)).find((
|
|
582
|
+
const item = stepsIn(nodesOf(list)).find((s2) => s2.id === back.step);
|
|
700
583
|
const p = prog(list.id);
|
|
701
584
|
if (item && !item.event && !progress.get(ended2)?.dismissed && !p.done.has(item.id)) {
|
|
702
585
|
markDone(p, item.id);
|
|
@@ -739,8 +622,8 @@ var createFlows = (env) => {
|
|
|
739
622
|
};
|
|
740
623
|
const allowed = (f) => f.audience === "answered" ? !!f.answered && answers.get(f.answered.question) === f.answered.option : f.audience !== "signed_in" || !!env.personId();
|
|
741
624
|
const predecessorEnded = (id) => {
|
|
742
|
-
const
|
|
743
|
-
return !
|
|
625
|
+
const g2 = flowById(id);
|
|
626
|
+
return !g2 || finished(g2);
|
|
744
627
|
};
|
|
745
628
|
const due = (f, trigger = momentOf(f)) => {
|
|
746
629
|
const path = env.path?.();
|
|
@@ -775,7 +658,7 @@ var createFlows = (env) => {
|
|
|
775
658
|
};
|
|
776
659
|
const answerStep = (flow, step, raw) => {
|
|
777
660
|
const q = step.question;
|
|
778
|
-
const value = q.type === "text" ? raw.trim().slice(0, 500) : raw;
|
|
661
|
+
const value = q.type === "text" ? raw.trim().slice(0, 500) : raw.startsWith(OTHER_PREFIX) ? OTHER_PREFIX + raw.slice(OTHER_PREFIX.length).trim().slice(0, 490) : raw;
|
|
779
662
|
answers.set(q.key, value);
|
|
780
663
|
drafts.delete(step.id);
|
|
781
664
|
markDone(prog(flow.id), step.id);
|
|
@@ -886,7 +769,7 @@ var createFlows = (env) => {
|
|
|
886
769
|
() => finish(c, r),
|
|
887
770
|
() => {
|
|
888
771
|
c.busy = false;
|
|
889
|
-
c.error =
|
|
772
|
+
c.error = t("Something went wrong. Please try again.");
|
|
890
773
|
emit();
|
|
891
774
|
}
|
|
892
775
|
);
|
|
@@ -923,11 +806,11 @@ var createFlows = (env) => {
|
|
|
923
806
|
for (const l of lists) {
|
|
924
807
|
const p = prog(l.id);
|
|
925
808
|
let here = false;
|
|
926
|
-
for (const
|
|
927
|
-
if (
|
|
928
|
-
markDone(p,
|
|
809
|
+
for (const s2 of stepsIn(nodesOf(l)))
|
|
810
|
+
if (s2.kind === "checklist" && s2.event && !p.done.has(s2.id) && matchesTrigger(s2.event, name)) {
|
|
811
|
+
markDone(p, s2.id);
|
|
929
812
|
here = true;
|
|
930
|
-
env.track(ONBOARDING_EVENT, { flow: l.id, step:
|
|
813
|
+
env.track(ONBOARDING_EVENT, { flow: l.id, step: s2.id, kind: s2.kind, action: "completed" });
|
|
931
814
|
}
|
|
932
815
|
if (here) {
|
|
933
816
|
ticked = true;
|
|
@@ -966,8 +849,8 @@ var createFlows = (env) => {
|
|
|
966
849
|
answer: (text2) => {
|
|
967
850
|
const c = cancel;
|
|
968
851
|
if (!c || c.busy || c.screen !== "followup") return;
|
|
969
|
-
const
|
|
970
|
-
c.answer =
|
|
852
|
+
const t2 = text2.slice(0, 500);
|
|
853
|
+
c.answer = t2.trim() ? t2 : void 0;
|
|
971
854
|
emit();
|
|
972
855
|
},
|
|
973
856
|
next: () => {
|
|
@@ -1109,11 +992,22 @@ var createFlows = (env) => {
|
|
|
1109
992
|
drafts.set(v.step.id, String(value).slice(0, 500));
|
|
1110
993
|
emit();
|
|
1111
994
|
},
|
|
995
|
+
skip: () => {
|
|
996
|
+
const v = onboardingView();
|
|
997
|
+
const q = v?.type === "step" && v.step.kind === "question" ? v.step.question : void 0;
|
|
998
|
+
if (v?.type !== "step" || !q?.optional) return;
|
|
999
|
+
drafts.delete(v.step.id);
|
|
1000
|
+
markDone(prog(v.flow.id), v.step.id);
|
|
1001
|
+
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: "question", action: "completed", question: q.key, skipped: true });
|
|
1002
|
+
celebrate(v.flow);
|
|
1003
|
+
save();
|
|
1004
|
+
emit();
|
|
1005
|
+
},
|
|
1112
1006
|
back: () => {
|
|
1113
1007
|
const v = onboardingView();
|
|
1114
1008
|
if (v?.type !== "step" || v.step.kind !== "question" || !v.back) return;
|
|
1115
1009
|
const line = stepsIn(nodesOf(v.flow));
|
|
1116
|
-
const at = line.findIndex((
|
|
1010
|
+
const at = line.findIndex((s2) => s2.id === v.step.id);
|
|
1117
1011
|
const p = prog(v.flow.id);
|
|
1118
1012
|
for (let i = at - 1; i >= 0; i--)
|
|
1119
1013
|
if (p.done.has(line[i].id)) {
|
|
@@ -1128,11 +1022,11 @@ var createFlows = (env) => {
|
|
|
1128
1022
|
const f = flowById(id);
|
|
1129
1023
|
if (!f) return null;
|
|
1130
1024
|
const p = progress.get(f.id);
|
|
1131
|
-
const steps = stepsIn(nodesOf(f)).map((
|
|
1132
|
-
const a =
|
|
1133
|
-
return { ...
|
|
1025
|
+
const steps = stepsIn(nodesOf(f)).map((s2) => {
|
|
1026
|
+
const a = s2.question ? answers.get(s2.question.key) : void 0;
|
|
1027
|
+
return { ...s2, done: !!p?.done.has(s2.id), ...a !== void 0 ? { answer: a } : {} };
|
|
1134
1028
|
});
|
|
1135
|
-
const done = steps.length > 0 && steps.every((
|
|
1029
|
+
const done = steps.length > 0 && steps.every((s2) => s2.done);
|
|
1136
1030
|
const dismissed = !!p?.dismissed;
|
|
1137
1031
|
return { item: f, steps, done, dismissed, eligible: !done && !dismissed && allowed(f) && due(f) };
|
|
1138
1032
|
},
|
|
@@ -1144,21 +1038,21 @@ var createFlows = (env) => {
|
|
|
1144
1038
|
env.track(ONBOARDING_EVENT, { flow: f.id, action: "started" });
|
|
1145
1039
|
}
|
|
1146
1040
|
const p = progress.get(f.id);
|
|
1147
|
-
for (const
|
|
1148
|
-
if (!p?.done.has(
|
|
1149
|
-
shown.add(`s:${
|
|
1150
|
-
env.track(ONBOARDING_EVENT, { flow: f.id, step:
|
|
1151
|
-
if (
|
|
1041
|
+
for (const s2 of stepsIn(nodesOf(f)))
|
|
1042
|
+
if (!p?.done.has(s2.id) && !shown.has(`s:${s2.id}`)) {
|
|
1043
|
+
shown.add(`s:${s2.id}`);
|
|
1044
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, step: s2.id, kind: s2.kind, action: "shown" });
|
|
1045
|
+
if (s2.kind !== "checklist") break;
|
|
1152
1046
|
}
|
|
1153
1047
|
},
|
|
1154
1048
|
complete: (id, stepId) => {
|
|
1155
1049
|
const f = flowById(id);
|
|
1156
|
-
const
|
|
1157
|
-
if (!f || !
|
|
1050
|
+
const s2 = f && stepsIn(nodesOf(f)).find((x) => x.id === stepId);
|
|
1051
|
+
if (!f || !s2 || s2.kind === "question") return;
|
|
1158
1052
|
const p = prog(f.id);
|
|
1159
|
-
if (p.done.has(
|
|
1160
|
-
markDone(p,
|
|
1161
|
-
env.track(ONBOARDING_EVENT, { flow: f.id, step:
|
|
1053
|
+
if (p.done.has(s2.id)) return;
|
|
1054
|
+
markDone(p, s2.id);
|
|
1055
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, step: s2.id, kind: s2.kind, action: "completed" });
|
|
1162
1056
|
ended(f);
|
|
1163
1057
|
},
|
|
1164
1058
|
dismissItem: (id) => {
|
|
@@ -1173,9 +1067,9 @@ var createFlows = (env) => {
|
|
|
1173
1067
|
},
|
|
1174
1068
|
answerItem: (id, question, value) => {
|
|
1175
1069
|
const f = flowById(id);
|
|
1176
|
-
const
|
|
1177
|
-
if (!f || !
|
|
1178
|
-
answerStep(f,
|
|
1070
|
+
const s2 = f && stepsIn(nodesOf(f)).find((x) => x.kind === "question" && (x.id === question || x.question?.key === question));
|
|
1071
|
+
if (!f || !s2?.question || !answerOk(s2.question, value)) return;
|
|
1072
|
+
answerStep(f, s2, value);
|
|
1179
1073
|
ended(f);
|
|
1180
1074
|
}
|
|
1181
1075
|
},
|
|
@@ -1204,6 +1098,237 @@ var createFlows = (env) => {
|
|
|
1204
1098
|
return controller;
|
|
1205
1099
|
};
|
|
1206
1100
|
|
|
1101
|
+
// src/onboarding-report.ts
|
|
1102
|
+
var KIND = { questions: "question", checklist: "checklist", tour: "tour" };
|
|
1103
|
+
var createOnboardingReport = (track, name, options = {}) => {
|
|
1104
|
+
const type = options.type ?? "questions";
|
|
1105
|
+
const kind = KIND[type] ?? "question";
|
|
1106
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1107
|
+
const send = (props) => track(ONBOARDING_EVENT, { code: eventSlug(name), name, type, ...options.order?.length ? { order: options.order } : {}, ...props });
|
|
1108
|
+
return {
|
|
1109
|
+
saw: (step, title) => {
|
|
1110
|
+
if (seen.has(step)) return;
|
|
1111
|
+
seen.add(step);
|
|
1112
|
+
send({ step, title, kind, action: "shown" });
|
|
1113
|
+
},
|
|
1114
|
+
answered: (question, answer, label) => send({ step: question, kind: "question", action: "completed", question, answer, label: label ?? answer }),
|
|
1115
|
+
done: (step) => send({ step, kind, action: "completed" }),
|
|
1116
|
+
closed: (step) => send({ ...step ? { step } : {}, kind, action: "dismissed" })
|
|
1117
|
+
};
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
// src/onboarding-state.ts
|
|
1121
|
+
var none = [];
|
|
1122
|
+
var useOnboardingItem = (flows, id, start) => {
|
|
1123
|
+
const [state, setState] = (0, import_react.useState)(() => flows && id ? flows.onboarding.item(id) : null);
|
|
1124
|
+
(0, import_react.useEffect)(() => {
|
|
1125
|
+
if (!flows || !id) {
|
|
1126
|
+
setState(null);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const up = () => {
|
|
1130
|
+
const s2 = flows.onboarding.item(id);
|
|
1131
|
+
setState(s2);
|
|
1132
|
+
if (s2 && !s2.done && !s2.dismissed) flows.onboarding.seen(id);
|
|
1133
|
+
};
|
|
1134
|
+
up();
|
|
1135
|
+
return flows.subscribe(up);
|
|
1136
|
+
}, [flows, id]);
|
|
1137
|
+
return (0, import_react.useMemo)(
|
|
1138
|
+
() => ({
|
|
1139
|
+
item: state?.item ?? null,
|
|
1140
|
+
steps: state?.steps ?? none,
|
|
1141
|
+
done: !!state?.done,
|
|
1142
|
+
dismissed: !!state?.dismissed,
|
|
1143
|
+
eligible: !!state?.eligible,
|
|
1144
|
+
complete: (stepId) => {
|
|
1145
|
+
if (flows && id) flows.onboarding.complete(id, stepId);
|
|
1146
|
+
},
|
|
1147
|
+
dismiss: () => {
|
|
1148
|
+
if (flows && id) flows.onboarding.dismissItem(id);
|
|
1149
|
+
},
|
|
1150
|
+
answer: (question, value) => {
|
|
1151
|
+
if (flows && id) flows.onboarding.answerItem(id, question, value);
|
|
1152
|
+
},
|
|
1153
|
+
start
|
|
1154
|
+
}),
|
|
1155
|
+
[state, flows, id, start]
|
|
1156
|
+
);
|
|
1157
|
+
};
|
|
1158
|
+
var useReportWith = (track, name, options) => {
|
|
1159
|
+
const key = JSON.stringify(options ?? null);
|
|
1160
|
+
return (0, import_react.useMemo)(() => createOnboardingReport(track, name, options), [track, name, key]);
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
// src/identity.ts
|
|
1164
|
+
var UNSIGNED_HELP = "401: sign the user id on your server with userHash(id) from @mrrocket/sdk/next/server and pass identify(id, props, { hash }).";
|
|
1165
|
+
var warnUnsigned = (status, log = (m) => console.warn(m)) => {
|
|
1166
|
+
const g2 = globalThis;
|
|
1167
|
+
if (status !== 401 || g2.__mrrocketUnsigned) return;
|
|
1168
|
+
g2.__mrrocketUnsigned = true;
|
|
1169
|
+
log(`[MRRocket] ${UNSIGNED_HELP}`);
|
|
1170
|
+
};
|
|
1171
|
+
var nextSigned = (signed, id, hash, log = (m) => console.warn(m)) => {
|
|
1172
|
+
const h3 = typeof hash === "string" ? hash.trim().toLowerCase() : "";
|
|
1173
|
+
if (/^[0-9a-f]{64}$/.test(h3)) return { id, hash: h3 };
|
|
1174
|
+
if (h3) log("[MRRocket] identify: that hash is no userHash(id); not sent.");
|
|
1175
|
+
return signed && signed.id === id ? signed : null;
|
|
1176
|
+
};
|
|
1177
|
+
var hashFor = (signed, id) => signed && id && signed.id === id ? signed.hash : void 0;
|
|
1178
|
+
var hashForAll = (signed, ids) => signed && ids.some((id) => id === signed.id) && ids.every((id) => !id || id === signed.id) ? signed.hash : void 0;
|
|
1179
|
+
|
|
1180
|
+
// src/api-client.ts
|
|
1181
|
+
var CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
1182
|
+
var CHAT_FILES_PER_MESSAGE = 4;
|
|
1183
|
+
var CORNERS = {
|
|
1184
|
+
round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },
|
|
1185
|
+
soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },
|
|
1186
|
+
square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 }
|
|
1187
|
+
};
|
|
1188
|
+
var BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
|
|
1189
|
+
var inkOn = (hex) => {
|
|
1190
|
+
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
1191
|
+
if (!m) return "#ffffff";
|
|
1192
|
+
const [r, g2, b] = [m[1], m[2], m[3]].map((x) => {
|
|
1193
|
+
const c = parseInt(x, 16) / 255;
|
|
1194
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
1195
|
+
});
|
|
1196
|
+
return 0.2126 * r + 0.7152 * g2 + 0.0722 * b > 0.45 ? "#111214" : "#ffffff";
|
|
1197
|
+
};
|
|
1198
|
+
var ApiError = class extends Error {
|
|
1199
|
+
constructor(message, status) {
|
|
1200
|
+
super(message);
|
|
1201
|
+
__publicField(this, "status");
|
|
1202
|
+
this.status = status;
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
var qs = (params, lang = currentLocale()) => Object.entries({ ...params, lang }).filter((e) => !!e[1]).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
1206
|
+
var withLang = (url) => {
|
|
1207
|
+
const q = qs({});
|
|
1208
|
+
return q ? `${url}?${q}` : url;
|
|
1209
|
+
};
|
|
1210
|
+
var call = async (url, init, signal) => {
|
|
1211
|
+
let res;
|
|
1212
|
+
try {
|
|
1213
|
+
res = await fetch(url, {
|
|
1214
|
+
...init,
|
|
1215
|
+
signal,
|
|
1216
|
+
// A form (a file upload) sets its own multipart type with the boundary.
|
|
1217
|
+
headers: typeof init?.body === "string" ? { "content-type": "application/json" } : void 0
|
|
1218
|
+
});
|
|
1219
|
+
} catch (e) {
|
|
1220
|
+
if (e.name === "AbortError") throw e;
|
|
1221
|
+
throw new ApiError(t("Could not reach the server. Check your connection and try again."), 0);
|
|
1222
|
+
}
|
|
1223
|
+
if (!res.ok) {
|
|
1224
|
+
warnUnsigned(res.status);
|
|
1225
|
+
let detail = "";
|
|
1226
|
+
try {
|
|
1227
|
+
detail = (await res.json()).error ?? "";
|
|
1228
|
+
} catch {
|
|
1229
|
+
}
|
|
1230
|
+
throw new ApiError(detail || (res.status === 429 ? t("Too many requests. Try again in a minute.") : t("Something went wrong. Please try again.")), res.status);
|
|
1231
|
+
}
|
|
1232
|
+
return await res.json();
|
|
1233
|
+
};
|
|
1234
|
+
var createApi = (c) => {
|
|
1235
|
+
const base = `${c.host}/api/sdk`;
|
|
1236
|
+
const who2 = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };
|
|
1237
|
+
const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };
|
|
1238
|
+
const post = (path, body, signal) => call(withLang(`${base}${path}`), { method: "POST", body: JSON.stringify({ ...who2, ...body }) }, signal);
|
|
1239
|
+
return {
|
|
1240
|
+
list: (o, signal) => call(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
|
|
1241
|
+
post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
1242
|
+
roadmap: (signal) => call(`${base}/feedback/roadmap?${qs(me)}`, void 0, signal),
|
|
1243
|
+
changelog: (signal) => call(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
1244
|
+
submit: (p) => post("/feedback", p),
|
|
1245
|
+
vote: (id) => post(`/feedback/${encodeURIComponent(id)}/vote`, {}),
|
|
1246
|
+
comments: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
1247
|
+
comment: (id, body) => post(`/feedback/${encodeURIComponent(id)}/comments`, { body }),
|
|
1248
|
+
survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
|
|
1249
|
+
seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
|
|
1250
|
+
respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
|
|
1251
|
+
/** One chat by id, else the visitor's newest; `conversations` lists them all. */
|
|
1252
|
+
chat: (signal, conversation) => call(`${base}/messages?${qs({ ...me, conversation })}`, void 0, signal),
|
|
1253
|
+
/**
|
|
1254
|
+
* A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). `conversation` is
|
|
1255
|
+
* the chat it answers, or "new" to start one; without it, the running chat or else a new one.
|
|
1256
|
+
*/
|
|
1257
|
+
sendChat: (m) => post("/messages", m),
|
|
1258
|
+
/** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
|
|
1259
|
+
uploadChatFile: (file, signal) => {
|
|
1260
|
+
const form = new FormData();
|
|
1261
|
+
form.append("key", c.apiKey);
|
|
1262
|
+
form.append("distinctId", c.distinctId);
|
|
1263
|
+
if (c.userHash) form.append("userHash", c.userHash);
|
|
1264
|
+
if ("uri" in file) form.append("file", file);
|
|
1265
|
+
else form.append("file", file, file.name || "file");
|
|
1266
|
+
return call(withLang(`${base}/messages/attachments`), { method: "POST", body: form }, signal);
|
|
1267
|
+
},
|
|
1268
|
+
/** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
|
|
1269
|
+
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me, "")}`,
|
|
1270
|
+
articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
|
|
1271
|
+
article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
1272
|
+
messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
1273
|
+
rateArticle: (id, helpful) => post(`/articles/${encodeURIComponent(id)}/rate`, { helpful })
|
|
1274
|
+
};
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
// src/batch.ts
|
|
1278
|
+
var MAX_BATCH = 100;
|
|
1279
|
+
var MAX_BYTES = 6e4;
|
|
1280
|
+
var byteLength = (s2) => {
|
|
1281
|
+
let n = 0;
|
|
1282
|
+
for (let i = 0; i < s2.length; i++) {
|
|
1283
|
+
const c = s2.charCodeAt(i);
|
|
1284
|
+
if (c < 128) n += 1;
|
|
1285
|
+
else if (c < 2048) n += 2;
|
|
1286
|
+
else if (c >= 55296 && c <= 56319 && i + 1 < s2.length && (s2.charCodeAt(i + 1) & 64512) === 56320) {
|
|
1287
|
+
n += 4;
|
|
1288
|
+
i++;
|
|
1289
|
+
} else n += 3;
|
|
1290
|
+
}
|
|
1291
|
+
return n;
|
|
1292
|
+
};
|
|
1293
|
+
var batchRequests = (key, events, sdk, userHash) => {
|
|
1294
|
+
const head = `{"key":${JSON.stringify(key)},${userHash ? `"userHash":${JSON.stringify(userHash)},` : ""}"sdk":${JSON.stringify(sdk)},"events":[`;
|
|
1295
|
+
const empty = byteLength(head) + 2;
|
|
1296
|
+
const out = [];
|
|
1297
|
+
let chunk = [];
|
|
1298
|
+
let items = [];
|
|
1299
|
+
let size = empty;
|
|
1300
|
+
const close = () => {
|
|
1301
|
+
if (chunk.length) out.push({ body: `${head}${chunk.join(",")}]}`, events: items });
|
|
1302
|
+
chunk = [];
|
|
1303
|
+
items = [];
|
|
1304
|
+
size = empty;
|
|
1305
|
+
};
|
|
1306
|
+
for (const event of events) {
|
|
1307
|
+
const json = JSON.stringify(event);
|
|
1308
|
+
const n = byteLength(json) + 1;
|
|
1309
|
+
if (empty + n > MAX_BYTES) continue;
|
|
1310
|
+
if (chunk.length === MAX_BATCH || size + n > MAX_BYTES) close();
|
|
1311
|
+
chunk.push(json);
|
|
1312
|
+
items.push(event);
|
|
1313
|
+
size += n;
|
|
1314
|
+
}
|
|
1315
|
+
close();
|
|
1316
|
+
return out;
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
// src/errors.ts
|
|
1320
|
+
var MAX_STACK = 8 * 1024;
|
|
1321
|
+
var clip = (s2, n) => s2.length > n ? `${s2.slice(0, n - 1)}\u2026` : s2;
|
|
1322
|
+
var describeError = (error) => {
|
|
1323
|
+
if (error instanceof Error) return { type: error.name || "Error", message: clip(error.message || String(error), 1e3), stack: error.stack ? clip(error.stack, MAX_STACK) : void 0 };
|
|
1324
|
+
if (typeof error === "string") return { type: "Error", message: clip(error, 1e3) };
|
|
1325
|
+
try {
|
|
1326
|
+
return { type: "Error", message: clip(JSON.stringify(error) ?? String(error), 1e3) };
|
|
1327
|
+
} catch {
|
|
1328
|
+
return { type: "Error", message: String(error) };
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1207
1332
|
// src/paywall.ts
|
|
1208
1333
|
var PAYWALL_EVENT = "$paywall";
|
|
1209
1334
|
var paywallPages = (config, w, distinctId) => {
|
|
@@ -1263,7 +1388,7 @@ var createPaywallHost = (env) => {
|
|
|
1263
1388
|
},
|
|
1264
1389
|
() => {
|
|
1265
1390
|
c.busy = false;
|
|
1266
|
-
c.error = "The purchase did not go through. Please try again.";
|
|
1391
|
+
c.error = t("The purchase did not go through. Please try again.");
|
|
1267
1392
|
emit();
|
|
1268
1393
|
}
|
|
1269
1394
|
);
|
|
@@ -1338,10 +1463,10 @@ var RageDetector = class {
|
|
|
1338
1463
|
__publicField(this, "clicks", []);
|
|
1339
1464
|
__publicField(this, "fired", false);
|
|
1340
1465
|
}
|
|
1341
|
-
push(x, y,
|
|
1342
|
-
const near = this.clicks.filter((c) =>
|
|
1466
|
+
push(x, y, t2) {
|
|
1467
|
+
const near = this.clicks.filter((c) => t2 - c.t <= this.windowMs && Math.hypot(c.x - x, c.y - y) <= this.radius);
|
|
1343
1468
|
if (near.length !== this.clicks.length) this.fired = false;
|
|
1344
|
-
this.clicks = [...near, { x, y, t }];
|
|
1469
|
+
this.clicks = [...near, { x, y, t: t2 }];
|
|
1345
1470
|
if (this.clicks.length >= this.count && !this.fired) {
|
|
1346
1471
|
this.fired = true;
|
|
1347
1472
|
return true;
|
|
@@ -1357,9 +1482,9 @@ var BUFFER_MS = 6e4;
|
|
|
1357
1482
|
var CHUNK_FRAMES = 12;
|
|
1358
1483
|
var CHUNK_CHARS = 7e5;
|
|
1359
1484
|
var MAX_MS = 30 * 6e4;
|
|
1360
|
-
var shareOf = (
|
|
1485
|
+
var shareOf = (s2) => {
|
|
1361
1486
|
let h3 = 2166136261;
|
|
1362
|
-
for (let i = 0; i <
|
|
1487
|
+
for (let i = 0; i < s2.length; i++) h3 = Math.imul(h3 ^ s2.charCodeAt(i), 16777619);
|
|
1363
1488
|
return (h3 >>> 0) / 4294967296;
|
|
1364
1489
|
};
|
|
1365
1490
|
var createRecorder = (deps, st) => {
|
|
@@ -1484,14 +1609,14 @@ var measureMasks = () => Promise.all(
|
|
|
1484
1609
|
(ref) => new Promise((resolve) => {
|
|
1485
1610
|
const v = ref.current;
|
|
1486
1611
|
if (!v) return resolve("none");
|
|
1487
|
-
const
|
|
1612
|
+
const t2 = setTimeout(() => resolve("failed"), 100);
|
|
1488
1613
|
try {
|
|
1489
1614
|
v.measureInWindow((x, y, w, h3) => {
|
|
1490
|
-
clearTimeout(
|
|
1615
|
+
clearTimeout(t2);
|
|
1491
1616
|
resolve(w > 0 && h3 > 0 ? [x, y, w, h3] : "none");
|
|
1492
1617
|
});
|
|
1493
1618
|
} catch {
|
|
1494
|
-
clearTimeout(
|
|
1619
|
+
clearTimeout(t2);
|
|
1495
1620
|
resolve("failed");
|
|
1496
1621
|
}
|
|
1497
1622
|
})
|
|
@@ -1528,7 +1653,7 @@ var MAX_CRUMBS = 20;
|
|
|
1528
1653
|
var MAX_BACKOFF_MS = 5 * 60 * 1e3;
|
|
1529
1654
|
var REQUEST_TIMEOUT_MS = 15e3;
|
|
1530
1655
|
var EAGER_COUNT = 20;
|
|
1531
|
-
var clip2 = (
|
|
1656
|
+
var clip2 = (s2, n) => s2.length > n ? `${s2.slice(0, n - 1)}\u2026` : s2;
|
|
1532
1657
|
var createClient = (options, deps) => {
|
|
1533
1658
|
const apiKey = (options.apiKey ?? "").trim();
|
|
1534
1659
|
const host = (options.host ?? DEFAULT_HOST).trim().replace(/\/+$/, "");
|
|
@@ -1556,6 +1681,8 @@ var createClient = (options, deps) => {
|
|
|
1556
1681
|
context = deps.context();
|
|
1557
1682
|
} catch {
|
|
1558
1683
|
}
|
|
1684
|
+
setDeviceLocale(context.locale);
|
|
1685
|
+
if (options.locale) setLocale(options.locale);
|
|
1559
1686
|
const appId = (deps.appId || "app").replace(/[^A-Za-z0-9._-]/g, "") || "app";
|
|
1560
1687
|
const release = options.release ?? (context.appVersion ? `${context.appVersion}${context.build ? ` (${context.build})` : ""}` : void 0);
|
|
1561
1688
|
const sdk = { v: SDK_VERSION, errors: errorsOn, platform: context.platform };
|
|
@@ -1601,7 +1728,7 @@ var createClient = (options, deps) => {
|
|
|
1601
1728
|
const fetchInApp = async (id) => {
|
|
1602
1729
|
if (!apiKey) return null;
|
|
1603
1730
|
const hash = hashOf(id);
|
|
1604
|
-
const r = await deps.fetch(`${host}/api/sdk/inapp?key=${encodeURIComponent(apiKey)}&distinctId=${encodeURIComponent(id)}${hash ? `&userHash=${hash}` : ""}`, { method: "GET" });
|
|
1731
|
+
const r = await deps.fetch(`${host}/api/sdk/inapp?key=${encodeURIComponent(apiKey)}&distinctId=${encodeURIComponent(id)}${hash ? `&userHash=${hash}` : ""}${langParam()}`, { method: "GET" });
|
|
1605
1732
|
warnUnsigned(r.status, deps.log);
|
|
1606
1733
|
return r.ok ? await r.json() : null;
|
|
1607
1734
|
};
|
|
@@ -1687,9 +1814,9 @@ var createClient = (options, deps) => {
|
|
|
1687
1814
|
const sendId = () => consent === "persistent" ? distinctId() : identified;
|
|
1688
1815
|
const hashOf = (id) => hashFor(signed, id);
|
|
1689
1816
|
const touchSession = () => {
|
|
1690
|
-
const
|
|
1691
|
-
if (!session ||
|
|
1692
|
-
else session.last =
|
|
1817
|
+
const t2 = now();
|
|
1818
|
+
if (!session || t2 - session.last >= SESSION_TIMEOUT_MS) session = { id: newDistinctId(), last: t2 };
|
|
1819
|
+
else session.last = t2;
|
|
1693
1820
|
return session.id;
|
|
1694
1821
|
};
|
|
1695
1822
|
const urlOf = (path = currentScreen) => `app://${appId}${path ?? "/"}`;
|
|
@@ -1878,7 +2005,7 @@ var createClient = (options, deps) => {
|
|
|
1878
2005
|
const fetchConfig = async () => {
|
|
1879
2006
|
if (!apiKey) return;
|
|
1880
2007
|
try {
|
|
1881
|
-
const r = await deps.fetch(`${host}/api/sdk/config?key=${encodeURIComponent(apiKey)}`, { method: "GET" });
|
|
2008
|
+
const r = await deps.fetch(`${host}/api/sdk/config?key=${encodeURIComponent(apiKey)}${langParam()}`, { method: "GET" });
|
|
1882
2009
|
if (!r.ok) {
|
|
1883
2010
|
warn(`Config request answered ${r.status}. Check the public key and the host.`);
|
|
1884
2011
|
return;
|
|
@@ -1886,6 +2013,7 @@ var createClient = (options, deps) => {
|
|
|
1886
2013
|
const c = await r.json();
|
|
1887
2014
|
if (c && Array.isArray(c.experiments)) {
|
|
1888
2015
|
config = c;
|
|
2016
|
+
setWords(c.words, c.lang);
|
|
1889
2017
|
if (consent !== "off") await set(KEYS.config, JSON.stringify(c));
|
|
1890
2018
|
changed();
|
|
1891
2019
|
}
|
|
@@ -2004,7 +2132,10 @@ var createClient = (options, deps) => {
|
|
|
2004
2132
|
}
|
|
2005
2133
|
try {
|
|
2006
2134
|
const c = cached ? JSON.parse(cached) : null;
|
|
2007
|
-
if (c && Array.isArray(c.experiments) && !config)
|
|
2135
|
+
if (c && Array.isArray(c.experiments) && !config) {
|
|
2136
|
+
config = c;
|
|
2137
|
+
setWords(c.words, c.lang);
|
|
2138
|
+
}
|
|
2008
2139
|
} catch {
|
|
2009
2140
|
await remove(KEYS.config);
|
|
2010
2141
|
}
|
|
@@ -2060,6 +2191,9 @@ var createClient = (options, deps) => {
|
|
|
2060
2191
|
await fetchConfig();
|
|
2061
2192
|
markReady();
|
|
2062
2193
|
refreshFlows();
|
|
2194
|
+
onLocale(() => {
|
|
2195
|
+
void fetchConfig().then(refreshFlows);
|
|
2196
|
+
});
|
|
2063
2197
|
};
|
|
2064
2198
|
void start().catch(() => {
|
|
2065
2199
|
loaded = true;
|
|
@@ -2248,13 +2382,13 @@ var createClient = (options, deps) => {
|
|
|
2248
2382
|
return client;
|
|
2249
2383
|
};
|
|
2250
2384
|
var activeRouteName = (state) => {
|
|
2251
|
-
let
|
|
2385
|
+
let s2 = state;
|
|
2252
2386
|
let name;
|
|
2253
|
-
for (let depth = 0;
|
|
2254
|
-
const route =
|
|
2387
|
+
for (let depth = 0; s2 && Array.isArray(s2.routes) && s2.routes.length && depth < 20; depth++) {
|
|
2388
|
+
const route = s2.routes[typeof s2.index === "number" ? s2.index : s2.routes.length - 1];
|
|
2255
2389
|
if (!route) break;
|
|
2256
2390
|
name = route.name ?? name;
|
|
2257
|
-
|
|
2391
|
+
s2 = route.state;
|
|
2258
2392
|
}
|
|
2259
2393
|
return name;
|
|
2260
2394
|
};
|
|
@@ -2410,9 +2544,9 @@ var Finale = ({ kind, accent }) => {
|
|
|
2410
2544
|
RN.View,
|
|
2411
2545
|
{ pointerEvents: "none", accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0 } },
|
|
2412
2546
|
...bits.map((b, i) => {
|
|
2413
|
-
const
|
|
2414
|
-
const fade =
|
|
2415
|
-
const scale = b.shape === "star" ?
|
|
2547
|
+
const t2 = values[i];
|
|
2548
|
+
const fade = t2.interpolate({ inputRange: [0, 0.7, 1], outputRange: [1, 1, 0] });
|
|
2549
|
+
const scale = b.shape === "star" ? t2.interpolate({ inputRange: [0, 0.25, 1], outputRange: [0.2, 1.15, 0.6] }) : 1;
|
|
2416
2550
|
return (0, import_react2.createElement)(
|
|
2417
2551
|
A.View,
|
|
2418
2552
|
{
|
|
@@ -2429,9 +2563,9 @@ var Finale = ({ kind, accent }) => {
|
|
|
2429
2563
|
justifyContent: "center",
|
|
2430
2564
|
opacity: fade,
|
|
2431
2565
|
transform: [
|
|
2432
|
-
{ translateX:
|
|
2433
|
-
{ translateY:
|
|
2434
|
-
{ rotate:
|
|
2566
|
+
{ translateX: t2.interpolate({ inputRange: [0, 1], outputRange: [0, b.dx] }) },
|
|
2567
|
+
{ translateY: t2.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, b.mid, b.dy] }) },
|
|
2568
|
+
{ rotate: t2.interpolate({ inputRange: [0, 1], outputRange: ["0deg", `${b.rot}deg`] }) },
|
|
2435
2569
|
{ scale }
|
|
2436
2570
|
]
|
|
2437
2571
|
}
|
|
@@ -2443,10 +2577,9 @@ var Finale = ({ kind, accent }) => {
|
|
|
2443
2577
|
};
|
|
2444
2578
|
var LIGHT = { bg: "#ffffff", fg: "#1c1d20", muted: "#5d6068", soft: "#f4f5f7", line: "#e4e5e8" };
|
|
2445
2579
|
var DARK = { bg: "#17181b", fg: "#f1f2f4", muted: "#a9acb3", soft: "#232428", line: "#2e2f35" };
|
|
2446
|
-
var months = (n) => `${n} ${n === 1 ? "month" : "months"}`;
|
|
2447
2580
|
var MRRocketFlows = (props) => {
|
|
2448
2581
|
const [, setTick] = (0, import_react2.useState)(0);
|
|
2449
|
-
(0, import_react2.useEffect)(() => props.flows.subscribe(() => setTick((
|
|
2582
|
+
(0, import_react2.useEffect)(() => props.flows.subscribe(() => setTick((t2) => t2 + 1)), [props.flows]);
|
|
2450
2583
|
const fin = props.flows.finale?.() ?? null;
|
|
2451
2584
|
const accent = props.appearance?.accent ?? props.flows.accent?.() ?? "#3f76ff";
|
|
2452
2585
|
return (0, import_react2.createElement)(import_react2.Fragment, null, (0, import_react2.createElement)(FlowsBody, props), fin ? (0, import_react2.createElement)(Finale, { key: fin.key, kind: fin.kind, accent }) : null);
|
|
@@ -2456,8 +2589,8 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2456
2589
|
const dark = appearance.theme === "dark" || (appearance.theme ?? "auto") === "auto" && scheme === "dark";
|
|
2457
2590
|
const p = { ...dark ? DARK : LIGHT, accent: appearance.accent ?? flows.accent?.() ?? "#3f76ff", danger: "#d92d20" };
|
|
2458
2591
|
const [, setTick] = (0, import_react2.useState)(0);
|
|
2459
|
-
(0, import_react2.useEffect)(() => flows.subscribe(() => setTick((
|
|
2460
|
-
(0, import_react2.useEffect)(() => paywall?.subscribe(() => setTick((
|
|
2592
|
+
(0, import_react2.useEffect)(() => flows.subscribe(() => setTick((t2) => t2 + 1)), [flows]);
|
|
2593
|
+
(0, import_react2.useEffect)(() => paywall?.subscribe(() => setTick((t2) => t2 + 1)), [paywall]);
|
|
2461
2594
|
const pw = paywall?.view() ?? null;
|
|
2462
2595
|
const v = flows.view();
|
|
2463
2596
|
const [comment, setComment] = (0, import_react2.useState)("");
|
|
@@ -2546,7 +2679,7 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2546
2679
|
{ length: w.total },
|
|
2547
2680
|
(_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= w.index ? p.accent : p.line } })
|
|
2548
2681
|
) : [],
|
|
2549
|
-
close("Close", host.close)
|
|
2682
|
+
close(t("Close"), host.close)
|
|
2550
2683
|
),
|
|
2551
2684
|
title(pg.title),
|
|
2552
2685
|
pg.body ? muted(pg.body, { fontSize: 15 }) : null,
|
|
@@ -2591,8 +2724,8 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2591
2724
|
)
|
|
2592
2725
|
) : null,
|
|
2593
2726
|
w.error ? text2(w.error, { color: p.danger, fontSize: 13 }) : null,
|
|
2594
|
-
btn(w.busy ? "One moment\u2026" : pg.button || "Continue", "primary", host.next, w.busy || pg.kind === "plans" && !w.plan),
|
|
2595
|
-
w.index ? btn("Back", "quiet", host.back, w.busy) : null
|
|
2727
|
+
btn(w.busy ? t("One moment\u2026") : pg.button || t("Continue"), "primary", host.next, w.busy || pg.kind === "plans" && !w.plan),
|
|
2728
|
+
w.index ? btn(t("Back"), "quiet", host.back, w.busy) : null
|
|
2596
2729
|
],
|
|
2597
2730
|
host.close
|
|
2598
2731
|
);
|
|
@@ -2607,7 +2740,7 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2607
2740
|
{ length: v.steps },
|
|
2608
2741
|
(_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i < v.step ? p.accent : p.line } })
|
|
2609
2742
|
) : [],
|
|
2610
|
-
close("Close", flows.cancel.close)
|
|
2743
|
+
close(t("Close"), flows.cancel.close)
|
|
2611
2744
|
);
|
|
2612
2745
|
const error = v.error ? text2(v.error, { color: p.danger, fontSize: 13 }) : null;
|
|
2613
2746
|
let body = [];
|
|
@@ -2638,45 +2771,45 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2638
2771
|
);
|
|
2639
2772
|
if (v.screen === "survey")
|
|
2640
2773
|
body = [
|
|
2641
|
-
title(v.flow.question || "Why are you leaving?"),
|
|
2774
|
+
title(v.flow.question || t("Why are you leaving?")),
|
|
2642
2775
|
choices(v.flow.reasons.map((r) => ({ key: r.id, label: r.label, on: v.reason === r.id, press: () => flows.cancel.pick(r.id) }))),
|
|
2643
|
-
btn("Continue", "primary", flows.cancel.next, !v.reason),
|
|
2644
|
-
btn("Never mind, keep my plan", "quiet", flows.cancel.close)
|
|
2776
|
+
btn(t("Continue"), "primary", flows.cancel.next, !v.reason),
|
|
2777
|
+
btn(t("Never mind, keep my plan"), "quiet", flows.cancel.close)
|
|
2645
2778
|
];
|
|
2646
2779
|
else if (v.screen === "followup" && v.followUp) {
|
|
2647
2780
|
const f = v.followUp;
|
|
2648
2781
|
const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
|
|
2649
2782
|
body = [
|
|
2650
|
-
label ? muted(
|
|
2783
|
+
label ? muted(tf("You said: {label}", { label })) : null,
|
|
2651
2784
|
title(f.question),
|
|
2652
|
-
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),
|
|
2653
|
-
btn("Continue", "primary", flows.cancel.next),
|
|
2654
|
-
btn("Never mind, keep my plan", "quiet", flows.cancel.close)
|
|
2785
|
+
f.answers.length ? choices(f.answers.map((a, i) => ({ key: String(i), label: f.answerLabels?.[i] || a, on: v.answer === a, press: () => flows.cancel.answer(a) }))) : input(t("Your answer (optional)"), v.answer ?? "", flows.cancel.answer),
|
|
2786
|
+
btn(t("Continue"), "primary", flows.cancel.next),
|
|
2787
|
+
btn(t("Never mind, keep my plan"), "quiet", flows.cancel.close)
|
|
2655
2788
|
];
|
|
2656
2789
|
} else if (v.screen === "offer" && v.offer) {
|
|
2657
2790
|
const o = v.offer;
|
|
2658
2791
|
const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
|
|
2659
|
-
const detail = o.kind === "discount" && o.percent ? v.price ? `${money(discounted(v.price.amount, o.percent), v.price.currency)
|
|
2792
|
+
const detail = o.kind === "discount" && o.percent ? v.price ? `${tf("{price} instead of {old}", { price: money(discounted(v.price.amount, o.percent), v.price.currency), old: money(v.price.amount, v.price.currency) })}${o.months ? ` ${forMonths(o.months)}` : ""}` : `${tf("{percent}% off", { percent: o.percent })}${o.months ? ` ${forMonths(o.months)}` : ""}` : o.kind === "pause" && o.months ? tf("Pause for up to {months}. Your data stays.", { months: monthsWord(o.months) }) : o.kind === "downgrade" && o.planName ? o.planName : null;
|
|
2660
2793
|
body = [
|
|
2661
|
-
label ? muted(
|
|
2794
|
+
label ? muted(tf("You said: {label}", { label })) : null,
|
|
2662
2795
|
title(o.headline),
|
|
2663
2796
|
detail ? (0, import_react2.createElement)(RN.View, { style: { backgroundColor: p.soft, borderRadius: 10, padding: 12 } }, text2(detail, { fontWeight: "600" })) : null,
|
|
2664
|
-
o.kind === "contact" ? input("What would make you stay?") : null,
|
|
2665
|
-
btn(v.busy ? "One moment\u2026" : offerButton(o), "primary", () => flows.cancel.accept(comment), v.busy),
|
|
2666
|
-
btn("No thanks, continue cancelling", "quiet", flows.cancel.decline, v.busy)
|
|
2797
|
+
o.kind === "contact" ? input(t("What would make you stay?")) : null,
|
|
2798
|
+
btn(v.busy ? t("One moment\u2026") : offerButton(o), "primary", () => flows.cancel.accept(comment), v.busy),
|
|
2799
|
+
btn(t("No thanks, continue cancelling"), "quiet", flows.cancel.decline, v.busy)
|
|
2667
2800
|
];
|
|
2668
2801
|
} else
|
|
2669
2802
|
body = [
|
|
2670
2803
|
title(v.flow.confirm.title),
|
|
2671
2804
|
v.flow.confirm.body ? muted(v.flow.confirm.body, { fontSize: 15 }) : null,
|
|
2672
|
-
v.steps > 1 ? input("Anything else we should know? (optional)") : null,
|
|
2673
|
-
btn(v.busy ? "One moment\u2026" : v.flow.confirm.button, "danger", () => flows.cancel.confirm(comment), v.busy),
|
|
2674
|
-
btn("Keep my plan", "secondary", flows.cancel.close, v.busy)
|
|
2805
|
+
v.steps > 1 ? input(t("Anything else we should know? (optional)")) : null,
|
|
2806
|
+
btn(v.busy ? t("One moment\u2026") : v.flow.confirm.button, "danger", () => flows.cancel.confirm(comment), v.busy),
|
|
2807
|
+
btn(t("Keep my plan"), "secondary", flows.cancel.close, v.busy)
|
|
2675
2808
|
];
|
|
2676
2809
|
return sheet([top, ...body, error], flows.cancel.close);
|
|
2677
2810
|
}
|
|
2678
|
-
const media = (
|
|
2679
|
-
const m2 = mediaOf(
|
|
2811
|
+
const media = (s2) => {
|
|
2812
|
+
const m2 = mediaOf(s2);
|
|
2680
2813
|
if (!m2) return null;
|
|
2681
2814
|
if (m2.kind === "image")
|
|
2682
2815
|
return RN.Image ? (0, import_react2.createElement)(RN.Image, {
|
|
@@ -2688,12 +2821,12 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2688
2821
|
}) : null;
|
|
2689
2822
|
const v2 = videoEmbed(m2.url);
|
|
2690
2823
|
const open = v2?.kind === "file" ? v2.src : m2.url;
|
|
2691
|
-
return RN.Linking ? btn(
|
|
2824
|
+
return RN.Linking ? btn(`\u25B6 ${t("Watch the video")}`, "secondary", () => void RN.Linking?.openURL(open).catch(() => {
|
|
2692
2825
|
})) : null;
|
|
2693
2826
|
};
|
|
2694
2827
|
if (v.type === "step" && v.step.kind === "question") {
|
|
2695
|
-
const
|
|
2696
|
-
const q =
|
|
2828
|
+
const s2 = v.step;
|
|
2829
|
+
const q = s2.question;
|
|
2697
2830
|
const ok = answerOk(q, v.answer);
|
|
2698
2831
|
return sheet(
|
|
2699
2832
|
[
|
|
@@ -2704,48 +2837,84 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2704
2837
|
{ length: v.total },
|
|
2705
2838
|
(_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= v.index ? p.accent : p.line } })
|
|
2706
2839
|
) : [],
|
|
2707
|
-
close("Close", flows.onboarding.dismiss)
|
|
2840
|
+
close(t("Close"), flows.onboarding.dismiss)
|
|
2708
2841
|
),
|
|
2709
2842
|
v.index === 0 && v.flow.icon ? (0, import_react2.createElement)(RN.View, { key: "icon" }, iconNode(v.flow.icon, 36, p.accent)) : null,
|
|
2710
|
-
title(
|
|
2711
|
-
|
|
2843
|
+
title(s2.title),
|
|
2844
|
+
s2.body ? muted(s2.body, { fontSize: 15 }) : null,
|
|
2712
2845
|
q?.type === "choice" ? (0, import_react2.createElement)(
|
|
2713
2846
|
RN.ScrollView,
|
|
2714
2847
|
{ key: "opts", style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
|
|
2715
|
-
...
|
|
2716
|
-
|
|
2848
|
+
...[...q.options ?? [], ...q.other ? [{ id: OTHER_PREFIX, label: q.otherLabel || t("Other") }] : []].map((o) => {
|
|
2849
|
+
const isOther = o.id === OTHER_PREFIX;
|
|
2850
|
+
const on = isOther ? !!v.answer?.startsWith(OTHER_PREFIX) : v.answer === o.id;
|
|
2851
|
+
return (0, import_react2.createElement)(
|
|
2717
2852
|
RN.Pressable,
|
|
2718
2853
|
{
|
|
2719
2854
|
key: o.id,
|
|
2720
|
-
onPress: () => flows.onboarding.answer(o.id),
|
|
2855
|
+
onPress: () => isOther && on ? void 0 : flows.onboarding.answer(o.id),
|
|
2721
2856
|
accessibilityRole: "radio",
|
|
2722
|
-
accessibilityState: { checked:
|
|
2857
|
+
accessibilityState: { checked: on },
|
|
2723
2858
|
style: {
|
|
2724
2859
|
minHeight: 44,
|
|
2725
2860
|
paddingHorizontal: 12,
|
|
2726
2861
|
borderRadius: 10,
|
|
2727
2862
|
borderWidth: 1,
|
|
2728
|
-
borderColor:
|
|
2729
|
-
backgroundColor:
|
|
2863
|
+
borderColor: on ? p.accent : p.line,
|
|
2864
|
+
backgroundColor: on ? p.soft : p.bg,
|
|
2730
2865
|
justifyContent: "center"
|
|
2731
2866
|
}
|
|
2732
2867
|
},
|
|
2733
2868
|
text2(o.label, { fontWeight: "500" })
|
|
2734
|
-
)
|
|
2735
|
-
)
|
|
2869
|
+
);
|
|
2870
|
+
}),
|
|
2871
|
+
q.other && v.answer?.startsWith(OTHER_PREFIX) ? (0, import_react2.createElement)(RN.TextInput, {
|
|
2872
|
+
key: "other",
|
|
2873
|
+
value: v.answer.slice(OTHER_PREFIX.length),
|
|
2874
|
+
onChangeText: (t2) => flows.onboarding.answer(OTHER_PREFIX + t2),
|
|
2875
|
+
onSubmitEditing: flows.onboarding.next,
|
|
2876
|
+
placeholder: t("Your answer"),
|
|
2877
|
+
placeholderTextColor: p.muted,
|
|
2878
|
+
maxLength: 490,
|
|
2879
|
+
accessibilityLabel: q.otherLabel || t("Other"),
|
|
2880
|
+
style: { height: 44, borderWidth: 1, borderColor: p.line, borderRadius: 10, paddingHorizontal: 12, color: p.fg }
|
|
2881
|
+
}) : null
|
|
2882
|
+
) : q?.type === "scale" ? (0, import_react2.createElement)(
|
|
2883
|
+
RN.View,
|
|
2884
|
+
{ key: "scale", style: { gap: 6 } },
|
|
2885
|
+
(0, import_react2.createElement)(
|
|
2886
|
+
RN.View,
|
|
2887
|
+
{ style: { flexDirection: "row", gap: 4 }, accessibilityRole: "radiogroup" },
|
|
2888
|
+
...scaleOf(q).map((n) => {
|
|
2889
|
+
const on = v.answer === String(n);
|
|
2890
|
+
return (0, import_react2.createElement)(
|
|
2891
|
+
RN.Pressable,
|
|
2892
|
+
{
|
|
2893
|
+
key: n,
|
|
2894
|
+
onPress: () => flows.onboarding.answer(String(n)),
|
|
2895
|
+
accessibilityRole: "radio",
|
|
2896
|
+
accessibilityState: { checked: on },
|
|
2897
|
+
style: { flex: 1, height: 44, borderRadius: 8, borderWidth: 1, borderColor: on ? p.accent : p.line, backgroundColor: on ? p.accent : p.bg, alignItems: "center", justifyContent: "center" }
|
|
2898
|
+
},
|
|
2899
|
+
text2(String(n), { fontWeight: "500", color: on ? "#fff" : p.fg })
|
|
2900
|
+
);
|
|
2901
|
+
})
|
|
2902
|
+
),
|
|
2903
|
+
q.lowLabel || q.highLabel ? (0, import_react2.createElement)(RN.View, { style: { flexDirection: "row", justifyContent: "space-between" } }, muted(q.lowLabel || ""), muted(q.highLabel || "")) : null
|
|
2736
2904
|
) : q ? (0, import_react2.createElement)(RN.TextInput, {
|
|
2737
2905
|
key: "answer",
|
|
2738
2906
|
value: v.answer ?? "",
|
|
2739
|
-
onChangeText: (
|
|
2907
|
+
onChangeText: (t2) => flows.onboarding.answer(t2),
|
|
2740
2908
|
onSubmitEditing: flows.onboarding.next,
|
|
2741
|
-
placeholder: q.placeholder || "Your answer",
|
|
2909
|
+
placeholder: q.placeholder || t("Your answer"),
|
|
2742
2910
|
placeholderTextColor: p.muted,
|
|
2743
2911
|
maxLength: 500,
|
|
2744
|
-
accessibilityLabel:
|
|
2912
|
+
accessibilityLabel: s2.title,
|
|
2745
2913
|
style: { height: 44, borderWidth: 1, borderColor: p.line, borderRadius: 10, paddingHorizontal: 12, color: p.fg }
|
|
2746
2914
|
}) : null,
|
|
2747
|
-
btn(
|
|
2748
|
-
|
|
2915
|
+
btn(s2.button || (v.last ? t("Done") : t("Continue")), "primary", flows.onboarding.next, !ok),
|
|
2916
|
+
q?.optional ? btn(q.skipLabel || t("Skip"), "quiet", flows.onboarding.skip) : null,
|
|
2917
|
+
v.back ? btn(t("Back"), "quiet", flows.onboarding.back) : null
|
|
2749
2918
|
],
|
|
2750
2919
|
flows.onboarding.dismiss
|
|
2751
2920
|
);
|
|
@@ -2756,14 +2925,14 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2756
2925
|
(0, import_react2.createElement)(
|
|
2757
2926
|
RN.View,
|
|
2758
2927
|
{ style: { flexDirection: "row", alignItems: "center" } },
|
|
2759
|
-
v.total > 1 ? muted(
|
|
2760
|
-
close("Close the tour", flows.onboarding.dismiss)
|
|
2928
|
+
v.total > 1 ? muted(tf("Step {n} of {total}", { n: v.index + 1, total: v.total })) : null,
|
|
2929
|
+
close(t("Close the tour"), flows.onboarding.dismiss)
|
|
2761
2930
|
),
|
|
2762
2931
|
v.step.kind === "modal" && v.flow.icon ? (0, import_react2.createElement)(RN.View, { key: "icon" }, iconNode(v.flow.icon, 36, p.accent)) : null,
|
|
2763
2932
|
media(v.step),
|
|
2764
2933
|
title(v.step.title),
|
|
2765
2934
|
v.step.body ? muted(v.step.body, { fontSize: 15 }) : null,
|
|
2766
|
-
btn(v.step.button || (v.last ? "Done" : "Next"), "primary", flows.onboarding.next)
|
|
2935
|
+
btn(v.step.button || (v.last ? t("Done") : t("Next")), "primary", flows.onboarding.next)
|
|
2767
2936
|
],
|
|
2768
2937
|
flows.onboarding.dismiss
|
|
2769
2938
|
);
|
|
@@ -2777,9 +2946,9 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2777
2946
|
RN.View,
|
|
2778
2947
|
{ key: "t", style: { flexDirection: "row", alignItems: "center" } },
|
|
2779
2948
|
title(v.flow.name),
|
|
2780
|
-
close("Hide the checklist", flows.onboarding.dismiss)
|
|
2949
|
+
close(t("Hide the checklist"), flows.onboarding.dismiss)
|
|
2781
2950
|
),
|
|
2782
|
-
muted(
|
|
2951
|
+
muted(tf("{done} of {total} done", { done, total: v.items.length })),
|
|
2783
2952
|
...v.items.map(
|
|
2784
2953
|
({ step, done: d, opens }) => (0, import_react2.createElement)(
|
|
2785
2954
|
RN.Pressable,
|
|
@@ -2831,14 +3000,14 @@ var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
|
2831
3000
|
RN.View,
|
|
2832
3001
|
{ key: "t", style: { flexDirection: "row", alignItems: "center", gap: 8 } },
|
|
2833
3002
|
(0, import_react2.createElement)(RN.View, { style: { flex: 1 } }, text2(m.title, { fontWeight: "700" }), m.body ? muted(m.body) : null),
|
|
2834
|
-
close("Dismiss", flows.message.dismiss)
|
|
3003
|
+
close(t("Dismiss"), flows.message.dismiss)
|
|
2835
3004
|
),
|
|
2836
3005
|
m.button ? btn(m.button, "primary", flows.message.click) : null
|
|
2837
3006
|
])
|
|
2838
3007
|
);
|
|
2839
3008
|
return sheet(
|
|
2840
3009
|
[
|
|
2841
|
-
(0, import_react2.createElement)(RN.View, { key: "t", style: { flexDirection: "row" } }, close("Dismiss", flows.message.dismiss)),
|
|
3010
|
+
(0, import_react2.createElement)(RN.View, { key: "t", style: { flexDirection: "row" } }, close(t("Dismiss"), flows.message.dismiss)),
|
|
2842
3011
|
title(m.title),
|
|
2843
3012
|
m.body ? muted(m.body, { fontSize: 15 }) : null,
|
|
2844
3013
|
m.button ? btn(m.button, "primary", flows.message.click) : null
|
|
@@ -2912,10 +3081,10 @@ var expoPush = () => {
|
|
|
2912
3081
|
},
|
|
2913
3082
|
token: async (projectId) => {
|
|
2914
3083
|
if (RN2.Platform.OS === "android" && n.setNotificationChannelAsync)
|
|
2915
|
-
await n.setNotificationChannelAsync("default", { name: "Default", importance: n.AndroidImportance?.HIGH ?? 4 });
|
|
3084
|
+
await n.setNotificationChannelAsync("default", { name: t("Default"), importance: n.AndroidImportance?.HIGH ?? 4 });
|
|
2916
3085
|
const id = projectId ?? constants.expoConfig?.extra?.eas?.projectId ?? constants.easConfig?.projectId;
|
|
2917
|
-
const
|
|
2918
|
-
return typeof
|
|
3086
|
+
const push = await n.getExpoPushTokenAsync(id ? { projectId: id } : void 0);
|
|
3087
|
+
return typeof push?.data === "string" && push.data ? push.data : null;
|
|
2919
3088
|
},
|
|
2920
3089
|
onOpen: (listener) => {
|
|
2921
3090
|
const sub = n.addNotificationResponseReceivedListener((r) => listener(dataOf(r) ?? {}));
|
|
@@ -2926,8 +3095,8 @@ var expoPush = () => {
|
|
|
2926
3095
|
};
|
|
2927
3096
|
var asyncStorage = () => {
|
|
2928
3097
|
const m = asyncStorageModule;
|
|
2929
|
-
const
|
|
2930
|
-
return
|
|
3098
|
+
const s2 = m?.default ?? m;
|
|
3099
|
+
return s2 && typeof s2.getItem === "function" && typeof s2.setItem === "function" && typeof s2.removeItem === "function" ? s2 : null;
|
|
2931
3100
|
};
|
|
2932
3101
|
var DEVICE_TYPES = { 1: "phone", 2: "tablet", 3: "desktop", 4: "tv" };
|
|
2933
3102
|
var text = (v, max = 80) => typeof v === "string" && v.trim() ? v.trim().slice(0, max) : typeof v === "number" ? String(v) : void 0;
|
|
@@ -2971,10 +3140,10 @@ var recorderDeps = () => {
|
|
|
2971
3140
|
}
|
|
2972
3141
|
return {
|
|
2973
3142
|
capture: async (width) => {
|
|
2974
|
-
const
|
|
2975
|
-
if (!
|
|
3143
|
+
const s2 = RN2.Dimensions.get("screen");
|
|
3144
|
+
if (!s2?.width) return null;
|
|
2976
3145
|
const w = Math.round(width);
|
|
2977
|
-
const h3 = Math.round(
|
|
3146
|
+
const h3 = Math.round(s2.height / s2.width * width);
|
|
2978
3147
|
const img = await capture({ format: "jpg", quality: 0.5, result: "base64", width: w, height: h3 });
|
|
2979
3148
|
return img ? { img: img.replace(/^data:image\/\w+;base64,/, ""), w, h: h3 } : null;
|
|
2980
3149
|
},
|
|
@@ -3001,8 +3170,8 @@ var nativeDeps = (overrides = {}) => ({
|
|
|
3001
3170
|
context: readContext,
|
|
3002
3171
|
appId: text(expoApplication?.applicationId, 150),
|
|
3003
3172
|
screen: () => {
|
|
3004
|
-
const
|
|
3005
|
-
return
|
|
3173
|
+
const s2 = RN2.Dimensions.get("screen");
|
|
3174
|
+
return s2 ? { w: s2.width, h: s2.height } : void 0;
|
|
3006
3175
|
},
|
|
3007
3176
|
initialUrl: () => RN2.Linking.getInitialURL(),
|
|
3008
3177
|
openUrl: (url) => void RN2.Linking.openURL(url).catch(() => void 0),
|
|
@@ -3100,7 +3269,7 @@ var kit = (p) => {
|
|
|
3100
3269
|
RN3.View,
|
|
3101
3270
|
{ style: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, paddingTop: 16, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: p.line } },
|
|
3102
3271
|
title(heading),
|
|
3103
|
-
closeX("Close", onClose)
|
|
3272
|
+
closeX(t("Close"), onClose)
|
|
3104
3273
|
),
|
|
3105
3274
|
...children
|
|
3106
3275
|
)
|
|
@@ -3108,13 +3277,13 @@ var kit = (p) => {
|
|
|
3108
3277
|
return { text: text2, muted, title, btn, closeX, field, chip, page };
|
|
3109
3278
|
};
|
|
3110
3279
|
var timeAgo = (iso) => {
|
|
3111
|
-
const
|
|
3112
|
-
if (Number.isNaN(
|
|
3113
|
-
const d = Math.max(0, Date.now() -
|
|
3114
|
-
if (d < 60) return "just now";
|
|
3115
|
-
if (d < 3600) return
|
|
3116
|
-
if (d < 86400) return
|
|
3117
|
-
return
|
|
3280
|
+
const at = new Date(iso).getTime();
|
|
3281
|
+
if (Number.isNaN(at)) return "";
|
|
3282
|
+
const d = Math.max(0, Date.now() - at) / 1e3;
|
|
3283
|
+
if (d < 60) return t("just now");
|
|
3284
|
+
if (d < 3600) return tf("{n}m ago", { n: Math.floor(d / 60) });
|
|
3285
|
+
if (d < 86400) return tf("{n}h ago", { n: Math.floor(d / 3600) });
|
|
3286
|
+
return tf("{n}d ago", { n: Math.floor(d / 86400) });
|
|
3118
3287
|
};
|
|
3119
3288
|
var opener = () => {
|
|
3120
3289
|
let open = false;
|
|
@@ -3153,8 +3322,8 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
3153
3322
|
}, [api, survey.id]);
|
|
3154
3323
|
(0, import_react3.useEffect)(() => {
|
|
3155
3324
|
if (!done) return;
|
|
3156
|
-
const
|
|
3157
|
-
return () => clearTimeout(
|
|
3325
|
+
const t2 = setTimeout(onDone, 2500);
|
|
3326
|
+
return () => clearTimeout(t2);
|
|
3158
3327
|
}, [done, onDone]);
|
|
3159
3328
|
const finish = (completed, all = answers) => {
|
|
3160
3329
|
if (finished.current) return;
|
|
@@ -3166,7 +3335,7 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
3166
3335
|
onDone();
|
|
3167
3336
|
};
|
|
3168
3337
|
const next = () => {
|
|
3169
|
-
if (q.required && !isAnswered(value)) return setError("Please answer this question.");
|
|
3338
|
+
if (q.required && !isAnswered(value)) return setError(t("Please answer this question."));
|
|
3170
3339
|
setError("");
|
|
3171
3340
|
if (!last) return setStep(step + 1);
|
|
3172
3341
|
finish(true);
|
|
@@ -3196,14 +3365,14 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
3196
3365
|
onPress: () => pick(n),
|
|
3197
3366
|
accessibilityRole: "radio",
|
|
3198
3367
|
accessibilityState: { checked: on },
|
|
3199
|
-
accessibilityLabel:
|
|
3368
|
+
accessibilityLabel: tf("{n} of {total}", { n, total: scale - 1 + start }),
|
|
3200
3369
|
style: { width: 40, height: 40, borderRadius: 8, alignItems: "center", justifyContent: "center", borderWidth: 1, borderColor: on ? p.accent : p.line, backgroundColor: on ? p.accent : p.bg }
|
|
3201
3370
|
},
|
|
3202
3371
|
k.text(String(n), { color: on ? "#ffffff" : p.fg, fontWeight: "600" })
|
|
3203
3372
|
);
|
|
3204
3373
|
})
|
|
3205
3374
|
),
|
|
3206
|
-
q.low || q.high || q.kind === "nps" ? (0, import_react3.createElement)(RN3.View, { style: { flexDirection: "row", justifyContent: "space-between" } }, k.muted(q.low ?? (q.kind === "nps" ? "Not likely" : "")), k.muted(q.high ?? (q.kind === "nps" ? "Very likely" : ""))) : null
|
|
3375
|
+
q.low || q.high || q.kind === "nps" ? (0, import_react3.createElement)(RN3.View, { style: { flexDirection: "row", justifyContent: "space-between" } }, k.muted(q.low ?? (q.kind === "nps" ? t("Not likely") : "")), k.muted(q.high ?? (q.kind === "nps" ? t("Very likely") : ""))) : null
|
|
3207
3376
|
);
|
|
3208
3377
|
} else if (q.kind === "single" || q.kind === "multi") {
|
|
3209
3378
|
const multi = q.kind === "multi";
|
|
@@ -3211,7 +3380,7 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
3211
3380
|
input = (0, import_react3.createElement)(
|
|
3212
3381
|
RN3.View,
|
|
3213
3382
|
{ style: { gap: 6 } },
|
|
3214
|
-
...(q.options ?? []).map((o) => {
|
|
3383
|
+
...(q.options ?? []).map((o, i) => {
|
|
3215
3384
|
const on = multi ? picked.includes(o) : value === o;
|
|
3216
3385
|
return (0, import_react3.createElement)(
|
|
3217
3386
|
RN3.Pressable,
|
|
@@ -3222,24 +3391,25 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
3222
3391
|
accessibilityState: { checked: on },
|
|
3223
3392
|
style: { minHeight: 44, paddingHorizontal: 12, borderRadius: 10, justifyContent: "center", borderWidth: 1, borderColor: on ? p.accent : p.line, backgroundColor: on ? p.soft : p.bg }
|
|
3224
3393
|
},
|
|
3225
|
-
|
|
3394
|
+
// The label in the person's language; what is recorded stays the option itself.
|
|
3395
|
+
k.text(q.labels?.[i] || o, { fontWeight: "500" })
|
|
3226
3396
|
);
|
|
3227
3397
|
})
|
|
3228
3398
|
);
|
|
3229
|
-
} else input = k.field({ value: value ?? "", onChange: pick, placeholder: q.placeholder ?? "Type your answer", multiline: true });
|
|
3230
|
-
const body = done ? [k.text(survey.thanks || "Thanks for your feedback.", { fontWeight: "600", textAlign: "center", paddingVertical: 16 })] : [
|
|
3399
|
+
} else input = k.field({ value: value ?? "", onChange: pick, placeholder: q.placeholder ?? t("Type your answer"), multiline: true });
|
|
3400
|
+
const body = done ? [k.text(survey.thanks || t("Thanks for your feedback."), { fontWeight: "600", textAlign: "center", paddingVertical: 16 })] : [
|
|
3231
3401
|
(0, import_react3.createElement)(
|
|
3232
3402
|
RN3.View,
|
|
3233
3403
|
{ key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
|
|
3234
3404
|
...survey.questions.map((x, i) => (0, import_react3.createElement)(RN3.View, { key: x.key, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= step ? p.accent : p.line } })),
|
|
3235
3405
|
(0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
|
|
3236
|
-
k.closeX("Close survey", dismiss)
|
|
3406
|
+
k.closeX(t("Close survey"), dismiss)
|
|
3237
3407
|
),
|
|
3238
|
-
(0, import_react3.createElement)(RN3.Text, { key: "q", accessibilityRole: "header", style: { color: p.fg, fontSize: 17, fontWeight: "700" } }, q.title, q.required ? "" : (0, import_react3.createElement)(RN3.Text, { style: { color: p.muted, fontSize: 13, fontWeight: "400" } }, "
|
|
3408
|
+
(0, import_react3.createElement)(RN3.Text, { key: "q", accessibilityRole: "header", style: { color: p.fg, fontSize: 17, fontWeight: "700" } }, q.title, q.required ? "" : (0, import_react3.createElement)(RN3.Text, { style: { color: p.muted, fontSize: 13, fontWeight: "400" } }, ` ${t("optional")}`)),
|
|
3239
3409
|
q.description ? k.muted(q.description) : null,
|
|
3240
3410
|
input,
|
|
3241
3411
|
error ? k.text(error, { color: p.danger, fontSize: 13 }) : null,
|
|
3242
|
-
k.btn(last ? "Send" : "Next", "primary", next)
|
|
3412
|
+
k.btn(last ? t("Send") : t("Next"), "primary", next)
|
|
3243
3413
|
];
|
|
3244
3414
|
return (0, import_react3.createElement)(
|
|
3245
3415
|
RN3.Modal,
|
|
@@ -3272,9 +3442,9 @@ var MRRocketSurveys = ({ client, appearance }) => {
|
|
|
3272
3442
|
const api = client.api();
|
|
3273
3443
|
if (stop || !api) return;
|
|
3274
3444
|
api.survey("", ctrl.signal).then((r) => {
|
|
3275
|
-
const
|
|
3276
|
-
if (stop || !
|
|
3277
|
-
setSurvey({ s, api });
|
|
3445
|
+
const s2 = r.survey;
|
|
3446
|
+
if (stop || !s2 || !s2.questions.length || client.isSurveyDone(s2.id)) return;
|
|
3447
|
+
setSurvey({ s: s2, api });
|
|
3278
3448
|
}).catch(() => void 0);
|
|
3279
3449
|
});
|
|
3280
3450
|
}, 3e3);
|
|
@@ -3312,7 +3482,7 @@ var FeedbackSheet = ({ client, p, title }) => {
|
|
|
3312
3482
|
const user = client.getUser();
|
|
3313
3483
|
(0, import_react3.useEffect)(() => {
|
|
3314
3484
|
const api = client.api();
|
|
3315
|
-
if (!api) return setError("Feedback is off while tracking is off.");
|
|
3485
|
+
if (!api) return setError(t("Feedback is off while tracking is off."));
|
|
3316
3486
|
const ctrl = new AbortController();
|
|
3317
3487
|
api.list({ sort: "top" }, ctrl.signal).then((l) => {
|
|
3318
3488
|
setPosts(l.posts);
|
|
@@ -3345,8 +3515,8 @@ var FeedbackSheet = ({ client, p, title }) => {
|
|
|
3345
3515
|
const tabs = (0, import_react3.createElement)(
|
|
3346
3516
|
RN3.View,
|
|
3347
3517
|
{ style: { flexDirection: "row", gap: 8, paddingHorizontal: 16, paddingTop: 12 } },
|
|
3348
|
-
k.chip("Top requests", tab === "top", () => setTab("top")),
|
|
3349
|
-
k.chip("Suggest", tab === "new", () => {
|
|
3518
|
+
k.chip(t("Top requests"), tab === "top", () => setTab("top")),
|
|
3519
|
+
k.chip(t("Suggest"), tab === "new", () => {
|
|
3350
3520
|
setTab("new");
|
|
3351
3521
|
setSent(false);
|
|
3352
3522
|
})
|
|
@@ -3355,8 +3525,8 @@ var FeedbackSheet = ({ client, p, title }) => {
|
|
|
3355
3525
|
RN3.ScrollView,
|
|
3356
3526
|
{ style: { flex: 1 }, contentContainerStyle: { padding: 16, gap: 10 } },
|
|
3357
3527
|
error ? k.text(error, { color: p.danger, fontSize: 13 }) : null,
|
|
3358
|
-
posts === null && !error ? k.muted("Loading\u2026") : null,
|
|
3359
|
-
posts?.length === 0 ? k.muted("No requests yet. Be the first to suggest one.") : null,
|
|
3528
|
+
posts === null && !error ? k.muted(t("Loading\u2026")) : null,
|
|
3529
|
+
posts?.length === 0 ? k.muted(t("No requests yet. Be the first to suggest one.")) : null,
|
|
3360
3530
|
...(posts ?? []).map(
|
|
3361
3531
|
(post) => (0, import_react3.createElement)(
|
|
3362
3532
|
RN3.View,
|
|
@@ -3367,7 +3537,7 @@ var FeedbackSheet = ({ client, p, title }) => {
|
|
|
3367
3537
|
onPress: () => vote(post),
|
|
3368
3538
|
accessibilityRole: "button",
|
|
3369
3539
|
accessibilityState: { selected: post.voted },
|
|
3370
|
-
accessibilityLabel:
|
|
3540
|
+
accessibilityLabel: post.voted ? tf("Remove your vote from {title}. {votes}", { title: post.title, votes: post.votes === 1 ? t("1 vote") : tf("{n} votes", { n: post.votes }) }) : tf("Upvote {title}. {votes}", { title: post.title, votes: post.votes === 1 ? t("1 vote") : tf("{n} votes", { n: post.votes }) }),
|
|
3371
3541
|
style: { width: 44, minHeight: 48, borderRadius: 10, alignItems: "center", justifyContent: "center", borderWidth: 1, borderColor: post.voted ? p.accent : p.line }
|
|
3372
3542
|
},
|
|
3373
3543
|
k.text("\u25B2", { fontSize: 11, color: post.voted ? p.accent : p.muted }),
|
|
@@ -3380,36 +3550,39 @@ var FeedbackSheet = ({ client, p, title }) => {
|
|
|
3380
3550
|
const form = (0, import_react3.createElement)(
|
|
3381
3551
|
RN3.ScrollView,
|
|
3382
3552
|
{ style: { flex: 1 }, contentContainerStyle: { padding: 16, gap: 10 }, keyboardShouldPersistTaps: "handled" },
|
|
3383
|
-
sent ? k.text("Thanks! Your request is on the board.", { fontWeight: "600" }) : null,
|
|
3553
|
+
sent ? k.text(t("Thanks! Your request is on the board."), { fontWeight: "600" }) : null,
|
|
3384
3554
|
boards.length > 1 ? (0, import_react3.createElement)(RN3.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: 6 } }, ...boards.map((b) => k.chip(b.label, board === b.id, () => setBoard(b.id), b.id))) : null,
|
|
3385
|
-
k.field({ value: subject, onChange: setSubject, placeholder: "Short title", max: 120 }),
|
|
3386
|
-
k.field({ value: details, onChange: setDetails, placeholder: "Details (optional)", multiline: true }),
|
|
3555
|
+
k.field({ value: subject, onChange: setSubject, placeholder: t("Short title"), max: 120 }),
|
|
3556
|
+
k.field({ value: details, onChange: setDetails, placeholder: t("Details (optional)"), multiline: true }),
|
|
3387
3557
|
...user?.email ? [] : [
|
|
3388
|
-
k.muted("Your name and email are optional; they let the team follow up."),
|
|
3389
|
-
k.field({ value: name, onChange: setName, placeholder: "Your name (optional)", max: 100 }),
|
|
3390
|
-
k.field({ value: email, onChange: setEmail, placeholder: "Your email (optional)", max: 200, email: true })
|
|
3558
|
+
k.muted(t("Your name and email are optional; they let the team follow up.")),
|
|
3559
|
+
k.field({ value: name, onChange: setName, placeholder: t("Your name (optional)"), max: 100 }),
|
|
3560
|
+
k.field({ value: email, onChange: setEmail, placeholder: t("Your email (optional)"), max: 200, email: true })
|
|
3391
3561
|
],
|
|
3392
3562
|
error ? k.text(error, { color: p.danger, fontSize: 13 }) : null,
|
|
3393
|
-
k.btn(sending ? "Sending\u2026" : "Send request", "primary", submit, subject.trim().length < 3 || sending)
|
|
3563
|
+
k.btn(sending ? t("Sending\u2026") : t("Send request"), "primary", submit, subject.trim().length < 3 || sending)
|
|
3394
3564
|
);
|
|
3395
3565
|
return k.page(title, close, [tabs, tab === "top" ? list : form]);
|
|
3396
3566
|
};
|
|
3397
3567
|
var MRRocketFeedback = ({ client, appearance, options = {} }) => {
|
|
3398
3568
|
const p = usePalette(appearance);
|
|
3399
3569
|
const open = feedbackOpen.useOpen();
|
|
3400
|
-
return open ? (0, import_react3.createElement)(FeedbackSheet, { client, p, title: options.title ?? "Feedback" }) : null;
|
|
3570
|
+
return open ? (0, import_react3.createElement)(FeedbackSheet, { client, p, title: options.title ?? t("Feedback") }) : null;
|
|
3401
3571
|
};
|
|
3402
3572
|
var POLL_OPEN_MS = 4e3;
|
|
3403
3573
|
var POLL_WAITING_MS = 8e3;
|
|
3404
3574
|
var POLL_IDLE_MS = 3e4;
|
|
3575
|
+
var POLL_QUIET_MS = 6e4;
|
|
3405
3576
|
var WAITING_FOR_MS = 10 * 6e4;
|
|
3406
3577
|
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
3407
|
-
var whoLabel =
|
|
3408
|
-
var who = (from, agentName) => from === "ai" && agentName?.trim() || whoLabel
|
|
3578
|
+
var whoLabel = (from) => from === "visitor" ? t("You") : from === "ai" ? t("AI agent") : t("Team");
|
|
3579
|
+
var who = (from, agentName) => from === "ai" && agentName?.trim() || whoLabel(from);
|
|
3409
3580
|
var msgView = "home";
|
|
3581
|
+
var msgChat = null;
|
|
3410
3582
|
var messengerOpen = opener();
|
|
3411
|
-
var openAt = (v = "home") => {
|
|
3583
|
+
var openAt = (v = "home", chatId = null) => {
|
|
3412
3584
|
msgView = v;
|
|
3585
|
+
msgChat = chatId;
|
|
3413
3586
|
messengerOpen.set(true);
|
|
3414
3587
|
};
|
|
3415
3588
|
var useMessenger = () => ({
|
|
@@ -3436,7 +3609,10 @@ try {
|
|
|
3436
3609
|
imagePicker = null;
|
|
3437
3610
|
}
|
|
3438
3611
|
var PHOTO_TYPES = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif" };
|
|
3439
|
-
var
|
|
3612
|
+
var samePlace = (a, b) => a.chatId === b.chatId && a.fresh === b.fresh;
|
|
3613
|
+
var shownChat = (at, chats) => at.fresh ? null : at.chatId ?? (chats.length === 1 ? chats[0].id : null);
|
|
3614
|
+
var isAnswer = (m) => m.from === "team" || m.from === "ai";
|
|
3615
|
+
var lineOf = (m) => m.body || (m.attachments?.length === 1 ? tf("Sent {name}", { name: m.attachments[0].name }) : m.attachments?.length ? tf("Sent {n} files", { n: m.attachments.length }) : "");
|
|
3440
3616
|
var svg2 = null;
|
|
3441
3617
|
try {
|
|
3442
3618
|
svg2 = require("react-native-svg");
|
|
@@ -3473,42 +3649,80 @@ var HU = {
|
|
|
3473
3649
|
var hairline = { shadowColor: "#000", shadowOpacity: 0.07, shadowRadius: 3, shadowOffset: { width: 0, height: 1 }, elevation: 1 };
|
|
3474
3650
|
var useChat = (client, o, open, view) => {
|
|
3475
3651
|
const [thread, setThread] = (0, import_react3.useState)(null);
|
|
3652
|
+
const [chats, setChats] = (0, import_react3.useState)([]);
|
|
3653
|
+
const [hasEmail, setHasEmail] = (0, import_react3.useState)(false);
|
|
3654
|
+
const [place, setPlace] = (0, import_react3.useState)({ chatId: null, fresh: false });
|
|
3476
3655
|
const [loaded, setLoaded] = (0, import_react3.useState)(false);
|
|
3477
3656
|
const [error, setError] = (0, import_react3.useState)("");
|
|
3478
3657
|
const [unseen, setUnseen] = (0, import_react3.useState)([]);
|
|
3479
|
-
const known = (0, import_react3.useRef)(
|
|
3658
|
+
const known = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
|
3659
|
+
const seeded = (0, import_react3.useRef)(false);
|
|
3480
3660
|
const watching = open && view === "messages";
|
|
3481
3661
|
const watch = (0, import_react3.useRef)(watching);
|
|
3482
3662
|
watch.current = watching;
|
|
3663
|
+
const at = (0, import_react3.useRef)(place);
|
|
3664
|
+
const list = (0, import_react3.useRef)(chats);
|
|
3665
|
+
const readFailed = (0, import_react3.useRef)(false);
|
|
3483
3666
|
const [active, setActive] = (0, import_react3.useState)(RN3.AppState.currentState === "active");
|
|
3484
3667
|
(0, import_react3.useEffect)(() => {
|
|
3485
3668
|
const sub = RN3.AppState.addEventListener("change", (st) => setActive(st === "active"));
|
|
3486
3669
|
return () => sub.remove();
|
|
3487
3670
|
}, []);
|
|
3671
|
+
const enter = (0, import_react3.useCallback)((chatId, fresh = false) => {
|
|
3672
|
+
const next = { chatId, fresh };
|
|
3673
|
+
if (samePlace(at.current, next)) return;
|
|
3674
|
+
at.current = next;
|
|
3675
|
+
setPlace(next);
|
|
3676
|
+
setThread(null);
|
|
3677
|
+
setError("");
|
|
3678
|
+
}, []);
|
|
3488
3679
|
const take = (0, import_react3.useCallback)(
|
|
3489
|
-
(
|
|
3490
|
-
const
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
}
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3680
|
+
(t2) => {
|
|
3681
|
+
const read = t2.conversation;
|
|
3682
|
+
const last = t2.messages.at(-1);
|
|
3683
|
+
const all = t2.conversations ?? (read && last ? [{ id: read.id, status: read.status, subject: lineOf(t2.messages[0]), last }] : []);
|
|
3684
|
+
const fresh = [];
|
|
3685
|
+
const learn = (chat, answers, newest) => {
|
|
3686
|
+
const was = known.current.get(chat);
|
|
3687
|
+
const since = was ?? (seeded.current ? "" : void 0);
|
|
3688
|
+
if (since !== void 0) fresh.push(...answers.filter((m) => m.at > since).map((m) => ({ ...m, chat })));
|
|
3689
|
+
known.current.set(chat, newest ?? was ?? "");
|
|
3690
|
+
};
|
|
3691
|
+
for (const c of all) if (c.id !== read?.id) learn(c.id, c.last && isAnswer(c.last) ? [c.last] : [], c.last?.at);
|
|
3692
|
+
if (read) learn(read.id, t2.messages.filter(isAnswer), last?.at);
|
|
3693
|
+
seeded.current = true;
|
|
3694
|
+
if (fresh.length && o.haptics !== false) buzz();
|
|
3695
|
+
const shown2 = shownChat(at.current, all);
|
|
3696
|
+
const counted = fresh.filter((m) => !watch.current || m.chat !== shown2);
|
|
3697
|
+
setUnseen((u) => [...u.filter((m) => all.some((c) => c.id === m.chat)), ...counted]);
|
|
3698
|
+
list.current = all;
|
|
3699
|
+
setChats(all);
|
|
3700
|
+
setHasEmail(t2.hasEmail);
|
|
3701
|
+
if (at.current.chatId === null || read?.id === at.current.chatId) setThread(t2);
|
|
3501
3702
|
},
|
|
3502
3703
|
[o.haptics]
|
|
3503
3704
|
);
|
|
3705
|
+
const arrived = (0, import_react3.useCallback)(
|
|
3706
|
+
(from, t2) => {
|
|
3707
|
+
const went = t2.conversation?.id;
|
|
3708
|
+
if (went && samePlace(at.current, from)) {
|
|
3709
|
+
at.current = { chatId: went, fresh: false };
|
|
3710
|
+
setPlace(at.current);
|
|
3711
|
+
}
|
|
3712
|
+
take(t2);
|
|
3713
|
+
},
|
|
3714
|
+
[take]
|
|
3715
|
+
);
|
|
3716
|
+
const shown = shownChat(place, chats);
|
|
3504
3717
|
(0, import_react3.useEffect)(() => {
|
|
3505
|
-
if (watching) setUnseen(
|
|
3506
|
-
}, [watching]);
|
|
3718
|
+
if (watching && shown) setUnseen((u) => u.filter((m) => m.chat !== shown));
|
|
3719
|
+
}, [watching, shown]);
|
|
3507
3720
|
(0, import_react3.useEffect)(() => {
|
|
3508
3721
|
if (!active) return;
|
|
3509
3722
|
let stop = false;
|
|
3510
3723
|
let timer;
|
|
3511
3724
|
let ctrl = null;
|
|
3725
|
+
let refused = false;
|
|
3512
3726
|
const tick = async () => {
|
|
3513
3727
|
const api = client.api();
|
|
3514
3728
|
if (!api) {
|
|
@@ -3516,23 +3730,32 @@ var useChat = (client, o, open, view) => {
|
|
|
3516
3730
|
return;
|
|
3517
3731
|
}
|
|
3518
3732
|
ctrl = new AbortController();
|
|
3519
|
-
let t = null;
|
|
3520
3733
|
try {
|
|
3521
3734
|
await client.ready;
|
|
3522
|
-
|
|
3735
|
+
const t2 = await api.chat(ctrl.signal, place.chatId ?? void 0);
|
|
3523
3736
|
if (stop) return;
|
|
3524
|
-
take(
|
|
3525
|
-
setError("");
|
|
3737
|
+
take(t2);
|
|
3738
|
+
if (readFailed.current) setError("");
|
|
3739
|
+
readFailed.current = false;
|
|
3526
3740
|
} catch (e) {
|
|
3527
3741
|
if (e.name === "AbortError" || stop) return;
|
|
3528
|
-
|
|
3742
|
+
refused = [401, 403].includes(e.status ?? 0);
|
|
3743
|
+
if (e.status === 404 && place.chatId) enter(null);
|
|
3744
|
+
else if (watch.current) {
|
|
3745
|
+
readFailed.current = true;
|
|
3746
|
+
setError(e.message);
|
|
3747
|
+
}
|
|
3529
3748
|
}
|
|
3530
3749
|
setLoaded(true);
|
|
3531
|
-
const
|
|
3532
|
-
const
|
|
3533
|
-
const
|
|
3534
|
-
const ms = watch.current ? POLL_OPEN_MS : waiting && recent ? POLL_WAITING_MS : waiting || open ? POLL_IDLE_MS : 0;
|
|
3750
|
+
const running = list.current.filter((c) => c.status !== "solved");
|
|
3751
|
+
const recent = running.some((c) => c.last && Date.now() - new Date(c.last.at).getTime() < WAITING_FOR_MS);
|
|
3752
|
+
const ms = watch.current ? POLL_OPEN_MS : running.length && recent ? POLL_WAITING_MS : running.length || open ? POLL_IDLE_MS : 0;
|
|
3535
3753
|
if (!stop && ms) timer = setTimeout(tick, ms);
|
|
3754
|
+
else if (!stop) timer = setTimeout(quiet, POLL_QUIET_MS);
|
|
3755
|
+
};
|
|
3756
|
+
const quiet = () => {
|
|
3757
|
+
if (client.getUser() && !refused) void tick();
|
|
3758
|
+
else timer = setTimeout(quiet, POLL_QUIET_MS);
|
|
3536
3759
|
};
|
|
3537
3760
|
void tick();
|
|
3538
3761
|
return () => {
|
|
@@ -3540,8 +3763,22 @@ var useChat = (client, o, open, view) => {
|
|
|
3540
3763
|
ctrl?.abort();
|
|
3541
3764
|
clearTimeout(timer);
|
|
3542
3765
|
};
|
|
3543
|
-
}, [client, take, active, open, watching]);
|
|
3544
|
-
return {
|
|
3766
|
+
}, [client, take, enter, active, open, watching, place]);
|
|
3767
|
+
return {
|
|
3768
|
+
// A new conversation has nothing to show yet; polls keep reading the newest for the list.
|
|
3769
|
+
thread: place.fresh ? null : thread,
|
|
3770
|
+
chats,
|
|
3771
|
+
hasEmail,
|
|
3772
|
+
place,
|
|
3773
|
+
/** Messages shows the list: several conversations and none chosen. */
|
|
3774
|
+
listing: place.chatId === null && !place.fresh && chats.length > 1,
|
|
3775
|
+
enter,
|
|
3776
|
+
loaded,
|
|
3777
|
+
error,
|
|
3778
|
+
setError,
|
|
3779
|
+
unseen,
|
|
3780
|
+
arrived
|
|
3781
|
+
};
|
|
3545
3782
|
};
|
|
3546
3783
|
var useArticles = (client, on, query) => {
|
|
3547
3784
|
const [list, setList] = (0, import_react3.useState)(null);
|
|
@@ -3549,7 +3786,7 @@ var useArticles = (client, on, query) => {
|
|
|
3549
3786
|
(0, import_react3.useEffect)(() => {
|
|
3550
3787
|
if (!on) return;
|
|
3551
3788
|
let stop = false;
|
|
3552
|
-
const
|
|
3789
|
+
const t2 = setTimeout(
|
|
3553
3790
|
() => void client.ready.then(
|
|
3554
3791
|
() => client.api()?.articles(query.trim() || void 0).then((r) => {
|
|
3555
3792
|
if (stop) return;
|
|
@@ -3561,27 +3798,27 @@ var useArticles = (client, on, query) => {
|
|
|
3561
3798
|
);
|
|
3562
3799
|
return () => {
|
|
3563
3800
|
stop = true;
|
|
3564
|
-
clearTimeout(
|
|
3801
|
+
clearTimeout(t2);
|
|
3565
3802
|
};
|
|
3566
3803
|
}, [client, on, query]);
|
|
3567
3804
|
return { list, any };
|
|
3568
3805
|
};
|
|
3569
3806
|
var spansOf = (p, spans, base) => spans.map(
|
|
3570
|
-
(
|
|
3807
|
+
(s2, i) => (0, import_react3.createElement)(
|
|
3571
3808
|
RN3.Text,
|
|
3572
3809
|
{
|
|
3573
3810
|
key: i,
|
|
3574
|
-
onPress:
|
|
3575
|
-
accessibilityRole:
|
|
3811
|
+
onPress: s2.href ? () => void RN3.Linking.openURL(s2.href).catch(() => void 0) : void 0,
|
|
3812
|
+
accessibilityRole: s2.href ? "link" : void 0,
|
|
3576
3813
|
style: {
|
|
3577
3814
|
...base,
|
|
3578
|
-
...
|
|
3579
|
-
...
|
|
3580
|
-
...
|
|
3581
|
-
...
|
|
3815
|
+
...s2.bold ? { fontWeight: "700" } : {},
|
|
3816
|
+
...s2.italic ? { fontStyle: "italic" } : {},
|
|
3817
|
+
...s2.code ? { fontFamily: RN3.Platform.OS === "ios" ? "Menlo" : "monospace", backgroundColor: p.soft } : {},
|
|
3818
|
+
...s2.href ? { color: p.accent, textDecorationLine: "underline" } : {}
|
|
3582
3819
|
}
|
|
3583
3820
|
},
|
|
3584
|
-
|
|
3821
|
+
s2.text
|
|
3585
3822
|
)
|
|
3586
3823
|
);
|
|
3587
3824
|
var ArticleView = ({ client, p, id, onAsk }) => {
|
|
@@ -3595,7 +3832,7 @@ var ArticleView = ({ client, p, id, onAsk }) => {
|
|
|
3595
3832
|
return () => ctrl.abort();
|
|
3596
3833
|
}, [client, id]);
|
|
3597
3834
|
if (error) return k.text(error, { color: p.danger });
|
|
3598
|
-
if (!a) return k.muted("Loading\u2026");
|
|
3835
|
+
if (!a) return k.muted(t("Loading\u2026"));
|
|
3599
3836
|
const base = { color: p.fg, fontSize: 15, lineHeight: 22 };
|
|
3600
3837
|
const blocks = a.blocks.map((b, i) => {
|
|
3601
3838
|
if (b.type === "h2" || b.type === "h3") return (0, import_react3.createElement)(RN3.Text, { key: i, accessibilityRole: "header", style: { ...base, fontSize: b.type === "h2" ? 17 : 16, fontWeight: "700", marginTop: 8 } }, ...spansOf(p, b.spans, {}));
|
|
@@ -3624,11 +3861,11 @@ var ArticleView = ({ client, p, id, onAsk }) => {
|
|
|
3624
3861
|
(0, import_react3.createElement)(
|
|
3625
3862
|
RN3.View,
|
|
3626
3863
|
{ style: { flexDirection: "row", alignItems: "center", gap: 8, padding: 12, borderRadius: 10, backgroundColor: p.soft, marginTop: 8 } },
|
|
3627
|
-
rated ? k.muted("Thanks for letting us know.") : k.muted("Did this answer your question?", { flex: 1 }),
|
|
3628
|
-
rated ? null : k.btn("Yes", "secondary", () => rate(true), false, { height: 34 }),
|
|
3629
|
-
rated ? null : k.btn("No", "secondary", () => rate(false), false, { height: 34 })
|
|
3864
|
+
rated ? k.muted(t("Thanks for letting us know.")) : k.muted(t("Did this answer your question?"), { flex: 1 }),
|
|
3865
|
+
rated ? null : k.btn(t("Yes"), "secondary", () => rate(true), false, { height: 34 }),
|
|
3866
|
+
rated ? null : k.btn(t("No"), "secondary", () => rate(false), false, { height: 34 })
|
|
3630
3867
|
),
|
|
3631
|
-
k.btn("Still need help? Send us a message", "secondary", onAsk)
|
|
3868
|
+
k.btn(t("Still need help? Send us a message"), "secondary", onAsk)
|
|
3632
3869
|
);
|
|
3633
3870
|
};
|
|
3634
3871
|
var MessengerSheet = ({
|
|
@@ -3642,14 +3879,15 @@ var MessengerSheet = ({
|
|
|
3642
3879
|
}) => {
|
|
3643
3880
|
const k = kit(p);
|
|
3644
3881
|
const [body, setBody] = (0, import_react3.useState)("");
|
|
3882
|
+
const [asked, setAsked] = (0, import_react3.useState)("");
|
|
3645
3883
|
const [email, setEmail] = (0, import_react3.useState)("");
|
|
3646
3884
|
const [pending, setPending] = (0, import_react3.useState)(null);
|
|
3647
3885
|
const [slow, setSlow] = (0, import_react3.useState)(false);
|
|
3648
3886
|
(0, import_react3.useEffect)(() => {
|
|
3649
3887
|
setSlow(false);
|
|
3650
3888
|
if (!pending) return;
|
|
3651
|
-
const
|
|
3652
|
-
return () => clearTimeout(
|
|
3889
|
+
const t2 = setTimeout(() => setSlow(true), 1500);
|
|
3890
|
+
return () => clearTimeout(t2);
|
|
3653
3891
|
}, [pending]);
|
|
3654
3892
|
const [photos, setPhotos] = (0, import_react3.useState)([]);
|
|
3655
3893
|
const [query, setQuery] = (0, import_react3.useState)("");
|
|
@@ -3657,24 +3895,25 @@ var MessengerSheet = ({
|
|
|
3657
3895
|
const arts = useArticles(client, helpOn, query);
|
|
3658
3896
|
const scroll = (0, import_react3.useRef)(null);
|
|
3659
3897
|
const close = () => messengerOpen.set(false);
|
|
3660
|
-
const greeting = o.greeting ?? "Hi! How can we help?";
|
|
3661
|
-
const { thread } = chat;
|
|
3662
|
-
const
|
|
3898
|
+
const greeting = o.greeting ?? t("Hi! How can we help?");
|
|
3899
|
+
const { thread, place } = chat;
|
|
3900
|
+
const pendingHere = pending && samePlace(pending.to, place) ? pending : null;
|
|
3901
|
+
const count = (thread?.messages.length ?? 0) + (pendingHere ? 1 : 0);
|
|
3663
3902
|
(0, import_react3.useEffect)(() => {
|
|
3664
3903
|
if (view === "messages") setTimeout(() => scroll.current?.scrollToEnd?.({ animated: true }), 50);
|
|
3665
|
-
}, [count, view]);
|
|
3666
|
-
const needEmail = (o.askEmail ?? true) && !
|
|
3904
|
+
}, [count, view, place]);
|
|
3905
|
+
const needEmail = (o.askEmail ?? true) && !chat.hasEmail && !client.getUser()?.email;
|
|
3667
3906
|
const emailOk = !email.trim() || EMAIL.test(email.trim());
|
|
3668
3907
|
const pickPhotos = async () => {
|
|
3669
3908
|
const api = client.api();
|
|
3670
3909
|
if (!api || !imagePicker?.launchImageLibraryAsync || pending) return;
|
|
3671
3910
|
const room = CHAT_FILES_PER_MESSAGE - photos.length;
|
|
3672
|
-
if (room <= 0) return chat.setError(
|
|
3911
|
+
if (room <= 0) return chat.setError(tf("Up to {n} files per message.", { n: CHAT_FILES_PER_MESSAGE }));
|
|
3673
3912
|
let res;
|
|
3674
3913
|
try {
|
|
3675
3914
|
res = await imagePicker.launchImageLibraryAsync({ allowsMultipleSelection: room > 1, selectionLimit: room, quality: 0.8, exif: false });
|
|
3676
3915
|
} catch (e) {
|
|
3677
|
-
return chat.setError(e.message || "The photos could not be opened.");
|
|
3916
|
+
return chat.setError(e.message || t("The photos could not be opened."));
|
|
3678
3917
|
}
|
|
3679
3918
|
if (res.canceled || res.cancelled || !res.assets?.length) return;
|
|
3680
3919
|
chat.setError("");
|
|
@@ -3683,11 +3922,11 @@ var MessengerSheet = ({
|
|
|
3683
3922
|
const name = a.fileName || a.uri.split("/").pop() || "photo.jpg";
|
|
3684
3923
|
const type = a.mimeType && Object.values(PHOTO_TYPES).includes(a.mimeType) ? a.mimeType : PHOTO_TYPES[name.split(".").pop()?.toLowerCase() ?? ""];
|
|
3685
3924
|
if (!type) {
|
|
3686
|
-
chat.setError("Only pictures (PNG, JPEG, WebP, GIF) can be sent.");
|
|
3925
|
+
chat.setError(t("Only pictures (PNG, JPEG, WebP, GIF) can be sent."));
|
|
3687
3926
|
continue;
|
|
3688
3927
|
}
|
|
3689
3928
|
if (a.fileSize && a.fileSize > CHAT_FILE_MAX_BYTES) {
|
|
3690
|
-
chat.setError(
|
|
3929
|
+
chat.setError(tf("{name} is larger than 10 MB.", { name }));
|
|
3691
3930
|
continue;
|
|
3692
3931
|
}
|
|
3693
3932
|
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() };
|
|
@@ -3706,29 +3945,29 @@ var MessengerSheet = ({
|
|
|
3706
3945
|
setPhotos((list) => [...list, ...added]);
|
|
3707
3946
|
};
|
|
3708
3947
|
const canSend = (!!body.trim() || photos.length > 0) && !pending && emailOk;
|
|
3709
|
-
const
|
|
3710
|
-
const text2 = body.trim();
|
|
3948
|
+
const deliver = async (text2, files, to) => {
|
|
3711
3949
|
const api = client.api();
|
|
3712
|
-
|
|
3713
|
-
if (!text2 && !files.length || pending || !emailOk || !api) return;
|
|
3950
|
+
if (!api) return;
|
|
3714
3951
|
setPending({
|
|
3715
3952
|
id: "pending",
|
|
3716
3953
|
from: "visitor",
|
|
3717
3954
|
body: text2,
|
|
3718
3955
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3956
|
+
to,
|
|
3719
3957
|
...files.length ? { attachments: files.map((f) => ({ id: f.key, name: f.name, type: f.type, size: f.size })) } : {}
|
|
3720
3958
|
});
|
|
3721
|
-
setBody("");
|
|
3722
|
-
setPhotos([]);
|
|
3723
3959
|
chat.setError("");
|
|
3724
3960
|
try {
|
|
3725
3961
|
await Promise.all(files.map((f) => f.up));
|
|
3726
3962
|
const ids = files.filter((f) => f.id).map((f) => f.id);
|
|
3727
|
-
if (!text2 && !ids.length) throw new Error("The photo could not be sent.");
|
|
3963
|
+
if (!text2 && !ids.length) throw new Error(t("The photo could not be sent."));
|
|
3728
3964
|
const win = RN3.Dimensions.get("screen");
|
|
3729
|
-
|
|
3965
|
+
const conversation = to.fresh ? "new" : to.chatId ?? thread?.conversation?.id;
|
|
3966
|
+
chat.arrived(
|
|
3967
|
+
to,
|
|
3730
3968
|
await api.sendChat({
|
|
3731
3969
|
body: text2,
|
|
3970
|
+
...conversation ? { conversation } : {},
|
|
3732
3971
|
email: needEmail && email.trim() ? email.trim() : void 0,
|
|
3733
3972
|
...ids.length ? { attachments: ids } : {},
|
|
3734
3973
|
// A little context for a bug report: the system and the screen size.
|
|
@@ -3736,13 +3975,44 @@ var MessengerSheet = ({
|
|
|
3736
3975
|
})
|
|
3737
3976
|
);
|
|
3738
3977
|
} catch (e) {
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3978
|
+
const gone = e.status === 404;
|
|
3979
|
+
chat.enter(gone ? null : to.chatId, gone || to.fresh);
|
|
3980
|
+
setBody((typed) => [text2, typed.trim()].filter(Boolean).join("\n"));
|
|
3981
|
+
setPhotos((list) => [...files.filter((f) => f.id), ...list]);
|
|
3982
|
+
chat.setError(gone ? t("This conversation is no longer available. Your message is still here to send as a new one.") : e.message);
|
|
3742
3983
|
} finally {
|
|
3743
3984
|
setPending(null);
|
|
3744
3985
|
}
|
|
3745
3986
|
};
|
|
3987
|
+
const send = () => {
|
|
3988
|
+
const text2 = body.trim();
|
|
3989
|
+
const files = photos;
|
|
3990
|
+
if (!text2 && !files.length || pending || !emailOk || !client.api()) return;
|
|
3991
|
+
setBody("");
|
|
3992
|
+
setPhotos([]);
|
|
3993
|
+
void deliver(text2, files, place);
|
|
3994
|
+
};
|
|
3995
|
+
const openMessages = () => {
|
|
3996
|
+
chat.enter(null);
|
|
3997
|
+
setView("messages");
|
|
3998
|
+
};
|
|
3999
|
+
const openChat = (id) => {
|
|
4000
|
+
chat.enter(id);
|
|
4001
|
+
setView("messages");
|
|
4002
|
+
};
|
|
4003
|
+
const startChat = () => {
|
|
4004
|
+
chat.enter(null, true);
|
|
4005
|
+
setView("messages");
|
|
4006
|
+
};
|
|
4007
|
+
const ask = () => {
|
|
4008
|
+
const text2 = asked.trim();
|
|
4009
|
+
if (!text2) return openMessages();
|
|
4010
|
+
if (pending || !client.api()) return;
|
|
4011
|
+
setAsked("");
|
|
4012
|
+
startChat();
|
|
4013
|
+
if (emailOk) void deliver(text2, [], { chatId: null, fresh: true });
|
|
4014
|
+
else setBody((typed) => [text2, typed.trim()].filter(Boolean).join("\n"));
|
|
4015
|
+
};
|
|
3746
4016
|
const localUris = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
|
3747
4017
|
for (const f of photos) localUris.current.set(f.key, f.uri);
|
|
3748
4018
|
const fileUrl = (id) => localUris.current.get(id) ?? client.api()?.attachmentUrl(id) ?? "";
|
|
@@ -3797,7 +4067,7 @@ var MessengerSheet = ({
|
|
|
3797
4067
|
k.text(`\u{1F4C4} ${a.title}`, { fontSize: 13 })
|
|
3798
4068
|
)
|
|
3799
4069
|
),
|
|
3800
|
-
m.at ? k.muted(`${mine ? "" : `${who(m.from, o.agentName)} \xB7 `}${isPending && slow ? "Sending\u2026" : timeAgo(m.at)}`, { fontSize: 11, textAlign: mine ? "right" : "left", paddingHorizontal: 6 }) : null
|
|
4070
|
+
m.at ? k.muted(`${mine ? "" : `${who(m.from, o.agentName)} \xB7 `}${isPending && slow ? t("Sending\u2026") : timeAgo(m.at)}`, { fontSize: 11, textAlign: mine ? "right" : "left", paddingHorizontal: 6 }) : null
|
|
3801
4071
|
);
|
|
3802
4072
|
};
|
|
3803
4073
|
const row = (a) => (0, import_react3.createElement)(
|
|
@@ -3810,55 +4080,76 @@ var MessengerSheet = ({
|
|
|
3810
4080
|
const searchRow = (onPress) => (0, import_react3.createElement)(
|
|
3811
4081
|
RN3.Pressable,
|
|
3812
4082
|
{ key: "s", onPress, accessibilityRole: "search", style: { flexDirection: "row", alignItems: "center", height: 44, borderRadius: r.field, paddingHorizontal: 14, backgroundColor: hu.def } },
|
|
3813
|
-
k.muted("Search for help", { flex: 1, fontSize: 15 }),
|
|
4083
|
+
k.muted(t("Search for help"), { flex: 1, fontSize: 15 }),
|
|
3814
4084
|
glyph("search", p.accent, 16)
|
|
3815
4085
|
);
|
|
3816
|
-
const search = k.field({ value: query, onChange: setQuery, placeholder: "Search for help", max: 200, style: { borderWidth: 0, borderRadius: r.field, backgroundColor: raised, ...surf } });
|
|
4086
|
+
const search = k.field({ value: query, onChange: setQuery, placeholder: t("Search for help"), max: 200, style: { borderWidth: 0, borderRadius: r.field, backgroundColor: raised, ...surf } });
|
|
3817
4087
|
const first = client.getUser()?.name?.trim().split(/\s+/)[0];
|
|
3818
|
-
const rest = greeting.replace(/^(hi|hello|hey|hallo)( there)?\s*[
|
|
4088
|
+
const rest = (o.greeting !== void 0 ? greeting.replace(/^(hi|hello|hey|hallo|moin|servus|bonjour|salut|hola|ciao|salve|hoi|ol[áa]|cze[śs][ćc]|hej|こんにちは)( there)?\s*[!,.!、。]*\s*/i, "").trim() : t("How can we help?")) || t("How can we help you today?");
|
|
3819
4089
|
const hero = (fg, sub) => (0, import_react3.createElement)(
|
|
3820
4090
|
RN3.Text,
|
|
3821
4091
|
{ key: "hero", accessibilityRole: "header", style: { color: fg, fontSize: 25, fontWeight: "600", letterSpacing: -0.6, lineHeight: 30 } },
|
|
3822
|
-
first ?
|
|
3823
|
-
|
|
4092
|
+
`${first ? tf("Hi, {name}", { name: first }) : t("Hi there")} \u{1F44B}
|
|
4093
|
+
`,
|
|
3824
4094
|
(0, import_react3.createElement)(RN3.Text, { style: { color: sub, fontWeight: "500" } }, rest)
|
|
3825
4095
|
);
|
|
3826
|
-
const
|
|
4096
|
+
const chatRow = (c) => {
|
|
4097
|
+
const m = c.last;
|
|
4098
|
+
const n = chat.unseen.filter((u) => u.chat === c.id).length;
|
|
4099
|
+
const closed = c.status === "solved" ? ` \xB7 ${t("Closed")}` : "";
|
|
4100
|
+
const subject = c.subject || (m ? lineOf(m) : "");
|
|
4101
|
+
const line = m ? lineOf(m) : "";
|
|
4102
|
+
return (0, import_react3.createElement)(
|
|
4103
|
+
RN3.Pressable,
|
|
4104
|
+
{ key: c.id, onPress: () => openChat(c.id), accessibilityRole: "button", accessibilityLabel: `${tf("Conversation: {subject}", { subject })}${closed}${n ? `, ${tf("{n} new", { n })}` : ""}` },
|
|
4105
|
+
card(
|
|
4106
|
+
[
|
|
4107
|
+
(0, import_react3.createElement)(
|
|
4108
|
+
RN3.View,
|
|
4109
|
+
{ key: "r", style: { flexDirection: "row", alignItems: "center", gap: 10 } },
|
|
4110
|
+
title ? teamMark(title, alpha(p.accent, 0.16), p.accent, 26) : null,
|
|
4111
|
+
(0, import_react3.createElement)(
|
|
4112
|
+
RN3.View,
|
|
4113
|
+
{ style: { flex: 1, gap: 1 } },
|
|
4114
|
+
(0, import_react3.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.fg, fontSize: 14, fontWeight: "500" } }, subject),
|
|
4115
|
+
m && line !== subject ? (0, import_react3.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.muted, fontSize: 13 } }, line) : null,
|
|
4116
|
+
m ? k.muted(`${who(m.from, o.agentName)} \xB7 ${timeAgo(m.at)}${closed}`, { fontSize: 11.5 }) : null
|
|
4117
|
+
),
|
|
4118
|
+
n ? (0, import_react3.createElement)(RN3.View, { style: { minWidth: 20, height: 20, paddingHorizontal: 5, borderRadius: 10, backgroundColor: p.danger, alignItems: "center", justifyContent: "center" } }, k.text(String(Math.min(n, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : glyph("next", p.muted, 14)
|
|
4119
|
+
)
|
|
4120
|
+
],
|
|
4121
|
+
{ paddingVertical: 10, paddingHorizontal: 12 }
|
|
4122
|
+
)
|
|
4123
|
+
);
|
|
4124
|
+
};
|
|
3827
4125
|
const showHelp = helpOn && arts.any !== false;
|
|
3828
4126
|
let content;
|
|
3829
4127
|
if (view === "home") {
|
|
4128
|
+
const running = chat.chats.filter((c) => c.status !== "solved");
|
|
4129
|
+
const rows = (running.length ? running : chat.chats).slice(0, 2);
|
|
3830
4130
|
const cards = [
|
|
3831
|
-
// "Ask a question":
|
|
4131
|
+
// "Ask a question": its own field; the words start a new conversation.
|
|
3832
4132
|
(0, import_react3.createElement)(
|
|
3833
4133
|
RN3.View,
|
|
3834
4134
|
{ key: "ask", style: { flexDirection: "row", alignItems: "center", gap: 8, height: 48, borderRadius: 24, paddingLeft: 18, paddingRight: 6, backgroundColor: hu.surface, ...surf } },
|
|
3835
|
-
k.field({ value:
|
|
4135
|
+
k.field({ value: asked, onChange: setAsked, placeholder: t("Ask a question"), max: 4e3, onSubmit: ask, style: { flex: 1, borderWidth: 0, paddingHorizontal: 0, backgroundColor: "transparent" } }),
|
|
3836
4136
|
(0, import_react3.createElement)(
|
|
3837
4137
|
RN3.Pressable,
|
|
3838
4138
|
{
|
|
3839
|
-
onPress:
|
|
3840
|
-
setView("messages");
|
|
3841
|
-
if (body.trim()) void send();
|
|
3842
|
-
},
|
|
4139
|
+
onPress: ask,
|
|
3843
4140
|
accessibilityRole: "button",
|
|
3844
|
-
accessibilityLabel: "Send",
|
|
3845
|
-
style: { width: 36, height: 36, borderRadius: 18, alignItems: "center", justifyContent: "center", backgroundColor:
|
|
4141
|
+
accessibilityLabel: t("Send"),
|
|
4142
|
+
style: { width: 36, height: 36, borderRadius: 18, alignItems: "center", justifyContent: "center", backgroundColor: asked.trim() ? p.accent : hu.def }
|
|
3846
4143
|
},
|
|
3847
|
-
glyph("up",
|
|
4144
|
+
glyph("up", asked.trim() ? ink : hu.muted, 18)
|
|
3848
4145
|
)
|
|
3849
4146
|
),
|
|
3850
|
-
|
|
4147
|
+
...rows.map(chatRow),
|
|
4148
|
+
chat.chats.length > rows.length ? (0, import_react3.createElement)(
|
|
3851
4149
|
RN3.Pressable,
|
|
3852
|
-
{ key: "
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
RN3.View,
|
|
3856
|
-
{ key: "r", style: { flexDirection: "row", alignItems: "center", gap: 10 } },
|
|
3857
|
-
title ? teamMark(title, alpha(p.accent, 0.16), p.accent, 26) : null,
|
|
3858
|
-
(0, import_react3.createElement)(RN3.View, { style: { flex: 1, gap: 1 } }, (0, import_react3.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" : who(last.from, o.agentName)} \xB7 ${timeAgo(last.at)}`, { fontSize: 11.5 })),
|
|
3859
|
-
chat.unseen.length ? (0, import_react3.createElement)(RN3.View, { style: { width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : glyph("next", p.muted, 14)
|
|
3860
|
-
)
|
|
3861
|
-
], { paddingVertical: 10, paddingHorizontal: 12 })
|
|
4150
|
+
{ key: "all", onPress: openMessages, accessibilityRole: "button", accessibilityLabel: tf("All conversations ({n})", { n: chat.chats.length }), style: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 4, paddingVertical: 6 } },
|
|
4151
|
+
k.muted(tf("All conversations ({n})", { n: chat.chats.length }), { fontSize: 13, fontWeight: "500" }),
|
|
4152
|
+
glyph("next", p.muted, 12)
|
|
3862
4153
|
) : null,
|
|
3863
4154
|
showHelp ? card([searchRow(() => setView("help")), ...(arts.list ?? []).slice(0, 4).map(row)], { padding: 8, gap: 2 }) : null
|
|
3864
4155
|
];
|
|
@@ -3873,7 +4164,14 @@ var MessengerSheet = ({
|
|
|
3873
4164
|
(0, import_react3.createElement)(RN3.View, { style: { backgroundColor: p.accent, paddingHorizontal: 22, paddingTop: 4, paddingBottom: 64 } }, hero(ink, alpha(ink, 0.8))),
|
|
3874
4165
|
(0, import_react3.createElement)(RN3.View, { style: { marginTop: -48, paddingHorizontal: 16, gap: 12 } }, ...cards)
|
|
3875
4166
|
);
|
|
3876
|
-
} else if (view === "messages")
|
|
4167
|
+
} else if (view === "messages" && chat.listing)
|
|
4168
|
+
content = (0, import_react3.createElement)(
|
|
4169
|
+
RN3.ScrollView,
|
|
4170
|
+
{ key: "chats", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 } },
|
|
4171
|
+
...chat.chats.map(chatRow),
|
|
4172
|
+
k.btn(t("New conversation"), "primary", startChat)
|
|
4173
|
+
);
|
|
4174
|
+
else if (view === "messages")
|
|
3877
4175
|
content = (0, import_react3.createElement)(
|
|
3878
4176
|
RN3.View,
|
|
3879
4177
|
{ key: "messages", style: { flex: 1, backgroundColor: hu.bg } },
|
|
@@ -3881,11 +4179,11 @@ var MessengerSheet = ({
|
|
|
3881
4179
|
RN3.ScrollView,
|
|
3882
4180
|
{ ref: scroll, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 } },
|
|
3883
4181
|
// The greeting opens an empty chat only, never above the visitor's first words (2026-10-30).
|
|
3884
|
-
!thread?.messages.length && !
|
|
3885
|
-
!chat.loaded ? k.muted("Loading\u2026") : null,
|
|
4182
|
+
!thread?.messages.length && !pendingHere && !place.chatId ? bubble({ id: "greeting", from: "team", body: greeting, at: "" }) : null,
|
|
4183
|
+
!chat.loaded && !place.fresh && !pendingHere || place.chatId && !thread ? k.muted(t("Loading\u2026")) : null,
|
|
3886
4184
|
...(thread?.messages ?? []).map((m) => bubble(m)),
|
|
3887
|
-
|
|
3888
|
-
thread?.conversation?.status === "solved" && !
|
|
4185
|
+
pendingHere ? bubble(pendingHere, true) : null,
|
|
4186
|
+
thread?.conversation?.status === "solved" && !pendingHere ? k.muted(t("This conversation is closed. Write to start a new one."), { textAlign: "center" }) : null
|
|
3889
4187
|
),
|
|
3890
4188
|
(0, import_react3.createElement)(
|
|
3891
4189
|
RN3.View,
|
|
@@ -3899,13 +4197,13 @@ var MessengerSheet = ({
|
|
|
3899
4197
|
(f) => (0, import_react3.createElement)(
|
|
3900
4198
|
RN3.View,
|
|
3901
4199
|
{ key: f.key, style: { width: 56, height: 56 } },
|
|
3902
|
-
(0, import_react3.createElement)(RN3.Image, { source: { uri: f.uri }, resizeMode: "cover", accessibilityLabel: f.id ? f.name :
|
|
4200
|
+
(0, import_react3.createElement)(RN3.Image, { source: { uri: f.uri }, resizeMode: "cover", accessibilityLabel: f.id ? f.name : tf("{name}, uploading", { name: f.name }), style: { width: 56, height: 56, borderRadius: Math.max(4, r.card - 4), backgroundColor: p.soft, opacity: f.id ? 1 : 0.5 } }),
|
|
3903
4201
|
(0, import_react3.createElement)(
|
|
3904
4202
|
RN3.Pressable,
|
|
3905
4203
|
{
|
|
3906
4204
|
onPress: () => setPhotos((list) => list.filter((x) => x.key !== f.key)),
|
|
3907
4205
|
accessibilityRole: "button",
|
|
3908
|
-
accessibilityLabel:
|
|
4206
|
+
accessibilityLabel: tf("Remove {name}", { name: f.name }),
|
|
3909
4207
|
hitSlop: 8,
|
|
3910
4208
|
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" }
|
|
3911
4209
|
},
|
|
@@ -3922,28 +4220,28 @@ var MessengerSheet = ({
|
|
|
3922
4220
|
needEmail ? k.field({
|
|
3923
4221
|
value: email,
|
|
3924
4222
|
onChange: setEmail,
|
|
3925
|
-
placeholder: "Email for our reply (optional)",
|
|
4223
|
+
placeholder: t("Email for our reply (optional)"),
|
|
3926
4224
|
email: true,
|
|
3927
4225
|
max: 200,
|
|
3928
4226
|
style: { minHeight: 0, marginRight: 10, marginBottom: 4, paddingHorizontal: 0, paddingTop: 0, paddingBottom: 8, borderWidth: 0, borderRadius: 0, borderBottomWidth: 1, borderBottomColor: emailOk ? p.line : p.danger, backgroundColor: "transparent", fontSize: 13.5 }
|
|
3929
4227
|
}) : null,
|
|
3930
|
-
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" } }),
|
|
4228
|
+
k.field({ value: body, onChange: setBody, placeholder: t("Write a message\u2026"), multiline: true, max: 4e3, style: { minHeight: 28, maxHeight: 120, borderWidth: 0, paddingHorizontal: 0, paddingVertical: 4, backgroundColor: "transparent" } }),
|
|
3931
4229
|
(0, import_react3.createElement)(
|
|
3932
4230
|
RN3.View,
|
|
3933
4231
|
{ style: { flexDirection: "row", alignItems: "center", marginLeft: -8 } },
|
|
3934
4232
|
imagePicker?.launchImageLibraryAsync ? (0, import_react3.createElement)(
|
|
3935
4233
|
RN3.Pressable,
|
|
3936
|
-
{ 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" } },
|
|
4234
|
+
{ onPress: () => void pickPhotos(), disabled: !!pending, accessibilityRole: "button", accessibilityLabel: t("Attach a photo or screenshot"), hitSlop: 6, style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center" } },
|
|
3937
4235
|
glyph("clip", hu.muted, 19)
|
|
3938
4236
|
) : null,
|
|
3939
4237
|
(0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
|
|
3940
4238
|
(0, import_react3.createElement)(
|
|
3941
4239
|
RN3.Pressable,
|
|
3942
4240
|
{
|
|
3943
|
-
onPress:
|
|
4241
|
+
onPress: send,
|
|
3944
4242
|
disabled: !canSend,
|
|
3945
4243
|
accessibilityRole: "button",
|
|
3946
|
-
accessibilityLabel: "Send",
|
|
4244
|
+
accessibilityLabel: t("Send"),
|
|
3947
4245
|
style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center", backgroundColor: canSend ? p.accent : hu.def }
|
|
3948
4246
|
},
|
|
3949
4247
|
glyph("up", canSend ? ink : hu.muted, 18)
|
|
@@ -3959,15 +4257,15 @@ var MessengerSheet = ({
|
|
|
3959
4257
|
RN3.ScrollView,
|
|
3960
4258
|
{ key: "help", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 }, keyboardShouldPersistTaps: "handled" },
|
|
3961
4259
|
search,
|
|
3962
|
-
arts.list === null ? k.muted("Loading\u2026") : null,
|
|
3963
|
-
arts.list?.length === 0 ? (0, import_react3.createElement)(RN3.View, { style: { gap: 10, alignItems: "center", paddingVertical: 24 } }, k.muted(query.trim() ?
|
|
4260
|
+
arts.list === null ? k.muted(t("Loading\u2026")) : null,
|
|
4261
|
+
arts.list?.length === 0 ? (0, import_react3.createElement)(RN3.View, { style: { gap: 10, alignItems: "center", paddingVertical: 24 } }, k.muted(query.trim() ? tf("Nothing found for \u201C{q}\u201D.", { q: query.trim() }) : t("No help articles yet.")), k.btn(t("Send us a message"), "primary", startChat)) : null,
|
|
3964
4262
|
...query.trim() ? arts.list?.length ? [card((arts.list ?? []).map(row), { padding: 8 })] : [] : [...groups].map(([name, items]) => (0, import_react3.createElement)(RN3.View, { key: name, style: { gap: 6 } }, k.muted(`${name} \xB7 ${items.length}`, { fontSize: 12, fontWeight: "600", paddingHorizontal: 2 }), card(items.map(row), { padding: 8 })))
|
|
3965
4263
|
);
|
|
3966
4264
|
} else
|
|
3967
4265
|
content = (0, import_react3.createElement)(
|
|
3968
4266
|
RN3.ScrollView,
|
|
3969
4267
|
{ key: `a-${view.article}`, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 32 } },
|
|
3970
|
-
(0, import_react3.createElement)(ArticleView, { client, p, id: view.article, onAsk:
|
|
4268
|
+
(0, import_react3.createElement)(ArticleView, { client, p, id: view.article, onAsk: startChat })
|
|
3971
4269
|
);
|
|
3972
4270
|
const current = typeof view === "string" ? view : "help";
|
|
3973
4271
|
const tab = (v, name, icon, badge = 0) => {
|
|
@@ -3976,7 +4274,7 @@ var MessengerSheet = ({
|
|
|
3976
4274
|
RN3.Pressable,
|
|
3977
4275
|
{
|
|
3978
4276
|
key: v,
|
|
3979
|
-
onPress: () => setView(v),
|
|
4277
|
+
onPress: () => v === "messages" ? openMessages() : setView(v),
|
|
3980
4278
|
accessibilityRole: "tab",
|
|
3981
4279
|
accessibilityLabel: name,
|
|
3982
4280
|
accessibilityState: { selected: on },
|
|
@@ -3992,21 +4290,21 @@ var MessengerSheet = ({
|
|
|
3992
4290
|
const ground = onAccent ? p.accent : hu.bg;
|
|
3993
4291
|
const back = (to, label) => (0, import_react3.createElement)(
|
|
3994
4292
|
RN3.Pressable,
|
|
3995
|
-
{ key: "back", onPress:
|
|
4293
|
+
{ key: "back", onPress: to, accessibilityRole: "button", accessibilityLabel: label, hitSlop: 10, style: { width: 32, height: 32, alignItems: "center", justifyContent: "center", transform: [{ rotate: "180deg" }] } },
|
|
3996
4294
|
glyph("next", hu.fg, 18)
|
|
3997
4295
|
);
|
|
3998
4296
|
const head = (0, import_react3.createElement)(
|
|
3999
4297
|
RN3.View,
|
|
4000
4298
|
{ key: "head", style: { flexDirection: "row", alignItems: "center", gap: 8, paddingLeft: 12, paddingRight: 12, paddingTop: 12, paddingBottom: 6, backgroundColor: ground } },
|
|
4001
|
-
typeof view !== "string" ? back("help", "All articles") : view === "messages" ? back("home", "Back to home") : view === "home" && (o.logo || title) ? o.logo ? (0, import_react3.createElement)(RN3.Image, { key: "logo", source: { uri: o.logo }, accessibilityIgnoresInvertColors: true, resizeMode: "contain", style: { height: 28, aspectRatio: 1, maxWidth: 132, borderRadius: 8, marginLeft: 4 } }) : teamMark(title, alpha(p.accent, 0.16), p.accent, 28) : null,
|
|
4002
|
-
// In the chat: who answers, in the middle.
|
|
4003
|
-
view === "messages" && title ? (0, import_react3.createElement)(
|
|
4299
|
+
typeof view !== "string" ? back(() => setView("help"), t("All articles")) : view === "messages" && !chat.listing && chat.chats.length > 1 ? back(openMessages, t("All conversations")) : view === "messages" ? back(() => setView("home"), t("Back to home")) : view === "home" && (o.logo || title) ? o.logo ? (0, import_react3.createElement)(RN3.Image, { key: "logo", source: { uri: o.logo }, accessibilityIgnoresInvertColors: true, resizeMode: "contain", style: { height: 28, aspectRatio: 1, maxWidth: 132, borderRadius: 8, marginLeft: 4 } }) : teamMark(title, alpha(p.accent, 0.16), p.accent, 28) : null,
|
|
4300
|
+
// In the chat: who answers, in the middle; over the list its name.
|
|
4301
|
+
view === "messages" && chat.listing ? (0, import_react3.createElement)(RN3.View, { style: { flex: 1, alignItems: "center" } }, k.text(t("Messages"), { color: hu.fg, fontSize: 15, fontWeight: "600" })) : view === "messages" && title ? (0, import_react3.createElement)(
|
|
4004
4302
|
RN3.View,
|
|
4005
4303
|
{ style: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8 } },
|
|
4006
4304
|
teamMark(title, alpha(p.accent, 0.16), p.accent, 28),
|
|
4007
4305
|
(0, import_react3.createElement)(RN3.Text, { numberOfLines: 1, style: { flexShrink: 1, color: hu.fg, fontSize: 15, fontWeight: "600" } }, title)
|
|
4008
4306
|
) : (0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
|
|
4009
|
-
(0, import_react3.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))
|
|
4307
|
+
(0, import_react3.createElement)(RN3.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: t("Close"), hitSlop: 12, style: { width: 32, height: 32, borderRadius: 10, alignItems: "center", justifyContent: "center" } }, glyph("cross", onAccent ? ink : p.muted, 18))
|
|
4010
4308
|
);
|
|
4011
4309
|
const powered = brand ? (0, import_react3.createElement)(
|
|
4012
4310
|
RN3.View,
|
|
@@ -4014,8 +4312,9 @@ var MessengerSheet = ({
|
|
|
4014
4312
|
(0, import_react3.createElement)(
|
|
4015
4313
|
RN3.Text,
|
|
4016
4314
|
{ onPress: () => void RN3.Linking.openURL(BRAND_URL).catch(() => void 0), accessibilityRole: "link", hitSlop: 8, style: { color: p.muted, fontSize: 11.5 } },
|
|
4017
|
-
"Powered by ",
|
|
4018
|
-
(0, import_react3.createElement)(RN3.Text, { style: { fontWeight: "600" } }, "MRRocket")
|
|
4315
|
+
t("Powered by {brand}").split("{brand}")[0],
|
|
4316
|
+
(0, import_react3.createElement)(RN3.Text, { style: { fontWeight: "600" } }, "MRRocket"),
|
|
4317
|
+
t("Powered by {brand}").split("{brand}")[1]
|
|
4019
4318
|
)
|
|
4020
4319
|
) : null;
|
|
4021
4320
|
return (0, import_react3.createElement)(
|
|
@@ -4026,12 +4325,12 @@ var MessengerSheet = ({
|
|
|
4026
4325
|
{ behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: hu.bg } },
|
|
4027
4326
|
head,
|
|
4028
4327
|
content,
|
|
4029
|
-
view === "messages" ? null : (0, import_react3.createElement)(
|
|
4328
|
+
view === "messages" && !chat.listing ? null : (0, import_react3.createElement)(
|
|
4030
4329
|
RN3.View,
|
|
4031
4330
|
{ key: "tabs", accessibilityRole: "tablist", style: { flexDirection: "row", marginHorizontal: 12, marginTop: 6, marginBottom: 6, padding: 4, borderRadius: 22, backgroundColor: hu.surface } },
|
|
4032
|
-
tab("home", "Home", "home"),
|
|
4033
|
-
tab("messages", "Messages", "chat", chat.unseen.length),
|
|
4034
|
-
showHelp ? tab("help", "Help", "help") : null
|
|
4331
|
+
tab("home", t("Home"), "home"),
|
|
4332
|
+
tab("messages", t("Messages"), "chat", chat.unseen.length),
|
|
4333
|
+
showHelp ? tab("help", t("Help"), "help") : null
|
|
4035
4334
|
),
|
|
4036
4335
|
powered,
|
|
4037
4336
|
(0, import_react3.createElement)(RN3.View, { style: { height: 16 } })
|
|
@@ -4046,7 +4345,7 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
4046
4345
|
() => client.api()?.messengerLook(ctrl.signal).then(setLook).catch(() => void 0)
|
|
4047
4346
|
);
|
|
4048
4347
|
return () => ctrl.abort();
|
|
4049
|
-
}, [client]);
|
|
4348
|
+
}, [client, wordsVersion()]);
|
|
4050
4349
|
const defined = Object.fromEntries(Object.entries(given).filter(([, v]) => v !== void 0));
|
|
4051
4350
|
const options = look ? {
|
|
4052
4351
|
title: look.title,
|
|
@@ -4070,25 +4369,29 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
4070
4369
|
const open = messengerOpen.useOpen();
|
|
4071
4370
|
const [view, setViewState] = (0, import_react3.useState)(msgView);
|
|
4072
4371
|
const [banner, setBanner] = (0, import_react3.useState)(null);
|
|
4073
|
-
(0, import_react3.useEffect)(() => {
|
|
4074
|
-
if (open) setViewState(msgView);
|
|
4075
|
-
}, [open]);
|
|
4076
4372
|
const setView = (v) => {
|
|
4077
4373
|
msgView = v;
|
|
4078
4374
|
setViewState(v);
|
|
4079
4375
|
};
|
|
4080
4376
|
const chat = useChat(client, options, open, view);
|
|
4377
|
+
const { enter } = chat;
|
|
4378
|
+
(0, import_react3.useEffect)(() => {
|
|
4379
|
+
if (!open) return;
|
|
4380
|
+
setViewState(msgView);
|
|
4381
|
+
if (msgView === "messages") enter(msgChat);
|
|
4382
|
+
}, [open, enter]);
|
|
4081
4383
|
const latest = chat.unseen.at(-1);
|
|
4082
4384
|
(0, import_react3.useEffect)(() => {
|
|
4083
4385
|
if (!latest || open || options.preview === false) return;
|
|
4084
4386
|
setBanner(latest);
|
|
4085
|
-
const
|
|
4086
|
-
return () => clearTimeout(
|
|
4387
|
+
const t2 = setTimeout(() => setBanner(null), 6e3);
|
|
4388
|
+
return () => clearTimeout(t2);
|
|
4087
4389
|
}, [latest, open, options.preview]);
|
|
4088
4390
|
(0, import_react3.useEffect)(() => {
|
|
4089
4391
|
if (open) setBanner(null);
|
|
4090
4392
|
}, [open]);
|
|
4091
4393
|
const unseen = chat.unseen.length;
|
|
4394
|
+
const news = latest?.chat ?? null;
|
|
4092
4395
|
const ink = inkOn(p.accent);
|
|
4093
4396
|
const label = options.label?.trim() ?? "";
|
|
4094
4397
|
const r = CORNERS[options.corners ?? "round"];
|
|
@@ -4103,9 +4406,9 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
4103
4406
|
(0, import_react3.createElement)(
|
|
4104
4407
|
RN3.Pressable,
|
|
4105
4408
|
{
|
|
4106
|
-
onPress: () => openAt(
|
|
4409
|
+
onPress: () => openAt(news ? "messages" : "home", news),
|
|
4107
4410
|
accessibilityRole: "button",
|
|
4108
|
-
accessibilityLabel: unseen ?
|
|
4411
|
+
accessibilityLabel: unseen === 1 ? t("Open chat, 1 new reply") : unseen ? tf("Open chat, {n} new replies", { n: unseen }) : t("Open chat"),
|
|
4109
4412
|
style: {
|
|
4110
4413
|
height: label ? 50 : 56,
|
|
4111
4414
|
minWidth: label ? 0 : 56,
|
|
@@ -4132,7 +4435,7 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
4132
4435
|
RN3.Pressable,
|
|
4133
4436
|
{
|
|
4134
4437
|
key: "banner",
|
|
4135
|
-
onPress: () => openAt("messages"),
|
|
4438
|
+
onPress: () => openAt("messages", banner.chat),
|
|
4136
4439
|
accessibilityRole: "button",
|
|
4137
4440
|
accessibilityLiveRegion: "polite",
|
|
4138
4441
|
style: {
|
|
@@ -4152,7 +4455,7 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
4152
4455
|
elevation: 6
|
|
4153
4456
|
}
|
|
4154
4457
|
},
|
|
4155
|
-
k.muted(`${who(banner.from, options.agentName)}${unseen > 1 ? ` \xB7 ${
|
|
4458
|
+
k.muted(`${who(banner.from, options.agentName)}${unseen > 1 ? ` \xB7 ${tf("{n} new", { n: unseen })}` : ""}`, { fontWeight: "600", fontSize: 12 }),
|
|
4156
4459
|
(0, import_react3.createElement)(RN3.Text, { numberOfLines: 3, style: { color: p.fg, fontSize: 15, lineHeight: 20 } }, banner.body)
|
|
4157
4460
|
) : null;
|
|
4158
4461
|
if (!launcher && !toast && !open) return null;
|
|
@@ -4195,6 +4498,10 @@ var getMRRocket = () => shared;
|
|
|
4195
4498
|
var Ctx = (0, import_react4.createContext)(null);
|
|
4196
4499
|
var MRRocketProvider = ({ client: given, children, consent, flows = true, appearance, surveys = true, messenger = {}, feedback = {}, heatmaps = true, ...options }) => {
|
|
4197
4500
|
const [client] = (0, import_react4.useState)(() => given ?? createMRRocket({ ...options, consent }));
|
|
4501
|
+
(0, import_react4.useSyncExternalStore)(onWords, wordsVersion, wordsVersion);
|
|
4502
|
+
(0, import_react4.useEffect)(() => {
|
|
4503
|
+
if (options.locale) setLocale(options.locale);
|
|
4504
|
+
}, [options.locale]);
|
|
4198
4505
|
(0, import_react4.useEffect)(() => {
|
|
4199
4506
|
if (consent) client.setConsent(consent);
|
|
4200
4507
|
}, [client, consent]);
|
|
@@ -4263,6 +4570,7 @@ var useOnboarding = (id) => {
|
|
|
4263
4570
|
const client = useClient();
|
|
4264
4571
|
return useOnboardingItem(client.flows, id, client.startOnboarding);
|
|
4265
4572
|
};
|
|
4573
|
+
var useOnboardingReport = (name, options) => useReportWith(useClient().track, name, options);
|
|
4266
4574
|
var useFlag = (key) => {
|
|
4267
4575
|
const client = useClient();
|
|
4268
4576
|
useVersion(client);
|