@samirify/messenger 0.1.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.
@@ -0,0 +1,649 @@
1
+ 'use strict';
2
+
3
+ var reactNative = require('react-native');
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var reactQuery = require('@tanstack/react-query');
7
+
8
+ // src/native/InboxScreen.tsx
9
+ var MessengerContext = react.createContext(null);
10
+ function useMessengerConfig() {
11
+ const ctx = react.useContext(MessengerContext);
12
+ if (!ctx) {
13
+ throw new Error(
14
+ "@gymlium/messenger: useMessengerConfig must be used within MessengerProvider"
15
+ );
16
+ }
17
+ return ctx;
18
+ }
19
+
20
+ // src/api.ts
21
+ async function listThreads(http) {
22
+ const res = await http.getJson(
23
+ "/api/messages/threads"
24
+ );
25
+ return { ...res, data: res.data?.threads ?? [] };
26
+ }
27
+ async function openThread(http, clientUserId) {
28
+ return http.postJson("/api/messages/threads", {
29
+ ...clientUserId ? { client_user_id: clientUserId } : {}
30
+ });
31
+ }
32
+ async function getThread(http, threadId, limit = 50, beforeId) {
33
+ const params = new URLSearchParams({ limit: String(limit) });
34
+ return http.getJson(`/api/messages/threads/${threadId}?${params.toString()}`);
35
+ }
36
+ async function sendMessage(http, threadId, body, image) {
37
+ const path = `/api/messages/threads/${threadId}/messages`;
38
+ if (image?.uri || image?.file) {
39
+ const form = new FormData();
40
+ if (body.trim()) form.append("body", body.trim());
41
+ const filename = image.name ?? image.uri?.split("/").pop() ?? "photo.jpg";
42
+ const match = /\.(\w+)$/.exec(filename);
43
+ const type = image.type ?? (match ? `image/${match[1]}` : "image/jpeg");
44
+ if (image.file) {
45
+ form.append("image", image.file, filename);
46
+ } else if (image.uri.startsWith("blob:") || image.uri.startsWith("data:")) {
47
+ const blob = await fetch(image.uri).then((r) => r.blob());
48
+ form.append("image", blob, filename);
49
+ } else {
50
+ form.append("image", {
51
+ uri: image.uri,
52
+ name: filename,
53
+ type
54
+ });
55
+ }
56
+ return http.postForm(path, form);
57
+ }
58
+ return http.postJson(path, { body });
59
+ }
60
+
61
+ // src/queryKeys.ts
62
+ var messengerKeys = {
63
+ all: ["messenger"],
64
+ threads: () => [...messengerKeys.all, "threads"],
65
+ thread: (id) => [...messengerKeys.all, "thread", id]
66
+ };
67
+
68
+ // src/hooks/useThreads.ts
69
+ function useThreads(options) {
70
+ const { http, enabled: featureOn } = useMessengerConfig();
71
+ const enabled = featureOn;
72
+ return reactQuery.useQuery({
73
+ queryKey: messengerKeys.threads(),
74
+ queryFn: async () => {
75
+ const res = await listThreads(http);
76
+ if (!res.success) {
77
+ throw new Error(res.message ?? "Failed to load threads");
78
+ }
79
+ return res.data;
80
+ },
81
+ enabled
82
+ });
83
+ }
84
+ function useOpenThread() {
85
+ const { http, navigation } = useMessengerConfig();
86
+ const qc = reactQuery.useQueryClient();
87
+ return reactQuery.useMutation({
88
+ mutationFn: async (clientUserId) => {
89
+ const res = await openThread(http, clientUserId);
90
+ if (!res.success || !res.data?.thread?.id) {
91
+ throw new Error(res.message ?? "Could not open thread");
92
+ }
93
+ return res.data.thread;
94
+ },
95
+ onSuccess: (thread) => {
96
+ void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
97
+ navigation.openThread(thread.id);
98
+ }
99
+ });
100
+ }
101
+
102
+ // src/i18nKeys.ts
103
+ var MessengerI18n = {
104
+ off: {
105
+ key: "mobile.messenger.off",
106
+ fallback: "Messaging is not enabled for this account."
107
+ },
108
+ inbox: { key: "mobile.messenger.inbox", fallback: "Chat" },
109
+ chat: { key: "mobile.messenger.chat", fallback: "Chat" },
110
+ messageCoach: {
111
+ key: "mobile.messenger.messageCoach",
112
+ fallback: "Message your coach"
113
+ },
114
+ empty: {
115
+ key: "mobile.messenger.empty",
116
+ fallback: "No conversations yet"
117
+ },
118
+ yourCoach: {
119
+ key: "mobile.messenger.yourCoach",
120
+ fallback: "Your coach"
121
+ },
122
+ photo: { key: "mobile.messenger.photo", fallback: "Photo" },
123
+ noMessages: {
124
+ key: "mobile.messenger.noMessages",
125
+ fallback: "No messages yet"
126
+ },
127
+ startConversation: {
128
+ key: "mobile.messenger.startConversation",
129
+ fallback: "Say hello \u2014 send the first message."
130
+ },
131
+ typeMessage: {
132
+ key: "mobile.messenger.typeMessage",
133
+ fallback: "Type a message\u2026"
134
+ },
135
+ client: { key: "mobile.trainer.client", fallback: "Client" },
136
+ back: { key: "common.back", fallback: "Back" }
137
+ };
138
+ function FeatureOff() {
139
+ const { t, theme, navigation, onFeatureOff } = useMessengerConfig();
140
+ if (onFeatureOff) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: onFeatureOff() });
141
+ const { colors, accentColor } = theme;
142
+ return /* @__PURE__ */ jsxRuntime.jsxs(
143
+ reactNative.View,
144
+ {
145
+ style: {
146
+ flex: 1,
147
+ backgroundColor: colors.background,
148
+ alignItems: "center",
149
+ justifyContent: "center",
150
+ padding: 24
151
+ },
152
+ children: [
153
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.textMuted, textAlign: "center" }, children: t(MessengerI18n.off.key, MessengerI18n.off.fallback) }),
154
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: navigation.goBack, style: { marginTop: 16 }, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: accentColor }, children: t(MessengerI18n.back.key, MessengerI18n.back.fallback) }) })
155
+ ]
156
+ }
157
+ );
158
+ }
159
+ function ThreadRow({ thread }) {
160
+ const { user, t, isRtl, theme, navigation } = useMessengerConfig();
161
+ const { colors, accentColor, fonts } = theme;
162
+ const unread = Number(thread.unread_count ?? 0);
163
+ return /* @__PURE__ */ jsxRuntime.jsx(
164
+ reactNative.TouchableOpacity,
165
+ {
166
+ onPress: () => navigation.openThread(thread.id),
167
+ activeOpacity: 0.8,
168
+ children: /* @__PURE__ */ jsxRuntime.jsx(
169
+ reactNative.View,
170
+ {
171
+ style: {
172
+ marginBottom: 10,
173
+ padding: 14,
174
+ borderRadius: 14,
175
+ backgroundColor: colors.surface,
176
+ borderWidth: 1,
177
+ borderColor: colors.border
178
+ },
179
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
180
+ reactNative.View,
181
+ {
182
+ style: {
183
+ flexDirection: isRtl ? "row-reverse" : "row",
184
+ alignItems: "center",
185
+ gap: 12
186
+ },
187
+ children: [
188
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
189
+ /* @__PURE__ */ jsxRuntime.jsx(
190
+ reactNative.Text,
191
+ {
192
+ style: {
193
+ color: colors.text,
194
+ fontFamily: fonts.displaySemiBold,
195
+ fontWeight: "600",
196
+ fontSize: 16
197
+ },
198
+ children: user?.role === "admin" ? thread.client_name ?? t(MessengerI18n.client.key, MessengerI18n.client.fallback) : t(
199
+ MessengerI18n.yourCoach.key,
200
+ MessengerI18n.yourCoach.fallback
201
+ )
202
+ }
203
+ ),
204
+ /* @__PURE__ */ jsxRuntime.jsx(
205
+ reactNative.Text,
206
+ {
207
+ style: {
208
+ color: colors.textSecondary,
209
+ fontFamily: fonts.body,
210
+ fontSize: 13,
211
+ marginTop: 4
212
+ },
213
+ numberOfLines: 1,
214
+ children: thread.last_body || (thread.last_image_url ? t(MessengerI18n.photo.key, MessengerI18n.photo.fallback) : t(
215
+ MessengerI18n.noMessages.key,
216
+ MessengerI18n.noMessages.fallback
217
+ ))
218
+ }
219
+ )
220
+ ] }),
221
+ unread > 0 && /* @__PURE__ */ jsxRuntime.jsx(
222
+ reactNative.View,
223
+ {
224
+ style: {
225
+ minWidth: 22,
226
+ height: 22,
227
+ borderRadius: 11,
228
+ backgroundColor: accentColor,
229
+ alignItems: "center",
230
+ justifyContent: "center",
231
+ paddingHorizontal: 6
232
+ },
233
+ children: /* @__PURE__ */ jsxRuntime.jsx(
234
+ reactNative.Text,
235
+ {
236
+ style: {
237
+ color: "#FFF",
238
+ fontSize: 11,
239
+ fontFamily: fonts.bodySemiBold,
240
+ fontWeight: "600"
241
+ },
242
+ children: unread
243
+ }
244
+ )
245
+ }
246
+ )
247
+ ]
248
+ }
249
+ )
250
+ }
251
+ )
252
+ }
253
+ );
254
+ }
255
+ function InboxScreen() {
256
+ const { enabled, user, t, isRtl, theme, navigation } = useMessengerConfig();
257
+ const { colors, accentColor, fonts } = theme;
258
+ const threadsQuery = useThreads();
259
+ const openThread2 = useOpenThread();
260
+ if (!enabled) return /* @__PURE__ */ jsxRuntime.jsx(FeatureOff, {});
261
+ const threads = threadsQuery.data ?? [];
262
+ const loading = threadsQuery.isLoading && threads.length === 0;
263
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, backgroundColor: colors.background }, children: [
264
+ /* @__PURE__ */ jsxRuntime.jsxs(
265
+ reactNative.View,
266
+ {
267
+ style: {
268
+ paddingTop: 56,
269
+ paddingHorizontal: 16,
270
+ paddingBottom: 12,
271
+ flexDirection: isRtl ? "row-reverse" : "row",
272
+ alignItems: "center",
273
+ gap: 12
274
+ },
275
+ children: [
276
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: navigation.goBack, hitSlop: 12, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 22, color: colors.text }, children: isRtl ? "\u2192" : "\u2190" }) }),
277
+ /* @__PURE__ */ jsxRuntime.jsx(
278
+ reactNative.Text,
279
+ {
280
+ style: {
281
+ color: colors.text,
282
+ fontSize: 22,
283
+ fontFamily: fonts.displayBold,
284
+ fontWeight: "700",
285
+ flex: 1
286
+ },
287
+ children: t(MessengerI18n.inbox.key, MessengerI18n.inbox.fallback)
288
+ }
289
+ )
290
+ ]
291
+ }
292
+ ),
293
+ /* @__PURE__ */ jsxRuntime.jsxs(
294
+ reactNative.ScrollView,
295
+ {
296
+ contentContainerStyle: { paddingHorizontal: 16, paddingBottom: 32 },
297
+ refreshControl: /* @__PURE__ */ jsxRuntime.jsx(
298
+ reactNative.RefreshControl,
299
+ {
300
+ refreshing: threadsQuery.isRefetching,
301
+ onRefresh: () => void threadsQuery.refetch(),
302
+ tintColor: accentColor
303
+ }
304
+ ),
305
+ children: [
306
+ user?.role === "client" && threads.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(
307
+ reactNative.TouchableOpacity,
308
+ {
309
+ onPress: () => openThread2.mutate(void 0),
310
+ disabled: openThread2.isPending,
311
+ style: {
312
+ marginBottom: 14,
313
+ paddingVertical: 14,
314
+ borderRadius: 12,
315
+ backgroundColor: accentColor,
316
+ alignItems: "center"
317
+ },
318
+ children: openThread2.isPending ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: "#FFF" }) : /* @__PURE__ */ jsxRuntime.jsx(
319
+ reactNative.Text,
320
+ {
321
+ style: {
322
+ color: "#FFF",
323
+ fontFamily: fonts.bodySemiBold,
324
+ fontWeight: "600"
325
+ },
326
+ children: t(
327
+ MessengerI18n.messageCoach.key,
328
+ MessengerI18n.messageCoach.fallback
329
+ )
330
+ }
331
+ )
332
+ }
333
+ ),
334
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: accentColor, style: { marginTop: 40 } }) : threads.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
335
+ reactNative.View,
336
+ {
337
+ style: {
338
+ alignItems: "center",
339
+ paddingVertical: 40,
340
+ borderRadius: 14,
341
+ backgroundColor: colors.surface,
342
+ borderWidth: 1,
343
+ borderColor: colors.border
344
+ },
345
+ children: [
346
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 28, color: colors.textMuted }, children: "\xB7\xB7\xB7" }),
347
+ /* @__PURE__ */ jsxRuntime.jsx(
348
+ reactNative.Text,
349
+ {
350
+ style: {
351
+ color: colors.textMuted,
352
+ marginTop: 12,
353
+ fontFamily: fonts.body,
354
+ textAlign: "center"
355
+ },
356
+ children: t(MessengerI18n.empty.key, MessengerI18n.empty.fallback)
357
+ }
358
+ )
359
+ ]
360
+ }
361
+ ) : threads.map((th) => /* @__PURE__ */ jsxRuntime.jsx(ThreadRow, { thread: th }, th.id))
362
+ ]
363
+ }
364
+ )
365
+ ] });
366
+ }
367
+ var POLL_MS = 18e3;
368
+ function useThread(threadId, options) {
369
+ const { http, enabled: featureOn } = useMessengerConfig();
370
+ const enabled = featureOn && Number.isFinite(threadId) && threadId > 0;
371
+ const poll = true;
372
+ return reactQuery.useQuery({
373
+ queryKey: messengerKeys.thread(threadId),
374
+ queryFn: async () => {
375
+ const res = await getThread(http, threadId);
376
+ if (!res.success) {
377
+ throw new Error(res.message ?? "Failed to load thread");
378
+ }
379
+ return res.data;
380
+ },
381
+ enabled,
382
+ refetchInterval: enabled && poll ? POLL_MS : false
383
+ });
384
+ }
385
+ function useSendMessage(threadId) {
386
+ const { http } = useMessengerConfig();
387
+ const qc = reactQuery.useQueryClient();
388
+ return reactQuery.useMutation({
389
+ mutationFn: async (input) => {
390
+ const res = await sendMessage(http, threadId, input.body, input.image);
391
+ if (!res.success || !res.data?.message) {
392
+ throw new Error(res.message ?? "Send failed");
393
+ }
394
+ return res.data.message;
395
+ },
396
+ onSuccess: (message) => {
397
+ qc.setQueryData(
398
+ messengerKeys.thread(threadId),
399
+ (prev) => {
400
+ if (!prev) return prev;
401
+ if (prev.messages.some((m) => m.id === message.id)) return prev;
402
+ return { ...prev, messages: [...prev.messages, message] };
403
+ }
404
+ );
405
+ void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
406
+ }
407
+ });
408
+ }
409
+ function MessageBubble({ message }) {
410
+ const { user, isRtl, theme } = useMessengerConfig();
411
+ const { colors, accentColor, fonts } = theme;
412
+ const mine = message.sender_user_id === user?.id;
413
+ return /* @__PURE__ */ jsxRuntime.jsxs(
414
+ reactNative.View,
415
+ {
416
+ style: {
417
+ alignSelf: mine ? isRtl ? "flex-start" : "flex-end" : isRtl ? "flex-end" : "flex-start",
418
+ maxWidth: "80%",
419
+ marginBottom: 10,
420
+ backgroundColor: mine ? accentColor : colors.surfaceSecondary,
421
+ borderRadius: 16,
422
+ paddingHorizontal: 12,
423
+ paddingVertical: 10
424
+ },
425
+ children: [
426
+ message.image_url ? /* @__PURE__ */ jsxRuntime.jsx(
427
+ reactNative.Image,
428
+ {
429
+ source: { uri: message.image_url },
430
+ style: {
431
+ width: 180,
432
+ height: 180,
433
+ borderRadius: 10,
434
+ marginBottom: message.body ? 8 : 0
435
+ }
436
+ }
437
+ ) : null,
438
+ message.body ? /* @__PURE__ */ jsxRuntime.jsx(
439
+ reactNative.Text,
440
+ {
441
+ style: {
442
+ color: mine ? "#FFF" : colors.text,
443
+ fontFamily: fonts.body,
444
+ fontSize: 15
445
+ },
446
+ children: message.body
447
+ }
448
+ ) : null
449
+ ]
450
+ }
451
+ );
452
+ }
453
+ function Composer({
454
+ sending,
455
+ onSend
456
+ }) {
457
+ const { t, isRtl, theme, pickImage } = useMessengerConfig();
458
+ const { colors, accentColor, fonts } = theme;
459
+ const [body, setBody] = react.useState("");
460
+ async function onPick() {
461
+ const image = await pickImage();
462
+ if (image) {
463
+ await onSend(body, image);
464
+ setBody("");
465
+ }
466
+ }
467
+ async function onPressSend() {
468
+ if (!body.trim() || sending) return;
469
+ const text = body;
470
+ await onSend(text, null);
471
+ setBody("");
472
+ }
473
+ return /* @__PURE__ */ jsxRuntime.jsxs(
474
+ reactNative.View,
475
+ {
476
+ style: {
477
+ flexDirection: isRtl ? "row-reverse" : "row",
478
+ alignItems: "flex-end",
479
+ gap: 8,
480
+ paddingHorizontal: 12,
481
+ paddingVertical: 10,
482
+ borderTopWidth: 1,
483
+ borderTopColor: colors.border,
484
+ paddingBottom: reactNative.Platform.OS === "ios" ? 28 : 12
485
+ },
486
+ children: [
487
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: onPick, hitSlop: 8, disabled: sending, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 22, color: accentColor }, children: "\u25A3" }) }),
488
+ /* @__PURE__ */ jsxRuntime.jsx(
489
+ reactNative.TextInput,
490
+ {
491
+ value: body,
492
+ onChangeText: setBody,
493
+ placeholder: t(
494
+ MessengerI18n.typeMessage.key,
495
+ MessengerI18n.typeMessage.fallback
496
+ ),
497
+ placeholderTextColor: colors.textMuted,
498
+ multiline: true,
499
+ style: {
500
+ flex: 1,
501
+ maxHeight: 100,
502
+ borderWidth: 1,
503
+ borderColor: colors.border,
504
+ borderRadius: 16,
505
+ paddingHorizontal: 14,
506
+ paddingVertical: 10,
507
+ color: colors.text,
508
+ fontFamily: fonts.body,
509
+ textAlign: isRtl ? "right" : "left",
510
+ backgroundColor: colors.surface
511
+ }
512
+ }
513
+ ),
514
+ /* @__PURE__ */ jsxRuntime.jsx(
515
+ reactNative.TouchableOpacity,
516
+ {
517
+ onPress: onPressSend,
518
+ disabled: sending || !body.trim(),
519
+ style: {
520
+ width: 40,
521
+ height: 40,
522
+ borderRadius: 20,
523
+ backgroundColor: body.trim() ? accentColor : colors.surfaceSecondary,
524
+ alignItems: "center",
525
+ justifyContent: "center"
526
+ },
527
+ children: sending ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: "#FFF", size: "small" }) : /* @__PURE__ */ jsxRuntime.jsx(
528
+ reactNative.Text,
529
+ {
530
+ style: {
531
+ color: body.trim() ? "#FFF" : colors.textMuted,
532
+ fontSize: 16,
533
+ fontWeight: "700"
534
+ },
535
+ children: "\u27A4"
536
+ }
537
+ )
538
+ }
539
+ )
540
+ ]
541
+ }
542
+ );
543
+ }
544
+ function FeatureOff2() {
545
+ const { t, theme, onFeatureOff } = useMessengerConfig();
546
+ if (onFeatureOff) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: onFeatureOff() });
547
+ return /* @__PURE__ */ jsxRuntime.jsx(
548
+ reactNative.View,
549
+ {
550
+ style: {
551
+ flex: 1,
552
+ backgroundColor: theme.colors.background,
553
+ alignItems: "center",
554
+ justifyContent: "center"
555
+ },
556
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: theme.colors.textMuted }, children: t(MessengerI18n.off.key, MessengerI18n.off.fallback) })
557
+ }
558
+ );
559
+ }
560
+ function ThreadScreen({ threadId }) {
561
+ const { enabled, t, isRtl, theme, navigation } = useMessengerConfig();
562
+ const { colors, accentColor, fonts } = theme;
563
+ const listRef = react.useRef(null);
564
+ const threadQuery = useThread(threadId);
565
+ const sendMutation = useSendMessage(threadId);
566
+ if (!enabled) return /* @__PURE__ */ jsxRuntime.jsx(FeatureOff2, {});
567
+ const messages = threadQuery.data?.messages ?? [];
568
+ const loading = threadQuery.isLoading && messages.length === 0;
569
+ async function handleSend(body, image) {
570
+ if (!body.trim() && !image || sendMutation.isPending) return;
571
+ await sendMutation.mutateAsync({ body, image });
572
+ setTimeout(() => listRef.current?.scrollToEnd({ animated: true }), 100);
573
+ }
574
+ return /* @__PURE__ */ jsxRuntime.jsxs(
575
+ reactNative.KeyboardAvoidingView,
576
+ {
577
+ style: { flex: 1, backgroundColor: colors.background },
578
+ behavior: reactNative.Platform.OS === "ios" ? "padding" : void 0,
579
+ keyboardVerticalOffset: 0,
580
+ children: [
581
+ /* @__PURE__ */ jsxRuntime.jsxs(
582
+ reactNative.View,
583
+ {
584
+ style: {
585
+ paddingTop: 56,
586
+ paddingHorizontal: 16,
587
+ paddingBottom: 12,
588
+ flexDirection: isRtl ? "row-reverse" : "row",
589
+ alignItems: "center",
590
+ gap: 12,
591
+ borderBottomWidth: 1,
592
+ borderBottomColor: colors.border
593
+ },
594
+ children: [
595
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.TouchableOpacity, { onPress: navigation.goBack, hitSlop: 12, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 22, color: colors.text }, children: isRtl ? "\u2192" : "\u2190" }) }),
596
+ /* @__PURE__ */ jsxRuntime.jsx(
597
+ reactNative.Text,
598
+ {
599
+ style: {
600
+ color: colors.text,
601
+ fontSize: 18,
602
+ fontFamily: fonts.displayBold,
603
+ fontWeight: "700",
604
+ flex: 1
605
+ },
606
+ children: t(MessengerI18n.chat.key, MessengerI18n.chat.fallback)
607
+ }
608
+ )
609
+ ]
610
+ }
611
+ ),
612
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: accentColor, style: { marginTop: 40 } }) : /* @__PURE__ */ jsxRuntime.jsx(
613
+ reactNative.FlatList,
614
+ {
615
+ ref: listRef,
616
+ data: messages,
617
+ keyExtractor: (m) => String(m.id),
618
+ contentContainerStyle: { padding: 16, paddingBottom: 8 },
619
+ onContentSizeChange: () => listRef.current?.scrollToEnd({ animated: false }),
620
+ ListEmptyComponent: /* @__PURE__ */ jsxRuntime.jsx(
621
+ reactNative.Text,
622
+ {
623
+ style: {
624
+ color: colors.textMuted,
625
+ textAlign: "center",
626
+ marginTop: 40,
627
+ fontFamily: fonts.body
628
+ },
629
+ children: t(
630
+ MessengerI18n.startConversation.key,
631
+ MessengerI18n.startConversation.fallback
632
+ )
633
+ }
634
+ ),
635
+ renderItem: ({ item }) => /* @__PURE__ */ jsxRuntime.jsx(MessageBubble, { message: item })
636
+ }
637
+ ),
638
+ /* @__PURE__ */ jsxRuntime.jsx(Composer, { sending: sendMutation.isPending, onSend: handleSend })
639
+ ]
640
+ }
641
+ );
642
+ }
643
+
644
+ exports.Composer = Composer;
645
+ exports.InboxScreen = InboxScreen;
646
+ exports.MessageBubble = MessageBubble;
647
+ exports.ThreadScreen = ThreadScreen;
648
+ //# sourceMappingURL=index.cjs.map
649
+ //# sourceMappingURL=index.cjs.map