@mrrocket/sdk 0.17.0 → 0.21.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 +30 -1
- package/README.md +24 -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 +15 -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 +28 -3
- package/dist/client.js +37 -5
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +7 -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/finish-web.d.ts +18 -0
- package/dist/finish-web.js +139 -0
- package/dist/finish-web.js.map +1 -0
- package/dist/flows-ui.d.ts +1 -10
- package/dist/flows-ui.js +175 -48
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows-web.d.ts +8 -1
- package/dist/flows-web.js +10 -1
- package/dist/flows-web.js.map +1 -1
- package/dist/flows.d.ts +193 -9
- package/dist/flows.js +284 -38
- 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/icons.d.ts +24 -0
- package/dist/icons.js +61 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +9 -3
- package/dist/messenger-ui.js +388 -191
- 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-state.d.ts +23 -0
- package/dist/onboarding-state.js +43 -0
- package/dist/onboarding-state.js.map +1 -0
- package/dist/paywall.js +2 -1
- package/dist/paywall.js.map +1 -1
- package/dist/react-native/index.d.cts +239 -19
- package/dist/react-native/index.d.ts +239 -19
- package/dist/react-native/index.js +1305 -518
- 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,
|
|
@@ -48,10 +50,70 @@ __export(native_exports, {
|
|
|
48
50
|
useVariant: () => useVariant
|
|
49
51
|
});
|
|
50
52
|
module.exports = __toCommonJS(native_exports);
|
|
51
|
-
var
|
|
53
|
+
var import_react4 = require("react");
|
|
54
|
+
|
|
55
|
+
// src/i18n.ts
|
|
56
|
+
var g = globalThis;
|
|
57
|
+
var s = g.__mrrocketI18n ?? (g.__mrrocketI18n = { version: 0, onWords: /* @__PURE__ */ new Set(), onLocale: /* @__PURE__ */ new Set() });
|
|
58
|
+
var tell = (listeners) => {
|
|
59
|
+
for (const fn of [...listeners]) {
|
|
60
|
+
try {
|
|
61
|
+
fn();
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var t = (word) => s.words?.[word] || word;
|
|
67
|
+
var tf = (word, vars) => t(word).replace(/\{(\w+)\}/g, (all, k) => k in vars ? String(vars[k]) : all);
|
|
68
|
+
var setWords = (words, lang) => {
|
|
69
|
+
const same2 = s.lang === lang && JSON.stringify(s.words ?? {}) === JSON.stringify(words ?? {});
|
|
70
|
+
s.words = words;
|
|
71
|
+
s.lang = lang;
|
|
72
|
+
if (same2) return;
|
|
73
|
+
s.version++;
|
|
74
|
+
tell(s.onWords);
|
|
75
|
+
};
|
|
76
|
+
var tidy = (locale) => {
|
|
77
|
+
const tag = locale?.trim().replace(/_/g, "-");
|
|
78
|
+
if (!tag) return void 0;
|
|
79
|
+
try {
|
|
80
|
+
return Intl.getCanonicalLocales(tag)[0];
|
|
81
|
+
} catch (e) {
|
|
82
|
+
return e instanceof RangeError ? void 0 : tag;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var setLocale = (locale) => {
|
|
86
|
+
const next = tidy(locale);
|
|
87
|
+
if (next === s.locale) return;
|
|
88
|
+
s.locale = next;
|
|
89
|
+
s.version++;
|
|
90
|
+
tell(s.onWords);
|
|
91
|
+
tell(s.onLocale);
|
|
92
|
+
};
|
|
93
|
+
var setDeviceLocale = (locale) => {
|
|
94
|
+
s.device = tidy(locale);
|
|
95
|
+
};
|
|
96
|
+
var currentLocale = () => s.locale ?? s.device ?? (typeof navigator !== "undefined" && navigator.language || void 0);
|
|
97
|
+
var langParam = () => {
|
|
98
|
+
const l = currentLocale();
|
|
99
|
+
return l ? `&lang=${encodeURIComponent(l)}` : "";
|
|
100
|
+
};
|
|
101
|
+
var wordsVersion = () => s.version;
|
|
102
|
+
var onWords = (fn) => {
|
|
103
|
+
s.onWords.add(fn);
|
|
104
|
+
return () => {
|
|
105
|
+
s.onWords.delete(fn);
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
var onLocale = (fn) => {
|
|
109
|
+
s.onLocale.add(fn);
|
|
110
|
+
return () => {
|
|
111
|
+
s.onLocale.delete(fn);
|
|
112
|
+
};
|
|
113
|
+
};
|
|
52
114
|
|
|
53
115
|
// src/core.ts
|
|
54
|
-
var SDK_VERSION = "0.
|
|
116
|
+
var SDK_VERSION = "0.21.0";
|
|
55
117
|
var propOf = (person, name) => {
|
|
56
118
|
const want = name.trim().toLowerCase();
|
|
57
119
|
for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;
|
|
@@ -77,7 +139,7 @@ var matchesCondition = (c, person) => {
|
|
|
77
139
|
var evaluateFlag = (flag, distinctId, person = {}) => {
|
|
78
140
|
if (!flag.on) return false;
|
|
79
141
|
const subject = { id: distinctId, ...person };
|
|
80
|
-
const set = flag.sets.length ? flag.sets.find((
|
|
142
|
+
const set = flag.sets.length ? flag.sets.find((s2) => s2.conditions.every((c) => matchesCondition(c, subject))) : { conditions: [], rollout: 100 };
|
|
81
143
|
if (!set) return false;
|
|
82
144
|
if (flagUnit(`${flag.key}:rollout:${distinctId}`) * 100 >= set.rollout) return false;
|
|
83
145
|
if (!flag.variants?.length) return true;
|
|
@@ -108,11 +170,11 @@ var parseQuery = (url) => {
|
|
|
108
170
|
const q = url.split("#")[0];
|
|
109
171
|
const at = q.indexOf("?");
|
|
110
172
|
if (at < 0) return out;
|
|
111
|
-
const dec = (
|
|
173
|
+
const dec = (s2) => {
|
|
112
174
|
try {
|
|
113
|
-
return decodeURIComponent(
|
|
175
|
+
return decodeURIComponent(s2.replace(/\+/g, " "));
|
|
114
176
|
} catch {
|
|
115
|
-
return
|
|
177
|
+
return s2;
|
|
116
178
|
}
|
|
117
179
|
};
|
|
118
180
|
for (const pair of q.slice(at + 1).split("&")) {
|
|
@@ -173,12 +235,52 @@ var textChanges = (config, distinctId, preview) => {
|
|
|
173
235
|
};
|
|
174
236
|
var newDistinctId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `ck_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
175
237
|
|
|
238
|
+
// src/onboarding-state.ts
|
|
239
|
+
var import_react = require("react");
|
|
240
|
+
var none = [];
|
|
241
|
+
var useOnboardingItem = (flows, id, start) => {
|
|
242
|
+
const [state, setState] = (0, import_react.useState)(() => flows && id ? flows.onboarding.item(id) : null);
|
|
243
|
+
(0, import_react.useEffect)(() => {
|
|
244
|
+
if (!flows || !id) {
|
|
245
|
+
setState(null);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const up = () => {
|
|
249
|
+
const s2 = flows.onboarding.item(id);
|
|
250
|
+
setState(s2);
|
|
251
|
+
if (s2 && !s2.done && !s2.dismissed) flows.onboarding.seen(id);
|
|
252
|
+
};
|
|
253
|
+
up();
|
|
254
|
+
return flows.subscribe(up);
|
|
255
|
+
}, [flows, id]);
|
|
256
|
+
return (0, import_react.useMemo)(
|
|
257
|
+
() => ({
|
|
258
|
+
item: state?.item ?? null,
|
|
259
|
+
steps: state?.steps ?? none,
|
|
260
|
+
done: !!state?.done,
|
|
261
|
+
dismissed: !!state?.dismissed,
|
|
262
|
+
eligible: !!state?.eligible,
|
|
263
|
+
complete: (stepId) => {
|
|
264
|
+
if (flows && id) flows.onboarding.complete(id, stepId);
|
|
265
|
+
},
|
|
266
|
+
dismiss: () => {
|
|
267
|
+
if (flows && id) flows.onboarding.dismissItem(id);
|
|
268
|
+
},
|
|
269
|
+
answer: (question, value) => {
|
|
270
|
+
if (flows && id) flows.onboarding.answerItem(id, question, value);
|
|
271
|
+
},
|
|
272
|
+
start
|
|
273
|
+
}),
|
|
274
|
+
[state, flows, id, start]
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
176
278
|
// src/identity.ts
|
|
177
279
|
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 }).";
|
|
178
280
|
var warnUnsigned = (status, log = (m) => console.warn(m)) => {
|
|
179
|
-
const
|
|
180
|
-
if (status !== 401 ||
|
|
181
|
-
|
|
281
|
+
const g2 = globalThis;
|
|
282
|
+
if (status !== 401 || g2.__mrrocketUnsigned) return;
|
|
283
|
+
g2.__mrrocketUnsigned = true;
|
|
182
284
|
log(`[MRRocket] ${UNSIGNED_HELP}`);
|
|
183
285
|
};
|
|
184
286
|
var nextSigned = (signed, id, hash, log = (m) => console.warn(m)) => {
|
|
@@ -202,11 +304,11 @@ var BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-b
|
|
|
202
304
|
var inkOn = (hex) => {
|
|
203
305
|
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
204
306
|
if (!m) return "#ffffff";
|
|
205
|
-
const [r,
|
|
307
|
+
const [r, g2, b] = [m[1], m[2], m[3]].map((x) => {
|
|
206
308
|
const c = parseInt(x, 16) / 255;
|
|
207
309
|
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
208
310
|
});
|
|
209
|
-
return 0.2126 * r + 0.7152 *
|
|
311
|
+
return 0.2126 * r + 0.7152 * g2 + 0.0722 * b > 0.45 ? "#111214" : "#ffffff";
|
|
210
312
|
};
|
|
211
313
|
var ApiError = class extends Error {
|
|
212
314
|
constructor(message, status) {
|
|
@@ -215,7 +317,11 @@ var ApiError = class extends Error {
|
|
|
215
317
|
this.status = status;
|
|
216
318
|
}
|
|
217
319
|
};
|
|
218
|
-
var qs = (params) => Object.entries(params).filter((e) => !!e[1]).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
320
|
+
var qs = (params, lang = currentLocale()) => Object.entries({ ...params, lang }).filter((e) => !!e[1]).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
321
|
+
var withLang = (url) => {
|
|
322
|
+
const q = qs({});
|
|
323
|
+
return q ? `${url}?${q}` : url;
|
|
324
|
+
};
|
|
219
325
|
var call = async (url, init, signal) => {
|
|
220
326
|
let res;
|
|
221
327
|
try {
|
|
@@ -227,7 +333,7 @@ var call = async (url, init, signal) => {
|
|
|
227
333
|
});
|
|
228
334
|
} catch (e) {
|
|
229
335
|
if (e.name === "AbortError") throw e;
|
|
230
|
-
throw new ApiError("Could not reach the server. Check your connection and try again.", 0);
|
|
336
|
+
throw new ApiError(t("Could not reach the server. Check your connection and try again."), 0);
|
|
231
337
|
}
|
|
232
338
|
if (!res.ok) {
|
|
233
339
|
warnUnsigned(res.status);
|
|
@@ -236,7 +342,7 @@ var call = async (url, init, signal) => {
|
|
|
236
342
|
detail = (await res.json()).error ?? "";
|
|
237
343
|
} catch {
|
|
238
344
|
}
|
|
239
|
-
throw new ApiError(detail || (res.status === 429 ? "Too many requests. Try again in a minute." : "Something went wrong. Please try again."), res.status);
|
|
345
|
+
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);
|
|
240
346
|
}
|
|
241
347
|
return await res.json();
|
|
242
348
|
};
|
|
@@ -244,7 +350,7 @@ var createApi = (c) => {
|
|
|
244
350
|
const base = `${c.host}/api/sdk`;
|
|
245
351
|
const who2 = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };
|
|
246
352
|
const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };
|
|
247
|
-
const post = (path, body, signal) => call(`${base}${path}
|
|
353
|
+
const post = (path, body, signal) => call(withLang(`${base}${path}`), { method: "POST", body: JSON.stringify({ ...who2, ...body }) }, signal);
|
|
248
354
|
return {
|
|
249
355
|
list: (o, signal) => call(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
|
|
250
356
|
post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
@@ -257,8 +363,12 @@ var createApi = (c) => {
|
|
|
257
363
|
survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
|
|
258
364
|
seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
|
|
259
365
|
respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
|
|
260
|
-
chat
|
|
261
|
-
|
|
366
|
+
/** One chat by id, else the visitor's newest; `conversations` lists them all. */
|
|
367
|
+
chat: (signal, conversation) => call(`${base}/messages?${qs({ ...me, conversation })}`, void 0, signal),
|
|
368
|
+
/**
|
|
369
|
+
* A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). `conversation` is
|
|
370
|
+
* the chat it answers, or "new" to start one; without it, the running chat or else a new one.
|
|
371
|
+
*/
|
|
262
372
|
sendChat: (m) => post("/messages", m),
|
|
263
373
|
/** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
|
|
264
374
|
uploadChatFile: (file, signal) => {
|
|
@@ -268,10 +378,10 @@ var createApi = (c) => {
|
|
|
268
378
|
if (c.userHash) form.append("userHash", c.userHash);
|
|
269
379
|
if ("uri" in file) form.append("file", file);
|
|
270
380
|
else form.append("file", file, file.name || "file");
|
|
271
|
-
return call(`${base}/messages/attachments
|
|
381
|
+
return call(withLang(`${base}/messages/attachments`), { method: "POST", body: form }, signal);
|
|
272
382
|
},
|
|
273
383
|
/** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
|
|
274
|
-
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,
|
|
384
|
+
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me, "")}`,
|
|
275
385
|
articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
|
|
276
386
|
article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
277
387
|
messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
|
|
@@ -282,13 +392,13 @@ var createApi = (c) => {
|
|
|
282
392
|
// src/batch.ts
|
|
283
393
|
var MAX_BATCH = 100;
|
|
284
394
|
var MAX_BYTES = 6e4;
|
|
285
|
-
var byteLength = (
|
|
395
|
+
var byteLength = (s2) => {
|
|
286
396
|
let n = 0;
|
|
287
|
-
for (let i = 0; i <
|
|
288
|
-
const c =
|
|
397
|
+
for (let i = 0; i < s2.length; i++) {
|
|
398
|
+
const c = s2.charCodeAt(i);
|
|
289
399
|
if (c < 128) n += 1;
|
|
290
400
|
else if (c < 2048) n += 2;
|
|
291
|
-
else if (c >= 55296 && c <= 56319 && i + 1 <
|
|
401
|
+
else if (c >= 55296 && c <= 56319 && i + 1 < s2.length && (s2.charCodeAt(i + 1) & 64512) === 56320) {
|
|
292
402
|
n += 4;
|
|
293
403
|
i++;
|
|
294
404
|
} else n += 3;
|
|
@@ -323,7 +433,7 @@ var batchRequests = (key, events, sdk, userHash) => {
|
|
|
323
433
|
|
|
324
434
|
// src/errors.ts
|
|
325
435
|
var MAX_STACK = 8 * 1024;
|
|
326
|
-
var clip = (
|
|
436
|
+
var clip = (s2, n) => s2.length > n ? `${s2.slice(0, n - 1)}\u2026` : s2;
|
|
327
437
|
var describeError = (error) => {
|
|
328
438
|
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 };
|
|
329
439
|
if (typeof error === "string") return { type: "Error", message: clip(error, 1e3) };
|
|
@@ -344,22 +454,52 @@ var PLAIN_CANCEL = {
|
|
|
344
454
|
question: "",
|
|
345
455
|
reasons: [],
|
|
346
456
|
offers: {},
|
|
347
|
-
|
|
457
|
+
// Read when it is shown, so it follows the visitor's language.
|
|
458
|
+
get confirm() {
|
|
459
|
+
return { title: t("Cancel your subscription?"), body: t("You keep access until the end of the billing period."), button: t("Cancel subscription") };
|
|
460
|
+
},
|
|
348
461
|
offersFor: "business"
|
|
349
462
|
};
|
|
350
|
-
var FAILED = "Something went wrong. Please try again.";
|
|
351
463
|
var PROGRESS_KEY = "mrrocket:onboarding";
|
|
352
|
-
var eventSlug = (
|
|
464
|
+
var eventSlug = (s2) => s2.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
353
465
|
var SIGNUP = /* @__PURE__ */ new Set(["signed_up", "signup", "sign_up", "registered", "account_created", "user_signed_up"]);
|
|
354
466
|
var matchesTrigger = (trigger, event) => {
|
|
355
|
-
const
|
|
467
|
+
const t2 = eventSlug(trigger);
|
|
356
468
|
const e = eventSlug(event);
|
|
357
|
-
if (!
|
|
358
|
-
return
|
|
469
|
+
if (!t2 || !e) return false;
|
|
470
|
+
return t2 === e || SIGNUP.has(t2) && SIGNUP.has(e);
|
|
359
471
|
};
|
|
360
472
|
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());
|
|
361
473
|
var isPageTrigger = (trigger) => trigger.trim().startsWith("/");
|
|
362
|
-
var
|
|
474
|
+
var NEEDS_NEWER = "Needs tracking code 0.18.0";
|
|
475
|
+
var momentOf = (f) => f.when ?? f.trigger;
|
|
476
|
+
var manual = (trigger) => eventSlug(trigger) === "from_a_checklist" || eventSlug(trigger) === "from_your_code" || eventSlug(trigger) === eventSlug(NEEDS_NEWER);
|
|
477
|
+
var AFTER_PREFIX = "after:";
|
|
478
|
+
var afterOf = (trigger) => {
|
|
479
|
+
const t2 = (trigger ?? "").trim();
|
|
480
|
+
return t2.toLowerCase().startsWith(AFTER_PREFIX) ? t2.slice(AFTER_PREFIX.length).trim() || null : null;
|
|
481
|
+
};
|
|
482
|
+
var opensOf = (step) => step.opens ?? (step.startsTour ? { kind: "tour", id: step.startsTour } : void 0);
|
|
483
|
+
var OTHER_PREFIX = "other:";
|
|
484
|
+
var scaleOf = (q) => {
|
|
485
|
+
const min = q?.min ?? 0;
|
|
486
|
+
const max = q?.max ?? 10;
|
|
487
|
+
return Array.from({ length: Math.max(0, Math.min(max - min, 20) + 1) }, (_, i) => min + i);
|
|
488
|
+
};
|
|
489
|
+
var answerOk = (q, value) => {
|
|
490
|
+
if (!q || value == null) return false;
|
|
491
|
+
if (q.type === "choice") {
|
|
492
|
+
if (q.other && value.startsWith(OTHER_PREFIX)) return value.slice(OTHER_PREFIX.length).trim().length > 0;
|
|
493
|
+
return (q.options ?? []).some((o) => o.id === value);
|
|
494
|
+
}
|
|
495
|
+
if (q.type === "scale") return scaleOf(q).some((n) => String(n) === value);
|
|
496
|
+
return value.trim().length > 0 && value.length <= 500;
|
|
497
|
+
};
|
|
498
|
+
var answerLabel = (q, value) => {
|
|
499
|
+
if (q?.type !== "choice") return value.trim();
|
|
500
|
+
if (q.other && value.startsWith(OTHER_PREFIX)) return value.slice(OTHER_PREFIX.length).trim();
|
|
501
|
+
return q.options?.find((o) => o.id === value)?.label ?? value;
|
|
502
|
+
};
|
|
363
503
|
var videoEmbed = (url) => {
|
|
364
504
|
const u = (url ?? "").trim();
|
|
365
505
|
if (!/^https?:\/\//i.test(u)) return null;
|
|
@@ -381,12 +521,14 @@ var runId = () => `cf_${Math.random().toString(36).slice(2, 10)}${Date.now().toS
|
|
|
381
521
|
var discounted = (amount, percent) => Math.round(amount * (1 - percent / 100) * 100) / 100;
|
|
382
522
|
var money = (amount, currency) => {
|
|
383
523
|
try {
|
|
384
|
-
return new Intl.NumberFormat(
|
|
524
|
+
return new Intl.NumberFormat(currentLocale(), { style: "currency", currency }).format(amount);
|
|
385
525
|
} catch {
|
|
386
526
|
return `${amount.toFixed(2)} ${currency}`;
|
|
387
527
|
}
|
|
388
528
|
};
|
|
389
|
-
var offerButton = (o) => o.kind === "contact" ? "Send to the team" : o.kind === "pause" ? "Pause instead" : o.kind === "downgrade" ? "Switch plan" : "Accept offer";
|
|
529
|
+
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");
|
|
530
|
+
var monthsWord = (n) => n === 1 ? t("1 month") : tf("{n} months", { n });
|
|
531
|
+
var forMonths = (n) => tf("for {months}", { months: monthsWord(n) });
|
|
390
532
|
var safeUrl = (url) => {
|
|
391
533
|
const u = (url ?? "").trim();
|
|
392
534
|
return /^https?:\/\//i.test(u) || /^\/(?!\/)/.test(u) ? u : void 0;
|
|
@@ -425,7 +567,12 @@ var createFlows = (env) => {
|
|
|
425
567
|
const now = () => env.now?.() ?? Date.now();
|
|
426
568
|
let timer = null;
|
|
427
569
|
let returnTo = null;
|
|
570
|
+
const answers = /* @__PURE__ */ new Map();
|
|
571
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
572
|
+
const celebrated = /* @__PURE__ */ new Set();
|
|
573
|
+
let finaleNow = null;
|
|
428
574
|
const flows = () => config?.onboarding ?? [];
|
|
575
|
+
const drawn = () => flows().filter((f) => f.shownBy !== "code");
|
|
429
576
|
const flowById = (id) => id ? flows().find((f) => f.id === id) : void 0;
|
|
430
577
|
const prog = (id) => {
|
|
431
578
|
let p = progress.get(id);
|
|
@@ -499,6 +646,14 @@ var createFlows = (env) => {
|
|
|
499
646
|
};
|
|
500
647
|
const keyOf = (pid) => `${PROGRESS_KEY}:${pid ?? "visitor"}`;
|
|
501
648
|
const eventsKeyOf = (pid) => `${PROGRESS_KEY}:events:${pid ?? "visitor"}`;
|
|
649
|
+
const answersKeyOf = (pid) => `${PROGRESS_KEY}:answers:${pid ?? "visitor"}`;
|
|
650
|
+
const celebrate = (f) => {
|
|
651
|
+
if (!f.finish || f.finish === "none" || f.shownBy === "code" || celebrated.has(f.id)) return;
|
|
652
|
+
const p = progress.get(f.id);
|
|
653
|
+
if (!p || p.dismissed || walkLine(f, nodesOf(f), p, false).at !== "end") return;
|
|
654
|
+
celebrated.add(f.id);
|
|
655
|
+
finaleNow = { kind: f.finish, key: `${f.id}:${now()}` };
|
|
656
|
+
};
|
|
502
657
|
const save = () => {
|
|
503
658
|
dirty = false;
|
|
504
659
|
const store = env.storage?.();
|
|
@@ -514,18 +669,19 @@ var createFlows = (env) => {
|
|
|
514
669
|
};
|
|
515
670
|
try {
|
|
516
671
|
store.set(keyOf(env.personId()), JSON.stringify(out));
|
|
672
|
+
if (answers.size) store.set(answersKeyOf(env.personId()), JSON.stringify(Object.fromEntries(answers)));
|
|
517
673
|
if (seenEvents.size) store.set(eventsKeyOf(env.personId()), JSON.stringify([...seenEvents].slice(-200)));
|
|
518
674
|
} catch {
|
|
519
675
|
}
|
|
520
676
|
};
|
|
521
677
|
const merge = (state) => {
|
|
522
|
-
for (const [id,
|
|
678
|
+
for (const [id, s2] of Object.entries(state ?? {})) {
|
|
523
679
|
const p = prog(id);
|
|
524
|
-
for (const d of
|
|
525
|
-
if (
|
|
526
|
-
for (const [k, v] of Object.entries(
|
|
527
|
-
for (const [k, v] of Object.entries(
|
|
528
|
-
for (const [k, v] of Object.entries(
|
|
680
|
+
for (const d of s2.done ?? []) p.done.add(d);
|
|
681
|
+
if (s2.dismissed) p.dismissed = true;
|
|
682
|
+
for (const [k, v] of Object.entries(s2.answers ?? {})) if (!p.answers.has(k)) p.answers.set(k, v === "yes");
|
|
683
|
+
for (const [k, v] of Object.entries(s2.at ?? {})) if (typeof v === "number" && !p.at.has(k)) p.at.set(k, v);
|
|
684
|
+
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);
|
|
529
685
|
}
|
|
530
686
|
};
|
|
531
687
|
const wakeAt = (until) => {
|
|
@@ -565,9 +721,18 @@ var createFlows = (env) => {
|
|
|
565
721
|
return {
|
|
566
722
|
type: "checklist",
|
|
567
723
|
flow,
|
|
568
|
-
items: items.map((
|
|
569
|
-
const
|
|
570
|
-
|
|
724
|
+
items: items.map((s2) => {
|
|
725
|
+
const o = opensOf(s2);
|
|
726
|
+
const t2 = o && (o.kind === "tour" || o.kind === "modal") && o.id !== flow.id ? flowById(o.id) : void 0;
|
|
727
|
+
const target = t2 && t2.shownBy !== "code" && nodesOf(t2).length ? t2 : void 0;
|
|
728
|
+
const opens = o && (o.kind === "page" ? !!o.target.trim() : o.kind === "code" || !!target) ? o : void 0;
|
|
729
|
+
return {
|
|
730
|
+
step: s2,
|
|
731
|
+
done: p.done.has(s2.id),
|
|
732
|
+
...target && o?.kind === "tour" ? { tour: target } : {},
|
|
733
|
+
...opens ? { opens } : {},
|
|
734
|
+
...target ? { target } : {}
|
|
735
|
+
};
|
|
571
736
|
})
|
|
572
737
|
};
|
|
573
738
|
}
|
|
@@ -578,7 +743,16 @@ var createFlows = (env) => {
|
|
|
578
743
|
const rest = line.slice(index + 1);
|
|
579
744
|
const fork = rest.findIndex((n) => n.kind === "condition");
|
|
580
745
|
const ahead = (fork < 0 ? rest : rest.slice(0, fork)).filter(isStep).length;
|
|
581
|
-
return {
|
|
746
|
+
return {
|
|
747
|
+
type: "step",
|
|
748
|
+
flow,
|
|
749
|
+
step,
|
|
750
|
+
index: w.before,
|
|
751
|
+
total: w.before + 1 + ahead,
|
|
752
|
+
last: !ahead && fork < 0,
|
|
753
|
+
// 0.18.0: a question's answer so far, and whether there is one before it to go back to.
|
|
754
|
+
...step.kind === "question" ? { answer: drafts.get(step.id) ?? (step.question ? answers.get(step.question.key) : void 0), back: w.before > 0 } : {}
|
|
755
|
+
};
|
|
582
756
|
};
|
|
583
757
|
const view = () => {
|
|
584
758
|
if (cancel) {
|
|
@@ -605,23 +779,25 @@ var createFlows = (env) => {
|
|
|
605
779
|
if (ob) return ob;
|
|
606
780
|
if (active && !holding) {
|
|
607
781
|
const back = returnTo;
|
|
608
|
-
const
|
|
782
|
+
const ended2 = active;
|
|
609
783
|
returnTo = null;
|
|
610
784
|
active = null;
|
|
611
785
|
const list = back ? flowById(back.flow) : void 0;
|
|
612
786
|
if (back && list) {
|
|
613
|
-
const item = stepsIn(nodesOf(list)).find((
|
|
787
|
+
const item = stepsIn(nodesOf(list)).find((s2) => s2.id === back.step);
|
|
614
788
|
const p = prog(list.id);
|
|
615
|
-
if (item && !item.event && !progress.get(
|
|
789
|
+
if (item && !item.event && !progress.get(ended2)?.dismissed && !p.done.has(item.id)) {
|
|
616
790
|
markDone(p, item.id);
|
|
617
791
|
record({ flow: list.id, step: item.id, kind: "checklist", action: "completed" });
|
|
792
|
+
celebrate(list);
|
|
618
793
|
}
|
|
619
794
|
active = list.id;
|
|
620
795
|
const again = onboardingView();
|
|
621
796
|
if (again) return again;
|
|
622
797
|
if (!holding) active = null;
|
|
623
798
|
} else if (known) {
|
|
624
|
-
const
|
|
799
|
+
const was = flowById(ended2);
|
|
800
|
+
const after = was ? momentOf(was) : void 0;
|
|
625
801
|
Promise.resolve().then(() => autoStart(after));
|
|
626
802
|
}
|
|
627
803
|
}
|
|
@@ -649,22 +825,64 @@ var createFlows = (env) => {
|
|
|
649
825
|
emit();
|
|
650
826
|
return true;
|
|
651
827
|
};
|
|
652
|
-
const allowed = (f) => f.audience !== "signed_in" || !!env.personId();
|
|
828
|
+
const allowed = (f) => f.audience === "answered" ? !!f.answered && answers.get(f.answered.question) === f.answered.option : f.audience !== "signed_in" || !!env.personId();
|
|
829
|
+
const predecessorEnded = (id) => {
|
|
830
|
+
const g2 = flowById(id);
|
|
831
|
+
return !g2 || finished(g2);
|
|
832
|
+
};
|
|
833
|
+
const due = (f, trigger = momentOf(f)) => {
|
|
834
|
+
const path = env.path?.();
|
|
835
|
+
const pred = afterOf(trigger);
|
|
836
|
+
if (pred) return predecessorEnded(pred);
|
|
837
|
+
if (manual(trigger)) return false;
|
|
838
|
+
if (isPageTrigger(trigger)) return path !== void 0 && pageMatches(trigger, path);
|
|
839
|
+
if (startsOnLoad(trigger)) return true;
|
|
840
|
+
for (const e of seenEvents) if (matchesTrigger(trigger, e)) return true;
|
|
841
|
+
return false;
|
|
842
|
+
};
|
|
653
843
|
const autoStart = (after) => {
|
|
654
844
|
if (!auto || active || !known) return;
|
|
655
845
|
if (!env.personId() && !env.storage?.()) return;
|
|
656
846
|
const path = env.path?.();
|
|
657
847
|
const same2 = after && !manual(after) ? eventSlug(after) : "";
|
|
658
|
-
const next =
|
|
659
|
-
(f) => (isPageTrigger(f
|
|
848
|
+
const next = drawn().find(
|
|
849
|
+
(f) => (afterOf(momentOf(f)) ? predecessorEnded(afterOf(momentOf(f))) : isPageTrigger(momentOf(f)) ? path !== void 0 && pageMatches(momentOf(f), path) : startsOnLoad(momentOf(f)) || !!same2 && eventSlug(momentOf(f)) === same2) && allowed(f) && nodesOf(f).length && !finished(f)
|
|
660
850
|
);
|
|
661
851
|
if (next) start(next);
|
|
662
852
|
};
|
|
853
|
+
const openItem = (list, stepId, target) => {
|
|
854
|
+
const p = prog(target.id);
|
|
855
|
+
p.done.clear();
|
|
856
|
+
p.at.clear();
|
|
857
|
+
p.dismissed = false;
|
|
858
|
+
returnTo = { flow: list.id, step: stepId };
|
|
859
|
+
active = target.id;
|
|
860
|
+
env.track(ONBOARDING_EVENT, { flow: target.id, action: "started", from: list.id });
|
|
861
|
+
save();
|
|
862
|
+
emit();
|
|
863
|
+
};
|
|
864
|
+
const answerStep = (flow, step, raw) => {
|
|
865
|
+
const q = step.question;
|
|
866
|
+
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;
|
|
867
|
+
answers.set(q.key, value);
|
|
868
|
+
drafts.delete(step.id);
|
|
869
|
+
markDone(prog(flow.id), step.id);
|
|
870
|
+
env.track(ONBOARDING_EVENT, { flow: flow.id, step: step.id, kind: "question", action: "completed", question: q.key, answer: value, label: answerLabel(q, value) });
|
|
871
|
+
celebrate(flow);
|
|
872
|
+
save();
|
|
873
|
+
};
|
|
874
|
+
const ended = (f) => {
|
|
875
|
+
save();
|
|
876
|
+
if (finished(f)) Promise.resolve().then(() => autoStart());
|
|
877
|
+
emit();
|
|
878
|
+
};
|
|
663
879
|
const refresh = () => {
|
|
664
880
|
const pid = env.personId();
|
|
665
881
|
if (progressFor !== null && progressFor !== pid) {
|
|
666
882
|
progress.clear();
|
|
667
883
|
seenEvents.clear();
|
|
884
|
+
answers.clear();
|
|
885
|
+
drafts.clear();
|
|
668
886
|
handled.clear();
|
|
669
887
|
active = null;
|
|
670
888
|
returnTo = null;
|
|
@@ -681,6 +899,10 @@ var createFlows = (env) => {
|
|
|
681
899
|
if (seen) {
|
|
682
900
|
for (const e of JSON.parse(seen)) if (typeof e === "string") seenEvents.add(e);
|
|
683
901
|
}
|
|
902
|
+
const said = await store.get(answersKeyOf(pid));
|
|
903
|
+
if (said) {
|
|
904
|
+
for (const [k, v] of Object.entries(JSON.parse(said))) if (typeof v === "string") answers.set(k, v);
|
|
905
|
+
}
|
|
684
906
|
} catch {
|
|
685
907
|
}
|
|
686
908
|
}
|
|
@@ -691,6 +913,7 @@ var createFlows = (env) => {
|
|
|
691
913
|
if (state && env.personId() === pid) {
|
|
692
914
|
merge(state.onboarding);
|
|
693
915
|
for (const e of state.events ?? []) if (typeof e === "string") seenEvents.add(eventSlug(e));
|
|
916
|
+
for (const [k, v] of Object.entries(state.answers ?? {})) if (typeof v === "string" && !answers.has(k)) answers.set(k, v);
|
|
694
917
|
messages = (state.messages ?? []).filter((m) => m && m.id && m.title);
|
|
695
918
|
}
|
|
696
919
|
}
|
|
@@ -751,7 +974,7 @@ var createFlows = (env) => {
|
|
|
751
974
|
() => finish(c, r),
|
|
752
975
|
() => {
|
|
753
976
|
c.busy = false;
|
|
754
|
-
c.error =
|
|
977
|
+
c.error = t("Something went wrong. Please try again.");
|
|
755
978
|
emit();
|
|
756
979
|
}
|
|
757
980
|
);
|
|
@@ -784,27 +1007,33 @@ var createFlows = (env) => {
|
|
|
784
1007
|
if (fresh) seenEvents.add(slug);
|
|
785
1008
|
const flow = flowById(active);
|
|
786
1009
|
let ticked = false;
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1010
|
+
const lists = [...flow ? [flow] : [], ...flows().filter((f) => f.shownBy === "code" && f.id !== flow?.id)];
|
|
1011
|
+
for (const l of lists) {
|
|
1012
|
+
const p = prog(l.id);
|
|
1013
|
+
let here = false;
|
|
1014
|
+
for (const s2 of stepsIn(nodesOf(l)))
|
|
1015
|
+
if (s2.kind === "checklist" && s2.event && !p.done.has(s2.id) && matchesTrigger(s2.event, name)) {
|
|
1016
|
+
markDone(p, s2.id);
|
|
1017
|
+
here = true;
|
|
1018
|
+
env.track(ONBOARDING_EVENT, { flow: l.id, step: s2.id, kind: s2.kind, action: "completed" });
|
|
794
1019
|
}
|
|
1020
|
+
if (here) {
|
|
1021
|
+
ticked = true;
|
|
1022
|
+
celebrate(l);
|
|
1023
|
+
}
|
|
795
1024
|
}
|
|
796
|
-
if (ticked || fresh && flow) {
|
|
1025
|
+
if (ticked || fresh && (flow || lists.length)) {
|
|
797
1026
|
save();
|
|
798
1027
|
emit();
|
|
799
1028
|
} else if (fresh) save();
|
|
800
1029
|
if (!auto || !env.personId() && !env.storage?.()) return;
|
|
801
|
-
const next =
|
|
1030
|
+
const next = drawn().find((f) => nodesOf(f).length && !manual(momentOf(f)) && matchesTrigger(momentOf(f), name) && allowed(f) && !finished(f));
|
|
802
1031
|
if (next) start(next);
|
|
803
1032
|
},
|
|
804
1033
|
openCancelFlow,
|
|
805
1034
|
startOnboarding: (id) => {
|
|
806
|
-
const f = id ? flowById(id) :
|
|
807
|
-
if (!f || !nodesOf(f).length) return false;
|
|
1035
|
+
const f = id ? flowById(id) : drawn().find((x) => nodesOf(x).length && !manual(momentOf(x)) && !afterOf(momentOf(x)) && !finished(x));
|
|
1036
|
+
if (!f || !nodesOf(f).length || f.shownBy === "code") return false;
|
|
808
1037
|
const p = prog(f.id);
|
|
809
1038
|
p.dismissed = false;
|
|
810
1039
|
return start(f);
|
|
@@ -825,8 +1054,8 @@ var createFlows = (env) => {
|
|
|
825
1054
|
answer: (text2) => {
|
|
826
1055
|
const c = cancel;
|
|
827
1056
|
if (!c || c.busy || c.screen !== "followup") return;
|
|
828
|
-
const
|
|
829
|
-
c.answer =
|
|
1057
|
+
const t2 = text2.slice(0, 500);
|
|
1058
|
+
c.answer = t2.trim() ? t2 : void 0;
|
|
830
1059
|
emit();
|
|
831
1060
|
},
|
|
832
1061
|
next: () => {
|
|
@@ -895,8 +1124,17 @@ var createFlows = (env) => {
|
|
|
895
1124
|
next: () => {
|
|
896
1125
|
const v = onboardingView();
|
|
897
1126
|
if (v?.type !== "step") return;
|
|
1127
|
+
if (v.step.kind === "question") {
|
|
1128
|
+
const q = v.step.question;
|
|
1129
|
+
const a = drafts.get(v.step.id) ?? (q ? answers.get(q.key) : void 0);
|
|
1130
|
+
if (!q || !answerOk(q, a)) return;
|
|
1131
|
+
answerStep(v.flow, v.step, a);
|
|
1132
|
+
emit();
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
898
1135
|
markDone(prog(v.flow.id), v.step.id);
|
|
899
1136
|
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: v.step.kind, action: "completed" });
|
|
1137
|
+
celebrate(v.flow);
|
|
900
1138
|
save();
|
|
901
1139
|
emit();
|
|
902
1140
|
const url = v.step.kind === "modal" ? safeUrl(v.step.url) : void 0;
|
|
@@ -909,6 +1147,7 @@ var createFlows = (env) => {
|
|
|
909
1147
|
if (!item || item.done) return;
|
|
910
1148
|
markDone(prog(v.flow.id), stepId);
|
|
911
1149
|
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "completed" });
|
|
1150
|
+
celebrate(v.flow);
|
|
912
1151
|
save();
|
|
913
1152
|
emit();
|
|
914
1153
|
},
|
|
@@ -925,20 +1164,123 @@ var createFlows = (env) => {
|
|
|
925
1164
|
startTour: (stepId) => {
|
|
926
1165
|
const v = onboardingView();
|
|
927
1166
|
if (v?.type !== "checklist") return false;
|
|
928
|
-
const
|
|
929
|
-
if (!
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1167
|
+
const target = v.items.find((i) => i.step.id === stepId)?.target;
|
|
1168
|
+
if (!target) return false;
|
|
1169
|
+
openItem(v.flow, stepId, target);
|
|
1170
|
+
return true;
|
|
1171
|
+
},
|
|
1172
|
+
open: (stepId) => {
|
|
1173
|
+
const v = onboardingView();
|
|
1174
|
+
if (v?.type !== "checklist") return false;
|
|
1175
|
+
const it = v.items.find((i) => i.step.id === stepId);
|
|
1176
|
+
const o = it?.opens;
|
|
1177
|
+
if (!it || !o) return false;
|
|
1178
|
+
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "opened", opens: o.kind });
|
|
1179
|
+
if ((o.kind === "tour" || o.kind === "modal") && it.target) {
|
|
1180
|
+
openItem(v.flow, stepId, it.target);
|
|
1181
|
+
return true;
|
|
1182
|
+
}
|
|
1183
|
+
if (!it.done && !it.step.event) {
|
|
1184
|
+
markDone(prog(v.flow.id), stepId);
|
|
1185
|
+
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "completed" });
|
|
1186
|
+
celebrate(v.flow);
|
|
1187
|
+
}
|
|
937
1188
|
save();
|
|
938
1189
|
emit();
|
|
1190
|
+
if (o.kind === "page") env.navigate?.(o.target.trim());
|
|
1191
|
+
else env.task?.(stepId, v.flow);
|
|
939
1192
|
return true;
|
|
1193
|
+
},
|
|
1194
|
+
answer: (value) => {
|
|
1195
|
+
const v = onboardingView();
|
|
1196
|
+
if (v?.type !== "step" || v.step.kind !== "question") return;
|
|
1197
|
+
drafts.set(v.step.id, String(value).slice(0, 500));
|
|
1198
|
+
emit();
|
|
1199
|
+
},
|
|
1200
|
+
skip: () => {
|
|
1201
|
+
const v = onboardingView();
|
|
1202
|
+
const q = v?.type === "step" && v.step.kind === "question" ? v.step.question : void 0;
|
|
1203
|
+
if (v?.type !== "step" || !q?.optional) return;
|
|
1204
|
+
drafts.delete(v.step.id);
|
|
1205
|
+
markDone(prog(v.flow.id), v.step.id);
|
|
1206
|
+
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: "question", action: "completed", question: q.key, skipped: true });
|
|
1207
|
+
celebrate(v.flow);
|
|
1208
|
+
save();
|
|
1209
|
+
emit();
|
|
1210
|
+
},
|
|
1211
|
+
back: () => {
|
|
1212
|
+
const v = onboardingView();
|
|
1213
|
+
if (v?.type !== "step" || v.step.kind !== "question" || !v.back) return;
|
|
1214
|
+
const line = stepsIn(nodesOf(v.flow));
|
|
1215
|
+
const at = line.findIndex((s2) => s2.id === v.step.id);
|
|
1216
|
+
const p = prog(v.flow.id);
|
|
1217
|
+
for (let i = at - 1; i >= 0; i--)
|
|
1218
|
+
if (p.done.has(line[i].id)) {
|
|
1219
|
+
p.done.delete(line[i].id);
|
|
1220
|
+
p.at.delete(line[i].id);
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
save();
|
|
1224
|
+
emit();
|
|
1225
|
+
},
|
|
1226
|
+
item: (id) => {
|
|
1227
|
+
const f = flowById(id);
|
|
1228
|
+
if (!f) return null;
|
|
1229
|
+
const p = progress.get(f.id);
|
|
1230
|
+
const steps = stepsIn(nodesOf(f)).map((s2) => {
|
|
1231
|
+
const a = s2.question ? answers.get(s2.question.key) : void 0;
|
|
1232
|
+
return { ...s2, done: !!p?.done.has(s2.id), ...a !== void 0 ? { answer: a } : {} };
|
|
1233
|
+
});
|
|
1234
|
+
const done = steps.length > 0 && steps.every((s2) => s2.done);
|
|
1235
|
+
const dismissed = !!p?.dismissed;
|
|
1236
|
+
return { item: f, steps, done, dismissed, eligible: !done && !dismissed && allowed(f) && due(f) };
|
|
1237
|
+
},
|
|
1238
|
+
seen: (id) => {
|
|
1239
|
+
const f = flowById(id);
|
|
1240
|
+
if (!f || f.shownBy !== "code" || !known) return;
|
|
1241
|
+
if (!shown.has(`f:${f.id}`)) {
|
|
1242
|
+
shown.add(`f:${f.id}`);
|
|
1243
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, action: "started" });
|
|
1244
|
+
}
|
|
1245
|
+
const p = progress.get(f.id);
|
|
1246
|
+
for (const s2 of stepsIn(nodesOf(f)))
|
|
1247
|
+
if (!p?.done.has(s2.id) && !shown.has(`s:${s2.id}`)) {
|
|
1248
|
+
shown.add(`s:${s2.id}`);
|
|
1249
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, step: s2.id, kind: s2.kind, action: "shown" });
|
|
1250
|
+
if (s2.kind !== "checklist") break;
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
complete: (id, stepId) => {
|
|
1254
|
+
const f = flowById(id);
|
|
1255
|
+
const s2 = f && stepsIn(nodesOf(f)).find((x) => x.id === stepId);
|
|
1256
|
+
if (!f || !s2 || s2.kind === "question") return;
|
|
1257
|
+
const p = prog(f.id);
|
|
1258
|
+
if (p.done.has(s2.id)) return;
|
|
1259
|
+
markDone(p, s2.id);
|
|
1260
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, step: s2.id, kind: s2.kind, action: "completed" });
|
|
1261
|
+
ended(f);
|
|
1262
|
+
},
|
|
1263
|
+
dismissItem: (id) => {
|
|
1264
|
+
const f = flowById(id);
|
|
1265
|
+
if (!f) return;
|
|
1266
|
+
const p = prog(f.id);
|
|
1267
|
+
if (p.dismissed) return;
|
|
1268
|
+
p.dismissed = true;
|
|
1269
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, action: "dismissed" });
|
|
1270
|
+
if (active === f.id) active = null;
|
|
1271
|
+
ended(f);
|
|
1272
|
+
},
|
|
1273
|
+
answerItem: (id, question, value) => {
|
|
1274
|
+
const f = flowById(id);
|
|
1275
|
+
const s2 = f && stepsIn(nodesOf(f)).find((x) => x.kind === "question" && (x.id === question || x.question?.key === question));
|
|
1276
|
+
if (!f || !s2?.question || !answerOk(s2.question, value)) return;
|
|
1277
|
+
answerStep(f, s2, value);
|
|
1278
|
+
ended(f);
|
|
940
1279
|
}
|
|
941
1280
|
},
|
|
1281
|
+
finale: () => finaleNow,
|
|
1282
|
+
accent: () => config?.onboardingAccent,
|
|
1283
|
+
answers: () => Object.fromEntries(answers),
|
|
942
1284
|
message: {
|
|
943
1285
|
click: () => {
|
|
944
1286
|
const v = view();
|
|
@@ -1020,7 +1362,7 @@ var createPaywallHost = (env) => {
|
|
|
1020
1362
|
},
|
|
1021
1363
|
() => {
|
|
1022
1364
|
c.busy = false;
|
|
1023
|
-
c.error = "The purchase did not go through. Please try again.";
|
|
1365
|
+
c.error = t("The purchase did not go through. Please try again.");
|
|
1024
1366
|
emit();
|
|
1025
1367
|
}
|
|
1026
1368
|
);
|
|
@@ -1095,10 +1437,10 @@ var RageDetector = class {
|
|
|
1095
1437
|
__publicField(this, "clicks", []);
|
|
1096
1438
|
__publicField(this, "fired", false);
|
|
1097
1439
|
}
|
|
1098
|
-
push(x, y,
|
|
1099
|
-
const near = this.clicks.filter((c) =>
|
|
1440
|
+
push(x, y, t2) {
|
|
1441
|
+
const near = this.clicks.filter((c) => t2 - c.t <= this.windowMs && Math.hypot(c.x - x, c.y - y) <= this.radius);
|
|
1100
1442
|
if (near.length !== this.clicks.length) this.fired = false;
|
|
1101
|
-
this.clicks = [...near, { x, y, t }];
|
|
1443
|
+
this.clicks = [...near, { x, y, t: t2 }];
|
|
1102
1444
|
if (this.clicks.length >= this.count && !this.fired) {
|
|
1103
1445
|
this.fired = true;
|
|
1104
1446
|
return true;
|
|
@@ -1114,9 +1456,9 @@ var BUFFER_MS = 6e4;
|
|
|
1114
1456
|
var CHUNK_FRAMES = 12;
|
|
1115
1457
|
var CHUNK_CHARS = 7e5;
|
|
1116
1458
|
var MAX_MS = 30 * 6e4;
|
|
1117
|
-
var shareOf = (
|
|
1459
|
+
var shareOf = (s2) => {
|
|
1118
1460
|
let h3 = 2166136261;
|
|
1119
|
-
for (let i = 0; i <
|
|
1461
|
+
for (let i = 0; i < s2.length; i++) h3 = Math.imul(h3 ^ s2.charCodeAt(i), 16777619);
|
|
1120
1462
|
return (h3 >>> 0) / 4294967296;
|
|
1121
1463
|
};
|
|
1122
1464
|
var createRecorder = (deps, st) => {
|
|
@@ -1241,14 +1583,14 @@ var measureMasks = () => Promise.all(
|
|
|
1241
1583
|
(ref) => new Promise((resolve) => {
|
|
1242
1584
|
const v = ref.current;
|
|
1243
1585
|
if (!v) return resolve("none");
|
|
1244
|
-
const
|
|
1586
|
+
const t2 = setTimeout(() => resolve("failed"), 100);
|
|
1245
1587
|
try {
|
|
1246
1588
|
v.measureInWindow((x, y, w, h3) => {
|
|
1247
|
-
clearTimeout(
|
|
1589
|
+
clearTimeout(t2);
|
|
1248
1590
|
resolve(w > 0 && h3 > 0 ? [x, y, w, h3] : "none");
|
|
1249
1591
|
});
|
|
1250
1592
|
} catch {
|
|
1251
|
-
clearTimeout(
|
|
1593
|
+
clearTimeout(t2);
|
|
1252
1594
|
resolve("failed");
|
|
1253
1595
|
}
|
|
1254
1596
|
})
|
|
@@ -1285,7 +1627,7 @@ var MAX_CRUMBS = 20;
|
|
|
1285
1627
|
var MAX_BACKOFF_MS = 5 * 60 * 1e3;
|
|
1286
1628
|
var REQUEST_TIMEOUT_MS = 15e3;
|
|
1287
1629
|
var EAGER_COUNT = 20;
|
|
1288
|
-
var clip2 = (
|
|
1630
|
+
var clip2 = (s2, n) => s2.length > n ? `${s2.slice(0, n - 1)}\u2026` : s2;
|
|
1289
1631
|
var createClient = (options, deps) => {
|
|
1290
1632
|
const apiKey = (options.apiKey ?? "").trim();
|
|
1291
1633
|
const host = (options.host ?? DEFAULT_HOST).trim().replace(/\/+$/, "");
|
|
@@ -1313,6 +1655,8 @@ var createClient = (options, deps) => {
|
|
|
1313
1655
|
context = deps.context();
|
|
1314
1656
|
} catch {
|
|
1315
1657
|
}
|
|
1658
|
+
setDeviceLocale(context.locale);
|
|
1659
|
+
if (options.locale) setLocale(options.locale);
|
|
1316
1660
|
const appId = (deps.appId || "app").replace(/[^A-Za-z0-9._-]/g, "") || "app";
|
|
1317
1661
|
const release = options.release ?? (context.appVersion ? `${context.appVersion}${context.build ? ` (${context.build})` : ""}` : void 0);
|
|
1318
1662
|
const sdk = { v: SDK_VERSION, errors: errorsOn, platform: context.platform };
|
|
@@ -1358,7 +1702,7 @@ var createClient = (options, deps) => {
|
|
|
1358
1702
|
const fetchInApp = async (id) => {
|
|
1359
1703
|
if (!apiKey) return null;
|
|
1360
1704
|
const hash = hashOf(id);
|
|
1361
|
-
const r = await deps.fetch(`${host}/api/sdk/inapp?key=${encodeURIComponent(apiKey)}&distinctId=${encodeURIComponent(id)}${hash ? `&userHash=${hash}` : ""}`, { method: "GET" });
|
|
1705
|
+
const r = await deps.fetch(`${host}/api/sdk/inapp?key=${encodeURIComponent(apiKey)}&distinctId=${encodeURIComponent(id)}${hash ? `&userHash=${hash}` : ""}${langParam()}`, { method: "GET" });
|
|
1362
1706
|
warnUnsigned(r.status, deps.log);
|
|
1363
1707
|
return r.ok ? await r.json() : null;
|
|
1364
1708
|
};
|
|
@@ -1372,7 +1716,14 @@ var createClient = (options, deps) => {
|
|
|
1372
1716
|
set: (k, v) => void set(k, v)
|
|
1373
1717
|
} : null,
|
|
1374
1718
|
openUrl: deps.openUrl,
|
|
1375
|
-
fetchInApp: (id) => fetchInApp(id)
|
|
1719
|
+
fetchInApp: (id) => fetchInApp(id),
|
|
1720
|
+
// 0.18.0: a task's page or screen goes to the app's navigator; without one only a link with a scheme opens.
|
|
1721
|
+
navigate: (target) => {
|
|
1722
|
+
if (options.onNavigate) return options.onNavigate(target);
|
|
1723
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target) && !/^(javascript|data|vbscript):/i.test(target)) deps.openUrl?.(target);
|
|
1724
|
+
else warn(`A checklist task opens "${target}": pass onNavigate to MRRocketProvider to open screens and paths.`);
|
|
1725
|
+
},
|
|
1726
|
+
task: (taskId, item) => options.onTask?.(taskId, item)
|
|
1376
1727
|
});
|
|
1377
1728
|
const refreshFlows = () => {
|
|
1378
1729
|
if (!isReady || closed) return;
|
|
@@ -1437,9 +1788,9 @@ var createClient = (options, deps) => {
|
|
|
1437
1788
|
const sendId = () => consent === "persistent" ? distinctId() : identified;
|
|
1438
1789
|
const hashOf = (id) => hashFor(signed, id);
|
|
1439
1790
|
const touchSession = () => {
|
|
1440
|
-
const
|
|
1441
|
-
if (!session ||
|
|
1442
|
-
else session.last =
|
|
1791
|
+
const t2 = now();
|
|
1792
|
+
if (!session || t2 - session.last >= SESSION_TIMEOUT_MS) session = { id: newDistinctId(), last: t2 };
|
|
1793
|
+
else session.last = t2;
|
|
1443
1794
|
return session.id;
|
|
1444
1795
|
};
|
|
1445
1796
|
const urlOf = (path = currentScreen) => `app://${appId}${path ?? "/"}`;
|
|
@@ -1628,7 +1979,7 @@ var createClient = (options, deps) => {
|
|
|
1628
1979
|
const fetchConfig = async () => {
|
|
1629
1980
|
if (!apiKey) return;
|
|
1630
1981
|
try {
|
|
1631
|
-
const r = await deps.fetch(`${host}/api/sdk/config?key=${encodeURIComponent(apiKey)}`, { method: "GET" });
|
|
1982
|
+
const r = await deps.fetch(`${host}/api/sdk/config?key=${encodeURIComponent(apiKey)}${langParam()}`, { method: "GET" });
|
|
1632
1983
|
if (!r.ok) {
|
|
1633
1984
|
warn(`Config request answered ${r.status}. Check the public key and the host.`);
|
|
1634
1985
|
return;
|
|
@@ -1636,6 +1987,7 @@ var createClient = (options, deps) => {
|
|
|
1636
1987
|
const c = await r.json();
|
|
1637
1988
|
if (c && Array.isArray(c.experiments)) {
|
|
1638
1989
|
config = c;
|
|
1990
|
+
setWords(c.words, c.lang);
|
|
1639
1991
|
if (consent !== "off") await set(KEYS.config, JSON.stringify(c));
|
|
1640
1992
|
changed();
|
|
1641
1993
|
}
|
|
@@ -1754,7 +2106,10 @@ var createClient = (options, deps) => {
|
|
|
1754
2106
|
}
|
|
1755
2107
|
try {
|
|
1756
2108
|
const c = cached ? JSON.parse(cached) : null;
|
|
1757
|
-
if (c && Array.isArray(c.experiments) && !config)
|
|
2109
|
+
if (c && Array.isArray(c.experiments) && !config) {
|
|
2110
|
+
config = c;
|
|
2111
|
+
setWords(c.words, c.lang);
|
|
2112
|
+
}
|
|
1758
2113
|
} catch {
|
|
1759
2114
|
await remove(KEYS.config);
|
|
1760
2115
|
}
|
|
@@ -1810,6 +2165,9 @@ var createClient = (options, deps) => {
|
|
|
1810
2165
|
await fetchConfig();
|
|
1811
2166
|
markReady();
|
|
1812
2167
|
refreshFlows();
|
|
2168
|
+
onLocale(() => {
|
|
2169
|
+
void fetchConfig().then(refreshFlows);
|
|
2170
|
+
});
|
|
1813
2171
|
};
|
|
1814
2172
|
void start().catch(() => {
|
|
1815
2173
|
loaded = true;
|
|
@@ -1998,42 +2356,227 @@ var createClient = (options, deps) => {
|
|
|
1998
2356
|
return client;
|
|
1999
2357
|
};
|
|
2000
2358
|
var activeRouteName = (state) => {
|
|
2001
|
-
let
|
|
2359
|
+
let s2 = state;
|
|
2002
2360
|
let name;
|
|
2003
|
-
for (let depth = 0;
|
|
2004
|
-
const route =
|
|
2361
|
+
for (let depth = 0; s2 && Array.isArray(s2.routes) && s2.routes.length && depth < 20; depth++) {
|
|
2362
|
+
const route = s2.routes[typeof s2.index === "number" ? s2.index : s2.routes.length - 1];
|
|
2005
2363
|
if (!route) break;
|
|
2006
2364
|
name = route.name ?? name;
|
|
2007
|
-
|
|
2365
|
+
s2 = route.state;
|
|
2008
2366
|
}
|
|
2009
2367
|
return name;
|
|
2010
2368
|
};
|
|
2011
2369
|
|
|
2012
2370
|
// src/native/flows-view.ts
|
|
2013
|
-
var
|
|
2371
|
+
var import_react2 = require("react");
|
|
2372
|
+
|
|
2373
|
+
// src/icons.ts
|
|
2374
|
+
var FLOW_ICONS = {
|
|
2375
|
+
check: { label: "Check", paths: ["M5 12.5l4.5 4.5L19 7.5"], emoji: "\u2705" },
|
|
2376
|
+
star: { label: "Star", paths: ["M12 3.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9l-5.2 2.7 1-5.8-4.3-4.1 5.9-.9z"], emoji: "\u2B50" },
|
|
2377
|
+
rocket: {
|
|
2378
|
+
label: "Rocket",
|
|
2379
|
+
paths: ["M12 3c3 2 4.5 5.5 4 10l-2 3h-4l-2-3c-.5-4.5 1-8 4-10z", "M8 13.5l-3 3 3.5.5", "M16 13.5l3 3-3.5.5", "M13.5 9.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z", "M11 19.5v1.5M13 19.5v1.5"],
|
|
2380
|
+
emoji: "\u{1F680}"
|
|
2381
|
+
},
|
|
2382
|
+
user: { label: "Person", paths: ["M15.5 8a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0z", "M5 20c.8-3.5 3.6-5.5 7-5.5s6.2 2 7 5.5"], emoji: "\u{1F464}" },
|
|
2383
|
+
users: {
|
|
2384
|
+
label: "Team",
|
|
2385
|
+
paths: ["M13 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z", "M3.5 19c.6-3 3-4.8 6.5-4.8s5.9 1.8 6.5 4.8", "M16 5.2a3 3 0 0 1 0 5.6", "M18 14.6c1.5.6 2.4 2 2.7 4.4"],
|
|
2386
|
+
emoji: "\u{1F465}"
|
|
2387
|
+
},
|
|
2388
|
+
settings: {
|
|
2389
|
+
label: "Settings",
|
|
2390
|
+
paths: ["M12 2.8v2.4M12 18.8v2.4M2.8 12h2.4M18.8 12h2.4M5.5 5.5l1.7 1.7M16.8 16.8l1.7 1.7M5.5 18.5l1.7-1.7M16.8 7.2l1.7-1.7", "M17 12a5 5 0 1 1-10 0 5 5 0 0 1 10 0z", "M14 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"],
|
|
2391
|
+
emoji: "\u2699\uFE0F"
|
|
2392
|
+
},
|
|
2393
|
+
bell: { label: "Bell", paths: ["M6 16v-5a6 6 0 0 1 12 0v5l1.5 2h-15z", "M10 20.5a2 2 0 0 0 4 0"], emoji: "\u{1F514}" },
|
|
2394
|
+
mail: { label: "Mail", paths: ["M4 6h16v12H4z", "M4 7l8 6 8-6"], emoji: "\u2709\uFE0F" },
|
|
2395
|
+
chat: { label: "Chat", paths: ["M4 5h16v11H9l-5 4z"], emoji: "\u{1F4AC}" },
|
|
2396
|
+
heart: { label: "Heart", paths: ["M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"], emoji: "\u2764\uFE0F" },
|
|
2397
|
+
flag: { label: "Flag", paths: ["M5 21V4", "M5 4h11l-2 4 2 4H5"], emoji: "\u{1F6A9}" },
|
|
2398
|
+
bolt: { label: "Bolt", paths: ["M13 3L5 13.5h6L10 21l8-10.5h-6z"], emoji: "\u26A1" },
|
|
2399
|
+
book: { label: "Book", paths: ["M4 5.5C6.5 4 9.5 4 12 6c2.5-2 5.5-2 8-.5V19c-2.5-1.5-5.5-1.5-8 .5-2.5-2-5.5-2-8-.5z", "M12 6v13.5"], emoji: "\u{1F4D6}" },
|
|
2400
|
+
calendar: { label: "Calendar", paths: ["M4 6h16v14H4z", "M4 10h16", "M8 3.5v4M16 3.5v4"], emoji: "\u{1F4C5}" },
|
|
2401
|
+
camera: { label: "Camera", paths: ["M4 8h3.5L9 5.5h6L16.5 8H20v11H4z", "M15.5 13a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0z"], emoji: "\u{1F4F7}" },
|
|
2402
|
+
cart: { label: "Cart", paths: ["M3.5 4.5H6l2 10h10l2-7H7", "M9.5 19.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z", "M18.5 19.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"], emoji: "\u{1F6D2}" },
|
|
2403
|
+
chart: { label: "Chart", paths: ["M4 20h16", "M7 16v-4M12 16V8M17 16V9"], emoji: "\u{1F4CA}" },
|
|
2404
|
+
gift: { label: "Gift", paths: ["M4 10h16v10H4z", "M3 7h18v3H3z", "M12 7v13", "M12 7c-1.5-3-5-3-5-1s3.5 1 5 1z", "M12 7c1.5-3 5-3 5-1s-3.5 1-5 1z"], emoji: "\u{1F381}" },
|
|
2405
|
+
home: { label: "Home", paths: ["M4 11l8-7 8 7", "M6 9.5V20h12V9.5", "M10 20v-6h4v6"], emoji: "\u{1F3E0}" },
|
|
2406
|
+
link: { label: "Link", paths: ["M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1", "M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"], emoji: "\u{1F517}" },
|
|
2407
|
+
lock: { label: "Lock", paths: ["M6 11h12v9H6z", "M8.5 11V8a3.5 3.5 0 0 1 7 0v3"], emoji: "\u{1F512}" },
|
|
2408
|
+
pencil: { label: "Pencil", paths: ["M4 20l1-4L16 5l3 3L8 19z", "M14 7l3 3"], emoji: "\u270F\uFE0F" },
|
|
2409
|
+
plus: { label: "Plus", paths: ["M12 5v14M5 12h14"], emoji: "\u2795" },
|
|
2410
|
+
search: { label: "Search", paths: ["M16 10.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z", "M14.5 14.5L20 20"], emoji: "\u{1F50D}" },
|
|
2411
|
+
upload: { label: "Upload", paths: ["M12 15V4", "M7.5 8.5L12 4l4.5 4.5", "M4 15v5h16v-5"], emoji: "\u2B06\uFE0F" },
|
|
2412
|
+
play: { label: "Play", paths: ["M8 5l11 7-11 7z"], emoji: "\u25B6\uFE0F" },
|
|
2413
|
+
sparkle: { label: "Sparkle", paths: ["M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z", "M19 16l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7z"], emoji: "\u2728" },
|
|
2414
|
+
globe: {
|
|
2415
|
+
label: "Globe",
|
|
2416
|
+
paths: ["M20 12a8 8 0 1 1-16 0 8 8 0 0 1 16 0z", "M4 12h16", "M12 4c2.2 2.2 3.3 4.9 3.3 8s-1.1 5.8-3.3 8c-2.2-2.2-3.3-4.9-3.3-8S9.8 6.2 12 4z"],
|
|
2417
|
+
emoji: "\u{1F310}"
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
var FLOW_ICON_NAMES = Object.keys(FLOW_ICONS);
|
|
2421
|
+
var iconOf = (icon) => {
|
|
2422
|
+
if (!icon) return void 0;
|
|
2423
|
+
if (icon.kind === "builtin") return icon.name && FLOW_ICONS[icon.name] ? icon : void 0;
|
|
2424
|
+
if (icon.kind === "emoji") return icon.value && icon.value.trim() && [...icon.value.trim()].length <= 8 ? icon : void 0;
|
|
2425
|
+
if (icon.kind === "image") return icon.url && /^https?:\/\//i.test(icon.url) ? icon : void 0;
|
|
2426
|
+
return void 0;
|
|
2427
|
+
};
|
|
2428
|
+
|
|
2429
|
+
// src/native/flows-view.ts
|
|
2014
2430
|
var RN = require("react-native");
|
|
2431
|
+
var svg = (() => {
|
|
2432
|
+
try {
|
|
2433
|
+
return require("react-native-svg");
|
|
2434
|
+
} catch {
|
|
2435
|
+
return null;
|
|
2436
|
+
}
|
|
2437
|
+
})();
|
|
2438
|
+
var iconNode = (icon, size, color) => {
|
|
2439
|
+
const i = iconOf(icon);
|
|
2440
|
+
if (!i) return null;
|
|
2441
|
+
if (i.kind === "image")
|
|
2442
|
+
return RN.Image ? (0, import_react2.createElement)(RN.Image, { source: { uri: i.url }, accessibilityElementsHidden: true, style: { width: size, height: size, borderRadius: size / 4 } }) : null;
|
|
2443
|
+
const Svg = svg?.Svg ?? svg?.default;
|
|
2444
|
+
if (i.kind === "builtin" && Svg && svg?.Path)
|
|
2445
|
+
return (0, import_react2.createElement)(
|
|
2446
|
+
Svg,
|
|
2447
|
+
{ width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
|
|
2448
|
+
...FLOW_ICONS[i.name].paths.map((d, k) => (0, import_react2.createElement)(svg.Path, { key: k, d }))
|
|
2449
|
+
);
|
|
2450
|
+
return (0, import_react2.createElement)(RN.Text, { style: { fontSize: size * 0.8, lineHeight: size, textAlign: "center", width: size } }, i.kind === "emoji" ? i.value : FLOW_ICONS[i.name].emoji);
|
|
2451
|
+
};
|
|
2452
|
+
var FINALE_COLORS = ["#f59e0b", "#10b981", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899"];
|
|
2453
|
+
var finaleBits = (kind, accent) => {
|
|
2454
|
+
const rnd = (a, b) => a + Math.random() * (b - a);
|
|
2455
|
+
const colors = [accent, accent, ...FINALE_COLORS];
|
|
2456
|
+
const pick = () => colors[Math.floor(Math.random() * colors.length)];
|
|
2457
|
+
if (kind === "confetti")
|
|
2458
|
+
return Array.from({ length: 48 }, (_, i) => ({
|
|
2459
|
+
x: i % 2 ? 12 : 82,
|
|
2460
|
+
y: 92,
|
|
2461
|
+
dx: (i % 2 ? 1 : -1) * rnd(40, 200),
|
|
2462
|
+
mid: -rnd(380, 620),
|
|
2463
|
+
dy: -rnd(120, 300),
|
|
2464
|
+
size: rnd(7, 11),
|
|
2465
|
+
rot: rnd(-540, 540),
|
|
2466
|
+
color: pick(),
|
|
2467
|
+
delay: rnd(0, 160),
|
|
2468
|
+
life: rnd(1500, 2e3),
|
|
2469
|
+
shape: "rect"
|
|
2470
|
+
}));
|
|
2471
|
+
if (kind === "fireworks")
|
|
2472
|
+
return Array.from({ length: 3 }, (_, b) => {
|
|
2473
|
+
const x = rnd(20, 75);
|
|
2474
|
+
const y = rnd(18, 45);
|
|
2475
|
+
const color = pick();
|
|
2476
|
+
return Array.from({ length: 18 }, (_2, i) => {
|
|
2477
|
+
const a = Math.PI * 2 * i / 18;
|
|
2478
|
+
const r = rnd(70, 130);
|
|
2479
|
+
return { x, y, dx: Math.cos(a) * r, mid: Math.sin(a) * r * 0.8, dy: Math.sin(a) * r + 40, size: 5, rot: 0, color, delay: b * 320, life: rnd(900, 1200), shape: "dot" };
|
|
2480
|
+
});
|
|
2481
|
+
}).flat();
|
|
2482
|
+
return Array.from({ length: 22 }, () => ({
|
|
2483
|
+
x: rnd(6, 88),
|
|
2484
|
+
y: rnd(20, 80),
|
|
2485
|
+
dx: 0,
|
|
2486
|
+
mid: -rnd(10, 25),
|
|
2487
|
+
dy: -rnd(30, 60),
|
|
2488
|
+
size: rnd(16, 28),
|
|
2489
|
+
rot: rnd(-40, 40),
|
|
2490
|
+
color: pick(),
|
|
2491
|
+
delay: rnd(0, 600),
|
|
2492
|
+
life: rnd(900, 1300),
|
|
2493
|
+
shape: "star"
|
|
2494
|
+
}));
|
|
2495
|
+
};
|
|
2496
|
+
var Finale = ({ kind, accent }) => {
|
|
2497
|
+
const A = RN.Animated;
|
|
2498
|
+
const [bits] = (0, import_react2.useState)(() => finaleBits(kind, accent));
|
|
2499
|
+
const [values] = (0, import_react2.useState)(() => A ? bits.map(() => new A.Value(0)) : []);
|
|
2500
|
+
const [on, setOn] = (0, import_react2.useState)(!!A);
|
|
2501
|
+
(0, import_react2.useEffect)(() => {
|
|
2502
|
+
if (!A) return;
|
|
2503
|
+
let stopped = false;
|
|
2504
|
+
let run = null;
|
|
2505
|
+
const go = () => {
|
|
2506
|
+
if (stopped) return;
|
|
2507
|
+
run = A.parallel(values.map((v, i) => A.timing(v, { toValue: 1, duration: bits[i].life, delay: bits[i].delay, useNativeDriver: true })));
|
|
2508
|
+
run.start(() => !stopped && setOn(false));
|
|
2509
|
+
};
|
|
2510
|
+
Promise.resolve(RN.AccessibilityInfo?.isReduceMotionEnabled?.()).then((reduce) => reduce ? !stopped && setOn(false) : go()).catch(go);
|
|
2511
|
+
return () => {
|
|
2512
|
+
stopped = true;
|
|
2513
|
+
run?.stop();
|
|
2514
|
+
};
|
|
2515
|
+
}, [A, bits, values]);
|
|
2516
|
+
if (!on || !A) return null;
|
|
2517
|
+
return (0, import_react2.createElement)(
|
|
2518
|
+
RN.View,
|
|
2519
|
+
{ pointerEvents: "none", accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0 } },
|
|
2520
|
+
...bits.map((b, i) => {
|
|
2521
|
+
const t2 = values[i];
|
|
2522
|
+
const fade = t2.interpolate({ inputRange: [0, 0.7, 1], outputRange: [1, 1, 0] });
|
|
2523
|
+
const scale = b.shape === "star" ? t2.interpolate({ inputRange: [0, 0.25, 1], outputRange: [0.2, 1.15, 0.6] }) : 1;
|
|
2524
|
+
return (0, import_react2.createElement)(
|
|
2525
|
+
A.View,
|
|
2526
|
+
{
|
|
2527
|
+
key: i,
|
|
2528
|
+
style: {
|
|
2529
|
+
position: "absolute",
|
|
2530
|
+
left: `${b.x}%`,
|
|
2531
|
+
top: `${b.y}%`,
|
|
2532
|
+
width: b.size,
|
|
2533
|
+
height: b.shape === "rect" ? b.size * 0.45 : b.size,
|
|
2534
|
+
borderRadius: b.shape === "dot" ? b.size / 2 : 2,
|
|
2535
|
+
backgroundColor: b.shape === "star" ? "transparent" : b.color,
|
|
2536
|
+
alignItems: "center",
|
|
2537
|
+
justifyContent: "center",
|
|
2538
|
+
opacity: fade,
|
|
2539
|
+
transform: [
|
|
2540
|
+
{ translateX: t2.interpolate({ inputRange: [0, 1], outputRange: [0, b.dx] }) },
|
|
2541
|
+
{ translateY: t2.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, b.mid, b.dy] }) },
|
|
2542
|
+
{ rotate: t2.interpolate({ inputRange: [0, 1], outputRange: ["0deg", `${b.rot}deg`] }) },
|
|
2543
|
+
{ scale }
|
|
2544
|
+
]
|
|
2545
|
+
}
|
|
2546
|
+
},
|
|
2547
|
+
b.shape === "star" ? (0, import_react2.createElement)(RN.Text, { style: { color: b.color, fontSize: b.size, lineHeight: b.size } }, "\u2605") : null
|
|
2548
|
+
);
|
|
2549
|
+
})
|
|
2550
|
+
);
|
|
2551
|
+
};
|
|
2015
2552
|
var LIGHT = { bg: "#ffffff", fg: "#1c1d20", muted: "#5d6068", soft: "#f4f5f7", line: "#e4e5e8" };
|
|
2016
2553
|
var DARK = { bg: "#17181b", fg: "#f1f2f4", muted: "#a9acb3", soft: "#232428", line: "#2e2f35" };
|
|
2017
|
-
var
|
|
2018
|
-
|
|
2554
|
+
var MRRocketFlows = (props) => {
|
|
2555
|
+
const [, setTick] = (0, import_react2.useState)(0);
|
|
2556
|
+
(0, import_react2.useEffect)(() => props.flows.subscribe(() => setTick((t2) => t2 + 1)), [props.flows]);
|
|
2557
|
+
const fin = props.flows.finale?.() ?? null;
|
|
2558
|
+
const accent = props.appearance?.accent ?? props.flows.accent?.() ?? "#3f76ff";
|
|
2559
|
+
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);
|
|
2560
|
+
};
|
|
2561
|
+
var FlowsBody = ({ flows, paywall, appearance = {} }) => {
|
|
2019
2562
|
const scheme = RN.useColorScheme?.();
|
|
2020
2563
|
const dark = appearance.theme === "dark" || (appearance.theme ?? "auto") === "auto" && scheme === "dark";
|
|
2021
|
-
const p = { ...dark ? DARK : LIGHT, accent: appearance.accent ?? "#3f76ff", danger: "#d92d20" };
|
|
2022
|
-
const [, setTick] = (0,
|
|
2023
|
-
(0,
|
|
2024
|
-
(0,
|
|
2564
|
+
const p = { ...dark ? DARK : LIGHT, accent: appearance.accent ?? flows.accent?.() ?? "#3f76ff", danger: "#d92d20" };
|
|
2565
|
+
const [, setTick] = (0, import_react2.useState)(0);
|
|
2566
|
+
(0, import_react2.useEffect)(() => flows.subscribe(() => setTick((t2) => t2 + 1)), [flows]);
|
|
2567
|
+
(0, import_react2.useEffect)(() => paywall?.subscribe(() => setTick((t2) => t2 + 1)), [paywall]);
|
|
2025
2568
|
const pw = paywall?.view() ?? null;
|
|
2026
2569
|
const v = flows.view();
|
|
2027
|
-
const [comment, setComment] = (0,
|
|
2570
|
+
const [comment, setComment] = (0, import_react2.useState)("");
|
|
2028
2571
|
const screen = v?.type === "cancel" ? v.screen : null;
|
|
2029
|
-
(0,
|
|
2572
|
+
(0, import_react2.useEffect)(() => {
|
|
2030
2573
|
if (!screen) setComment("");
|
|
2031
2574
|
}, [screen]);
|
|
2032
2575
|
if (!v && !pw) return null;
|
|
2033
|
-
const text2 = (children, style = {}) => (0,
|
|
2576
|
+
const text2 = (children, style = {}) => (0, import_react2.createElement)(RN.Text, { style: { color: p.fg, fontSize: 15, ...style } }, children);
|
|
2034
2577
|
const muted = (children, style = {}) => text2(children, { color: p.muted, fontSize: 13, ...style });
|
|
2035
|
-
const title = (children) => (0,
|
|
2036
|
-
const btn = (label, kind, onPress, disabled = false) => (0,
|
|
2578
|
+
const title = (children) => (0, import_react2.createElement)(RN.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 18, fontWeight: "700" } }, children);
|
|
2579
|
+
const btn = (label, kind, onPress, disabled = false) => (0, import_react2.createElement)(
|
|
2037
2580
|
RN.Pressable,
|
|
2038
2581
|
{
|
|
2039
2582
|
key: label,
|
|
@@ -2057,12 +2600,12 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2057
2600
|
fontWeight: kind === "quiet" ? "500" : "600"
|
|
2058
2601
|
})
|
|
2059
2602
|
);
|
|
2060
|
-
const close = (label, onPress) => (0,
|
|
2603
|
+
const close = (label, onPress) => (0, import_react2.createElement)(
|
|
2061
2604
|
RN.Pressable,
|
|
2062
2605
|
{ onPress, accessibilityRole: "button", accessibilityLabel: label, hitSlop: 12, style: { marginLeft: "auto", padding: 4 } },
|
|
2063
2606
|
text2("\xD7", { fontSize: 22, color: p.muted })
|
|
2064
2607
|
);
|
|
2065
|
-
const card = (children, style = {}) => (0,
|
|
2608
|
+
const card = (children, style = {}) => (0, import_react2.createElement)(
|
|
2066
2609
|
RN.View,
|
|
2067
2610
|
{
|
|
2068
2611
|
style: {
|
|
@@ -2079,16 +2622,16 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2079
2622
|
},
|
|
2080
2623
|
...children
|
|
2081
2624
|
);
|
|
2082
|
-
const sheet = (children, onClose) => (0,
|
|
2625
|
+
const sheet = (children, onClose) => (0, import_react2.createElement)(
|
|
2083
2626
|
RN.Modal,
|
|
2084
2627
|
{ visible: true, transparent: true, animationType: "fade", onRequestClose: onClose },
|
|
2085
|
-
(0,
|
|
2628
|
+
(0, import_react2.createElement)(
|
|
2086
2629
|
RN.View,
|
|
2087
2630
|
{ style: { flex: 1, backgroundColor: "rgba(0,0,0,0.4)", justifyContent: "flex-end" } },
|
|
2088
2631
|
card(children, { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, paddingBottom: 34 })
|
|
2089
2632
|
)
|
|
2090
2633
|
);
|
|
2091
|
-
const input = (placeholder, value = comment, onChange = setComment) => (0,
|
|
2634
|
+
const input = (placeholder, value = comment, onChange = setComment) => (0, import_react2.createElement)(RN.TextInput, {
|
|
2092
2635
|
value,
|
|
2093
2636
|
onChangeText: onChange,
|
|
2094
2637
|
placeholder,
|
|
@@ -2103,19 +2646,19 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2103
2646
|
const host = paywall;
|
|
2104
2647
|
return sheet(
|
|
2105
2648
|
[
|
|
2106
|
-
(0,
|
|
2649
|
+
(0, import_react2.createElement)(
|
|
2107
2650
|
RN.View,
|
|
2108
2651
|
{ key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
|
|
2109
2652
|
...w.total > 1 ? Array.from(
|
|
2110
2653
|
{ length: w.total },
|
|
2111
|
-
(_, i) => (0,
|
|
2654
|
+
(_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= w.index ? p.accent : p.line } })
|
|
2112
2655
|
) : [],
|
|
2113
|
-
close("Close", host.close)
|
|
2656
|
+
close(t("Close"), host.close)
|
|
2114
2657
|
),
|
|
2115
2658
|
title(pg.title),
|
|
2116
2659
|
pg.body ? muted(pg.body, { fontSize: 15 }) : null,
|
|
2117
2660
|
...pg.kind === "benefits" ? (pg.items ?? []).map(
|
|
2118
|
-
(item, i) => (0,
|
|
2661
|
+
(item, i) => (0, import_react2.createElement)(
|
|
2119
2662
|
RN.View,
|
|
2120
2663
|
{ key: `b${i}`, style: { flexDirection: "row", gap: 10, alignItems: "flex-start" } },
|
|
2121
2664
|
text2("\u2713", { color: p.accent, fontWeight: "700" }),
|
|
@@ -2123,13 +2666,13 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2123
2666
|
)
|
|
2124
2667
|
) : [],
|
|
2125
2668
|
...pg.kind === "proof" ? (pg.quotes ?? []).map(
|
|
2126
|
-
(q, i) => (0,
|
|
2669
|
+
(q, i) => (0, import_react2.createElement)(RN.View, { key: `q${i}`, style: { backgroundColor: p.soft, borderRadius: 12, padding: 12, gap: 6 } }, text2(q.text), q.by ? muted(q.by) : null)
|
|
2127
2670
|
) : [],
|
|
2128
|
-
pg.kind === "plans" ? (0,
|
|
2671
|
+
pg.kind === "plans" ? (0, import_react2.createElement)(
|
|
2129
2672
|
RN.View,
|
|
2130
2673
|
{ key: "plans", accessibilityRole: "radiogroup", style: { gap: 6 } },
|
|
2131
2674
|
...w.plans.map(
|
|
2132
|
-
(plan) => (0,
|
|
2675
|
+
(plan) => (0, import_react2.createElement)(
|
|
2133
2676
|
RN.Pressable,
|
|
2134
2677
|
{
|
|
2135
2678
|
key: plan.id,
|
|
@@ -2149,14 +2692,14 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2149
2692
|
gap: 10
|
|
2150
2693
|
}
|
|
2151
2694
|
},
|
|
2152
|
-
(0,
|
|
2695
|
+
(0, import_react2.createElement)(RN.View, { style: { flex: 1 } }, text2(plan.name, { fontWeight: "600" }), plan.note ? muted(plan.note) : null),
|
|
2153
2696
|
text2(plan.price, { fontWeight: "700" })
|
|
2154
2697
|
)
|
|
2155
2698
|
)
|
|
2156
2699
|
) : null,
|
|
2157
2700
|
w.error ? text2(w.error, { color: p.danger, fontSize: 13 }) : null,
|
|
2158
|
-
btn(w.busy ? "One moment\u2026" : pg.button || "Continue", "primary", host.next, w.busy || pg.kind === "plans" && !w.plan),
|
|
2159
|
-
w.index ? btn("Back", "quiet", host.back, w.busy) : null
|
|
2701
|
+
btn(w.busy ? t("One moment\u2026") : pg.button || t("Continue"), "primary", host.next, w.busy || pg.kind === "plans" && !w.plan),
|
|
2702
|
+
w.index ? btn(t("Back"), "quiet", host.back, w.busy) : null
|
|
2160
2703
|
],
|
|
2161
2704
|
host.close
|
|
2162
2705
|
);
|
|
@@ -2164,22 +2707,22 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2164
2707
|
if (pw) return paywallSheet(pw);
|
|
2165
2708
|
if (!v) return null;
|
|
2166
2709
|
if (v.type === "cancel") {
|
|
2167
|
-
const top = (0,
|
|
2710
|
+
const top = (0, import_react2.createElement)(
|
|
2168
2711
|
RN.View,
|
|
2169
2712
|
{ style: { flexDirection: "row", alignItems: "center", gap: 4 } },
|
|
2170
2713
|
...v.steps > 1 ? Array.from(
|
|
2171
2714
|
{ length: v.steps },
|
|
2172
|
-
(_, i) => (0,
|
|
2715
|
+
(_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i < v.step ? p.accent : p.line } })
|
|
2173
2716
|
) : [],
|
|
2174
|
-
close("Close", flows.cancel.close)
|
|
2717
|
+
close(t("Close"), flows.cancel.close)
|
|
2175
2718
|
);
|
|
2176
2719
|
const error = v.error ? text2(v.error, { color: p.danger, fontSize: 13 }) : null;
|
|
2177
2720
|
let body = [];
|
|
2178
|
-
const choices = (items) => (0,
|
|
2721
|
+
const choices = (items) => (0, import_react2.createElement)(
|
|
2179
2722
|
RN.ScrollView,
|
|
2180
2723
|
{ style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
|
|
2181
2724
|
...items.map(
|
|
2182
|
-
(c) => (0,
|
|
2725
|
+
(c) => (0, import_react2.createElement)(
|
|
2183
2726
|
RN.Pressable,
|
|
2184
2727
|
{
|
|
2185
2728
|
key: c.key,
|
|
@@ -2202,48 +2745,48 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2202
2745
|
);
|
|
2203
2746
|
if (v.screen === "survey")
|
|
2204
2747
|
body = [
|
|
2205
|
-
title(v.flow.question || "Why are you leaving?"),
|
|
2748
|
+
title(v.flow.question || t("Why are you leaving?")),
|
|
2206
2749
|
choices(v.flow.reasons.map((r) => ({ key: r.id, label: r.label, on: v.reason === r.id, press: () => flows.cancel.pick(r.id) }))),
|
|
2207
|
-
btn("Continue", "primary", flows.cancel.next, !v.reason),
|
|
2208
|
-
btn("Never mind, keep my plan", "quiet", flows.cancel.close)
|
|
2750
|
+
btn(t("Continue"), "primary", flows.cancel.next, !v.reason),
|
|
2751
|
+
btn(t("Never mind, keep my plan"), "quiet", flows.cancel.close)
|
|
2209
2752
|
];
|
|
2210
2753
|
else if (v.screen === "followup" && v.followUp) {
|
|
2211
2754
|
const f = v.followUp;
|
|
2212
2755
|
const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
|
|
2213
2756
|
body = [
|
|
2214
|
-
label ? muted(
|
|
2757
|
+
label ? muted(tf("You said: {label}", { label })) : null,
|
|
2215
2758
|
title(f.question),
|
|
2216
|
-
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),
|
|
2217
|
-
btn("Continue", "primary", flows.cancel.next),
|
|
2218
|
-
btn("Never mind, keep my plan", "quiet", flows.cancel.close)
|
|
2759
|
+
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),
|
|
2760
|
+
btn(t("Continue"), "primary", flows.cancel.next),
|
|
2761
|
+
btn(t("Never mind, keep my plan"), "quiet", flows.cancel.close)
|
|
2219
2762
|
];
|
|
2220
2763
|
} else if (v.screen === "offer" && v.offer) {
|
|
2221
2764
|
const o = v.offer;
|
|
2222
2765
|
const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
|
|
2223
|
-
const detail = o.kind === "discount" && o.percent ? v.price ? `${money(discounted(v.price.amount, o.percent), v.price.currency)
|
|
2766
|
+
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;
|
|
2224
2767
|
body = [
|
|
2225
|
-
label ? muted(
|
|
2768
|
+
label ? muted(tf("You said: {label}", { label })) : null,
|
|
2226
2769
|
title(o.headline),
|
|
2227
|
-
detail ? (0,
|
|
2228
|
-
o.kind === "contact" ? input("What would make you stay?") : null,
|
|
2229
|
-
btn(v.busy ? "One moment\u2026" : offerButton(o), "primary", () => flows.cancel.accept(comment), v.busy),
|
|
2230
|
-
btn("No thanks, continue cancelling", "quiet", flows.cancel.decline, v.busy)
|
|
2770
|
+
detail ? (0, import_react2.createElement)(RN.View, { style: { backgroundColor: p.soft, borderRadius: 10, padding: 12 } }, text2(detail, { fontWeight: "600" })) : null,
|
|
2771
|
+
o.kind === "contact" ? input(t("What would make you stay?")) : null,
|
|
2772
|
+
btn(v.busy ? t("One moment\u2026") : offerButton(o), "primary", () => flows.cancel.accept(comment), v.busy),
|
|
2773
|
+
btn(t("No thanks, continue cancelling"), "quiet", flows.cancel.decline, v.busy)
|
|
2231
2774
|
];
|
|
2232
2775
|
} else
|
|
2233
2776
|
body = [
|
|
2234
2777
|
title(v.flow.confirm.title),
|
|
2235
2778
|
v.flow.confirm.body ? muted(v.flow.confirm.body, { fontSize: 15 }) : null,
|
|
2236
|
-
v.steps > 1 ? input("Anything else we should know? (optional)") : null,
|
|
2237
|
-
btn(v.busy ? "One moment\u2026" : v.flow.confirm.button, "danger", () => flows.cancel.confirm(comment), v.busy),
|
|
2238
|
-
btn("Keep my plan", "secondary", flows.cancel.close, v.busy)
|
|
2779
|
+
v.steps > 1 ? input(t("Anything else we should know? (optional)")) : null,
|
|
2780
|
+
btn(v.busy ? t("One moment\u2026") : v.flow.confirm.button, "danger", () => flows.cancel.confirm(comment), v.busy),
|
|
2781
|
+
btn(t("Keep my plan"), "secondary", flows.cancel.close, v.busy)
|
|
2239
2782
|
];
|
|
2240
2783
|
return sheet([top, ...body, error], flows.cancel.close);
|
|
2241
2784
|
}
|
|
2242
|
-
const media = (
|
|
2243
|
-
const m2 = mediaOf(
|
|
2785
|
+
const media = (s2) => {
|
|
2786
|
+
const m2 = mediaOf(s2);
|
|
2244
2787
|
if (!m2) return null;
|
|
2245
2788
|
if (m2.kind === "image")
|
|
2246
|
-
return RN.Image ? (0,
|
|
2789
|
+
return RN.Image ? (0, import_react2.createElement)(RN.Image, {
|
|
2247
2790
|
key: "media",
|
|
2248
2791
|
source: { uri: m2.url },
|
|
2249
2792
|
accessibilityLabel: m2.alt,
|
|
@@ -2252,51 +2795,148 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2252
2795
|
}) : null;
|
|
2253
2796
|
const v2 = videoEmbed(m2.url);
|
|
2254
2797
|
const open = v2?.kind === "file" ? v2.src : m2.url;
|
|
2255
|
-
return RN.Linking ? btn(
|
|
2798
|
+
return RN.Linking ? btn(`\u25B6 ${t("Watch the video")}`, "secondary", () => void RN.Linking?.openURL(open).catch(() => {
|
|
2256
2799
|
})) : null;
|
|
2257
2800
|
};
|
|
2801
|
+
if (v.type === "step" && v.step.kind === "question") {
|
|
2802
|
+
const s2 = v.step;
|
|
2803
|
+
const q = s2.question;
|
|
2804
|
+
const ok = answerOk(q, v.answer);
|
|
2805
|
+
return sheet(
|
|
2806
|
+
[
|
|
2807
|
+
(0, import_react2.createElement)(
|
|
2808
|
+
RN.View,
|
|
2809
|
+
{ key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
|
|
2810
|
+
...v.total > 1 ? Array.from(
|
|
2811
|
+
{ length: v.total },
|
|
2812
|
+
(_, i) => (0, import_react2.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= v.index ? p.accent : p.line } })
|
|
2813
|
+
) : [],
|
|
2814
|
+
close(t("Close"), flows.onboarding.dismiss)
|
|
2815
|
+
),
|
|
2816
|
+
v.index === 0 && v.flow.icon ? (0, import_react2.createElement)(RN.View, { key: "icon" }, iconNode(v.flow.icon, 36, p.accent)) : null,
|
|
2817
|
+
title(s2.title),
|
|
2818
|
+
s2.body ? muted(s2.body, { fontSize: 15 }) : null,
|
|
2819
|
+
q?.type === "choice" ? (0, import_react2.createElement)(
|
|
2820
|
+
RN.ScrollView,
|
|
2821
|
+
{ key: "opts", style: { maxHeight: 320 }, contentContainerStyle: { gap: 6 }, accessibilityRole: "radiogroup" },
|
|
2822
|
+
...[...q.options ?? [], ...q.other ? [{ id: OTHER_PREFIX, label: q.otherLabel || t("Other") }] : []].map((o) => {
|
|
2823
|
+
const isOther = o.id === OTHER_PREFIX;
|
|
2824
|
+
const on = isOther ? !!v.answer?.startsWith(OTHER_PREFIX) : v.answer === o.id;
|
|
2825
|
+
return (0, import_react2.createElement)(
|
|
2826
|
+
RN.Pressable,
|
|
2827
|
+
{
|
|
2828
|
+
key: o.id,
|
|
2829
|
+
onPress: () => isOther && on ? void 0 : flows.onboarding.answer(o.id),
|
|
2830
|
+
accessibilityRole: "radio",
|
|
2831
|
+
accessibilityState: { checked: on },
|
|
2832
|
+
style: {
|
|
2833
|
+
minHeight: 44,
|
|
2834
|
+
paddingHorizontal: 12,
|
|
2835
|
+
borderRadius: 10,
|
|
2836
|
+
borderWidth: 1,
|
|
2837
|
+
borderColor: on ? p.accent : p.line,
|
|
2838
|
+
backgroundColor: on ? p.soft : p.bg,
|
|
2839
|
+
justifyContent: "center"
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2842
|
+
text2(o.label, { fontWeight: "500" })
|
|
2843
|
+
);
|
|
2844
|
+
}),
|
|
2845
|
+
q.other && v.answer?.startsWith(OTHER_PREFIX) ? (0, import_react2.createElement)(RN.TextInput, {
|
|
2846
|
+
key: "other",
|
|
2847
|
+
value: v.answer.slice(OTHER_PREFIX.length),
|
|
2848
|
+
onChangeText: (t2) => flows.onboarding.answer(OTHER_PREFIX + t2),
|
|
2849
|
+
onSubmitEditing: flows.onboarding.next,
|
|
2850
|
+
placeholder: t("Your answer"),
|
|
2851
|
+
placeholderTextColor: p.muted,
|
|
2852
|
+
maxLength: 490,
|
|
2853
|
+
accessibilityLabel: q.otherLabel || t("Other"),
|
|
2854
|
+
style: { height: 44, borderWidth: 1, borderColor: p.line, borderRadius: 10, paddingHorizontal: 12, color: p.fg }
|
|
2855
|
+
}) : null
|
|
2856
|
+
) : q?.type === "scale" ? (0, import_react2.createElement)(
|
|
2857
|
+
RN.View,
|
|
2858
|
+
{ key: "scale", style: { gap: 6 } },
|
|
2859
|
+
(0, import_react2.createElement)(
|
|
2860
|
+
RN.View,
|
|
2861
|
+
{ style: { flexDirection: "row", gap: 4 }, accessibilityRole: "radiogroup" },
|
|
2862
|
+
...scaleOf(q).map((n) => {
|
|
2863
|
+
const on = v.answer === String(n);
|
|
2864
|
+
return (0, import_react2.createElement)(
|
|
2865
|
+
RN.Pressable,
|
|
2866
|
+
{
|
|
2867
|
+
key: n,
|
|
2868
|
+
onPress: () => flows.onboarding.answer(String(n)),
|
|
2869
|
+
accessibilityRole: "radio",
|
|
2870
|
+
accessibilityState: { checked: on },
|
|
2871
|
+
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" }
|
|
2872
|
+
},
|
|
2873
|
+
text2(String(n), { fontWeight: "500", color: on ? "#fff" : p.fg })
|
|
2874
|
+
);
|
|
2875
|
+
})
|
|
2876
|
+
),
|
|
2877
|
+
q.lowLabel || q.highLabel ? (0, import_react2.createElement)(RN.View, { style: { flexDirection: "row", justifyContent: "space-between" } }, muted(q.lowLabel || ""), muted(q.highLabel || "")) : null
|
|
2878
|
+
) : q ? (0, import_react2.createElement)(RN.TextInput, {
|
|
2879
|
+
key: "answer",
|
|
2880
|
+
value: v.answer ?? "",
|
|
2881
|
+
onChangeText: (t2) => flows.onboarding.answer(t2),
|
|
2882
|
+
onSubmitEditing: flows.onboarding.next,
|
|
2883
|
+
placeholder: q.placeholder || t("Your answer"),
|
|
2884
|
+
placeholderTextColor: p.muted,
|
|
2885
|
+
maxLength: 500,
|
|
2886
|
+
accessibilityLabel: s2.title,
|
|
2887
|
+
style: { height: 44, borderWidth: 1, borderColor: p.line, borderRadius: 10, paddingHorizontal: 12, color: p.fg }
|
|
2888
|
+
}) : null,
|
|
2889
|
+
btn(s2.button || (v.last ? t("Done") : t("Continue")), "primary", flows.onboarding.next, !ok),
|
|
2890
|
+
q?.optional ? btn(q.skipLabel || t("Skip"), "quiet", flows.onboarding.skip) : null,
|
|
2891
|
+
v.back ? btn(t("Back"), "quiet", flows.onboarding.back) : null
|
|
2892
|
+
],
|
|
2893
|
+
flows.onboarding.dismiss
|
|
2894
|
+
);
|
|
2895
|
+
}
|
|
2258
2896
|
if (v.type === "step")
|
|
2259
2897
|
return sheet(
|
|
2260
2898
|
[
|
|
2261
|
-
(0,
|
|
2899
|
+
(0, import_react2.createElement)(
|
|
2262
2900
|
RN.View,
|
|
2263
2901
|
{ style: { flexDirection: "row", alignItems: "center" } },
|
|
2264
|
-
v.total > 1 ? muted(
|
|
2265
|
-
close("Close the tour", flows.onboarding.dismiss)
|
|
2902
|
+
v.total > 1 ? muted(tf("Step {n} of {total}", { n: v.index + 1, total: v.total })) : null,
|
|
2903
|
+
close(t("Close the tour"), flows.onboarding.dismiss)
|
|
2266
2904
|
),
|
|
2905
|
+
v.step.kind === "modal" && v.flow.icon ? (0, import_react2.createElement)(RN.View, { key: "icon" }, iconNode(v.flow.icon, 36, p.accent)) : null,
|
|
2267
2906
|
media(v.step),
|
|
2268
2907
|
title(v.step.title),
|
|
2269
2908
|
v.step.body ? muted(v.step.body, { fontSize: 15 }) : null,
|
|
2270
|
-
btn(v.step.button || (v.last ? "Done" : "Next"), "primary", flows.onboarding.next)
|
|
2909
|
+
btn(v.step.button || (v.last ? t("Done") : t("Next")), "primary", flows.onboarding.next)
|
|
2271
2910
|
],
|
|
2272
2911
|
flows.onboarding.dismiss
|
|
2273
2912
|
);
|
|
2274
2913
|
if (v.type === "checklist") {
|
|
2275
2914
|
const done = v.items.filter((i) => i.done).length;
|
|
2276
|
-
return (0,
|
|
2915
|
+
return (0, import_react2.createElement)(
|
|
2277
2916
|
RN.View,
|
|
2278
2917
|
{ pointerEvents: "box-none", style: { position: "absolute", left: 12, right: 12, bottom: 28 } },
|
|
2279
2918
|
card([
|
|
2280
|
-
(0,
|
|
2919
|
+
(0, import_react2.createElement)(
|
|
2281
2920
|
RN.View,
|
|
2282
2921
|
{ key: "t", style: { flexDirection: "row", alignItems: "center" } },
|
|
2283
2922
|
title(v.flow.name),
|
|
2284
|
-
close("Hide the checklist", flows.onboarding.dismiss)
|
|
2923
|
+
close(t("Hide the checklist"), flows.onboarding.dismiss)
|
|
2285
2924
|
),
|
|
2286
|
-
muted(
|
|
2925
|
+
muted(tf("{done} of {total} done", { done, total: v.items.length })),
|
|
2287
2926
|
...v.items.map(
|
|
2288
|
-
({ step, done: d,
|
|
2927
|
+
({ step, done: d, opens }) => (0, import_react2.createElement)(
|
|
2289
2928
|
RN.Pressable,
|
|
2290
2929
|
{
|
|
2291
2930
|
key: step.id,
|
|
2292
|
-
// 0.17.0:
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2931
|
+
// 0.17.0/0.18.0: a task that opens something (a tour, a modal, a screen, the app's code) does it;
|
|
2932
|
+
// a finished tour or modal ticks it, a screen or the app's code once opened (unless an event does).
|
|
2933
|
+
disabled: d || !!step.event && !opens,
|
|
2934
|
+
onPress: () => opens ? flows.onboarding.open(step.id) : flows.onboarding.check(step.id),
|
|
2935
|
+
accessibilityRole: opens && !d ? "button" : "checkbox",
|
|
2296
2936
|
accessibilityState: { checked: d },
|
|
2297
2937
|
style: { flexDirection: "row", gap: 10, alignItems: "flex-start", paddingVertical: 6 }
|
|
2298
2938
|
},
|
|
2299
|
-
(0,
|
|
2939
|
+
(0, import_react2.createElement)(
|
|
2300
2940
|
RN.View,
|
|
2301
2941
|
{
|
|
2302
2942
|
style: {
|
|
@@ -2312,7 +2952,8 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2312
2952
|
},
|
|
2313
2953
|
d ? text2("\u2713", { color: "#fff", fontSize: 12 }) : null
|
|
2314
2954
|
),
|
|
2315
|
-
(
|
|
2955
|
+
step.icon ? iconNode(step.icon, 20, p.accent) : null,
|
|
2956
|
+
(0, import_react2.createElement)(
|
|
2316
2957
|
RN.View,
|
|
2317
2958
|
{ style: { flex: 1 } },
|
|
2318
2959
|
text2(step.title, { fontWeight: "600", color: d ? p.muted : p.fg, textDecorationLine: d ? "line-through" : "none" }),
|
|
@@ -2325,22 +2966,22 @@ var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
|
2325
2966
|
}
|
|
2326
2967
|
const m = v.message;
|
|
2327
2968
|
if (m.style === "banner")
|
|
2328
|
-
return (0,
|
|
2969
|
+
return (0, import_react2.createElement)(
|
|
2329
2970
|
RN.View,
|
|
2330
2971
|
{ pointerEvents: "box-none", style: { position: "absolute", left: 12, right: 12, top: 56 } },
|
|
2331
2972
|
card([
|
|
2332
|
-
(0,
|
|
2973
|
+
(0, import_react2.createElement)(
|
|
2333
2974
|
RN.View,
|
|
2334
2975
|
{ key: "t", style: { flexDirection: "row", alignItems: "center", gap: 8 } },
|
|
2335
|
-
(0,
|
|
2336
|
-
close("Dismiss", flows.message.dismiss)
|
|
2976
|
+
(0, import_react2.createElement)(RN.View, { style: { flex: 1 } }, text2(m.title, { fontWeight: "700" }), m.body ? muted(m.body) : null),
|
|
2977
|
+
close(t("Dismiss"), flows.message.dismiss)
|
|
2337
2978
|
),
|
|
2338
2979
|
m.button ? btn(m.button, "primary", flows.message.click) : null
|
|
2339
2980
|
])
|
|
2340
2981
|
);
|
|
2341
2982
|
return sheet(
|
|
2342
2983
|
[
|
|
2343
|
-
(0,
|
|
2984
|
+
(0, import_react2.createElement)(RN.View, { key: "t", style: { flexDirection: "row" } }, close(t("Dismiss"), flows.message.dismiss)),
|
|
2344
2985
|
title(m.title),
|
|
2345
2986
|
m.body ? muted(m.body, { fontSize: 15 }) : null,
|
|
2346
2987
|
m.button ? btn(m.button, "primary", flows.message.click) : null
|
|
@@ -2414,10 +3055,10 @@ var expoPush = () => {
|
|
|
2414
3055
|
},
|
|
2415
3056
|
token: async (projectId) => {
|
|
2416
3057
|
if (RN2.Platform.OS === "android" && n.setNotificationChannelAsync)
|
|
2417
|
-
await n.setNotificationChannelAsync("default", { name: "Default", importance: n.AndroidImportance?.HIGH ?? 4 });
|
|
3058
|
+
await n.setNotificationChannelAsync("default", { name: t("Default"), importance: n.AndroidImportance?.HIGH ?? 4 });
|
|
2418
3059
|
const id = projectId ?? constants.expoConfig?.extra?.eas?.projectId ?? constants.easConfig?.projectId;
|
|
2419
|
-
const
|
|
2420
|
-
return typeof
|
|
3060
|
+
const push = await n.getExpoPushTokenAsync(id ? { projectId: id } : void 0);
|
|
3061
|
+
return typeof push?.data === "string" && push.data ? push.data : null;
|
|
2421
3062
|
},
|
|
2422
3063
|
onOpen: (listener) => {
|
|
2423
3064
|
const sub = n.addNotificationResponseReceivedListener((r) => listener(dataOf(r) ?? {}));
|
|
@@ -2428,8 +3069,8 @@ var expoPush = () => {
|
|
|
2428
3069
|
};
|
|
2429
3070
|
var asyncStorage = () => {
|
|
2430
3071
|
const m = asyncStorageModule;
|
|
2431
|
-
const
|
|
2432
|
-
return
|
|
3072
|
+
const s2 = m?.default ?? m;
|
|
3073
|
+
return s2 && typeof s2.getItem === "function" && typeof s2.setItem === "function" && typeof s2.removeItem === "function" ? s2 : null;
|
|
2433
3074
|
};
|
|
2434
3075
|
var DEVICE_TYPES = { 1: "phone", 2: "tablet", 3: "desktop", 4: "tv" };
|
|
2435
3076
|
var text = (v, max = 80) => typeof v === "string" && v.trim() ? v.trim().slice(0, max) : typeof v === "number" ? String(v) : void 0;
|
|
@@ -2473,10 +3114,10 @@ var recorderDeps = () => {
|
|
|
2473
3114
|
}
|
|
2474
3115
|
return {
|
|
2475
3116
|
capture: async (width) => {
|
|
2476
|
-
const
|
|
2477
|
-
if (!
|
|
3117
|
+
const s2 = RN2.Dimensions.get("screen");
|
|
3118
|
+
if (!s2?.width) return null;
|
|
2478
3119
|
const w = Math.round(width);
|
|
2479
|
-
const h3 = Math.round(
|
|
3120
|
+
const h3 = Math.round(s2.height / s2.width * width);
|
|
2480
3121
|
const img = await capture({ format: "jpg", quality: 0.5, result: "base64", width: w, height: h3 });
|
|
2481
3122
|
return img ? { img: img.replace(/^data:image\/\w+;base64,/, ""), w, h: h3 } : null;
|
|
2482
3123
|
},
|
|
@@ -2503,8 +3144,8 @@ var nativeDeps = (overrides = {}) => ({
|
|
|
2503
3144
|
context: readContext,
|
|
2504
3145
|
appId: text(expoApplication?.applicationId, 150),
|
|
2505
3146
|
screen: () => {
|
|
2506
|
-
const
|
|
2507
|
-
return
|
|
3147
|
+
const s2 = RN2.Dimensions.get("screen");
|
|
3148
|
+
return s2 ? { w: s2.width, h: s2.height } : void 0;
|
|
2508
3149
|
},
|
|
2509
3150
|
initialUrl: () => RN2.Linking.getInitialURL(),
|
|
2510
3151
|
openUrl: (url) => void RN2.Linking.openURL(url).catch(() => void 0),
|
|
@@ -2523,7 +3164,7 @@ var optionalModules = () => ({
|
|
|
2523
3164
|
});
|
|
2524
3165
|
|
|
2525
3166
|
// src/native/widgets-view.ts
|
|
2526
|
-
var
|
|
3167
|
+
var import_react3 = require("react");
|
|
2527
3168
|
var RN3 = require("react-native");
|
|
2528
3169
|
var usePalette = (appearance = {}) => {
|
|
2529
3170
|
const scheme = RN3.useColorScheme?.();
|
|
@@ -2531,10 +3172,10 @@ var usePalette = (appearance = {}) => {
|
|
|
2531
3172
|
return { ...dark ? DARK : LIGHT, accent: appearance.accent ?? "#3f76ff", danger: "#d92d20" };
|
|
2532
3173
|
};
|
|
2533
3174
|
var kit = (p) => {
|
|
2534
|
-
const text2 = (children, style = {}) => (0,
|
|
3175
|
+
const text2 = (children, style = {}) => (0, import_react3.createElement)(RN3.Text, { style: { color: p.fg, fontSize: 15, ...style } }, children);
|
|
2535
3176
|
const muted = (children, style = {}) => text2(children, { color: p.muted, fontSize: 13, ...style });
|
|
2536
|
-
const title = (children) => (0,
|
|
2537
|
-
const btn = (label, kind, onPress, disabled = false, style = {}) => (0,
|
|
3177
|
+
const title = (children) => (0, import_react3.createElement)(RN3.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 17, fontWeight: "700", flex: 1 } }, children);
|
|
3178
|
+
const btn = (label, kind, onPress, disabled = false, style = {}) => (0, import_react3.createElement)(
|
|
2538
3179
|
RN3.Pressable,
|
|
2539
3180
|
{
|
|
2540
3181
|
key: label,
|
|
@@ -2557,8 +3198,8 @@ var kit = (p) => {
|
|
|
2557
3198
|
},
|
|
2558
3199
|
text2(label, { color: kind === "primary" ? "#ffffff" : p.fg, fontWeight: "600" })
|
|
2559
3200
|
);
|
|
2560
|
-
const closeX = (label, onPress) => (0,
|
|
2561
|
-
const field = (o) => (0,
|
|
3201
|
+
const closeX = (label, onPress) => (0, import_react3.createElement)(RN3.Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, hitSlop: 12, style: { padding: 4 } }, text2("\xD7", { fontSize: 24, color: p.muted }));
|
|
3202
|
+
const field = (o) => (0, import_react3.createElement)(RN3.TextInput, {
|
|
2562
3203
|
value: o.value,
|
|
2563
3204
|
onChangeText: o.onChange,
|
|
2564
3205
|
placeholder: o.placeholder,
|
|
@@ -2581,7 +3222,7 @@ var kit = (p) => {
|
|
|
2581
3222
|
...o.style
|
|
2582
3223
|
}
|
|
2583
3224
|
});
|
|
2584
|
-
const chip = (label, on, onPress, key = label) => (0,
|
|
3225
|
+
const chip = (label, on, onPress, key = label) => (0, import_react3.createElement)(
|
|
2585
3226
|
RN3.Pressable,
|
|
2586
3227
|
{
|
|
2587
3228
|
key,
|
|
@@ -2592,17 +3233,17 @@ var kit = (p) => {
|
|
|
2592
3233
|
},
|
|
2593
3234
|
text2(label, { fontSize: 13, fontWeight: "600", color: on ? p.accent : p.fg })
|
|
2594
3235
|
);
|
|
2595
|
-
const page = (heading, onClose, children) => (0,
|
|
3236
|
+
const page = (heading, onClose, children) => (0, import_react3.createElement)(
|
|
2596
3237
|
RN3.Modal,
|
|
2597
3238
|
{ visible: true, animationType: "slide", presentationStyle: "pageSheet", onRequestClose: onClose },
|
|
2598
|
-
(0,
|
|
3239
|
+
(0, import_react3.createElement)(
|
|
2599
3240
|
RN3.KeyboardAvoidingView,
|
|
2600
3241
|
{ behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: p.bg } },
|
|
2601
|
-
(0,
|
|
3242
|
+
(0, import_react3.createElement)(
|
|
2602
3243
|
RN3.View,
|
|
2603
3244
|
{ style: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, paddingTop: 16, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: p.line } },
|
|
2604
3245
|
title(heading),
|
|
2605
|
-
closeX("Close", onClose)
|
|
3246
|
+
closeX(t("Close"), onClose)
|
|
2606
3247
|
),
|
|
2607
3248
|
...children
|
|
2608
3249
|
)
|
|
@@ -2610,13 +3251,13 @@ var kit = (p) => {
|
|
|
2610
3251
|
return { text: text2, muted, title, btn, closeX, field, chip, page };
|
|
2611
3252
|
};
|
|
2612
3253
|
var timeAgo = (iso) => {
|
|
2613
|
-
const
|
|
2614
|
-
if (Number.isNaN(
|
|
2615
|
-
const d = Math.max(0, Date.now() -
|
|
2616
|
-
if (d < 60) return "just now";
|
|
2617
|
-
if (d < 3600) return
|
|
2618
|
-
if (d < 86400) return
|
|
2619
|
-
return
|
|
3254
|
+
const at = new Date(iso).getTime();
|
|
3255
|
+
if (Number.isNaN(at)) return "";
|
|
3256
|
+
const d = Math.max(0, Date.now() - at) / 1e3;
|
|
3257
|
+
if (d < 60) return t("just now");
|
|
3258
|
+
if (d < 3600) return tf("{n}m ago", { n: Math.floor(d / 60) });
|
|
3259
|
+
if (d < 86400) return tf("{n}h ago", { n: Math.floor(d / 3600) });
|
|
3260
|
+
return tf("{n}d ago", { n: Math.floor(d / 86400) });
|
|
2620
3261
|
};
|
|
2621
3262
|
var opener = () => {
|
|
2622
3263
|
let open = false;
|
|
@@ -2626,8 +3267,8 @@ var opener = () => {
|
|
|
2626
3267
|
listeners.forEach((l) => l(v));
|
|
2627
3268
|
};
|
|
2628
3269
|
const useOpen = () => {
|
|
2629
|
-
const [v, setV] = (0,
|
|
2630
|
-
(0,
|
|
3270
|
+
const [v, setV] = (0, import_react3.useState)(open);
|
|
3271
|
+
(0, import_react3.useEffect)(() => {
|
|
2631
3272
|
listeners.add(setV);
|
|
2632
3273
|
setV(open);
|
|
2633
3274
|
return () => {
|
|
@@ -2641,22 +3282,22 @@ var opener = () => {
|
|
|
2641
3282
|
var isAnswered = (a) => Array.isArray(a) ? a.length > 0 : typeof a === "number" ? true : !!a?.trim();
|
|
2642
3283
|
var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
2643
3284
|
const k = kit(p);
|
|
2644
|
-
const [step, setStep] = (0,
|
|
2645
|
-
const [answers, setAnswers] = (0,
|
|
2646
|
-
const [done, setDone] = (0,
|
|
2647
|
-
const [error, setError] = (0,
|
|
2648
|
-
const started = (0,
|
|
2649
|
-
const finished = (0,
|
|
3285
|
+
const [step, setStep] = (0, import_react3.useState)(0);
|
|
3286
|
+
const [answers, setAnswers] = (0, import_react3.useState)({});
|
|
3287
|
+
const [done, setDone] = (0, import_react3.useState)(false);
|
|
3288
|
+
const [error, setError] = (0, import_react3.useState)("");
|
|
3289
|
+
const started = (0, import_react3.useRef)(Date.now());
|
|
3290
|
+
const finished = (0, import_react3.useRef)(false);
|
|
2650
3291
|
const q = survey.questions[step];
|
|
2651
3292
|
const last = step === survey.questions.length - 1;
|
|
2652
3293
|
const value = answers[q.key];
|
|
2653
|
-
(0,
|
|
3294
|
+
(0, import_react3.useEffect)(() => {
|
|
2654
3295
|
api.seen(survey.id).catch(() => void 0);
|
|
2655
3296
|
}, [api, survey.id]);
|
|
2656
|
-
(0,
|
|
3297
|
+
(0, import_react3.useEffect)(() => {
|
|
2657
3298
|
if (!done) return;
|
|
2658
|
-
const
|
|
2659
|
-
return () => clearTimeout(
|
|
3299
|
+
const t2 = setTimeout(onDone, 2500);
|
|
3300
|
+
return () => clearTimeout(t2);
|
|
2660
3301
|
}, [done, onDone]);
|
|
2661
3302
|
const finish = (completed, all = answers) => {
|
|
2662
3303
|
if (finished.current) return;
|
|
@@ -2668,7 +3309,7 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
2668
3309
|
onDone();
|
|
2669
3310
|
};
|
|
2670
3311
|
const next = () => {
|
|
2671
|
-
if (q.required && !isAnswered(value)) return setError("Please answer this question.");
|
|
3312
|
+
if (q.required && !isAnswered(value)) return setError(t("Please answer this question."));
|
|
2672
3313
|
setError("");
|
|
2673
3314
|
if (!last) return setStep(step + 1);
|
|
2674
3315
|
finish(true);
|
|
@@ -2682,40 +3323,40 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
2682
3323
|
if (q.kind === "nps" || q.kind === "rating") {
|
|
2683
3324
|
const scale = q.kind === "nps" ? 11 : Math.min(Math.max(q.scale ?? 5, 2), 10);
|
|
2684
3325
|
const start = q.kind === "nps" ? 0 : 1;
|
|
2685
|
-
input = (0,
|
|
3326
|
+
input = (0, import_react3.createElement)(
|
|
2686
3327
|
RN3.View,
|
|
2687
3328
|
{ style: { gap: 6 } },
|
|
2688
|
-
(0,
|
|
3329
|
+
(0, import_react3.createElement)(
|
|
2689
3330
|
RN3.View,
|
|
2690
3331
|
{ style: { flexDirection: "row", flexWrap: "wrap", gap: 6 }, accessibilityRole: "radiogroup", accessibilityLabel: q.title },
|
|
2691
3332
|
...Array.from({ length: scale }, (_, i) => {
|
|
2692
3333
|
const n = i + start;
|
|
2693
3334
|
const on = value === n;
|
|
2694
|
-
return (0,
|
|
3335
|
+
return (0, import_react3.createElement)(
|
|
2695
3336
|
RN3.Pressable,
|
|
2696
3337
|
{
|
|
2697
3338
|
key: n,
|
|
2698
3339
|
onPress: () => pick(n),
|
|
2699
3340
|
accessibilityRole: "radio",
|
|
2700
3341
|
accessibilityState: { checked: on },
|
|
2701
|
-
accessibilityLabel:
|
|
3342
|
+
accessibilityLabel: tf("{n} of {total}", { n, total: scale - 1 + start }),
|
|
2702
3343
|
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 }
|
|
2703
3344
|
},
|
|
2704
3345
|
k.text(String(n), { color: on ? "#ffffff" : p.fg, fontWeight: "600" })
|
|
2705
3346
|
);
|
|
2706
3347
|
})
|
|
2707
3348
|
),
|
|
2708
|
-
q.low || q.high || q.kind === "nps" ? (0,
|
|
3349
|
+
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
|
|
2709
3350
|
);
|
|
2710
3351
|
} else if (q.kind === "single" || q.kind === "multi") {
|
|
2711
3352
|
const multi = q.kind === "multi";
|
|
2712
3353
|
const picked = multi ? value ?? [] : [];
|
|
2713
|
-
input = (0,
|
|
3354
|
+
input = (0, import_react3.createElement)(
|
|
2714
3355
|
RN3.View,
|
|
2715
3356
|
{ style: { gap: 6 } },
|
|
2716
|
-
...(q.options ?? []).map((o) => {
|
|
3357
|
+
...(q.options ?? []).map((o, i) => {
|
|
2717
3358
|
const on = multi ? picked.includes(o) : value === o;
|
|
2718
|
-
return (0,
|
|
3359
|
+
return (0, import_react3.createElement)(
|
|
2719
3360
|
RN3.Pressable,
|
|
2720
3361
|
{
|
|
2721
3362
|
key: o,
|
|
@@ -2724,32 +3365,33 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
2724
3365
|
accessibilityState: { checked: on },
|
|
2725
3366
|
style: { minHeight: 44, paddingHorizontal: 12, borderRadius: 10, justifyContent: "center", borderWidth: 1, borderColor: on ? p.accent : p.line, backgroundColor: on ? p.soft : p.bg }
|
|
2726
3367
|
},
|
|
2727
|
-
|
|
3368
|
+
// The label in the person's language; what is recorded stays the option itself.
|
|
3369
|
+
k.text(q.labels?.[i] || o, { fontWeight: "500" })
|
|
2728
3370
|
);
|
|
2729
3371
|
})
|
|
2730
3372
|
);
|
|
2731
|
-
} else input = k.field({ value: value ?? "", onChange: pick, placeholder: q.placeholder ?? "Type your answer", multiline: true });
|
|
2732
|
-
const body = done ? [k.text(survey.thanks || "Thanks for your feedback.", { fontWeight: "600", textAlign: "center", paddingVertical: 16 })] : [
|
|
2733
|
-
(0,
|
|
3373
|
+
} else input = k.field({ value: value ?? "", onChange: pick, placeholder: q.placeholder ?? t("Type your answer"), multiline: true });
|
|
3374
|
+
const body = done ? [k.text(survey.thanks || t("Thanks for your feedback."), { fontWeight: "600", textAlign: "center", paddingVertical: 16 })] : [
|
|
3375
|
+
(0, import_react3.createElement)(
|
|
2734
3376
|
RN3.View,
|
|
2735
3377
|
{ key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
|
|
2736
|
-
...survey.questions.map((x, i) => (0,
|
|
2737
|
-
(0,
|
|
2738
|
-
k.closeX("Close survey", dismiss)
|
|
3378
|
+
...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 } })),
|
|
3379
|
+
(0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
|
|
3380
|
+
k.closeX(t("Close survey"), dismiss)
|
|
2739
3381
|
),
|
|
2740
|
-
(0,
|
|
3382
|
+
(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")}`)),
|
|
2741
3383
|
q.description ? k.muted(q.description) : null,
|
|
2742
3384
|
input,
|
|
2743
3385
|
error ? k.text(error, { color: p.danger, fontSize: 13 }) : null,
|
|
2744
|
-
k.btn(last ? "Send" : "Next", "primary", next)
|
|
3386
|
+
k.btn(last ? t("Send") : t("Next"), "primary", next)
|
|
2745
3387
|
];
|
|
2746
|
-
return (0,
|
|
3388
|
+
return (0, import_react3.createElement)(
|
|
2747
3389
|
RN3.Modal,
|
|
2748
3390
|
{ visible: true, transparent: true, animationType: "slide", onRequestClose: dismiss },
|
|
2749
|
-
(0,
|
|
3391
|
+
(0, import_react3.createElement)(
|
|
2750
3392
|
RN3.KeyboardAvoidingView,
|
|
2751
3393
|
{ behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: "rgba(0,0,0,0.35)", justifyContent: "flex-end" } },
|
|
2752
|
-
(0,
|
|
3394
|
+
(0, import_react3.createElement)(
|
|
2753
3395
|
RN3.View,
|
|
2754
3396
|
{
|
|
2755
3397
|
accessibilityViewIsModal: true,
|
|
@@ -2763,10 +3405,10 @@ var SurveySheet = ({ survey, api, p, onDone }) => {
|
|
|
2763
3405
|
};
|
|
2764
3406
|
var MRRocketSurveys = ({ client, appearance }) => {
|
|
2765
3407
|
const p = usePalette(appearance);
|
|
2766
|
-
const [survey, setSurvey] = (0,
|
|
2767
|
-
const [who2, setWho] = (0,
|
|
2768
|
-
(0,
|
|
2769
|
-
(0,
|
|
3408
|
+
const [survey, setSurvey] = (0, import_react3.useState)(null);
|
|
3409
|
+
const [who2, setWho] = (0, import_react3.useState)(() => client.getDistinctId());
|
|
3410
|
+
(0, import_react3.useEffect)(() => client.subscribe(() => setWho(client.getDistinctId())), [client]);
|
|
3411
|
+
(0, import_react3.useEffect)(() => {
|
|
2770
3412
|
let stop = false;
|
|
2771
3413
|
const ctrl = new AbortController();
|
|
2772
3414
|
const timer = setTimeout(() => {
|
|
@@ -2774,9 +3416,9 @@ var MRRocketSurveys = ({ client, appearance }) => {
|
|
|
2774
3416
|
const api = client.api();
|
|
2775
3417
|
if (stop || !api) return;
|
|
2776
3418
|
api.survey("", ctrl.signal).then((r) => {
|
|
2777
|
-
const
|
|
2778
|
-
if (stop || !
|
|
2779
|
-
setSurvey({ s, api });
|
|
3419
|
+
const s2 = r.survey;
|
|
3420
|
+
if (stop || !s2 || !s2.questions.length || client.isSurveyDone(s2.id)) return;
|
|
3421
|
+
setSurvey({ s: s2, api });
|
|
2780
3422
|
}).catch(() => void 0);
|
|
2781
3423
|
});
|
|
2782
3424
|
}, 3e3);
|
|
@@ -2786,35 +3428,35 @@ var MRRocketSurveys = ({ client, appearance }) => {
|
|
|
2786
3428
|
ctrl.abort();
|
|
2787
3429
|
};
|
|
2788
3430
|
}, [client, who2]);
|
|
2789
|
-
const done = (0,
|
|
3431
|
+
const done = (0, import_react3.useCallback)(() => {
|
|
2790
3432
|
setSurvey((cur) => {
|
|
2791
3433
|
if (cur) client.markSurveyDone(cur.s.id);
|
|
2792
3434
|
return null;
|
|
2793
3435
|
});
|
|
2794
3436
|
}, [client]);
|
|
2795
3437
|
if (!survey) return null;
|
|
2796
|
-
return (0,
|
|
3438
|
+
return (0, import_react3.createElement)(SurveySheet, { key: survey.s.id, survey: survey.s, api: survey.api, p, onDone: done });
|
|
2797
3439
|
};
|
|
2798
3440
|
var feedbackOpen = opener();
|
|
2799
3441
|
var useFeedback = () => ({ open: () => feedbackOpen.set(true), close: () => feedbackOpen.set(false) });
|
|
2800
3442
|
var FeedbackSheet = ({ client, p, title }) => {
|
|
2801
3443
|
const k = kit(p);
|
|
2802
|
-
const [tab, setTab] = (0,
|
|
2803
|
-
const [posts, setPosts] = (0,
|
|
2804
|
-
const [boards, setBoards] = (0,
|
|
2805
|
-
const [error, setError] = (0,
|
|
2806
|
-
const [board, setBoard] = (0,
|
|
2807
|
-
const [subject, setSubject] = (0,
|
|
2808
|
-
const [details, setDetails] = (0,
|
|
2809
|
-
const [name, setName] = (0,
|
|
2810
|
-
const [email, setEmail] = (0,
|
|
2811
|
-
const [sending, setSending] = (0,
|
|
2812
|
-
const [sent, setSent] = (0,
|
|
3444
|
+
const [tab, setTab] = (0, import_react3.useState)("top");
|
|
3445
|
+
const [posts, setPosts] = (0, import_react3.useState)(null);
|
|
3446
|
+
const [boards, setBoards] = (0, import_react3.useState)([]);
|
|
3447
|
+
const [error, setError] = (0, import_react3.useState)("");
|
|
3448
|
+
const [board, setBoard] = (0, import_react3.useState)("feature");
|
|
3449
|
+
const [subject, setSubject] = (0, import_react3.useState)("");
|
|
3450
|
+
const [details, setDetails] = (0, import_react3.useState)("");
|
|
3451
|
+
const [name, setName] = (0, import_react3.useState)("");
|
|
3452
|
+
const [email, setEmail] = (0, import_react3.useState)("");
|
|
3453
|
+
const [sending, setSending] = (0, import_react3.useState)(false);
|
|
3454
|
+
const [sent, setSent] = (0, import_react3.useState)(false);
|
|
2813
3455
|
const close = () => feedbackOpen.set(false);
|
|
2814
3456
|
const user = client.getUser();
|
|
2815
|
-
(0,
|
|
3457
|
+
(0, import_react3.useEffect)(() => {
|
|
2816
3458
|
const api = client.api();
|
|
2817
|
-
if (!api) return setError("Feedback is off while tracking is off.");
|
|
3459
|
+
if (!api) return setError(t("Feedback is off while tracking is off."));
|
|
2818
3460
|
const ctrl = new AbortController();
|
|
2819
3461
|
api.list({ sort: "top" }, ctrl.signal).then((l) => {
|
|
2820
3462
|
setPosts(l.posts);
|
|
@@ -2844,74 +3486,77 @@ var FeedbackSheet = ({ client, p, title }) => {
|
|
|
2844
3486
|
setSent(true);
|
|
2845
3487
|
}).catch((e) => setError(e.message)).finally(() => setSending(false));
|
|
2846
3488
|
};
|
|
2847
|
-
const tabs = (0,
|
|
3489
|
+
const tabs = (0, import_react3.createElement)(
|
|
2848
3490
|
RN3.View,
|
|
2849
3491
|
{ style: { flexDirection: "row", gap: 8, paddingHorizontal: 16, paddingTop: 12 } },
|
|
2850
|
-
k.chip("Top requests", tab === "top", () => setTab("top")),
|
|
2851
|
-
k.chip("Suggest", tab === "new", () => {
|
|
3492
|
+
k.chip(t("Top requests"), tab === "top", () => setTab("top")),
|
|
3493
|
+
k.chip(t("Suggest"), tab === "new", () => {
|
|
2852
3494
|
setTab("new");
|
|
2853
3495
|
setSent(false);
|
|
2854
3496
|
})
|
|
2855
3497
|
);
|
|
2856
|
-
const list = (0,
|
|
3498
|
+
const list = (0, import_react3.createElement)(
|
|
2857
3499
|
RN3.ScrollView,
|
|
2858
3500
|
{ style: { flex: 1 }, contentContainerStyle: { padding: 16, gap: 10 } },
|
|
2859
3501
|
error ? k.text(error, { color: p.danger, fontSize: 13 }) : null,
|
|
2860
|
-
posts === null && !error ? k.muted("Loading\u2026") : null,
|
|
2861
|
-
posts?.length === 0 ? k.muted("No requests yet. Be the first to suggest one.") : null,
|
|
3502
|
+
posts === null && !error ? k.muted(t("Loading\u2026")) : null,
|
|
3503
|
+
posts?.length === 0 ? k.muted(t("No requests yet. Be the first to suggest one.")) : null,
|
|
2862
3504
|
...(posts ?? []).map(
|
|
2863
|
-
(post) => (0,
|
|
3505
|
+
(post) => (0, import_react3.createElement)(
|
|
2864
3506
|
RN3.View,
|
|
2865
3507
|
{ key: post.id, style: { flexDirection: "row", gap: 12, alignItems: "flex-start", paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: p.line } },
|
|
2866
|
-
(0,
|
|
3508
|
+
(0, import_react3.createElement)(
|
|
2867
3509
|
RN3.Pressable,
|
|
2868
3510
|
{
|
|
2869
3511
|
onPress: () => vote(post),
|
|
2870
3512
|
accessibilityRole: "button",
|
|
2871
3513
|
accessibilityState: { selected: post.voted },
|
|
2872
|
-
accessibilityLabel:
|
|
3514
|
+
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 }) }),
|
|
2873
3515
|
style: { width: 44, minHeight: 48, borderRadius: 10, alignItems: "center", justifyContent: "center", borderWidth: 1, borderColor: post.voted ? p.accent : p.line }
|
|
2874
3516
|
},
|
|
2875
3517
|
k.text("\u25B2", { fontSize: 11, color: post.voted ? p.accent : p.muted }),
|
|
2876
3518
|
k.text(String(post.votes), { fontWeight: "700", fontSize: 14, color: post.voted ? p.accent : p.fg })
|
|
2877
3519
|
),
|
|
2878
|
-
(0,
|
|
3520
|
+
(0, import_react3.createElement)(RN3.View, { style: { flex: 1, gap: 2 } }, k.text(post.title, { fontWeight: "600" }), post.body ? k.muted(post.body.slice(0, 160)) : null)
|
|
2879
3521
|
)
|
|
2880
3522
|
)
|
|
2881
3523
|
);
|
|
2882
|
-
const form = (0,
|
|
3524
|
+
const form = (0, import_react3.createElement)(
|
|
2883
3525
|
RN3.ScrollView,
|
|
2884
3526
|
{ style: { flex: 1 }, contentContainerStyle: { padding: 16, gap: 10 }, keyboardShouldPersistTaps: "handled" },
|
|
2885
|
-
sent ? k.text("Thanks! Your request is on the board.", { fontWeight: "600" }) : null,
|
|
2886
|
-
boards.length > 1 ? (0,
|
|
2887
|
-
k.field({ value: subject, onChange: setSubject, placeholder: "Short title", max: 120 }),
|
|
2888
|
-
k.field({ value: details, onChange: setDetails, placeholder: "Details (optional)", multiline: true }),
|
|
3527
|
+
sent ? k.text(t("Thanks! Your request is on the board."), { fontWeight: "600" }) : null,
|
|
3528
|
+
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,
|
|
3529
|
+
k.field({ value: subject, onChange: setSubject, placeholder: t("Short title"), max: 120 }),
|
|
3530
|
+
k.field({ value: details, onChange: setDetails, placeholder: t("Details (optional)"), multiline: true }),
|
|
2889
3531
|
...user?.email ? [] : [
|
|
2890
|
-
k.muted("Your name and email are optional; they let the team follow up."),
|
|
2891
|
-
k.field({ value: name, onChange: setName, placeholder: "Your name (optional)", max: 100 }),
|
|
2892
|
-
k.field({ value: email, onChange: setEmail, placeholder: "Your email (optional)", max: 200, email: true })
|
|
3532
|
+
k.muted(t("Your name and email are optional; they let the team follow up.")),
|
|
3533
|
+
k.field({ value: name, onChange: setName, placeholder: t("Your name (optional)"), max: 100 }),
|
|
3534
|
+
k.field({ value: email, onChange: setEmail, placeholder: t("Your email (optional)"), max: 200, email: true })
|
|
2893
3535
|
],
|
|
2894
3536
|
error ? k.text(error, { color: p.danger, fontSize: 13 }) : null,
|
|
2895
|
-
k.btn(sending ? "Sending\u2026" : "Send request", "primary", submit, subject.trim().length < 3 || sending)
|
|
3537
|
+
k.btn(sending ? t("Sending\u2026") : t("Send request"), "primary", submit, subject.trim().length < 3 || sending)
|
|
2896
3538
|
);
|
|
2897
3539
|
return k.page(title, close, [tabs, tab === "top" ? list : form]);
|
|
2898
3540
|
};
|
|
2899
3541
|
var MRRocketFeedback = ({ client, appearance, options = {} }) => {
|
|
2900
3542
|
const p = usePalette(appearance);
|
|
2901
3543
|
const open = feedbackOpen.useOpen();
|
|
2902
|
-
return open ? (0,
|
|
3544
|
+
return open ? (0, import_react3.createElement)(FeedbackSheet, { client, p, title: options.title ?? t("Feedback") }) : null;
|
|
2903
3545
|
};
|
|
2904
3546
|
var POLL_OPEN_MS = 4e3;
|
|
2905
3547
|
var POLL_WAITING_MS = 8e3;
|
|
2906
3548
|
var POLL_IDLE_MS = 3e4;
|
|
3549
|
+
var POLL_QUIET_MS = 6e4;
|
|
2907
3550
|
var WAITING_FOR_MS = 10 * 6e4;
|
|
2908
3551
|
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
2909
|
-
var whoLabel =
|
|
2910
|
-
var who = (from, agentName) => from === "ai" && agentName?.trim() || whoLabel
|
|
3552
|
+
var whoLabel = (from) => from === "visitor" ? t("You") : from === "ai" ? t("AI agent") : t("Team");
|
|
3553
|
+
var who = (from, agentName) => from === "ai" && agentName?.trim() || whoLabel(from);
|
|
2911
3554
|
var msgView = "home";
|
|
3555
|
+
var msgChat = null;
|
|
2912
3556
|
var messengerOpen = opener();
|
|
2913
|
-
var openAt = (v = "home") => {
|
|
3557
|
+
var openAt = (v = "home", chatId = null) => {
|
|
2914
3558
|
msgView = v;
|
|
3559
|
+
msgChat = chatId;
|
|
2915
3560
|
messengerOpen.set(true);
|
|
2916
3561
|
};
|
|
2917
3562
|
var useMessenger = () => ({
|
|
@@ -2938,12 +3583,15 @@ try {
|
|
|
2938
3583
|
imagePicker = null;
|
|
2939
3584
|
}
|
|
2940
3585
|
var PHOTO_TYPES = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif" };
|
|
2941
|
-
var
|
|
2942
|
-
var
|
|
3586
|
+
var samePlace = (a, b) => a.chatId === b.chatId && a.fresh === b.fresh;
|
|
3587
|
+
var shownChat = (at, chats) => at.fresh ? null : at.chatId ?? (chats.length === 1 ? chats[0].id : null);
|
|
3588
|
+
var isAnswer = (m) => m.from === "team" || m.from === "ai";
|
|
3589
|
+
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 }) : "");
|
|
3590
|
+
var svg2 = null;
|
|
2943
3591
|
try {
|
|
2944
|
-
|
|
3592
|
+
svg2 = require("react-native-svg");
|
|
2945
3593
|
} catch {
|
|
2946
|
-
|
|
3594
|
+
svg2 = null;
|
|
2947
3595
|
}
|
|
2948
3596
|
var GLYPH = {
|
|
2949
3597
|
chat: "M2.5 4.5a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v4.5a2 2 0 0 1-2 2H7l-3 2.5V11a2 2 0 0 1-1.5-2z",
|
|
@@ -2958,15 +3606,15 @@ var GLYPH = {
|
|
|
2958
3606
|
};
|
|
2959
3607
|
var FALLBACK = { chat: "\u{1F4AC}", home: "\u2302", help: "?", send: "\u2192", up: "\u2191", next: "\u203A", search: "\u2315", cross: "\xD7", clip: "\u{1F4CE}" };
|
|
2960
3608
|
var glyph = (name, color, size = 20) => {
|
|
2961
|
-
const Svg =
|
|
2962
|
-
if (!
|
|
2963
|
-
return (0,
|
|
3609
|
+
const Svg = svg2?.Svg ?? svg2?.default;
|
|
3610
|
+
if (!svg2 || !Svg) return (0, import_react3.createElement)(RN3.Text, { style: { color, fontSize: Math.round(size * 0.85), lineHeight: size + 2, textAlign: "center", minWidth: size } }, FALLBACK[name]);
|
|
3611
|
+
return (0, import_react3.createElement)(Svg, { width: size, height: size, viewBox: "0 0 16 16", fill: "none" }, (0, import_react3.createElement)(svg2.Path, { d: GLYPH[name], stroke: color, strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }));
|
|
2964
3612
|
};
|
|
2965
3613
|
var alpha = (hex, a) => /^#[0-9a-f]{6}$/i.test(hex) ? `${hex}${Math.round(a * 255).toString(16).padStart(2, "0")}` : hex;
|
|
2966
|
-
var teamMark = (title, bg, fg, size = 36) => (0,
|
|
3614
|
+
var teamMark = (title, bg, fg, size = 36) => (0, import_react3.createElement)(
|
|
2967
3615
|
RN3.View,
|
|
2968
3616
|
{ style: { width: size, height: size, borderRadius: size / 2, backgroundColor: bg, alignItems: "center", justifyContent: "center" } },
|
|
2969
|
-
(0,
|
|
3617
|
+
(0, import_react3.createElement)(RN3.Text, { style: { color: fg, fontSize: Math.round(size * 0.42), fontWeight: "700" } }, (title.trim()[0] ?? "?").toUpperCase())
|
|
2970
3618
|
);
|
|
2971
3619
|
var HU = {
|
|
2972
3620
|
light: { bg: "#ffffff", surface: "#f4f4f5", def: "#ececee", segment: "#ffffff", fg: "#18181b", muted: "#71717a" },
|
|
@@ -2974,43 +3622,81 @@ var HU = {
|
|
|
2974
3622
|
};
|
|
2975
3623
|
var hairline = { shadowColor: "#000", shadowOpacity: 0.07, shadowRadius: 3, shadowOffset: { width: 0, height: 1 }, elevation: 1 };
|
|
2976
3624
|
var useChat = (client, o, open, view) => {
|
|
2977
|
-
const [thread, setThread] = (0,
|
|
2978
|
-
const [
|
|
2979
|
-
const [
|
|
2980
|
-
const [
|
|
2981
|
-
const
|
|
3625
|
+
const [thread, setThread] = (0, import_react3.useState)(null);
|
|
3626
|
+
const [chats, setChats] = (0, import_react3.useState)([]);
|
|
3627
|
+
const [hasEmail, setHasEmail] = (0, import_react3.useState)(false);
|
|
3628
|
+
const [place, setPlace] = (0, import_react3.useState)({ chatId: null, fresh: false });
|
|
3629
|
+
const [loaded, setLoaded] = (0, import_react3.useState)(false);
|
|
3630
|
+
const [error, setError] = (0, import_react3.useState)("");
|
|
3631
|
+
const [unseen, setUnseen] = (0, import_react3.useState)([]);
|
|
3632
|
+
const known = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
|
3633
|
+
const seeded = (0, import_react3.useRef)(false);
|
|
2982
3634
|
const watching = open && view === "messages";
|
|
2983
|
-
const watch = (0,
|
|
3635
|
+
const watch = (0, import_react3.useRef)(watching);
|
|
2984
3636
|
watch.current = watching;
|
|
2985
|
-
const
|
|
2986
|
-
(0,
|
|
3637
|
+
const at = (0, import_react3.useRef)(place);
|
|
3638
|
+
const list = (0, import_react3.useRef)(chats);
|
|
3639
|
+
const readFailed = (0, import_react3.useRef)(false);
|
|
3640
|
+
const [active, setActive] = (0, import_react3.useState)(RN3.AppState.currentState === "active");
|
|
3641
|
+
(0, import_react3.useEffect)(() => {
|
|
2987
3642
|
const sub = RN3.AppState.addEventListener("change", (st) => setActive(st === "active"));
|
|
2988
3643
|
return () => sub.remove();
|
|
2989
3644
|
}, []);
|
|
2990
|
-
const
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3645
|
+
const enter = (0, import_react3.useCallback)((chatId, fresh = false) => {
|
|
3646
|
+
const next = { chatId, fresh };
|
|
3647
|
+
if (samePlace(at.current, next)) return;
|
|
3648
|
+
at.current = next;
|
|
3649
|
+
setPlace(next);
|
|
3650
|
+
setThread(null);
|
|
3651
|
+
setError("");
|
|
3652
|
+
}, []);
|
|
3653
|
+
const take = (0, import_react3.useCallback)(
|
|
3654
|
+
(t2) => {
|
|
3655
|
+
const read = t2.conversation;
|
|
3656
|
+
const last = t2.messages.at(-1);
|
|
3657
|
+
const all = t2.conversations ?? (read && last ? [{ id: read.id, status: read.status, subject: lineOf(t2.messages[0]), last }] : []);
|
|
3658
|
+
const fresh = [];
|
|
3659
|
+
const learn = (chat, answers, newest) => {
|
|
3660
|
+
const was = known.current.get(chat);
|
|
3661
|
+
const since = was ?? (seeded.current ? "" : void 0);
|
|
3662
|
+
if (since !== void 0) fresh.push(...answers.filter((m) => m.at > since).map((m) => ({ ...m, chat })));
|
|
3663
|
+
known.current.set(chat, newest ?? was ?? "");
|
|
3664
|
+
};
|
|
3665
|
+
for (const c of all) if (c.id !== read?.id) learn(c.id, c.last && isAnswer(c.last) ? [c.last] : [], c.last?.at);
|
|
3666
|
+
if (read) learn(read.id, t2.messages.filter(isAnswer), last?.at);
|
|
3667
|
+
seeded.current = true;
|
|
3668
|
+
if (fresh.length && o.haptics !== false) buzz();
|
|
3669
|
+
const shown2 = shownChat(at.current, all);
|
|
3670
|
+
const counted = fresh.filter((m) => !watch.current || m.chat !== shown2);
|
|
3671
|
+
setUnseen((u) => [...u.filter((m) => all.some((c) => c.id === m.chat)), ...counted]);
|
|
3672
|
+
list.current = all;
|
|
3673
|
+
setChats(all);
|
|
3674
|
+
setHasEmail(t2.hasEmail);
|
|
3675
|
+
if (at.current.chatId === null || read?.id === at.current.chatId) setThread(t2);
|
|
3003
3676
|
},
|
|
3004
3677
|
[o.haptics]
|
|
3005
3678
|
);
|
|
3006
|
-
(0,
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3679
|
+
const arrived = (0, import_react3.useCallback)(
|
|
3680
|
+
(from, t2) => {
|
|
3681
|
+
const went = t2.conversation?.id;
|
|
3682
|
+
if (went && samePlace(at.current, from)) {
|
|
3683
|
+
at.current = { chatId: went, fresh: false };
|
|
3684
|
+
setPlace(at.current);
|
|
3685
|
+
}
|
|
3686
|
+
take(t2);
|
|
3687
|
+
},
|
|
3688
|
+
[take]
|
|
3689
|
+
);
|
|
3690
|
+
const shown = shownChat(place, chats);
|
|
3691
|
+
(0, import_react3.useEffect)(() => {
|
|
3692
|
+
if (watching && shown) setUnseen((u) => u.filter((m) => m.chat !== shown));
|
|
3693
|
+
}, [watching, shown]);
|
|
3694
|
+
(0, import_react3.useEffect)(() => {
|
|
3010
3695
|
if (!active) return;
|
|
3011
3696
|
let stop = false;
|
|
3012
3697
|
let timer;
|
|
3013
3698
|
let ctrl = null;
|
|
3699
|
+
let refused = false;
|
|
3014
3700
|
const tick = async () => {
|
|
3015
3701
|
const api = client.api();
|
|
3016
3702
|
if (!api) {
|
|
@@ -3018,23 +3704,32 @@ var useChat = (client, o, open, view) => {
|
|
|
3018
3704
|
return;
|
|
3019
3705
|
}
|
|
3020
3706
|
ctrl = new AbortController();
|
|
3021
|
-
let t = null;
|
|
3022
3707
|
try {
|
|
3023
3708
|
await client.ready;
|
|
3024
|
-
|
|
3709
|
+
const t2 = await api.chat(ctrl.signal, place.chatId ?? void 0);
|
|
3025
3710
|
if (stop) return;
|
|
3026
|
-
take(
|
|
3027
|
-
setError("");
|
|
3711
|
+
take(t2);
|
|
3712
|
+
if (readFailed.current) setError("");
|
|
3713
|
+
readFailed.current = false;
|
|
3028
3714
|
} catch (e) {
|
|
3029
3715
|
if (e.name === "AbortError" || stop) return;
|
|
3030
|
-
|
|
3716
|
+
refused = [401, 403].includes(e.status ?? 0);
|
|
3717
|
+
if (e.status === 404 && place.chatId) enter(null);
|
|
3718
|
+
else if (watch.current) {
|
|
3719
|
+
readFailed.current = true;
|
|
3720
|
+
setError(e.message);
|
|
3721
|
+
}
|
|
3031
3722
|
}
|
|
3032
3723
|
setLoaded(true);
|
|
3033
|
-
const
|
|
3034
|
-
const
|
|
3035
|
-
const
|
|
3036
|
-
const ms = watch.current ? POLL_OPEN_MS : waiting && recent ? POLL_WAITING_MS : waiting || open ? POLL_IDLE_MS : 0;
|
|
3724
|
+
const running = list.current.filter((c) => c.status !== "solved");
|
|
3725
|
+
const recent = running.some((c) => c.last && Date.now() - new Date(c.last.at).getTime() < WAITING_FOR_MS);
|
|
3726
|
+
const ms = watch.current ? POLL_OPEN_MS : running.length && recent ? POLL_WAITING_MS : running.length || open ? POLL_IDLE_MS : 0;
|
|
3037
3727
|
if (!stop && ms) timer = setTimeout(tick, ms);
|
|
3728
|
+
else if (!stop) timer = setTimeout(quiet, POLL_QUIET_MS);
|
|
3729
|
+
};
|
|
3730
|
+
const quiet = () => {
|
|
3731
|
+
if (client.getUser() && !refused) void tick();
|
|
3732
|
+
else timer = setTimeout(quiet, POLL_QUIET_MS);
|
|
3038
3733
|
};
|
|
3039
3734
|
void tick();
|
|
3040
3735
|
return () => {
|
|
@@ -3042,16 +3737,30 @@ var useChat = (client, o, open, view) => {
|
|
|
3042
3737
|
ctrl?.abort();
|
|
3043
3738
|
clearTimeout(timer);
|
|
3044
3739
|
};
|
|
3045
|
-
}, [client, take, active, open, watching]);
|
|
3046
|
-
return {
|
|
3740
|
+
}, [client, take, enter, active, open, watching, place]);
|
|
3741
|
+
return {
|
|
3742
|
+
// A new conversation has nothing to show yet; polls keep reading the newest for the list.
|
|
3743
|
+
thread: place.fresh ? null : thread,
|
|
3744
|
+
chats,
|
|
3745
|
+
hasEmail,
|
|
3746
|
+
place,
|
|
3747
|
+
/** Messages shows the list: several conversations and none chosen. */
|
|
3748
|
+
listing: place.chatId === null && !place.fresh && chats.length > 1,
|
|
3749
|
+
enter,
|
|
3750
|
+
loaded,
|
|
3751
|
+
error,
|
|
3752
|
+
setError,
|
|
3753
|
+
unseen,
|
|
3754
|
+
arrived
|
|
3755
|
+
};
|
|
3047
3756
|
};
|
|
3048
3757
|
var useArticles = (client, on, query) => {
|
|
3049
|
-
const [list, setList] = (0,
|
|
3050
|
-
const [any, setAny] = (0,
|
|
3051
|
-
(0,
|
|
3758
|
+
const [list, setList] = (0, import_react3.useState)(null);
|
|
3759
|
+
const [any, setAny] = (0, import_react3.useState)(null);
|
|
3760
|
+
(0, import_react3.useEffect)(() => {
|
|
3052
3761
|
if (!on) return;
|
|
3053
3762
|
let stop = false;
|
|
3054
|
-
const
|
|
3763
|
+
const t2 = setTimeout(
|
|
3055
3764
|
() => void client.ready.then(
|
|
3056
3765
|
() => client.api()?.articles(query.trim() || void 0).then((r) => {
|
|
3057
3766
|
if (stop) return;
|
|
@@ -3063,74 +3772,74 @@ var useArticles = (client, on, query) => {
|
|
|
3063
3772
|
);
|
|
3064
3773
|
return () => {
|
|
3065
3774
|
stop = true;
|
|
3066
|
-
clearTimeout(
|
|
3775
|
+
clearTimeout(t2);
|
|
3067
3776
|
};
|
|
3068
3777
|
}, [client, on, query]);
|
|
3069
3778
|
return { list, any };
|
|
3070
3779
|
};
|
|
3071
3780
|
var spansOf = (p, spans, base) => spans.map(
|
|
3072
|
-
(
|
|
3781
|
+
(s2, i) => (0, import_react3.createElement)(
|
|
3073
3782
|
RN3.Text,
|
|
3074
3783
|
{
|
|
3075
3784
|
key: i,
|
|
3076
|
-
onPress:
|
|
3077
|
-
accessibilityRole:
|
|
3785
|
+
onPress: s2.href ? () => void RN3.Linking.openURL(s2.href).catch(() => void 0) : void 0,
|
|
3786
|
+
accessibilityRole: s2.href ? "link" : void 0,
|
|
3078
3787
|
style: {
|
|
3079
3788
|
...base,
|
|
3080
|
-
...
|
|
3081
|
-
...
|
|
3082
|
-
...
|
|
3083
|
-
...
|
|
3789
|
+
...s2.bold ? { fontWeight: "700" } : {},
|
|
3790
|
+
...s2.italic ? { fontStyle: "italic" } : {},
|
|
3791
|
+
...s2.code ? { fontFamily: RN3.Platform.OS === "ios" ? "Menlo" : "monospace", backgroundColor: p.soft } : {},
|
|
3792
|
+
...s2.href ? { color: p.accent, textDecorationLine: "underline" } : {}
|
|
3084
3793
|
}
|
|
3085
3794
|
},
|
|
3086
|
-
|
|
3795
|
+
s2.text
|
|
3087
3796
|
)
|
|
3088
3797
|
);
|
|
3089
3798
|
var ArticleView = ({ client, p, id, onAsk }) => {
|
|
3090
3799
|
const k = kit(p);
|
|
3091
|
-
const [a, setA] = (0,
|
|
3092
|
-
const [error, setError] = (0,
|
|
3093
|
-
const [rated, setRated] = (0,
|
|
3094
|
-
(0,
|
|
3800
|
+
const [a, setA] = (0, import_react3.useState)(null);
|
|
3801
|
+
const [error, setError] = (0, import_react3.useState)("");
|
|
3802
|
+
const [rated, setRated] = (0, import_react3.useState)(false);
|
|
3803
|
+
(0, import_react3.useEffect)(() => {
|
|
3095
3804
|
const ctrl = new AbortController();
|
|
3096
3805
|
client.api()?.article(id, ctrl.signal).then(setA).catch((e) => e.name !== "AbortError" && setError(e.message));
|
|
3097
3806
|
return () => ctrl.abort();
|
|
3098
3807
|
}, [client, id]);
|
|
3099
3808
|
if (error) return k.text(error, { color: p.danger });
|
|
3100
|
-
if (!a) return k.muted("Loading\u2026");
|
|
3809
|
+
if (!a) return k.muted(t("Loading\u2026"));
|
|
3101
3810
|
const base = { color: p.fg, fontSize: 15, lineHeight: 22 };
|
|
3102
3811
|
const blocks = a.blocks.map((b, i) => {
|
|
3103
|
-
if (b.type === "h2" || b.type === "h3") return (0,
|
|
3104
|
-
if (b.type === "p") return (0,
|
|
3105
|
-
if (b.type === "quote") return (0,
|
|
3812
|
+
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, {}));
|
|
3813
|
+
if (b.type === "p") return (0, import_react3.createElement)(RN3.Text, { key: i, style: base }, ...spansOf(p, b.spans, {}));
|
|
3814
|
+
if (b.type === "quote") return (0, import_react3.createElement)(RN3.View, { key: i, style: { borderLeftWidth: 3, borderLeftColor: p.line, paddingLeft: 10 } }, (0, import_react3.createElement)(RN3.Text, { style: { ...base, color: p.muted } }, ...spansOf(p, b.spans, {})));
|
|
3106
3815
|
if (b.type === "ul" || b.type === "ol")
|
|
3107
|
-
return (0,
|
|
3816
|
+
return (0, import_react3.createElement)(
|
|
3108
3817
|
RN3.View,
|
|
3109
3818
|
{ key: i, style: { gap: 4 } },
|
|
3110
|
-
...b.items.map((it, j) => (0,
|
|
3819
|
+
...b.items.map((it, j) => (0, import_react3.createElement)(RN3.View, { key: j, style: { flexDirection: "row", gap: 8 } }, k.text(b.type === "ol" ? `${j + 1}.` : "\u2022", base), (0, import_react3.createElement)(RN3.Text, { style: { ...base, flex: 1 } }, ...spansOf(p, it, {}))))
|
|
3111
3820
|
);
|
|
3112
|
-
if (b.type === "code") return (0,
|
|
3113
|
-
return (0,
|
|
3821
|
+
if (b.type === "code") return (0, import_react3.createElement)(RN3.View, { key: i, style: { backgroundColor: p.soft, borderRadius: 8, padding: 10 } }, k.text(b.text, { fontFamily: RN3.Platform.OS === "ios" ? "Menlo" : "monospace", fontSize: 13 }));
|
|
3822
|
+
return (0, import_react3.createElement)(RN3.View, { key: i, style: { height: 1, backgroundColor: p.line } });
|
|
3114
3823
|
});
|
|
3115
3824
|
const rate = (helpful) => {
|
|
3116
3825
|
setRated(true);
|
|
3117
3826
|
client.api()?.rateArticle(id, helpful).catch(() => void 0);
|
|
3118
3827
|
if (!helpful) onAsk();
|
|
3119
3828
|
};
|
|
3120
|
-
return (0,
|
|
3829
|
+
return (0, import_react3.createElement)(
|
|
3121
3830
|
RN3.View,
|
|
3122
3831
|
{ style: { gap: 10 } },
|
|
3123
3832
|
k.muted(a.collection),
|
|
3124
|
-
(0,
|
|
3833
|
+
(0, import_react3.createElement)(RN3.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 20, fontWeight: "700" } }, a.title),
|
|
3125
3834
|
...blocks,
|
|
3126
|
-
(0,
|
|
3835
|
+
(0, import_react3.createElement)(
|
|
3127
3836
|
RN3.View,
|
|
3128
3837
|
{ style: { flexDirection: "row", alignItems: "center", gap: 8, padding: 12, borderRadius: 10, backgroundColor: p.soft, marginTop: 8 } },
|
|
3129
|
-
rated ? k.muted("Thanks for letting us know.") : k.muted("Did this answer your question?", { flex: 1 }),
|
|
3130
|
-
rated ? null : k.btn("Yes", "secondary", () => rate(true), false, { height: 34 }),
|
|
3131
|
-
rated ? null : k.btn("No", "secondary", () => rate(false), false, { height: 34 })
|
|
3838
|
+
rated ? k.muted(t("Thanks for letting us know.")) : k.muted(t("Did this answer your question?"), { flex: 1 }),
|
|
3839
|
+
rated ? null : k.btn(t("Yes"), "secondary", () => rate(true), false, { height: 34 }),
|
|
3840
|
+
rated ? null : k.btn(t("No"), "secondary", () => rate(false), false, { height: 34 })
|
|
3132
3841
|
),
|
|
3133
|
-
k.btn("Still need help? Send us a message", "secondary", onAsk)
|
|
3842
|
+
k.btn(t("Still need help? Send us a message"), "secondary", onAsk)
|
|
3134
3843
|
);
|
|
3135
3844
|
};
|
|
3136
3845
|
var MessengerSheet = ({
|
|
@@ -3143,33 +3852,42 @@ var MessengerSheet = ({
|
|
|
3143
3852
|
brand
|
|
3144
3853
|
}) => {
|
|
3145
3854
|
const k = kit(p);
|
|
3146
|
-
const [body, setBody] = (0,
|
|
3147
|
-
const [
|
|
3148
|
-
const [
|
|
3149
|
-
const [
|
|
3150
|
-
const [
|
|
3855
|
+
const [body, setBody] = (0, import_react3.useState)("");
|
|
3856
|
+
const [asked, setAsked] = (0, import_react3.useState)("");
|
|
3857
|
+
const [email, setEmail] = (0, import_react3.useState)("");
|
|
3858
|
+
const [pending, setPending] = (0, import_react3.useState)(null);
|
|
3859
|
+
const [slow, setSlow] = (0, import_react3.useState)(false);
|
|
3860
|
+
(0, import_react3.useEffect)(() => {
|
|
3861
|
+
setSlow(false);
|
|
3862
|
+
if (!pending) return;
|
|
3863
|
+
const t2 = setTimeout(() => setSlow(true), 1500);
|
|
3864
|
+
return () => clearTimeout(t2);
|
|
3865
|
+
}, [pending]);
|
|
3866
|
+
const [photos, setPhotos] = (0, import_react3.useState)([]);
|
|
3867
|
+
const [query, setQuery] = (0, import_react3.useState)("");
|
|
3151
3868
|
const helpOn = o.help !== false;
|
|
3152
3869
|
const arts = useArticles(client, helpOn, query);
|
|
3153
|
-
const scroll = (0,
|
|
3870
|
+
const scroll = (0, import_react3.useRef)(null);
|
|
3154
3871
|
const close = () => messengerOpen.set(false);
|
|
3155
|
-
const greeting = o.greeting ?? "Hi! How can we help?";
|
|
3156
|
-
const { thread } = chat;
|
|
3157
|
-
const
|
|
3158
|
-
(0
|
|
3872
|
+
const greeting = o.greeting ?? t("Hi! How can we help?");
|
|
3873
|
+
const { thread, place } = chat;
|
|
3874
|
+
const pendingHere = pending && samePlace(pending.to, place) ? pending : null;
|
|
3875
|
+
const count = (thread?.messages.length ?? 0) + (pendingHere ? 1 : 0);
|
|
3876
|
+
(0, import_react3.useEffect)(() => {
|
|
3159
3877
|
if (view === "messages") setTimeout(() => scroll.current?.scrollToEnd?.({ animated: true }), 50);
|
|
3160
|
-
}, [count, view]);
|
|
3161
|
-
const needEmail = (o.askEmail ?? true) && !
|
|
3878
|
+
}, [count, view, place]);
|
|
3879
|
+
const needEmail = (o.askEmail ?? true) && !chat.hasEmail && !client.getUser()?.email;
|
|
3162
3880
|
const emailOk = !email.trim() || EMAIL.test(email.trim());
|
|
3163
3881
|
const pickPhotos = async () => {
|
|
3164
3882
|
const api = client.api();
|
|
3165
3883
|
if (!api || !imagePicker?.launchImageLibraryAsync || pending) return;
|
|
3166
3884
|
const room = CHAT_FILES_PER_MESSAGE - photos.length;
|
|
3167
|
-
if (room <= 0) return chat.setError(
|
|
3885
|
+
if (room <= 0) return chat.setError(tf("Up to {n} files per message.", { n: CHAT_FILES_PER_MESSAGE }));
|
|
3168
3886
|
let res;
|
|
3169
3887
|
try {
|
|
3170
3888
|
res = await imagePicker.launchImageLibraryAsync({ allowsMultipleSelection: room > 1, selectionLimit: room, quality: 0.8, exif: false });
|
|
3171
3889
|
} catch (e) {
|
|
3172
|
-
return chat.setError(e.message || "The photos could not be opened.");
|
|
3890
|
+
return chat.setError(e.message || t("The photos could not be opened."));
|
|
3173
3891
|
}
|
|
3174
3892
|
if (res.canceled || res.cancelled || !res.assets?.length) return;
|
|
3175
3893
|
chat.setError("");
|
|
@@ -3178,11 +3896,11 @@ var MessengerSheet = ({
|
|
|
3178
3896
|
const name = a.fileName || a.uri.split("/").pop() || "photo.jpg";
|
|
3179
3897
|
const type = a.mimeType && Object.values(PHOTO_TYPES).includes(a.mimeType) ? a.mimeType : PHOTO_TYPES[name.split(".").pop()?.toLowerCase() ?? ""];
|
|
3180
3898
|
if (!type) {
|
|
3181
|
-
chat.setError("Only pictures (PNG, JPEG, WebP, GIF) can be sent.");
|
|
3899
|
+
chat.setError(t("Only pictures (PNG, JPEG, WebP, GIF) can be sent."));
|
|
3182
3900
|
continue;
|
|
3183
3901
|
}
|
|
3184
3902
|
if (a.fileSize && a.fileSize > CHAT_FILE_MAX_BYTES) {
|
|
3185
|
-
chat.setError(
|
|
3903
|
+
chat.setError(tf("{name} is larger than 10 MB.", { name }));
|
|
3186
3904
|
continue;
|
|
3187
3905
|
}
|
|
3188
3906
|
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() };
|
|
@@ -3201,29 +3919,29 @@ var MessengerSheet = ({
|
|
|
3201
3919
|
setPhotos((list) => [...list, ...added]);
|
|
3202
3920
|
};
|
|
3203
3921
|
const canSend = (!!body.trim() || photos.length > 0) && !pending && emailOk;
|
|
3204
|
-
const
|
|
3205
|
-
const text2 = body.trim();
|
|
3922
|
+
const deliver = async (text2, files, to) => {
|
|
3206
3923
|
const api = client.api();
|
|
3207
|
-
|
|
3208
|
-
if (!text2 && !files.length || pending || !emailOk || !api) return;
|
|
3924
|
+
if (!api) return;
|
|
3209
3925
|
setPending({
|
|
3210
3926
|
id: "pending",
|
|
3211
3927
|
from: "visitor",
|
|
3212
3928
|
body: text2,
|
|
3213
3929
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3930
|
+
to,
|
|
3214
3931
|
...files.length ? { attachments: files.map((f) => ({ id: f.key, name: f.name, type: f.type, size: f.size })) } : {}
|
|
3215
3932
|
});
|
|
3216
|
-
setBody("");
|
|
3217
|
-
setPhotos([]);
|
|
3218
3933
|
chat.setError("");
|
|
3219
3934
|
try {
|
|
3220
3935
|
await Promise.all(files.map((f) => f.up));
|
|
3221
3936
|
const ids = files.filter((f) => f.id).map((f) => f.id);
|
|
3222
|
-
if (!text2 && !ids.length) throw new Error("The photo could not be sent.");
|
|
3937
|
+
if (!text2 && !ids.length) throw new Error(t("The photo could not be sent."));
|
|
3223
3938
|
const win = RN3.Dimensions.get("screen");
|
|
3224
|
-
|
|
3939
|
+
const conversation = to.fresh ? "new" : to.chatId ?? thread?.conversation?.id;
|
|
3940
|
+
chat.arrived(
|
|
3941
|
+
to,
|
|
3225
3942
|
await api.sendChat({
|
|
3226
3943
|
body: text2,
|
|
3944
|
+
...conversation ? { conversation } : {},
|
|
3227
3945
|
email: needEmail && email.trim() ? email.trim() : void 0,
|
|
3228
3946
|
...ids.length ? { attachments: ids } : {},
|
|
3229
3947
|
// A little context for a bug report: the system and the screen size.
|
|
@@ -3231,14 +3949,45 @@ var MessengerSheet = ({
|
|
|
3231
3949
|
})
|
|
3232
3950
|
);
|
|
3233
3951
|
} catch (e) {
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3952
|
+
const gone = e.status === 404;
|
|
3953
|
+
chat.enter(gone ? null : to.chatId, gone || to.fresh);
|
|
3954
|
+
setBody((typed) => [text2, typed.trim()].filter(Boolean).join("\n"));
|
|
3955
|
+
setPhotos((list) => [...files.filter((f) => f.id), ...list]);
|
|
3956
|
+
chat.setError(gone ? t("This conversation is no longer available. Your message is still here to send as a new one.") : e.message);
|
|
3237
3957
|
} finally {
|
|
3238
3958
|
setPending(null);
|
|
3239
3959
|
}
|
|
3240
3960
|
};
|
|
3241
|
-
const
|
|
3961
|
+
const send = () => {
|
|
3962
|
+
const text2 = body.trim();
|
|
3963
|
+
const files = photos;
|
|
3964
|
+
if (!text2 && !files.length || pending || !emailOk || !client.api()) return;
|
|
3965
|
+
setBody("");
|
|
3966
|
+
setPhotos([]);
|
|
3967
|
+
void deliver(text2, files, place);
|
|
3968
|
+
};
|
|
3969
|
+
const openMessages = () => {
|
|
3970
|
+
chat.enter(null);
|
|
3971
|
+
setView("messages");
|
|
3972
|
+
};
|
|
3973
|
+
const openChat = (id) => {
|
|
3974
|
+
chat.enter(id);
|
|
3975
|
+
setView("messages");
|
|
3976
|
+
};
|
|
3977
|
+
const startChat = () => {
|
|
3978
|
+
chat.enter(null, true);
|
|
3979
|
+
setView("messages");
|
|
3980
|
+
};
|
|
3981
|
+
const ask = () => {
|
|
3982
|
+
const text2 = asked.trim();
|
|
3983
|
+
if (!text2) return openMessages();
|
|
3984
|
+
if (pending || !client.api()) return;
|
|
3985
|
+
setAsked("");
|
|
3986
|
+
startChat();
|
|
3987
|
+
if (emailOk) void deliver(text2, [], { chatId: null, fresh: true });
|
|
3988
|
+
else setBody((typed) => [text2, typed.trim()].filter(Boolean).join("\n"));
|
|
3989
|
+
};
|
|
3990
|
+
const localUris = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
|
3242
3991
|
for (const f of photos) localUris.current.set(f.key, f.uri);
|
|
3243
3992
|
const fileUrl = (id) => localUris.current.get(id) ?? client.api()?.attachmentUrl(id) ?? "";
|
|
3244
3993
|
const ink = inkOn(p.accent);
|
|
@@ -3253,11 +4002,11 @@ var MessengerSheet = ({
|
|
|
3253
4002
|
const title = o.title?.trim() ?? "";
|
|
3254
4003
|
const bubble = (m, isPending = false) => {
|
|
3255
4004
|
const mine = m.from === "visitor";
|
|
3256
|
-
return (0,
|
|
4005
|
+
return (0, import_react3.createElement)(
|
|
3257
4006
|
RN3.View,
|
|
3258
4007
|
{ key: m.id, style: { alignSelf: mine ? "flex-end" : "flex-start", maxWidth: "82%", gap: 4 } },
|
|
3259
4008
|
...(m.attachments ?? []).map(
|
|
3260
|
-
(f) => (0,
|
|
4009
|
+
(f) => (0, import_react3.createElement)(
|
|
3261
4010
|
RN3.Pressable,
|
|
3262
4011
|
{
|
|
3263
4012
|
key: f.id,
|
|
@@ -3266,10 +4015,10 @@ var MessengerSheet = ({
|
|
|
3266
4015
|
accessibilityLabel: f.name,
|
|
3267
4016
|
style: { alignSelf: mine ? "flex-end" : "flex-start", opacity: isPending ? 0.7 : 1 }
|
|
3268
4017
|
},
|
|
3269
|
-
f.type.startsWith("image/") ? (0,
|
|
4018
|
+
f.type.startsWith("image/") ? (0, import_react3.createElement)(RN3.Image, { source: { uri: fileUrl(f.id) }, resizeMode: "cover", style: { width: 200, height: 150, borderRadius: r.bubble, backgroundColor: p.soft } }) : (0, import_react3.createElement)(RN3.View, { style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } }, k.text(`\u{1F4C4} ${f.name}`, { fontSize: 13 }))
|
|
3270
4019
|
)
|
|
3271
4020
|
),
|
|
3272
|
-
!m.body && m.attachments?.length ? null : (0,
|
|
4021
|
+
!m.body && m.attachments?.length ? null : (0, import_react3.createElement)(
|
|
3273
4022
|
RN3.View,
|
|
3274
4023
|
{
|
|
3275
4024
|
style: {
|
|
@@ -3286,120 +4035,149 @@ var MessengerSheet = ({
|
|
|
3286
4035
|
k.text(m.body, { color: mine ? ink : hu.fg })
|
|
3287
4036
|
),
|
|
3288
4037
|
...(m.articles ?? []).map(
|
|
3289
|
-
(a) => (0,
|
|
4038
|
+
(a) => (0, import_react3.createElement)(
|
|
3290
4039
|
RN3.Pressable,
|
|
3291
4040
|
{ key: a.id, onPress: () => setView({ article: a.id }), accessibilityRole: "link", style: { backgroundColor: p.soft, borderRadius: r.card, paddingHorizontal: 10, paddingVertical: 7 } },
|
|
3292
4041
|
k.text(`\u{1F4C4} ${a.title}`, { fontSize: 13 })
|
|
3293
4042
|
)
|
|
3294
4043
|
),
|
|
3295
|
-
m.at ? k.muted(`${mine ? "" : `${who(m.from, o.agentName)} \xB7 `}${isPending ? "Sending\u2026" : timeAgo(m.at)}`, { fontSize: 11, textAlign: mine ? "right" : "left", paddingHorizontal: 6 }) : null
|
|
4044
|
+
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
|
|
3296
4045
|
);
|
|
3297
4046
|
};
|
|
3298
|
-
const row = (a) => (0,
|
|
4047
|
+
const row = (a) => (0, import_react3.createElement)(
|
|
3299
4048
|
RN3.Pressable,
|
|
3300
4049
|
{ key: a.id, onPress: () => setView({ article: a.id }), accessibilityRole: "button", style: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 11, paddingHorizontal: 8 } },
|
|
3301
4050
|
k.text(a.title, { flex: 1 }),
|
|
3302
4051
|
glyph("next", p.accent, 14)
|
|
3303
4052
|
);
|
|
3304
|
-
const card = (children, style = {}) => (0,
|
|
3305
|
-
const searchRow = (onPress) => (0,
|
|
4053
|
+
const card = (children, style = {}) => (0, import_react3.createElement)(RN3.View, { style: { backgroundColor: raised, borderRadius: r.card, padding: 16, ...surf, ...style } }, ...children);
|
|
4054
|
+
const searchRow = (onPress) => (0, import_react3.createElement)(
|
|
3306
4055
|
RN3.Pressable,
|
|
3307
4056
|
{ key: "s", onPress, accessibilityRole: "search", style: { flexDirection: "row", alignItems: "center", height: 44, borderRadius: r.field, paddingHorizontal: 14, backgroundColor: hu.def } },
|
|
3308
|
-
k.muted("Search for help", { flex: 1, fontSize: 15 }),
|
|
4057
|
+
k.muted(t("Search for help"), { flex: 1, fontSize: 15 }),
|
|
3309
4058
|
glyph("search", p.accent, 16)
|
|
3310
4059
|
);
|
|
3311
|
-
const search = k.field({ value: query, onChange: setQuery, placeholder: "Search for help", max: 200, style: { borderWidth: 0, borderRadius: r.field, backgroundColor: raised, ...surf } });
|
|
4060
|
+
const search = k.field({ value: query, onChange: setQuery, placeholder: t("Search for help"), max: 200, style: { borderWidth: 0, borderRadius: r.field, backgroundColor: raised, ...surf } });
|
|
3312
4061
|
const first = client.getUser()?.name?.trim().split(/\s+/)[0];
|
|
3313
|
-
const rest = greeting.replace(/^(hi|hello|hey|hallo)( there)?\s*[
|
|
3314
|
-
const hero = (fg, sub) => (0,
|
|
4062
|
+
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?");
|
|
4063
|
+
const hero = (fg, sub) => (0, import_react3.createElement)(
|
|
3315
4064
|
RN3.Text,
|
|
3316
4065
|
{ key: "hero", accessibilityRole: "header", style: { color: fg, fontSize: 25, fontWeight: "600", letterSpacing: -0.6, lineHeight: 30 } },
|
|
3317
|
-
first ?
|
|
3318
|
-
|
|
3319
|
-
(0,
|
|
4066
|
+
`${first ? tf("Hi, {name}", { name: first }) : t("Hi there")} \u{1F44B}
|
|
4067
|
+
`,
|
|
4068
|
+
(0, import_react3.createElement)(RN3.Text, { style: { color: sub, fontWeight: "500" } }, rest)
|
|
3320
4069
|
);
|
|
3321
|
-
const
|
|
4070
|
+
const chatRow = (c) => {
|
|
4071
|
+
const m = c.last;
|
|
4072
|
+
const n = chat.unseen.filter((u) => u.chat === c.id).length;
|
|
4073
|
+
const closed = c.status === "solved" ? ` \xB7 ${t("Closed")}` : "";
|
|
4074
|
+
const subject = c.subject || (m ? lineOf(m) : "");
|
|
4075
|
+
const line = m ? lineOf(m) : "";
|
|
4076
|
+
return (0, import_react3.createElement)(
|
|
4077
|
+
RN3.Pressable,
|
|
4078
|
+
{ key: c.id, onPress: () => openChat(c.id), accessibilityRole: "button", accessibilityLabel: `${tf("Conversation: {subject}", { subject })}${closed}${n ? `, ${tf("{n} new", { n })}` : ""}` },
|
|
4079
|
+
card(
|
|
4080
|
+
[
|
|
4081
|
+
(0, import_react3.createElement)(
|
|
4082
|
+
RN3.View,
|
|
4083
|
+
{ key: "r", style: { flexDirection: "row", alignItems: "center", gap: 10 } },
|
|
4084
|
+
title ? teamMark(title, alpha(p.accent, 0.16), p.accent, 26) : null,
|
|
4085
|
+
(0, import_react3.createElement)(
|
|
4086
|
+
RN3.View,
|
|
4087
|
+
{ style: { flex: 1, gap: 1 } },
|
|
4088
|
+
(0, import_react3.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.fg, fontSize: 14, fontWeight: "500" } }, subject),
|
|
4089
|
+
m && line !== subject ? (0, import_react3.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.muted, fontSize: 13 } }, line) : null,
|
|
4090
|
+
m ? k.muted(`${who(m.from, o.agentName)} \xB7 ${timeAgo(m.at)}${closed}`, { fontSize: 11.5 }) : null
|
|
4091
|
+
),
|
|
4092
|
+
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)
|
|
4093
|
+
)
|
|
4094
|
+
],
|
|
4095
|
+
{ paddingVertical: 10, paddingHorizontal: 12 }
|
|
4096
|
+
)
|
|
4097
|
+
);
|
|
4098
|
+
};
|
|
3322
4099
|
const showHelp = helpOn && arts.any !== false;
|
|
3323
4100
|
let content;
|
|
3324
4101
|
if (view === "home") {
|
|
4102
|
+
const running = chat.chats.filter((c) => c.status !== "solved");
|
|
4103
|
+
const rows = (running.length ? running : chat.chats).slice(0, 2);
|
|
3325
4104
|
const cards = [
|
|
3326
|
-
// "Ask a question":
|
|
3327
|
-
(0,
|
|
4105
|
+
// "Ask a question": its own field; the words start a new conversation.
|
|
4106
|
+
(0, import_react3.createElement)(
|
|
3328
4107
|
RN3.View,
|
|
3329
4108
|
{ key: "ask", style: { flexDirection: "row", alignItems: "center", gap: 8, height: 48, borderRadius: 24, paddingLeft: 18, paddingRight: 6, backgroundColor: hu.surface, ...surf } },
|
|
3330
|
-
k.field({ value:
|
|
3331
|
-
(0,
|
|
4109
|
+
k.field({ value: asked, onChange: setAsked, placeholder: t("Ask a question"), max: 4e3, onSubmit: ask, style: { flex: 1, borderWidth: 0, paddingHorizontal: 0, backgroundColor: "transparent" } }),
|
|
4110
|
+
(0, import_react3.createElement)(
|
|
3332
4111
|
RN3.Pressable,
|
|
3333
4112
|
{
|
|
3334
|
-
onPress:
|
|
3335
|
-
setView("messages");
|
|
3336
|
-
if (body.trim()) void send();
|
|
3337
|
-
},
|
|
4113
|
+
onPress: ask,
|
|
3338
4114
|
accessibilityRole: "button",
|
|
3339
|
-
accessibilityLabel: "Send",
|
|
3340
|
-
style: { width: 36, height: 36, borderRadius: 18, alignItems: "center", justifyContent: "center", backgroundColor:
|
|
4115
|
+
accessibilityLabel: t("Send"),
|
|
4116
|
+
style: { width: 36, height: 36, borderRadius: 18, alignItems: "center", justifyContent: "center", backgroundColor: asked.trim() ? p.accent : hu.def }
|
|
3341
4117
|
},
|
|
3342
|
-
glyph("up",
|
|
4118
|
+
glyph("up", asked.trim() ? ink : hu.muted, 18)
|
|
3343
4119
|
)
|
|
3344
4120
|
),
|
|
3345
|
-
|
|
4121
|
+
...rows.map(chatRow),
|
|
4122
|
+
chat.chats.length > rows.length ? (0, import_react3.createElement)(
|
|
3346
4123
|
RN3.Pressable,
|
|
3347
|
-
{ key: "
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
RN3.View,
|
|
3351
|
-
{ key: "r", style: { flexDirection: "row", alignItems: "center", gap: 10 } },
|
|
3352
|
-
title ? teamMark(title, alpha(p.accent, 0.16), p.accent, 26) : null,
|
|
3353
|
-
(0, import_react2.createElement)(RN3.View, { style: { flex: 1, gap: 1 } }, (0, import_react2.createElement)(RN3.Text, { numberOfLines: 1, style: { color: hu.fg, fontSize: 14, fontWeight: "500" } }, last.body || (last.attachments?.length ? `Sent ${last.attachments.length === 1 ? last.attachments[0].name : `${last.attachments.length} files`}` : "")), k.muted(`${last.from === "visitor" ? "You" : who(last.from, o.agentName)} \xB7 ${timeAgo(last.at)}`, { fontSize: 11.5 })),
|
|
3354
|
-
chat.unseen.length ? (0, import_react2.createElement)(RN3.View, { style: { width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : glyph("next", p.muted, 14)
|
|
3355
|
-
)
|
|
3356
|
-
], { paddingVertical: 10, paddingHorizontal: 12 })
|
|
4124
|
+
{ 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 } },
|
|
4125
|
+
k.muted(tf("All conversations ({n})", { n: chat.chats.length }), { fontSize: 13, fontWeight: "500" }),
|
|
4126
|
+
glyph("next", p.muted, 12)
|
|
3357
4127
|
) : null,
|
|
3358
4128
|
showHelp ? card([searchRow(() => setView("help")), ...(arts.list ?? []).slice(0, 4).map(row)], { padding: 8, gap: 2 }) : null
|
|
3359
4129
|
];
|
|
3360
|
-
content = plain ? (0,
|
|
4130
|
+
content = plain ? (0, import_react3.createElement)(
|
|
3361
4131
|
RN3.ScrollView,
|
|
3362
4132
|
{ key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 24, gap: 12 }, keyboardShouldPersistTaps: "handled" },
|
|
3363
|
-
(0,
|
|
4133
|
+
(0, import_react3.createElement)(RN3.View, { key: "top", style: { paddingHorizontal: 6, paddingBottom: 10 } }, hero(p.fg, p.muted)),
|
|
3364
4134
|
...cards
|
|
3365
|
-
) : (0,
|
|
4135
|
+
) : (0, import_react3.createElement)(
|
|
3366
4136
|
RN3.ScrollView,
|
|
3367
4137
|
{ key: "home", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingBottom: 24 }, keyboardShouldPersistTaps: "handled" },
|
|
3368
|
-
(0,
|
|
3369
|
-
(0,
|
|
4138
|
+
(0, import_react3.createElement)(RN3.View, { style: { backgroundColor: p.accent, paddingHorizontal: 22, paddingTop: 4, paddingBottom: 64 } }, hero(ink, alpha(ink, 0.8))),
|
|
4139
|
+
(0, import_react3.createElement)(RN3.View, { style: { marginTop: -48, paddingHorizontal: 16, gap: 12 } }, ...cards)
|
|
4140
|
+
);
|
|
4141
|
+
} else if (view === "messages" && chat.listing)
|
|
4142
|
+
content = (0, import_react3.createElement)(
|
|
4143
|
+
RN3.ScrollView,
|
|
4144
|
+
{ key: "chats", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 } },
|
|
4145
|
+
...chat.chats.map(chatRow),
|
|
4146
|
+
k.btn(t("New conversation"), "primary", startChat)
|
|
3370
4147
|
);
|
|
3371
|
-
|
|
3372
|
-
content = (0,
|
|
4148
|
+
else if (view === "messages")
|
|
4149
|
+
content = (0, import_react3.createElement)(
|
|
3373
4150
|
RN3.View,
|
|
3374
4151
|
{ key: "messages", style: { flex: 1, backgroundColor: hu.bg } },
|
|
3375
|
-
(0,
|
|
4152
|
+
(0, import_react3.createElement)(
|
|
3376
4153
|
RN3.ScrollView,
|
|
3377
4154
|
{ ref: scroll, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 } },
|
|
3378
|
-
|
|
3379
|
-
!
|
|
4155
|
+
// The greeting opens an empty chat only, never above the visitor's first words (2026-10-30).
|
|
4156
|
+
!thread?.messages.length && !pendingHere && !place.chatId ? bubble({ id: "greeting", from: "team", body: greeting, at: "" }) : null,
|
|
4157
|
+
!chat.loaded && !place.fresh && !pendingHere || place.chatId && !thread ? k.muted(t("Loading\u2026")) : null,
|
|
3380
4158
|
...(thread?.messages ?? []).map((m) => bubble(m)),
|
|
3381
|
-
|
|
3382
|
-
thread?.conversation?.status === "solved" && !
|
|
4159
|
+
pendingHere ? bubble(pendingHere, true) : null,
|
|
4160
|
+
thread?.conversation?.status === "solved" && !pendingHere ? k.muted(t("This conversation is closed. Write to start a new one."), { textAlign: "center" }) : null
|
|
3383
4161
|
),
|
|
3384
|
-
(0,
|
|
4162
|
+
(0, import_react3.createElement)(
|
|
3385
4163
|
RN3.View,
|
|
3386
4164
|
{ style: { paddingHorizontal: 12, paddingTop: 8, paddingBottom: 6, gap: 8 } },
|
|
3387
4165
|
chat.error ? k.text(chat.error, { color: p.danger, fontSize: 12 }) : null,
|
|
3388
4166
|
// Photos picked for the next message, each with a way to take it out again.
|
|
3389
|
-
photos.length ? (0,
|
|
4167
|
+
photos.length ? (0, import_react3.createElement)(
|
|
3390
4168
|
RN3.View,
|
|
3391
4169
|
{ style: { flexDirection: "row", flexWrap: "wrap", gap: 10, paddingTop: 6, paddingHorizontal: 2 } },
|
|
3392
4170
|
...photos.map(
|
|
3393
|
-
(f) => (0,
|
|
4171
|
+
(f) => (0, import_react3.createElement)(
|
|
3394
4172
|
RN3.View,
|
|
3395
4173
|
{ key: f.key, style: { width: 56, height: 56 } },
|
|
3396
|
-
(0,
|
|
3397
|
-
(0,
|
|
4174
|
+
(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 } }),
|
|
4175
|
+
(0, import_react3.createElement)(
|
|
3398
4176
|
RN3.Pressable,
|
|
3399
4177
|
{
|
|
3400
4178
|
onPress: () => setPhotos((list) => list.filter((x) => x.key !== f.key)),
|
|
3401
4179
|
accessibilityRole: "button",
|
|
3402
|
-
accessibilityLabel:
|
|
4180
|
+
accessibilityLabel: tf("Remove {name}", { name: f.name }),
|
|
3403
4181
|
hitSlop: 8,
|
|
3404
4182
|
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" }
|
|
3405
4183
|
},
|
|
@@ -3409,35 +4187,35 @@ var MessengerSheet = ({
|
|
|
3409
4187
|
)
|
|
3410
4188
|
) : null,
|
|
3411
4189
|
// The message field on top, the paperclip (when the app has expo-image-picker) and the send button under it.
|
|
3412
|
-
(0,
|
|
4190
|
+
(0, import_react3.createElement)(
|
|
3413
4191
|
RN3.View,
|
|
3414
4192
|
{ style: { gap: 2, borderRadius: 20, paddingTop: 10, paddingBottom: 6, paddingRight: 6, paddingLeft: 16, backgroundColor: hu.surface, ...surf } },
|
|
3415
4193
|
// The optional email: the box's first line, under a hairline the message follows.
|
|
3416
4194
|
needEmail ? k.field({
|
|
3417
4195
|
value: email,
|
|
3418
4196
|
onChange: setEmail,
|
|
3419
|
-
placeholder: "Email for our reply (optional)",
|
|
4197
|
+
placeholder: t("Email for our reply (optional)"),
|
|
3420
4198
|
email: true,
|
|
3421
4199
|
max: 200,
|
|
3422
4200
|
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 }
|
|
3423
4201
|
}) : null,
|
|
3424
|
-
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" } }),
|
|
3425
|
-
(0,
|
|
4202
|
+
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" } }),
|
|
4203
|
+
(0, import_react3.createElement)(
|
|
3426
4204
|
RN3.View,
|
|
3427
4205
|
{ style: { flexDirection: "row", alignItems: "center", marginLeft: -8 } },
|
|
3428
|
-
imagePicker?.launchImageLibraryAsync ? (0,
|
|
4206
|
+
imagePicker?.launchImageLibraryAsync ? (0, import_react3.createElement)(
|
|
3429
4207
|
RN3.Pressable,
|
|
3430
|
-
{ 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" } },
|
|
4208
|
+
{ 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" } },
|
|
3431
4209
|
glyph("clip", hu.muted, 19)
|
|
3432
4210
|
) : null,
|
|
3433
|
-
(0,
|
|
3434
|
-
(0,
|
|
4211
|
+
(0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
|
|
4212
|
+
(0, import_react3.createElement)(
|
|
3435
4213
|
RN3.Pressable,
|
|
3436
4214
|
{
|
|
3437
|
-
onPress:
|
|
4215
|
+
onPress: send,
|
|
3438
4216
|
disabled: !canSend,
|
|
3439
4217
|
accessibilityRole: "button",
|
|
3440
|
-
accessibilityLabel: "Send",
|
|
4218
|
+
accessibilityLabel: t("Send"),
|
|
3441
4219
|
style: { width: 34, height: 34, borderRadius: 17, alignItems: "center", justifyContent: "center", backgroundColor: canSend ? p.accent : hu.def }
|
|
3442
4220
|
},
|
|
3443
4221
|
glyph("up", canSend ? ink : hu.muted, 18)
|
|
@@ -3449,28 +4227,28 @@ var MessengerSheet = ({
|
|
|
3449
4227
|
else if (view === "help") {
|
|
3450
4228
|
const groups = /* @__PURE__ */ new Map();
|
|
3451
4229
|
for (const a of arts.list ?? []) groups.set(a.collection, [...groups.get(a.collection) ?? [], a]);
|
|
3452
|
-
content = (0,
|
|
4230
|
+
content = (0, import_react3.createElement)(
|
|
3453
4231
|
RN3.ScrollView,
|
|
3454
4232
|
{ key: "help", style: { flex: 1, backgroundColor: canvas }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 16, gap: 12 }, keyboardShouldPersistTaps: "handled" },
|
|
3455
4233
|
search,
|
|
3456
|
-
arts.list === null ? k.muted("Loading\u2026") : null,
|
|
3457
|
-
arts.list?.length === 0 ? (0,
|
|
3458
|
-
...query.trim() ? arts.list?.length ? [card((arts.list ?? []).map(row), { padding: 8 })] : [] : [...groups].map(([name, items]) => (0,
|
|
4234
|
+
arts.list === null ? k.muted(t("Loading\u2026")) : null,
|
|
4235
|
+
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,
|
|
4236
|
+
...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 })))
|
|
3459
4237
|
);
|
|
3460
4238
|
} else
|
|
3461
|
-
content = (0,
|
|
4239
|
+
content = (0, import_react3.createElement)(
|
|
3462
4240
|
RN3.ScrollView,
|
|
3463
4241
|
{ key: `a-${view.article}`, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 32 } },
|
|
3464
|
-
(0,
|
|
4242
|
+
(0, import_react3.createElement)(ArticleView, { client, p, id: view.article, onAsk: startChat })
|
|
3465
4243
|
);
|
|
3466
4244
|
const current = typeof view === "string" ? view : "help";
|
|
3467
4245
|
const tab = (v, name, icon, badge = 0) => {
|
|
3468
4246
|
const on = current === v;
|
|
3469
|
-
return (0,
|
|
4247
|
+
return (0, import_react3.createElement)(
|
|
3470
4248
|
RN3.Pressable,
|
|
3471
4249
|
{
|
|
3472
4250
|
key: v,
|
|
3473
|
-
onPress: () => setView(v),
|
|
4251
|
+
onPress: () => v === "messages" ? openMessages() : setView(v),
|
|
3474
4252
|
accessibilityRole: "tab",
|
|
3475
4253
|
accessibilityLabel: name,
|
|
3476
4254
|
accessibilityState: { selected: on },
|
|
@@ -3479,68 +4257,69 @@ var MessengerSheet = ({
|
|
|
3479
4257
|
},
|
|
3480
4258
|
glyph(icon, on ? hu.fg : hu.muted, 17),
|
|
3481
4259
|
on ? k.text(name, { fontSize: 13.5, fontWeight: "500", color: hu.fg }) : null,
|
|
3482
|
-
badge ? (0,
|
|
4260
|
+
badge ? (0, import_react3.createElement)(RN3.View, { style: { position: "absolute", top: 7, right: 12, width: 8, height: 8, borderRadius: 4, backgroundColor: p.danger } }) : null
|
|
3483
4261
|
);
|
|
3484
4262
|
};
|
|
3485
4263
|
const onAccent = view === "home" && !plain;
|
|
3486
4264
|
const ground = onAccent ? p.accent : hu.bg;
|
|
3487
|
-
const back = (to, label) => (0,
|
|
4265
|
+
const back = (to, label) => (0, import_react3.createElement)(
|
|
3488
4266
|
RN3.Pressable,
|
|
3489
|
-
{ key: "back", onPress:
|
|
4267
|
+
{ key: "back", onPress: to, accessibilityRole: "button", accessibilityLabel: label, hitSlop: 10, style: { width: 32, height: 32, alignItems: "center", justifyContent: "center", transform: [{ rotate: "180deg" }] } },
|
|
3490
4268
|
glyph("next", hu.fg, 18)
|
|
3491
4269
|
);
|
|
3492
|
-
const head = (0,
|
|
4270
|
+
const head = (0, import_react3.createElement)(
|
|
3493
4271
|
RN3.View,
|
|
3494
4272
|
{ key: "head", style: { flexDirection: "row", alignItems: "center", gap: 8, paddingLeft: 12, paddingRight: 12, paddingTop: 12, paddingBottom: 6, backgroundColor: ground } },
|
|
3495
|
-
typeof view !== "string" ? back("help", "All articles") : view === "messages" ? back("home", "Back to home") : view === "home" && (o.logo || title) ? o.logo ? (0,
|
|
3496
|
-
// In the chat: who answers, in the middle.
|
|
3497
|
-
view === "messages" && title ? (0,
|
|
4273
|
+
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,
|
|
4274
|
+
// In the chat: who answers, in the middle; over the list its name.
|
|
4275
|
+
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)(
|
|
3498
4276
|
RN3.View,
|
|
3499
4277
|
{ style: { flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8 } },
|
|
3500
4278
|
teamMark(title, alpha(p.accent, 0.16), p.accent, 28),
|
|
3501
|
-
(0,
|
|
3502
|
-
) : (0,
|
|
3503
|
-
(0,
|
|
4279
|
+
(0, import_react3.createElement)(RN3.Text, { numberOfLines: 1, style: { flexShrink: 1, color: hu.fg, fontSize: 15, fontWeight: "600" } }, title)
|
|
4280
|
+
) : (0, import_react3.createElement)(RN3.View, { style: { flex: 1 } }),
|
|
4281
|
+
(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))
|
|
3504
4282
|
);
|
|
3505
|
-
const powered = brand ? (0,
|
|
4283
|
+
const powered = brand ? (0, import_react3.createElement)(
|
|
3506
4284
|
RN3.View,
|
|
3507
4285
|
{ key: "brand", style: { paddingTop: 2, paddingBottom: 6, alignItems: "center", backgroundColor: hu.bg } },
|
|
3508
|
-
(0,
|
|
4286
|
+
(0, import_react3.createElement)(
|
|
3509
4287
|
RN3.Text,
|
|
3510
4288
|
{ onPress: () => void RN3.Linking.openURL(BRAND_URL).catch(() => void 0), accessibilityRole: "link", hitSlop: 8, style: { color: p.muted, fontSize: 11.5 } },
|
|
3511
|
-
"Powered by ",
|
|
3512
|
-
(0,
|
|
4289
|
+
t("Powered by {brand}").split("{brand}")[0],
|
|
4290
|
+
(0, import_react3.createElement)(RN3.Text, { style: { fontWeight: "600" } }, "MRRocket"),
|
|
4291
|
+
t("Powered by {brand}").split("{brand}")[1]
|
|
3513
4292
|
)
|
|
3514
4293
|
) : null;
|
|
3515
|
-
return (0,
|
|
4294
|
+
return (0, import_react3.createElement)(
|
|
3516
4295
|
RN3.Modal,
|
|
3517
4296
|
{ visible: true, animationType: "slide", presentationStyle: "pageSheet", onRequestClose: close },
|
|
3518
|
-
(0,
|
|
4297
|
+
(0, import_react3.createElement)(
|
|
3519
4298
|
RN3.KeyboardAvoidingView,
|
|
3520
4299
|
{ behavior: RN3.Platform.OS === "ios" ? "padding" : void 0, style: { flex: 1, backgroundColor: hu.bg } },
|
|
3521
4300
|
head,
|
|
3522
4301
|
content,
|
|
3523
|
-
view === "messages" ? null : (0,
|
|
4302
|
+
view === "messages" && !chat.listing ? null : (0, import_react3.createElement)(
|
|
3524
4303
|
RN3.View,
|
|
3525
4304
|
{ key: "tabs", accessibilityRole: "tablist", style: { flexDirection: "row", marginHorizontal: 12, marginTop: 6, marginBottom: 6, padding: 4, borderRadius: 22, backgroundColor: hu.surface } },
|
|
3526
|
-
tab("home", "Home", "home"),
|
|
3527
|
-
tab("messages", "Messages", "chat", chat.unseen.length),
|
|
3528
|
-
showHelp ? tab("help", "Help", "help") : null
|
|
4305
|
+
tab("home", t("Home"), "home"),
|
|
4306
|
+
tab("messages", t("Messages"), "chat", chat.unseen.length),
|
|
4307
|
+
showHelp ? tab("help", t("Help"), "help") : null
|
|
3529
4308
|
),
|
|
3530
4309
|
powered,
|
|
3531
|
-
(0,
|
|
4310
|
+
(0, import_react3.createElement)(RN3.View, { style: { height: 16 } })
|
|
3532
4311
|
)
|
|
3533
4312
|
);
|
|
3534
4313
|
};
|
|
3535
4314
|
var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
3536
|
-
const [look, setLook] = (0,
|
|
3537
|
-
(0,
|
|
4315
|
+
const [look, setLook] = (0, import_react3.useState)(null);
|
|
4316
|
+
(0, import_react3.useEffect)(() => {
|
|
3538
4317
|
const ctrl = new AbortController();
|
|
3539
4318
|
void client.ready.then(
|
|
3540
4319
|
() => client.api()?.messengerLook(ctrl.signal).then(setLook).catch(() => void 0)
|
|
3541
4320
|
);
|
|
3542
4321
|
return () => ctrl.abort();
|
|
3543
|
-
}, [client]);
|
|
4322
|
+
}, [client, wordsVersion()]);
|
|
3544
4323
|
const defined = Object.fromEntries(Object.entries(given).filter(([, v]) => v !== void 0));
|
|
3545
4324
|
const options = look ? {
|
|
3546
4325
|
title: look.title,
|
|
@@ -3562,27 +4341,31 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
3562
4341
|
const p = usePalette({ theme: appearance?.theme ?? look?.theme, accent: appearance?.accent ?? look?.accent });
|
|
3563
4342
|
const k = kit(p);
|
|
3564
4343
|
const open = messengerOpen.useOpen();
|
|
3565
|
-
const [view, setViewState] = (0,
|
|
3566
|
-
const [banner, setBanner] = (0,
|
|
3567
|
-
(0, import_react2.useEffect)(() => {
|
|
3568
|
-
if (open) setViewState(msgView);
|
|
3569
|
-
}, [open]);
|
|
4344
|
+
const [view, setViewState] = (0, import_react3.useState)(msgView);
|
|
4345
|
+
const [banner, setBanner] = (0, import_react3.useState)(null);
|
|
3570
4346
|
const setView = (v) => {
|
|
3571
4347
|
msgView = v;
|
|
3572
4348
|
setViewState(v);
|
|
3573
4349
|
};
|
|
3574
4350
|
const chat = useChat(client, options, open, view);
|
|
4351
|
+
const { enter } = chat;
|
|
4352
|
+
(0, import_react3.useEffect)(() => {
|
|
4353
|
+
if (!open) return;
|
|
4354
|
+
setViewState(msgView);
|
|
4355
|
+
if (msgView === "messages") enter(msgChat);
|
|
4356
|
+
}, [open, enter]);
|
|
3575
4357
|
const latest = chat.unseen.at(-1);
|
|
3576
|
-
(0,
|
|
4358
|
+
(0, import_react3.useEffect)(() => {
|
|
3577
4359
|
if (!latest || open || options.preview === false) return;
|
|
3578
4360
|
setBanner(latest);
|
|
3579
|
-
const
|
|
3580
|
-
return () => clearTimeout(
|
|
4361
|
+
const t2 = setTimeout(() => setBanner(null), 6e3);
|
|
4362
|
+
return () => clearTimeout(t2);
|
|
3581
4363
|
}, [latest, open, options.preview]);
|
|
3582
|
-
(0,
|
|
4364
|
+
(0, import_react3.useEffect)(() => {
|
|
3583
4365
|
if (open) setBanner(null);
|
|
3584
4366
|
}, [open]);
|
|
3585
4367
|
const unseen = chat.unseen.length;
|
|
4368
|
+
const news = latest?.chat ?? null;
|
|
3586
4369
|
const ink = inkOn(p.accent);
|
|
3587
4370
|
const label = options.label?.trim() ?? "";
|
|
3588
4371
|
const r = CORNERS[options.corners ?? "round"];
|
|
@@ -3591,15 +4374,15 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
3591
4374
|
...edge === "top" ? { top: 60 } : edge === "bottom" ? { bottom: 34 } : { top: 0, bottom: 0, justifyContent: "center" },
|
|
3592
4375
|
...side === "left" ? { left: 20 } : side === "right" ? { right: 20 } : { left: 0, right: 0, alignItems: "center" }
|
|
3593
4376
|
};
|
|
3594
|
-
const launcher = options.launcher ? (0,
|
|
4377
|
+
const launcher = options.launcher ? (0, import_react3.createElement)(
|
|
3595
4378
|
RN3.View,
|
|
3596
4379
|
{ key: "launcher", pointerEvents: "box-none", style: { position: "absolute", ...place } },
|
|
3597
|
-
(0,
|
|
4380
|
+
(0, import_react3.createElement)(
|
|
3598
4381
|
RN3.Pressable,
|
|
3599
4382
|
{
|
|
3600
|
-
onPress: () => openAt(
|
|
4383
|
+
onPress: () => openAt(news ? "messages" : "home", news),
|
|
3601
4384
|
accessibilityRole: "button",
|
|
3602
|
-
accessibilityLabel: unseen ?
|
|
4385
|
+
accessibilityLabel: unseen === 1 ? t("Open chat, 1 new reply") : unseen ? tf("Open chat, {n} new replies", { n: unseen }) : t("Open chat"),
|
|
3603
4386
|
style: {
|
|
3604
4387
|
height: label ? 50 : 56,
|
|
3605
4388
|
minWidth: label ? 0 : 56,
|
|
@@ -3619,14 +4402,14 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
3619
4402
|
},
|
|
3620
4403
|
glyph("chat", ink, label ? 20 : 26),
|
|
3621
4404
|
label ? k.text(label, { color: ink, fontWeight: "700" }) : null,
|
|
3622
|
-
unseen ? (0,
|
|
4405
|
+
unseen ? (0, import_react3.createElement)(RN3.View, { style: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: p.danger, alignItems: "center", justifyContent: "center", borderWidth: 2, borderColor: p.bg } }, k.text(String(Math.min(unseen, 9)), { color: "#fff", fontSize: 11, fontWeight: "700" })) : null
|
|
3623
4406
|
)
|
|
3624
4407
|
) : null;
|
|
3625
|
-
const toast = banner ? (0,
|
|
4408
|
+
const toast = banner ? (0, import_react3.createElement)(
|
|
3626
4409
|
RN3.Pressable,
|
|
3627
4410
|
{
|
|
3628
4411
|
key: "banner",
|
|
3629
|
-
onPress: () => openAt("messages"),
|
|
4412
|
+
onPress: () => openAt("messages", banner.chat),
|
|
3630
4413
|
accessibilityRole: "button",
|
|
3631
4414
|
accessibilityLiveRegion: "polite",
|
|
3632
4415
|
style: {
|
|
@@ -3646,30 +4429,30 @@ var MRRocketMessenger = ({ client, appearance, options: given = {} }) => {
|
|
|
3646
4429
|
elevation: 6
|
|
3647
4430
|
}
|
|
3648
4431
|
},
|
|
3649
|
-
k.muted(`${who(banner.from, options.agentName)}${unseen > 1 ? ` \xB7 ${
|
|
3650
|
-
(0,
|
|
4432
|
+
k.muted(`${who(banner.from, options.agentName)}${unseen > 1 ? ` \xB7 ${tf("{n} new", { n: unseen })}` : ""}`, { fontWeight: "600", fontSize: 12 }),
|
|
4433
|
+
(0, import_react3.createElement)(RN3.Text, { numberOfLines: 3, style: { color: p.fg, fontSize: 15, lineHeight: 20 } }, banner.body)
|
|
3651
4434
|
) : null;
|
|
3652
4435
|
if (!launcher && !toast && !open) return null;
|
|
3653
|
-
return (0,
|
|
4436
|
+
return (0, import_react3.createElement)(
|
|
3654
4437
|
RN3.View,
|
|
3655
4438
|
{ pointerEvents: "box-none", style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 } },
|
|
3656
4439
|
launcher,
|
|
3657
4440
|
toast,
|
|
3658
|
-
open ? (0,
|
|
4441
|
+
open ? (0, import_react3.createElement)(MessengerSheet, { client, p, o: options, chat, view, setView, brand: look?.branding !== false }) : null
|
|
3659
4442
|
);
|
|
3660
4443
|
};
|
|
3661
4444
|
|
|
3662
4445
|
// src/native/index.tsx
|
|
3663
4446
|
var RN4 = require("react-native");
|
|
3664
4447
|
var MRRocketMask = ({ children, style }) => {
|
|
3665
|
-
const ref = (0,
|
|
3666
|
-
(0,
|
|
4448
|
+
const ref = (0, import_react4.useRef)(null);
|
|
4449
|
+
(0, import_react4.useEffect)(() => {
|
|
3667
4450
|
maskedViews.add(ref);
|
|
3668
4451
|
return () => {
|
|
3669
4452
|
maskedViews.delete(ref);
|
|
3670
4453
|
};
|
|
3671
4454
|
}, []);
|
|
3672
|
-
return (0,
|
|
4455
|
+
return (0, import_react4.createElement)(RN4.View, { ref, collapsable: false, style }, children);
|
|
3673
4456
|
};
|
|
3674
4457
|
var shared = null;
|
|
3675
4458
|
var sharedKey = "";
|
|
@@ -3686,16 +4469,20 @@ var createMRRocket = (options) => {
|
|
|
3686
4469
|
return shared;
|
|
3687
4470
|
};
|
|
3688
4471
|
var getMRRocket = () => shared;
|
|
3689
|
-
var Ctx = (0,
|
|
4472
|
+
var Ctx = (0, import_react4.createContext)(null);
|
|
3690
4473
|
var MRRocketProvider = ({ client: given, children, consent, flows = true, appearance, surveys = true, messenger = {}, feedback = {}, heatmaps = true, ...options }) => {
|
|
3691
|
-
const [client] = (0,
|
|
3692
|
-
(0,
|
|
4474
|
+
const [client] = (0, import_react4.useState)(() => given ?? createMRRocket({ ...options, consent }));
|
|
4475
|
+
(0, import_react4.useSyncExternalStore)(onWords, wordsVersion, wordsVersion);
|
|
4476
|
+
(0, import_react4.useEffect)(() => {
|
|
4477
|
+
if (options.locale) setLocale(options.locale);
|
|
4478
|
+
}, [options.locale]);
|
|
4479
|
+
(0, import_react4.useEffect)(() => {
|
|
3693
4480
|
if (consent) client.setConsent(consent);
|
|
3694
4481
|
}, [client, consent]);
|
|
3695
|
-
(0,
|
|
4482
|
+
(0, import_react4.useEffect)(() => {
|
|
3696
4483
|
if (options.replay !== void 0) client.setReplay(options.replay);
|
|
3697
4484
|
}, [client, options.replay]);
|
|
3698
|
-
const start = (0,
|
|
4485
|
+
const start = (0, import_react4.useRef)(null);
|
|
3699
4486
|
const touch = (e, end) => {
|
|
3700
4487
|
const n = e.nativeEvent;
|
|
3701
4488
|
const x = n.pageX ?? n.changedTouches?.[0]?.pageX;
|
|
@@ -3704,22 +4491,22 @@ var MRRocketProvider = ({ client: given, children, consent, flows = true, appear
|
|
|
3704
4491
|
if (!end) start.current = { x, y, t: Date.now() };
|
|
3705
4492
|
else if (start.current && Math.hypot(x - start.current.x, y - start.current.y) < 10 && Date.now() - start.current.t < 500) client.tap(x, y);
|
|
3706
4493
|
};
|
|
3707
|
-
return (0,
|
|
4494
|
+
return (0, import_react4.createElement)(
|
|
3708
4495
|
Ctx.Provider,
|
|
3709
4496
|
{ value: client },
|
|
3710
|
-
heatmaps ? (0,
|
|
3711
|
-
flows ? (0,
|
|
3712
|
-
surveys ? (0,
|
|
3713
|
-
feedback ? (0,
|
|
3714
|
-
messenger ? (0,
|
|
4497
|
+
heatmaps ? (0, import_react4.createElement)(RN4.View, { style: { flex: 1 }, onTouchStartCapture: (e) => touch(e, false), onTouchEndCapture: (e) => touch(e, true) }, children) : children,
|
|
4498
|
+
flows ? (0, import_react4.createElement)(MRRocketFlows, { flows: client.flows, paywall: client.paywall, appearance }) : null,
|
|
4499
|
+
surveys ? (0, import_react4.createElement)(MRRocketSurveys, { client, appearance }) : null,
|
|
4500
|
+
feedback ? (0, import_react4.createElement)(MRRocketFeedback, { client, appearance, options: feedback }) : null,
|
|
4501
|
+
messenger ? (0, import_react4.createElement)(MRRocketMessenger, { client, appearance, options: messenger }) : null
|
|
3715
4502
|
);
|
|
3716
4503
|
};
|
|
3717
4504
|
var useClient = () => {
|
|
3718
|
-
const c = (0,
|
|
4505
|
+
const c = (0, import_react4.useContext)(Ctx);
|
|
3719
4506
|
if (!c) throw new Error("MRRocket: wrap your app in <MRRocketProvider apiKey=\u2026>.");
|
|
3720
4507
|
return c;
|
|
3721
4508
|
};
|
|
3722
|
-
var useVersion = (client) => (0,
|
|
4509
|
+
var useVersion = (client) => (0, import_react4.useSyncExternalStore)(client.subscribe, client.getVersion, client.getVersion);
|
|
3723
4510
|
var useMRRocket = () => {
|
|
3724
4511
|
const client = useClient();
|
|
3725
4512
|
useVersion(client);
|
|
@@ -3753,9 +4540,9 @@ var useReview = () => {
|
|
|
3753
4540
|
const client = useClient();
|
|
3754
4541
|
return { request: client.requestReview };
|
|
3755
4542
|
};
|
|
3756
|
-
var useOnboarding = () => {
|
|
4543
|
+
var useOnboarding = (id) => {
|
|
3757
4544
|
const client = useClient();
|
|
3758
|
-
return
|
|
4545
|
+
return useOnboardingItem(client.flows, id, client.startOnboarding);
|
|
3759
4546
|
};
|
|
3760
4547
|
var useFlag = (key) => {
|
|
3761
4548
|
const client = useClient();
|
|
@@ -3772,7 +4559,7 @@ var useVariant = (key) => {
|
|
|
3772
4559
|
useVersion(client);
|
|
3773
4560
|
const config = client.getConfig();
|
|
3774
4561
|
const variant = client.peekVariant(key);
|
|
3775
|
-
(0,
|
|
4562
|
+
(0, import_react4.useEffect)(() => {
|
|
3776
4563
|
if (config) client.expose(key, variant);
|
|
3777
4564
|
}, [client, config, key, variant]);
|
|
3778
4565
|
return variant;
|
|
@@ -3783,7 +4570,7 @@ var useText = (id, fallback) => {
|
|
|
3783
4570
|
const config = client.getConfig();
|
|
3784
4571
|
const text2 = client.peekText(id);
|
|
3785
4572
|
const person = client.getDistinctId();
|
|
3786
|
-
(0,
|
|
4573
|
+
(0, import_react4.useEffect)(() => {
|
|
3787
4574
|
if (config) client.exposeText(id);
|
|
3788
4575
|
}, [client, config, id, person]);
|
|
3789
4576
|
return text2 ?? fallback;
|
|
@@ -3808,13 +4595,13 @@ var useExpoRouterTracking = () => {
|
|
|
3808
4595
|
console.warn("[MRRocket] useExpoRouterTracking needs expo-router. With React Navigation use useNavigationTracking.");
|
|
3809
4596
|
}
|
|
3810
4597
|
const pathname = (router?.usePathname ?? noPathname)();
|
|
3811
|
-
(0,
|
|
4598
|
+
(0, import_react4.useEffect)(() => {
|
|
3812
4599
|
if (pathname) client.screen(pathname);
|
|
3813
4600
|
}, [client, pathname]);
|
|
3814
4601
|
};
|
|
3815
4602
|
var useNavigationTracking = () => {
|
|
3816
4603
|
const client = useClient();
|
|
3817
|
-
return (0,
|
|
4604
|
+
return (0, import_react4.useCallback)(
|
|
3818
4605
|
(state) => {
|
|
3819
4606
|
const name = activeRouteName(state);
|
|
3820
4607
|
if (name) client.screen(name);
|