@samirify/messenger 0.1.0 → 0.1.2

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.
@@ -1,189 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ var chunkM2F3K6UX_cjs = require('../chunk-M2F3K6UX.cjs');
4
+ var chunk42TCO5DV_cjs = require('../chunk-42TCO5DV.cjs');
3
5
  var framerMotion = require('framer-motion');
4
- var react = require('react');
5
6
  var jsxRuntime = require('react/jsx-runtime');
6
- var reactQuery = require('@tanstack/react-query');
7
-
8
- // src/web/ChatDock.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
- function useOptionalMessengerConfig() {
20
- return react.useContext(MessengerContext);
21
- }
22
- var MessengerUiContext = react.createContext(null);
23
- function useMessengerUi() {
24
- const ctx = react.useContext(MessengerUiContext);
25
- if (!ctx) {
26
- throw new Error(
27
- "@gymlium/messenger: useMessengerUi must be used within MessengerUiProvider"
28
- );
29
- }
30
- return ctx;
31
- }
32
-
33
- // src/api.ts
34
- async function listThreads(http) {
35
- const res = await http.getJson(
36
- "/api/messages/threads"
37
- );
38
- return { ...res, data: res.data?.threads ?? [] };
39
- }
40
- async function openThread(http, clientUserId) {
41
- return http.postJson("/api/messages/threads", {
42
- ...clientUserId ? { client_user_id: clientUserId } : {}
43
- });
44
- }
45
- async function getThread(http, threadId, limit = 50, beforeId) {
46
- const params = new URLSearchParams({ limit: String(limit) });
47
- return http.getJson(`/api/messages/threads/${threadId}?${params.toString()}`);
48
- }
49
- async function sendMessage(http, threadId, body, image) {
50
- const path = `/api/messages/threads/${threadId}/messages`;
51
- if (image?.uri || image?.file) {
52
- const form = new FormData();
53
- if (body.trim()) form.append("body", body.trim());
54
- const filename = image.name ?? image.uri?.split("/").pop() ?? "photo.jpg";
55
- const match = /\.(\w+)$/.exec(filename);
56
- const type = image.type ?? (match ? `image/${match[1]}` : "image/jpeg");
57
- if (image.file) {
58
- form.append("image", image.file, filename);
59
- } else if (image.uri.startsWith("blob:") || image.uri.startsWith("data:")) {
60
- const blob = await fetch(image.uri).then((r) => r.blob());
61
- form.append("image", blob, filename);
62
- } else {
63
- form.append("image", {
64
- uri: image.uri,
65
- name: filename,
66
- type
67
- });
68
- }
69
- return http.postForm(path, form);
70
- }
71
- return http.postJson(path, { body });
72
- }
73
- function sumUnread(threads) {
74
- return threads.reduce((sum, th) => sum + Number(th.unread_count ?? 0), 0);
75
- }
76
-
77
- // src/queryKeys.ts
78
- var messengerKeys = {
79
- all: ["messenger"],
80
- threads: () => [...messengerKeys.all, "threads"],
81
- thread: (id) => [...messengerKeys.all, "thread", id]
82
- };
83
-
84
- // src/hooks/useUnreadTotal.ts
85
- function useUnreadTotal(options) {
86
- const cfg = useOptionalMessengerConfig();
87
- const featureOn = cfg?.enabled === true;
88
- const http = cfg?.http;
89
- const enabled = (options?.enabled ?? true) && featureOn && !!http;
90
- const query = reactQuery.useQuery({
91
- queryKey: messengerKeys.threads(),
92
- queryFn: async () => {
93
- const res = await listThreads(http);
94
- if (!res.success) {
95
- throw new Error(res.message ?? "Failed to load threads");
96
- }
97
- return res.data;
98
- },
99
- enabled
100
- });
101
- const total = react.useMemo(
102
- () => query.data ? sumUnread(query.data) : 0,
103
- [query.data]
104
- );
105
- return {
106
- total,
107
- isLoading: query.isLoading,
108
- isFetching: query.isFetching,
109
- refetch: query.refetch,
110
- enabled: featureOn
111
- };
112
- }
7
+ var react = require('react');
113
8
 
114
- // src/i18nKeys.ts
115
- var MessengerI18n = {
116
- off: {
117
- key: "mobile.messenger.off",
118
- fallback: "Messaging is not enabled for this account."
119
- },
120
- chat: { key: "mobile.messenger.chat", fallback: "Chat" },
121
- /** Dock / list title — prefer over inbox for web UI copy. */
122
- messages: { key: "mobile.messenger.messages", fallback: "Chat" },
123
- messagesHint: {
124
- key: "mobile.messenger.messagesHint",
125
- fallback: "Message your coach anytime"
126
- },
127
- messageCoach: {
128
- key: "mobile.messenger.messageCoach",
129
- fallback: "Message your coach"
130
- },
131
- empty: {
132
- key: "mobile.messenger.empty",
133
- fallback: "No conversations yet"
134
- },
135
- yourCoach: {
136
- key: "mobile.messenger.yourCoach",
137
- fallback: "Your coach"
138
- },
139
- photo: { key: "mobile.messenger.photo", fallback: "Photo" },
140
- noMessages: {
141
- key: "mobile.messenger.noMessages",
142
- fallback: "No messages yet"
143
- },
144
- startConversation: {
145
- key: "mobile.messenger.startConversation",
146
- fallback: "Say hello \u2014 send the first message."
147
- },
148
- typeMessage: {
149
- key: "mobile.messenger.typeMessage",
150
- fallback: "Type a message\u2026"
151
- },
152
- client: { key: "mobile.trainer.client", fallback: "Client" },
153
- back: { key: "common.back", fallback: "Back" }
154
- };
155
- function useThreads(options) {
156
- const { http, enabled: featureOn } = useMessengerConfig();
157
- const enabled = featureOn;
158
- return reactQuery.useQuery({
159
- queryKey: messengerKeys.threads(),
160
- queryFn: async () => {
161
- const res = await listThreads(http);
162
- if (!res.success) {
163
- throw new Error(res.message ?? "Failed to load threads");
164
- }
165
- return res.data;
166
- },
167
- enabled
168
- });
169
- }
170
- function useOpenThread() {
171
- const { http, navigation } = useMessengerConfig();
172
- const qc = reactQuery.useQueryClient();
173
- return reactQuery.useMutation({
174
- mutationFn: async (clientUserId) => {
175
- const res = await openThread(http, clientUserId);
176
- if (!res.success || !res.data?.thread?.id) {
177
- throw new Error(res.message ?? "Could not open thread");
178
- }
179
- return res.data.thread;
180
- },
181
- onSuccess: (thread) => {
182
- void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
183
- navigation.openThread(thread.id);
184
- }
185
- });
186
- }
187
9
  function initials(name) {
188
10
  const parts = name.trim().split(/\s+/).filter(Boolean);
189
11
  if (parts.length === 0) return "?";
@@ -194,10 +16,10 @@ function ThreadRow({
194
16
  thread,
195
17
  onSelect
196
18
  }) {
197
- const { user, t, isRtl, theme } = useMessengerConfig();
19
+ const { user, t, isRtl, theme } = chunk42TCO5DV_cjs.useMessengerConfig();
198
20
  const unread = Number(thread.unread_count ?? 0);
199
- const title = user?.role === "admin" ? thread.client_name ?? t(MessengerI18n.client.key, MessengerI18n.client.fallback) : t(MessengerI18n.yourCoach.key, MessengerI18n.yourCoach.fallback);
200
- const preview = thread.last_body || (thread.last_image_url ? t(MessengerI18n.photo.key, MessengerI18n.photo.fallback) : t(MessengerI18n.noMessages.key, MessengerI18n.noMessages.fallback));
21
+ const title = user?.role === "admin" ? thread.client_name ?? t(chunk42TCO5DV_cjs.MessengerI18n.client.key, chunk42TCO5DV_cjs.MessengerI18n.client.fallback) : t(chunk42TCO5DV_cjs.MessengerI18n.yourCoach.key, chunk42TCO5DV_cjs.MessengerI18n.yourCoach.fallback);
22
+ const preview = thread.last_body || (thread.last_image_url ? t(chunk42TCO5DV_cjs.MessengerI18n.photo.key, chunk42TCO5DV_cjs.MessengerI18n.photo.fallback) : t(chunk42TCO5DV_cjs.MessengerI18n.noMessages.key, chunk42TCO5DV_cjs.MessengerI18n.noMessages.fallback));
201
23
  return /* @__PURE__ */ jsxRuntime.jsxs(
202
24
  "button",
203
25
  {
@@ -327,9 +149,9 @@ function ThreadRow({
327
149
  function ThreadList({
328
150
  onSelectThread
329
151
  }) {
330
- const { user, t, theme } = useMessengerConfig();
331
- const threadsQuery = useThreads();
332
- const openThread2 = useOpenThread();
152
+ const { user, t, theme } = chunk42TCO5DV_cjs.useMessengerConfig();
153
+ const threadsQuery = chunk42TCO5DV_cjs.useThreads();
154
+ const openThread = chunk42TCO5DV_cjs.useOpenThread();
333
155
  const threads = threadsQuery.data ?? [];
334
156
  return /* @__PURE__ */ jsxRuntime.jsxs(
335
157
  "div",
@@ -346,8 +168,8 @@ function ThreadList({
346
168
  "button",
347
169
  {
348
170
  type: "button",
349
- onClick: () => openThread2.mutate(void 0),
350
- disabled: openThread2.isPending,
171
+ onClick: () => openThread.mutate(void 0),
172
+ disabled: openThread.isPending,
351
173
  style: {
352
174
  width: "100%",
353
175
  padding: "13px 16px",
@@ -363,12 +185,12 @@ function ThreadList({
363
185
  fontFamily: theme.fonts.bodySemiBold,
364
186
  fontWeight: 600,
365
187
  fontSize: 14,
366
- cursor: openThread2.isPending ? "default" : "pointer",
188
+ cursor: openThread.isPending ? "default" : "pointer",
367
189
  boxShadow: `0 10px 24px color-mix(in srgb, ${theme.accentColor} 32%, transparent)`
368
190
  },
369
- children: openThread2.isPending ? "\u2026" : t(
370
- MessengerI18n.messageCoach.key,
371
- MessengerI18n.messageCoach.fallback
191
+ children: openThread.isPending ? "\u2026" : t(
192
+ chunk42TCO5DV_cjs.MessengerI18n.messageCoach.key,
193
+ chunk42TCO5DV_cjs.MessengerI18n.messageCoach.fallback
372
194
  )
373
195
  }
374
196
  ) }),
@@ -424,7 +246,7 @@ function ThreadList({
424
246
  fontSize: 15,
425
247
  color: theme.colors.text
426
248
  },
427
- children: t(MessengerI18n.empty.key, MessengerI18n.empty.fallback)
249
+ children: t(chunk42TCO5DV_cjs.MessengerI18n.empty.key, chunk42TCO5DV_cjs.MessengerI18n.empty.fallback)
428
250
  }
429
251
  ),
430
252
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -438,8 +260,8 @@ function ThreadList({
438
260
  lineHeight: 1.45
439
261
  },
440
262
  children: t(
441
- MessengerI18n.startConversation.key,
442
- MessengerI18n.startConversation.fallback
263
+ chunk42TCO5DV_cjs.MessengerI18n.startConversation.key,
264
+ chunk42TCO5DV_cjs.MessengerI18n.startConversation.fallback
443
265
  )
444
266
  }
445
267
  )
@@ -463,48 +285,6 @@ function ThreadList({
463
285
  }
464
286
  );
465
287
  }
466
- var POLL_MS = 18e3;
467
- function useThread(threadId, options) {
468
- const { http, enabled: featureOn } = useMessengerConfig();
469
- const enabled = featureOn && Number.isFinite(threadId) && threadId > 0;
470
- const poll = true;
471
- return reactQuery.useQuery({
472
- queryKey: messengerKeys.thread(threadId),
473
- queryFn: async () => {
474
- const res = await getThread(http, threadId);
475
- if (!res.success) {
476
- throw new Error(res.message ?? "Failed to load thread");
477
- }
478
- return res.data;
479
- },
480
- enabled,
481
- refetchInterval: enabled && poll ? POLL_MS : false
482
- });
483
- }
484
- function useSendMessage(threadId) {
485
- const { http } = useMessengerConfig();
486
- const qc = reactQuery.useQueryClient();
487
- return reactQuery.useMutation({
488
- mutationFn: async (input) => {
489
- const res = await sendMessage(http, threadId, input.body, input.image);
490
- if (!res.success || !res.data?.message) {
491
- throw new Error(res.message ?? "Send failed");
492
- }
493
- return res.data.message;
494
- },
495
- onSuccess: (message) => {
496
- qc.setQueryData(
497
- messengerKeys.thread(threadId),
498
- (prev) => {
499
- if (!prev) return prev;
500
- if (prev.messages.some((m) => m.id === message.id)) return prev;
501
- return { ...prev, messages: [...prev.messages, message] };
502
- }
503
- );
504
- void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
505
- }
506
- });
507
- }
508
288
  function ChatBubbleIcon({ size = 26 }) {
509
289
  return /* @__PURE__ */ jsxRuntime.jsxs(
510
290
  "svg",
@@ -595,7 +375,7 @@ function WebComposer({
595
375
  sending,
596
376
  onSend
597
377
  }) {
598
- const { t, isRtl, theme, pickImage } = useMessengerConfig();
378
+ const { t, isRtl, theme, pickImage } = chunk42TCO5DV_cjs.useMessengerConfig();
599
379
  const [body, setBody] = react.useState("");
600
380
  const [focused, setFocused] = react.useState(false);
601
381
  const fileRef = react.useRef(null);
@@ -698,8 +478,8 @@ function WebComposer({
698
478
  }
699
479
  },
700
480
  placeholder: t(
701
- MessengerI18n.typeMessage.key,
702
- MessengerI18n.typeMessage.fallback
481
+ chunk42TCO5DV_cjs.MessengerI18n.typeMessage.key,
482
+ chunk42TCO5DV_cjs.MessengerI18n.typeMessage.fallback
703
483
  ),
704
484
  rows: 1,
705
485
  style: {
@@ -756,7 +536,7 @@ function initials2(name) {
756
536
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
757
537
  }
758
538
  function Bubble({ message }) {
759
- const { user, isRtl, theme } = useMessengerConfig();
539
+ const { user, isRtl, theme } = chunk42TCO5DV_cjs.useMessengerConfig();
760
540
  const mine = message.sender_user_id === user?.id;
761
541
  const alignEnd = mine !== isRtl;
762
542
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -804,14 +584,14 @@ function Conversation({
804
584
  onBack,
805
585
  onClose
806
586
  }) {
807
- const { user, t, isRtl, theme } = useMessengerConfig();
587
+ const { user, t, isRtl, theme } = chunk42TCO5DV_cjs.useMessengerConfig();
808
588
  const listRef = react.useRef(null);
809
- const threadQuery = useThread(threadId);
810
- const threadsQuery = useThreads();
811
- const sendMutation = useSendMessage(threadId);
589
+ const threadQuery = chunk42TCO5DV_cjs.useThread(threadId);
590
+ const threadsQuery = chunk42TCO5DV_cjs.useThreads();
591
+ const sendMutation = chunk42TCO5DV_cjs.useSendMessage(threadId);
812
592
  const messages = threadQuery.data?.messages ?? [];
813
593
  const threadMeta = threadsQuery.data?.find((th) => th.id === threadId);
814
- const title = user?.role === "admin" ? threadMeta?.client_name ?? t(MessengerI18n.client.key, MessengerI18n.client.fallback) : t(MessengerI18n.yourCoach.key, MessengerI18n.yourCoach.fallback);
594
+ const title = user?.role === "admin" ? threadMeta?.client_name ?? t(chunk42TCO5DV_cjs.MessengerI18n.client.key, chunk42TCO5DV_cjs.MessengerI18n.client.fallback) : t(chunk42TCO5DV_cjs.MessengerI18n.yourCoach.key, chunk42TCO5DV_cjs.MessengerI18n.yourCoach.fallback);
815
595
  react.useEffect(() => {
816
596
  const el = listRef.current;
817
597
  if (el) el.scrollTop = el.scrollHeight;
@@ -840,7 +620,7 @@ function Conversation({
840
620
  {
841
621
  type: "button",
842
622
  onClick: onBack,
843
- "aria-label": t(MessengerI18n.back.key, MessengerI18n.back.fallback),
623
+ "aria-label": t(chunk42TCO5DV_cjs.MessengerI18n.back.key, chunk42TCO5DV_cjs.MessengerI18n.back.fallback),
844
624
  style: {
845
625
  border: `1px solid color-mix(in srgb, ${theme.colors.border} 90%, transparent)`,
846
626
  background: `color-mix(in srgb, ${theme.colors.surfaceSecondary} 75%, transparent)`,
@@ -902,7 +682,7 @@ function Conversation({
902
682
  color: theme.colors.textMuted,
903
683
  marginTop: 1
904
684
  },
905
- children: t(MessengerI18n.chat.key, MessengerI18n.chat.fallback)
685
+ children: t(chunk42TCO5DV_cjs.MessengerI18n.chat.key, chunk42TCO5DV_cjs.MessengerI18n.chat.fallback)
906
686
  }
907
687
  )
908
688
  ] }),
@@ -958,8 +738,8 @@ function Conversation({
958
738
  padding: "0 24px"
959
739
  },
960
740
  children: t(
961
- MessengerI18n.startConversation.key,
962
- MessengerI18n.startConversation.fallback
741
+ chunk42TCO5DV_cjs.MessengerI18n.startConversation.key,
742
+ chunk42TCO5DV_cjs.MessengerI18n.startConversation.fallback
963
743
  )
964
744
  }
965
745
  ) : messages.map((m) => /* @__PURE__ */ jsxRuntime.jsx(Bubble, { message: m }, m.id))
@@ -975,7 +755,7 @@ function ChatPanel({
975
755
  onClose,
976
756
  variant = "dock"
977
757
  }) {
978
- const { enabled, t, isRtl, theme, onFeatureOff } = useMessengerConfig();
758
+ const { enabled, t, isRtl, theme, onFeatureOff } = chunk42TCO5DV_cjs.useMessengerConfig();
979
759
  if (!enabled) {
980
760
  if (onFeatureOff) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: onFeatureOff() });
981
761
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -987,7 +767,7 @@ function ChatPanel({
987
767
  color: theme.colors.textMuted,
988
768
  fontFamily: theme.fonts.body
989
769
  },
990
- children: t(MessengerI18n.off.key, MessengerI18n.off.fallback)
770
+ children: t(chunk42TCO5DV_cjs.MessengerI18n.off.key, chunk42TCO5DV_cjs.MessengerI18n.off.fallback)
991
771
  }
992
772
  );
993
773
  }
@@ -1040,7 +820,7 @@ function ChatPanel({
1040
820
  color: theme.colors.text,
1041
821
  lineHeight: 1.15
1042
822
  },
1043
- children: t(MessengerI18n.messages.key, MessengerI18n.messages.fallback)
823
+ children: t(chunk42TCO5DV_cjs.MessengerI18n.messages.key, chunk42TCO5DV_cjs.MessengerI18n.messages.fallback)
1044
824
  }
1045
825
  ),
1046
826
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1052,7 +832,7 @@ function ChatPanel({
1052
832
  fontSize: 12.5,
1053
833
  color: theme.colors.textMuted
1054
834
  },
1055
- children: t(MessengerI18n.messagesHint.key, MessengerI18n.messagesHint.fallback)
835
+ children: t(chunk42TCO5DV_cjs.MessengerI18n.messagesHint.key, chunk42TCO5DV_cjs.MessengerI18n.messagesHint.fallback)
1056
836
  }
1057
837
  )
1058
838
  ] }),
@@ -1113,13 +893,13 @@ function ChatPanel({
1113
893
  ] });
1114
894
  }
1115
895
  function ChatDock() {
1116
- const { enabled, isRtl, theme, t } = useMessengerConfig();
1117
- const ui = useMessengerUi();
1118
- const unread = useUnreadTotal({ enabled });
896
+ const { enabled, isRtl, theme, t } = chunk42TCO5DV_cjs.useMessengerConfig();
897
+ const ui = chunkM2F3K6UX_cjs.useMessengerUi();
898
+ const unread = chunkM2F3K6UX_cjs.useUnreadTotal({ enabled });
1119
899
  if (!enabled) return null;
1120
900
  const side = isRtl ? "left" : "right";
1121
901
  const open = ui.open;
1122
- const label = open ? "Close chat" : t(MessengerI18n.messages.key, MessengerI18n.messages.fallback);
902
+ const label = open ? "Close chat" : t(chunk42TCO5DV_cjs.MessengerI18n.messages.key, chunk42TCO5DV_cjs.MessengerI18n.messages.fallback);
1123
903
  return /* @__PURE__ */ jsxRuntime.jsxs(
1124
904
  "div",
1125
905
  {
@@ -1269,6 +1049,30 @@ function ChatDock() {
1269
1049
  );
1270
1050
  }
1271
1051
 
1052
+ Object.defineProperty(exports, "MessengerUiProvider", {
1053
+ enumerable: true,
1054
+ get: function () { return chunkM2F3K6UX_cjs.MessengerUiProvider; }
1055
+ });
1056
+ Object.defineProperty(exports, "useMessengerUi", {
1057
+ enumerable: true,
1058
+ get: function () { return chunkM2F3K6UX_cjs.useMessengerUi; }
1059
+ });
1060
+ Object.defineProperty(exports, "useOptionalMessengerUi", {
1061
+ enumerable: true,
1062
+ get: function () { return chunkM2F3K6UX_cjs.useOptionalMessengerUi; }
1063
+ });
1064
+ Object.defineProperty(exports, "MessengerProvider", {
1065
+ enumerable: true,
1066
+ get: function () { return chunk42TCO5DV_cjs.MessengerProvider; }
1067
+ });
1068
+ Object.defineProperty(exports, "useMessengerConfig", {
1069
+ enumerable: true,
1070
+ get: function () { return chunk42TCO5DV_cjs.useMessengerConfig; }
1071
+ });
1072
+ Object.defineProperty(exports, "useOptionalMessengerConfig", {
1073
+ enumerable: true,
1074
+ get: function () { return chunk42TCO5DV_cjs.useOptionalMessengerConfig; }
1075
+ });
1272
1076
  exports.ChatDock = ChatDock;
1273
1077
  exports.ChatPanel = ChatPanel;
1274
1078
  exports.Conversation = Conversation;