@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.
- package/dist/chunk-42TCO5DV.cjs +214 -0
- package/dist/chunk-42TCO5DV.cjs.map +1 -0
- package/dist/chunk-M2F3K6UX.cjs +82 -0
- package/dist/chunk-M2F3K6UX.cjs.map +1 -0
- package/dist/chunk-O5XNJHGZ.js +198 -0
- package/dist/chunk-O5XNJHGZ.js.map +1 -0
- package/dist/chunk-OTJZXVON.js +77 -0
- package/dist/chunk-OTJZXVON.js.map +1 -0
- package/dist/index.cjs +78 -281
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -26
- package/dist/index.d.ts +5 -26
- package/dist/index.js +3 -263
- package/dist/index.js.map +1 -1
- package/dist/native/index.cjs +34 -206
- package/dist/native/index.cjs.map +1 -1
- package/dist/native/index.js +6 -178
- package/dist/native/index.js.map +1 -1
- package/dist/uiController-CM_cznOa.d.cts +26 -0
- package/dist/uiController-CNCLZw4I.d.ts +26 -0
- package/dist/web/index.cjs +63 -259
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +1 -0
- package/dist/web/index.d.ts +1 -0
- package/dist/web/index.js +10 -228
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var reactQuery = require('@tanstack/react-query');
|
|
6
|
+
|
|
7
|
+
// src/api.ts
|
|
8
|
+
async function listThreads(http) {
|
|
9
|
+
const res = await http.getJson(
|
|
10
|
+
"/api/messages/threads"
|
|
11
|
+
);
|
|
12
|
+
return { ...res, data: res.data?.threads ?? [] };
|
|
13
|
+
}
|
|
14
|
+
async function openThread(http, clientUserId) {
|
|
15
|
+
return http.postJson("/api/messages/threads", {
|
|
16
|
+
...clientUserId ? { client_user_id: clientUserId } : {}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function getThread(http, threadId, limit = 50, beforeId) {
|
|
20
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
21
|
+
if (beforeId != null) params.set("before_id", String(beforeId));
|
|
22
|
+
return http.getJson(`/api/messages/threads/${threadId}?${params.toString()}`);
|
|
23
|
+
}
|
|
24
|
+
async function sendMessage(http, threadId, body, image) {
|
|
25
|
+
const path = `/api/messages/threads/${threadId}/messages`;
|
|
26
|
+
if (image?.uri || image?.file) {
|
|
27
|
+
const form = new FormData();
|
|
28
|
+
if (body.trim()) form.append("body", body.trim());
|
|
29
|
+
const filename = image.name ?? image.uri?.split("/").pop() ?? "photo.jpg";
|
|
30
|
+
const match = /\.(\w+)$/.exec(filename);
|
|
31
|
+
const type = image.type ?? (match ? `image/${match[1]}` : "image/jpeg");
|
|
32
|
+
if (image.file) {
|
|
33
|
+
form.append("image", image.file, filename);
|
|
34
|
+
} else if (image.uri.startsWith("blob:") || image.uri.startsWith("data:")) {
|
|
35
|
+
const blob = await fetch(image.uri).then((r) => r.blob());
|
|
36
|
+
form.append("image", blob, filename);
|
|
37
|
+
} else {
|
|
38
|
+
form.append("image", {
|
|
39
|
+
uri: image.uri,
|
|
40
|
+
name: filename,
|
|
41
|
+
type
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return http.postForm(path, form);
|
|
45
|
+
}
|
|
46
|
+
return http.postJson(path, { body });
|
|
47
|
+
}
|
|
48
|
+
async function markThreadRead(http, threadId) {
|
|
49
|
+
return http.patchJson(`/api/messages/threads/${threadId}/read`, {});
|
|
50
|
+
}
|
|
51
|
+
function sumUnread(threads) {
|
|
52
|
+
return threads.reduce((sum, th) => sum + Number(th.unread_count ?? 0), 0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/queryKeys.ts
|
|
56
|
+
var messengerKeys = {
|
|
57
|
+
all: ["messenger"],
|
|
58
|
+
threads: () => [...messengerKeys.all, "threads"],
|
|
59
|
+
thread: (id) => [...messengerKeys.all, "thread", id]
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/i18nKeys.ts
|
|
63
|
+
var MessengerI18n = {
|
|
64
|
+
off: {
|
|
65
|
+
key: "mobile.messenger.off",
|
|
66
|
+
fallback: "Messaging is not enabled for this account."
|
|
67
|
+
},
|
|
68
|
+
inbox: { key: "mobile.messenger.inbox", fallback: "Chat" },
|
|
69
|
+
chat: { key: "mobile.messenger.chat", fallback: "Chat" },
|
|
70
|
+
/** Dock / list title — prefer over inbox for web UI copy. */
|
|
71
|
+
messages: { key: "mobile.messenger.messages", fallback: "Chat" },
|
|
72
|
+
messagesHint: {
|
|
73
|
+
key: "mobile.messenger.messagesHint",
|
|
74
|
+
fallback: "Message your coach anytime"
|
|
75
|
+
},
|
|
76
|
+
messageCoach: {
|
|
77
|
+
key: "mobile.messenger.messageCoach",
|
|
78
|
+
fallback: "Message your coach"
|
|
79
|
+
},
|
|
80
|
+
empty: {
|
|
81
|
+
key: "mobile.messenger.empty",
|
|
82
|
+
fallback: "No conversations yet"
|
|
83
|
+
},
|
|
84
|
+
yourCoach: {
|
|
85
|
+
key: "mobile.messenger.yourCoach",
|
|
86
|
+
fallback: "Your coach"
|
|
87
|
+
},
|
|
88
|
+
photo: { key: "mobile.messenger.photo", fallback: "Photo" },
|
|
89
|
+
noMessages: {
|
|
90
|
+
key: "mobile.messenger.noMessages",
|
|
91
|
+
fallback: "No messages yet"
|
|
92
|
+
},
|
|
93
|
+
startConversation: {
|
|
94
|
+
key: "mobile.messenger.startConversation",
|
|
95
|
+
fallback: "Say hello \u2014 send the first message."
|
|
96
|
+
},
|
|
97
|
+
typeMessage: {
|
|
98
|
+
key: "mobile.messenger.typeMessage",
|
|
99
|
+
fallback: "Type a message\u2026"
|
|
100
|
+
},
|
|
101
|
+
client: { key: "mobile.trainer.client", fallback: "Client" },
|
|
102
|
+
back: { key: "common.back", fallback: "Back" }
|
|
103
|
+
};
|
|
104
|
+
var MessengerContext = react.createContext(null);
|
|
105
|
+
function MessengerProvider({
|
|
106
|
+
config,
|
|
107
|
+
children
|
|
108
|
+
}) {
|
|
109
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MessengerContext.Provider, { value: config, children });
|
|
110
|
+
}
|
|
111
|
+
function useMessengerConfig() {
|
|
112
|
+
const ctx = react.useContext(MessengerContext);
|
|
113
|
+
if (!ctx) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
"@gymlium/messenger: useMessengerConfig must be used within MessengerProvider"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return ctx;
|
|
119
|
+
}
|
|
120
|
+
function useOptionalMessengerConfig() {
|
|
121
|
+
return react.useContext(MessengerContext);
|
|
122
|
+
}
|
|
123
|
+
function useThreads(options) {
|
|
124
|
+
const { http, enabled: featureOn } = useMessengerConfig();
|
|
125
|
+
const enabled = (options?.enabled ?? true) && featureOn;
|
|
126
|
+
return reactQuery.useQuery({
|
|
127
|
+
queryKey: messengerKeys.threads(),
|
|
128
|
+
queryFn: async () => {
|
|
129
|
+
const res = await listThreads(http);
|
|
130
|
+
if (!res.success) {
|
|
131
|
+
throw new Error(res.message ?? "Failed to load threads");
|
|
132
|
+
}
|
|
133
|
+
return res.data;
|
|
134
|
+
},
|
|
135
|
+
enabled
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
var POLL_MS = 18e3;
|
|
139
|
+
function useThread(threadId, options) {
|
|
140
|
+
const { http, enabled: featureOn } = useMessengerConfig();
|
|
141
|
+
const enabled = (options?.enabled ?? true) && featureOn && Number.isFinite(threadId) && threadId > 0;
|
|
142
|
+
const poll = options?.poll ?? true;
|
|
143
|
+
return reactQuery.useQuery({
|
|
144
|
+
queryKey: messengerKeys.thread(threadId),
|
|
145
|
+
queryFn: async () => {
|
|
146
|
+
const res = await getThread(http, threadId);
|
|
147
|
+
if (!res.success) {
|
|
148
|
+
throw new Error(res.message ?? "Failed to load thread");
|
|
149
|
+
}
|
|
150
|
+
return res.data;
|
|
151
|
+
},
|
|
152
|
+
enabled,
|
|
153
|
+
refetchInterval: enabled && poll ? POLL_MS : false
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function useSendMessage(threadId) {
|
|
157
|
+
const { http } = useMessengerConfig();
|
|
158
|
+
const qc = reactQuery.useQueryClient();
|
|
159
|
+
return reactQuery.useMutation({
|
|
160
|
+
mutationFn: async (input) => {
|
|
161
|
+
const res = await sendMessage(http, threadId, input.body, input.image);
|
|
162
|
+
if (!res.success || !res.data?.message) {
|
|
163
|
+
throw new Error(res.message ?? "Send failed");
|
|
164
|
+
}
|
|
165
|
+
return res.data.message;
|
|
166
|
+
},
|
|
167
|
+
onSuccess: (message) => {
|
|
168
|
+
qc.setQueryData(
|
|
169
|
+
messengerKeys.thread(threadId),
|
|
170
|
+
(prev) => {
|
|
171
|
+
if (!prev) return prev;
|
|
172
|
+
if (prev.messages.some((m) => m.id === message.id)) return prev;
|
|
173
|
+
return { ...prev, messages: [...prev.messages, message] };
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function useOpenThread() {
|
|
181
|
+
const { http, navigation } = useMessengerConfig();
|
|
182
|
+
const qc = reactQuery.useQueryClient();
|
|
183
|
+
return reactQuery.useMutation({
|
|
184
|
+
mutationFn: async (clientUserId) => {
|
|
185
|
+
const res = await openThread(http, clientUserId);
|
|
186
|
+
if (!res.success || !res.data?.thread?.id) {
|
|
187
|
+
throw new Error(res.message ?? "Could not open thread");
|
|
188
|
+
}
|
|
189
|
+
return res.data.thread;
|
|
190
|
+
},
|
|
191
|
+
onSuccess: (thread) => {
|
|
192
|
+
void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
|
|
193
|
+
navigation.openThread(thread.id);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
exports.MessengerI18n = MessengerI18n;
|
|
199
|
+
exports.MessengerProvider = MessengerProvider;
|
|
200
|
+
exports.getThread = getThread;
|
|
201
|
+
exports.listThreads = listThreads;
|
|
202
|
+
exports.markThreadRead = markThreadRead;
|
|
203
|
+
exports.messengerKeys = messengerKeys;
|
|
204
|
+
exports.openThread = openThread;
|
|
205
|
+
exports.sendMessage = sendMessage;
|
|
206
|
+
exports.sumUnread = sumUnread;
|
|
207
|
+
exports.useMessengerConfig = useMessengerConfig;
|
|
208
|
+
exports.useOpenThread = useOpenThread;
|
|
209
|
+
exports.useOptionalMessengerConfig = useOptionalMessengerConfig;
|
|
210
|
+
exports.useSendMessage = useSendMessage;
|
|
211
|
+
exports.useThread = useThread;
|
|
212
|
+
exports.useThreads = useThreads;
|
|
213
|
+
//# sourceMappingURL=chunk-42TCO5DV.cjs.map
|
|
214
|
+
//# sourceMappingURL=chunk-42TCO5DV.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api.ts","../src/queryKeys.ts","../src/i18nKeys.ts","../src/provider.tsx","../src/hooks/useThreads.ts","../src/hooks/useThread.ts","../src/hooks/useSendMessage.ts","../src/hooks/useOpenThread.ts"],"names":["createContext","useContext","useQuery","useQueryClient","useMutation"],"mappings":";;;;;;;AAQA,eAAsB,YACpB,IAAA,EACqC;AACrC,EAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,OAAA;AAAA,IACrB;AAAA,GACF;AACA,EAAA,OAAO,EAAE,GAAG,GAAA,EAAK,IAAA,EAAM,IAAI,IAAA,EAAM,OAAA,IAAW,EAAC,EAAE;AACjD;AAEA,eAAsB,UAAA,CACpB,MACA,YAAA,EAC+C;AAC/C,EAAA,OAAO,IAAA,CAAK,SAAoC,uBAAA,EAAyB;AAAA,IACvE,GAAI,YAAA,GAAe,EAAE,cAAA,EAAgB,YAAA,KAAiB;AAAC,GACxD,CAAA;AACH;AAEA,eAAsB,SAAA,CACpB,IAAA,EACA,QAAA,EACA,KAAA,GAAQ,IACR,QAAA,EACwE;AACxE,EAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,EAAE,OAAO,MAAA,CAAO,KAAK,GAAG,CAAA;AAC3D,EAAA,IAAI,YAAY,IAAA,EAAM,MAAA,CAAO,IAAI,WAAA,EAAa,MAAA,CAAO,QAAQ,CAAC,CAAA;AAC9D,EAAA,OAAO,IAAA,CAAK,QAAQ,CAAA,sBAAA,EAAyB,QAAQ,IAAI,MAAA,CAAO,QAAA,EAAU,CAAA,CAAE,CAAA;AAC9E;AAEA,eAAsB,WAAA,CACpB,IAAA,EACA,QAAA,EACA,IAAA,EACA,KAAA,EAC8C;AAC9C,EAAA,MAAM,IAAA,GAAO,yBAAyB,QAAQ,CAAA,SAAA,CAAA;AAC9C,EAAA,IAAI,KAAA,EAAO,GAAA,IAAO,KAAA,EAAO,IAAA,EAAM;AAC7B,IAAA,MAAM,IAAA,GAAO,IAAI,QAAA,EAAS;AAC1B,IAAA,IAAI,IAAA,CAAK,MAAK,EAAG,IAAA,CAAK,OAAO,MAAA,EAAQ,IAAA,CAAK,MAAM,CAAA;AAChD,IAAA,MAAM,QAAA,GACJ,MAAM,IAAA,IACN,KAAA,CAAM,KAAK,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,EAAI,IAC1B,WAAA;AACF,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,IAAA,CAAK,QAAQ,CAAA;AACtC,IAAA,MAAM,IAAA,GACJ,MAAM,IAAA,KAAS,KAAA,GAAQ,SAAS,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,GAAK,YAAA,CAAA;AAE/C,IAAA,IAAI,MAAM,IAAA,EAAM;AACd,MAAA,IAAA,CAAK,MAAA,CAAO,OAAA,EAAS,KAAA,CAAM,IAAA,EAAM,QAAQ,CAAA;AAAA,IAC3C,CAAA,MAAA,IAAW,KAAA,CAAM,GAAA,CAAI,UAAA,CAAW,OAAO,KAAK,KAAA,CAAM,GAAA,CAAI,UAAA,CAAW,OAAO,CAAA,EAAG;AACzE,MAAA,MAAM,IAAA,GAAO,MAAM,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,EAAM,CAAA;AACxD,MAAA,IAAA,CAAK,MAAA,CAAO,OAAA,EAAS,IAAA,EAAM,QAAQ,CAAA;AAAA,IACrC,CAAA,MAAO;AAEL,MAAA,IAAA,CAAK,OAAO,OAAA,EAAS;AAAA,QACnB,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,IAAA,EAAM,QAAA;AAAA,QACN;AAAA,OACkB,CAAA;AAAA,IACtB;AACA,IAAA,OAAO,IAAA,CAAK,QAAA,CAAmC,IAAA,EAAM,IAAI,CAAA;AAAA,EAC3D;AACA,EAAA,OAAO,IAAA,CAAK,QAAA,CAAmC,IAAA,EAAM,EAAE,MAAM,CAAA;AAC/D;AAEA,eAAsB,cAAA,CACpB,MACA,QAAA,EAC0C;AAC1C,EAAA,OAAO,KAAK,SAAA,CAAU,CAAA,sBAAA,EAAyB,QAAQ,CAAA,KAAA,CAAA,EAAS,EAAE,CAAA;AACpE;AAEO,SAAS,UAAU,OAAA,EAAkC;AAC1D,EAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,CAAC,GAAA,EAAK,EAAA,KAAO,GAAA,GAAM,MAAA,CAAO,EAAA,CAAG,YAAA,IAAgB,CAAC,CAAA,EAAG,CAAC,CAAA;AAC1E;;;AClFO,IAAM,aAAA,GAAgB;AAAA,EAC3B,GAAA,EAAK,CAAC,WAAW,CAAA;AAAA,EACjB,SAAS,MAAM,CAAC,GAAG,aAAA,CAAc,KAAK,SAAS,CAAA;AAAA,EAC/C,MAAA,EAAQ,CAAC,EAAA,KAAe,CAAC,GAAG,aAAA,CAAc,GAAA,EAAK,UAAU,EAAE;AAC7D;;;ACHO,IAAM,aAAA,GAAgB;AAAA,EAC3B,GAAA,EAAK;AAAA,IACH,GAAA,EAAK,sBAAA;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,KAAA,EAAO,EAAE,GAAA,EAAK,wBAAA,EAA0B,UAAU,MAAA,EAAO;AAAA,EACzD,IAAA,EAAM,EAAE,GAAA,EAAK,uBAAA,EAAyB,UAAU,MAAA,EAAO;AAAA;AAAA,EAEvD,QAAA,EAAU,EAAE,GAAA,EAAK,2BAAA,EAA6B,UAAU,MAAA,EAAO;AAAA,EAC/D,YAAA,EAAc;AAAA,IACZ,GAAA,EAAK,+BAAA;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,GAAA,EAAK,+BAAA;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,KAAA,EAAO;AAAA,IACL,GAAA,EAAK,wBAAA;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,SAAA,EAAW;AAAA,IACT,GAAA,EAAK,4BAAA;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,KAAA,EAAO,EAAE,GAAA,EAAK,wBAAA,EAA0B,UAAU,OAAA,EAAQ;AAAA,EAC1D,UAAA,EAAY;AAAA,IACV,GAAA,EAAK,6BAAA;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,iBAAA,EAAmB;AAAA,IACjB,GAAA,EAAK,oCAAA;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,WAAA,EAAa;AAAA,IACX,GAAA,EAAK,8BAAA;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,MAAA,EAAQ,EAAE,GAAA,EAAK,uBAAA,EAAyB,UAAU,QAAA,EAAS;AAAA,EAC3D,IAAA,EAAM,EAAE,GAAA,EAAK,aAAA,EAAe,UAAU,MAAA;AACxC;ACtCA,IAAM,gBAAA,GAAmBA,oBAAsC,IAAI,CAAA;AAE5D,SAAS,iBAAA,CAAkB;AAAA,EAChC,MAAA;AAAA,EACA;AACF,CAAA,EAGG;AACD,EAAA,sCACG,gBAAA,CAAiB,QAAA,EAAjB,EAA0B,KAAA,EAAO,QAC/B,QAAA,EACH,CAAA;AAEJ;AAEO,SAAS,kBAAA,GAAsC;AACpD,EAAA,MAAM,GAAA,GAAMC,iBAAW,gBAAgB,CAAA;AACvC,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAGO,SAAS,0BAAA,GAAqD;AACnE,EAAA,OAAOA,iBAAW,gBAAgB,CAAA;AACpC;AC3BO,SAAS,WAAW,OAAA,EAAiC;AAC1D,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,SAAA,KAAc,kBAAA,EAAmB;AACxD,EAAA,MAAM,OAAA,GAAA,CAAW,OAAA,EAAS,OAAA,IAAW,IAAA,KAAS,SAAA;AAE9C,EAAA,OAAOC,mBAAA,CAAS;AAAA,IACd,QAAA,EAAU,cAAc,OAAA,EAAQ;AAAA,IAChC,SAAS,YAAY;AACnB,MAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAY,IAAI,CAAA;AAClC,MAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,GAAA,CAAI,OAAA,IAAW,wBAAwB,CAAA;AAAA,MACzD;AACA,MAAA,OAAO,GAAA,CAAI,IAAA;AAAA,IACb,CAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH;ACfA,IAAM,OAAA,GAAU,IAAA;AAET,SAAS,SAAA,CACd,UACA,OAAA,EACA;AACA,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,SAAA,KAAc,kBAAA,EAAmB;AACxD,EAAA,MAAM,OAAA,GAAA,CACH,SAAS,OAAA,IAAW,IAAA,KAAS,aAAa,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA,IAAK,QAAA,GAAW,CAAA;AACrF,EAAA,MAAM,IAAA,GAAO,SAAS,IAAA,IAAQ,IAAA;AAE9B,EAAA,OAAOA,mBAAAA,CAAS;AAAA,IACd,QAAA,EAAU,aAAA,CAAc,MAAA,CAAO,QAAQ,CAAA;AAAA,IACvC,SAAS,YAAY;AACnB,MAAA,MAAM,GAAA,GAAM,MAAM,SAAA,CAAU,IAAA,EAAM,QAAQ,CAAA;AAC1C,MAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,GAAA,CAAI,OAAA,IAAW,uBAAuB,CAAA;AAAA,MACxD;AACA,MAAA,OAAO,GAAA,CAAI,IAAA;AAAA,IACb,CAAA;AAAA,IACA,OAAA;AAAA,IACA,eAAA,EAAiB,OAAA,IAAW,IAAA,GAAO,OAAA,GAAU;AAAA,GAC9C,CAAA;AACH;ACtBO,SAAS,eAAe,QAAA,EAAkB;AAC/C,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,kBAAA,EAAmB;AACpC,EAAA,MAAM,KAAKC,yBAAA,EAAe;AAE1B,EAAA,OAAOC,sBAAA,CAAY;AAAA,IACjB,UAAA,EAAY,OAAO,KAAA,KAAwD;AACzE,MAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAY,IAAA,EAAM,UAAU,KAAA,CAAM,IAAA,EAAM,MAAM,KAAK,CAAA;AACrE,MAAA,IAAI,CAAC,GAAA,CAAI,OAAA,IAAW,CAAC,GAAA,CAAI,MAAM,OAAA,EAAS;AACtC,QAAA,MAAM,IAAI,KAAA,CAAM,GAAA,CAAI,OAAA,IAAW,aAAa,CAAA;AAAA,MAC9C;AACA,MAAA,OAAO,IAAI,IAAA,CAAK,OAAA;AAAA,IAClB,CAAA;AAAA,IACA,SAAA,EAAW,CAAC,OAAA,KAAyB;AACnC,MAAA,EAAA,CAAG,YAAA;AAAA,QACD,aAAA,CAAc,OAAO,QAAQ,CAAA;AAAA,QAC7B,CAAC,IAAA,KAAmE;AAClE,UAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,UAAA,IAAI,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,EAAA,KAAO,OAAA,CAAQ,EAAE,CAAA,EAAG,OAAO,IAAA;AAC3D,UAAA,OAAO,EAAE,GAAG,IAAA,EAAM,QAAA,EAAU,CAAC,GAAG,IAAA,CAAK,QAAA,EAAU,OAAO,CAAA,EAAE;AAAA,QAC1D;AAAA,OACF;AACA,MAAA,KAAK,GAAG,iBAAA,CAAkB,EAAE,UAAU,aAAA,CAAc,OAAA,IAAW,CAAA;AAAA,IACjE;AAAA,GACD,CAAA;AACH;ACxBO,SAAS,aAAA,GAAgB;AAC9B,EAAA,MAAM,EAAE,IAAA,EAAM,UAAA,EAAW,GAAI,kBAAA,EAAmB;AAChD,EAAA,MAAM,KAAKD,yBAAAA,EAAe;AAE1B,EAAA,OAAOC,sBAAAA,CAAY;AAAA,IACjB,UAAA,EAAY,OAAO,YAAA,KAA0B;AAC3C,MAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,IAAA,EAAM,YAAY,CAAA;AAC/C,MAAA,IAAI,CAAC,GAAA,CAAI,OAAA,IAAW,CAAC,GAAA,CAAI,IAAA,EAAM,QAAQ,EAAA,EAAI;AACzC,QAAA,MAAM,IAAI,KAAA,CAAM,GAAA,CAAI,OAAA,IAAW,uBAAuB,CAAA;AAAA,MACxD;AACA,MAAA,OAAO,IAAI,IAAA,CAAK,MAAA;AAAA,IAClB,CAAA;AAAA,IACA,SAAA,EAAW,CAAC,MAAA,KAA0B;AACpC,MAAA,KAAK,GAAG,iBAAA,CAAkB,EAAE,UAAU,aAAA,CAAc,OAAA,IAAW,CAAA;AAC/D,MAAA,UAAA,CAAW,UAAA,CAAW,OAAO,EAAE,CAAA;AAAA,IACjC;AAAA,GACD,CAAA;AACH","file":"chunk-42TCO5DV.cjs","sourcesContent":["import type {\n ApiResult,\n ChatMessage,\n MessageThread,\n MessengerHttp,\n PickedImage,\n} from \"./types\";\n\nexport async function listThreads(\n http: MessengerHttp\n): Promise<ApiResult<MessageThread[]>> {\n const res = await http.getJson<{ threads: MessageThread[] }>(\n \"/api/messages/threads\"\n );\n return { ...res, data: res.data?.threads ?? [] };\n}\n\nexport async function openThread(\n http: MessengerHttp,\n clientUserId?: number\n): Promise<ApiResult<{ thread: MessageThread }>> {\n return http.postJson<{ thread: MessageThread }>(\"/api/messages/threads\", {\n ...(clientUserId ? { client_user_id: clientUserId } : {}),\n });\n}\n\nexport async function getThread(\n http: MessengerHttp,\n threadId: number,\n limit = 50,\n beforeId?: number\n): Promise<ApiResult<{ thread: MessageThread; messages: ChatMessage[] }>> {\n const params = new URLSearchParams({ limit: String(limit) });\n if (beforeId != null) params.set(\"before_id\", String(beforeId));\n return http.getJson(`/api/messages/threads/${threadId}?${params.toString()}`);\n}\n\nexport async function sendMessage(\n http: MessengerHttp,\n threadId: number,\n body: string,\n image?: PickedImage | null\n): Promise<ApiResult<{ message: ChatMessage }>> {\n const path = `/api/messages/threads/${threadId}/messages`;\n if (image?.uri || image?.file) {\n const form = new FormData();\n if (body.trim()) form.append(\"body\", body.trim());\n const filename =\n image.name ??\n image.uri?.split(\"/\").pop() ??\n \"photo.jpg\";\n const match = /\\.(\\w+)$/.exec(filename);\n const type =\n image.type ?? (match ? `image/${match[1]}` : \"image/jpeg\");\n\n if (image.file) {\n form.append(\"image\", image.file, filename);\n } else if (image.uri.startsWith(\"blob:\") || image.uri.startsWith(\"data:\")) {\n const blob = await fetch(image.uri).then((r) => r.blob());\n form.append(\"image\", blob, filename);\n } else {\n // React Native FormData file shape\n form.append(\"image\", {\n uri: image.uri,\n name: filename,\n type,\n } as unknown as Blob);\n }\n return http.postForm<{ message: ChatMessage }>(path, form);\n }\n return http.postJson<{ message: ChatMessage }>(path, { body });\n}\n\nexport async function markThreadRead(\n http: MessengerHttp,\n threadId: number\n): Promise<ApiResult<{ success: boolean }>> {\n return http.patchJson(`/api/messages/threads/${threadId}/read`, {});\n}\n\nexport function sumUnread(threads: MessageThread[]): number {\n return threads.reduce((sum, th) => sum + Number(th.unread_count ?? 0), 0);\n}\n","export const messengerKeys = {\n all: [\"messenger\"] as const,\n threads: () => [...messengerKeys.all, \"threads\"] as const,\n thread: (id: number) => [...messengerKeys.all, \"thread\", id] as const,\n};\n","/** Stable i18n keys + English fallbacks used by the package UI. */\nexport const MessengerI18n = {\n off: {\n key: \"mobile.messenger.off\",\n fallback: \"Messaging is not enabled for this account.\",\n },\n inbox: { key: \"mobile.messenger.inbox\", fallback: \"Chat\" },\n chat: { key: \"mobile.messenger.chat\", fallback: \"Chat\" },\n /** Dock / list title — prefer over inbox for web UI copy. */\n messages: { key: \"mobile.messenger.messages\", fallback: \"Chat\" },\n messagesHint: {\n key: \"mobile.messenger.messagesHint\",\n fallback: \"Message your coach anytime\",\n },\n messageCoach: {\n key: \"mobile.messenger.messageCoach\",\n fallback: \"Message your coach\",\n },\n empty: {\n key: \"mobile.messenger.empty\",\n fallback: \"No conversations yet\",\n },\n yourCoach: {\n key: \"mobile.messenger.yourCoach\",\n fallback: \"Your coach\",\n },\n photo: { key: \"mobile.messenger.photo\", fallback: \"Photo\" },\n noMessages: {\n key: \"mobile.messenger.noMessages\",\n fallback: \"No messages yet\",\n },\n startConversation: {\n key: \"mobile.messenger.startConversation\",\n fallback: \"Say hello — send the first message.\",\n },\n typeMessage: {\n key: \"mobile.messenger.typeMessage\",\n fallback: \"Type a message…\",\n },\n client: { key: \"mobile.trainer.client\", fallback: \"Client\" },\n back: { key: \"common.back\", fallback: \"Back\" },\n} as const;\n","import React, { createContext, useContext, type ReactNode } from \"react\";\nimport type { MessengerConfig } from \"./types\";\n\nconst MessengerContext = createContext<MessengerConfig | null>(null);\n\nexport function MessengerProvider({\n config,\n children,\n}: {\n config: MessengerConfig;\n children: ReactNode;\n}) {\n return (\n <MessengerContext.Provider value={config}>\n {children}\n </MessengerContext.Provider>\n );\n}\n\nexport function useMessengerConfig(): MessengerConfig {\n const ctx = useContext(MessengerContext);\n if (!ctx) {\n throw new Error(\n \"@gymlium/messenger: useMessengerConfig must be used within MessengerProvider\"\n );\n }\n return ctx;\n}\n\n/** Safe variant for optional host chrome (returns null outside provider). */\nexport function useOptionalMessengerConfig(): MessengerConfig | null {\n return useContext(MessengerContext);\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport { listThreads } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useMessengerConfig } from \"../provider\";\n\nexport function useThreads(options?: { enabled?: boolean }) {\n const { http, enabled: featureOn } = useMessengerConfig();\n const enabled = (options?.enabled ?? true) && featureOn;\n\n return useQuery({\n queryKey: messengerKeys.threads(),\n queryFn: async () => {\n const res = await listThreads(http);\n if (!res.success) {\n throw new Error(res.message ?? \"Failed to load threads\");\n }\n return res.data;\n },\n enabled,\n });\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport { getThread } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useMessengerConfig } from \"../provider\";\n\nconst POLL_MS = 18_000;\n\nexport function useThread(\n threadId: number,\n options?: { enabled?: boolean; poll?: boolean }\n) {\n const { http, enabled: featureOn } = useMessengerConfig();\n const enabled =\n (options?.enabled ?? true) && featureOn && Number.isFinite(threadId) && threadId > 0;\n const poll = options?.poll ?? true;\n\n return useQuery({\n queryKey: messengerKeys.thread(threadId),\n queryFn: async () => {\n const res = await getThread(http, threadId);\n if (!res.success) {\n throw new Error(res.message ?? \"Failed to load thread\");\n }\n return res.data;\n },\n enabled,\n refetchInterval: enabled && poll ? POLL_MS : false,\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { sendMessage } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useMessengerConfig } from \"../provider\";\nimport type { ChatMessage, PickedImage } from \"../types\";\n\nexport function useSendMessage(threadId: number) {\n const { http } = useMessengerConfig();\n const qc = useQueryClient();\n\n return useMutation({\n mutationFn: async (input: { body: string; image?: PickedImage | null }) => {\n const res = await sendMessage(http, threadId, input.body, input.image);\n if (!res.success || !res.data?.message) {\n throw new Error(res.message ?? \"Send failed\");\n }\n return res.data.message as ChatMessage;\n },\n onSuccess: (message: ChatMessage) => {\n qc.setQueryData(\n messengerKeys.thread(threadId),\n (prev: { thread: unknown; messages: ChatMessage[] } | undefined) => {\n if (!prev) return prev;\n if (prev.messages.some((m) => m.id === message.id)) return prev;\n return { ...prev, messages: [...prev.messages, message] };\n }\n );\n void qc.invalidateQueries({ queryKey: messengerKeys.threads() });\n },\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { openThread } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useMessengerConfig } from \"../provider\";\nimport type { MessageThread } from \"../types\";\n\nexport function useOpenThread() {\n const { http, navigation } = useMessengerConfig();\n const qc = useQueryClient();\n\n return useMutation({\n mutationFn: async (clientUserId?: number) => {\n const res = await openThread(http, clientUserId);\n if (!res.success || !res.data?.thread?.id) {\n throw new Error(res.message ?? \"Could not open thread\");\n }\n return res.data.thread as MessageThread;\n },\n onSuccess: (thread: MessageThread) => {\n void qc.invalidateQueries({ queryKey: messengerKeys.threads() });\n navigation.openThread(thread.id);\n },\n });\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk42TCO5DV_cjs = require('./chunk-42TCO5DV.cjs');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var reactQuery = require('@tanstack/react-query');
|
|
7
|
+
|
|
8
|
+
var MessengerUiContext = react.createContext(null);
|
|
9
|
+
function MessengerUiProvider({ children }) {
|
|
10
|
+
const [open, setOpen] = react.useState(false);
|
|
11
|
+
const [threadId, setThreadId] = react.useState(null);
|
|
12
|
+
const openInbox = react.useCallback(() => {
|
|
13
|
+
setThreadId(null);
|
|
14
|
+
setOpen(true);
|
|
15
|
+
}, []);
|
|
16
|
+
const openThread = react.useCallback((id) => {
|
|
17
|
+
setThreadId(id);
|
|
18
|
+
setOpen(true);
|
|
19
|
+
}, []);
|
|
20
|
+
const close = react.useCallback(() => {
|
|
21
|
+
setOpen(false);
|
|
22
|
+
}, []);
|
|
23
|
+
const value = react.useMemo(
|
|
24
|
+
() => ({
|
|
25
|
+
open,
|
|
26
|
+
threadId,
|
|
27
|
+
openInbox,
|
|
28
|
+
openThread,
|
|
29
|
+
close,
|
|
30
|
+
setOpen
|
|
31
|
+
}),
|
|
32
|
+
[open, threadId, openInbox, openThread, close]
|
|
33
|
+
);
|
|
34
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MessengerUiContext.Provider, { value, children });
|
|
35
|
+
}
|
|
36
|
+
function useMessengerUi() {
|
|
37
|
+
const ctx = react.useContext(MessengerUiContext);
|
|
38
|
+
if (!ctx) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"@gymlium/messenger: useMessengerUi must be used within MessengerUiProvider"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return ctx;
|
|
44
|
+
}
|
|
45
|
+
function useOptionalMessengerUi() {
|
|
46
|
+
return react.useContext(MessengerUiContext);
|
|
47
|
+
}
|
|
48
|
+
function useUnreadTotal(options) {
|
|
49
|
+
const cfg = chunk42TCO5DV_cjs.useOptionalMessengerConfig();
|
|
50
|
+
const featureOn = cfg?.enabled === true;
|
|
51
|
+
const http = cfg?.http;
|
|
52
|
+
const enabled = (options?.enabled ?? true) && featureOn && !!http;
|
|
53
|
+
const query = reactQuery.useQuery({
|
|
54
|
+
queryKey: chunk42TCO5DV_cjs.messengerKeys.threads(),
|
|
55
|
+
queryFn: async () => {
|
|
56
|
+
const res = await chunk42TCO5DV_cjs.listThreads(http);
|
|
57
|
+
if (!res.success) {
|
|
58
|
+
throw new Error(res.message ?? "Failed to load threads");
|
|
59
|
+
}
|
|
60
|
+
return res.data;
|
|
61
|
+
},
|
|
62
|
+
enabled
|
|
63
|
+
});
|
|
64
|
+
const total = react.useMemo(
|
|
65
|
+
() => query.data ? chunk42TCO5DV_cjs.sumUnread(query.data) : 0,
|
|
66
|
+
[query.data]
|
|
67
|
+
);
|
|
68
|
+
return {
|
|
69
|
+
total,
|
|
70
|
+
isLoading: query.isLoading,
|
|
71
|
+
isFetching: query.isFetching,
|
|
72
|
+
refetch: query.refetch,
|
|
73
|
+
enabled: featureOn
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.MessengerUiProvider = MessengerUiProvider;
|
|
78
|
+
exports.useMessengerUi = useMessengerUi;
|
|
79
|
+
exports.useOptionalMessengerUi = useOptionalMessengerUi;
|
|
80
|
+
exports.useUnreadTotal = useUnreadTotal;
|
|
81
|
+
//# sourceMappingURL=chunk-M2F3K6UX.cjs.map
|
|
82
|
+
//# sourceMappingURL=chunk-M2F3K6UX.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/uiController.tsx","../src/hooks/useUnreadTotal.ts"],"names":["createContext","useState","useCallback","useMemo","jsx","useContext","useOptionalMessengerConfig","useQuery","messengerKeys","listThreads","sumUnread"],"mappings":";;;;;;;AAkBA,IAAM,kBAAA,GAAqBA,oBAAuC,IAAI,CAAA;AAE/D,SAAS,mBAAA,CAAoB,EAAE,QAAA,EAAS,EAA4B;AACzE,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIC,eAAS,KAAK,CAAA;AACtC,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAwB,IAAI,CAAA;AAE5D,EAAA,MAAM,SAAA,GAAYC,kBAAY,MAAM;AAClC,IAAA,WAAA,CAAY,IAAI,CAAA;AAChB,IAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,EACd,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAaA,iBAAA,CAAY,CAAC,EAAA,KAAe;AAC7C,IAAA,WAAA,CAAY,EAAE,CAAA;AACd,IAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,EACd,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQA,kBAAY,MAAM;AAC9B,IAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQC,aAAA;AAAA,IACZ,OAAO;AAAA,MACL,IAAA;AAAA,MACA,QAAA;AAAA,MACA,SAAA;AAAA,MACA,UAAA;AAAA,MACA,KAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA,CAAC,IAAA,EAAM,QAAA,EAAU,SAAA,EAAW,YAAY,KAAK;AAAA,GAC/C;AAEA,EAAA,uBACEC,cAAA,CAAC,kBAAA,CAAmB,QAAA,EAAnB,EAA4B,OAC1B,QAAA,EACH,CAAA;AAEJ;AAEO,SAAS,cAAA,GAAmC;AACjD,EAAA,MAAM,GAAA,GAAMC,iBAAW,kBAAkB,CAAA;AACzC,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,sBAAA,GAAkD;AAChE,EAAA,OAAOA,iBAAW,kBAAkB,CAAA;AACtC;AC3DO,SAAS,eAAe,OAAA,EAAiC;AAC9D,EAAA,MAAM,MAAMC,4CAAA,EAA2B;AACvC,EAAA,MAAM,SAAA,GAAY,KAAK,OAAA,KAAY,IAAA;AACnC,EAAA,MAAM,OAAO,GAAA,EAAK,IAAA;AAClB,EAAA,MAAM,WAAW,OAAA,EAAS,OAAA,IAAW,IAAA,KAAS,SAAA,IAAa,CAAC,CAAC,IAAA;AAE7D,EAAA,MAAM,QAAQC,mBAAA,CAAS;AAAA,IACrB,QAAA,EAAUC,gCAAc,OAAA,EAAQ;AAAA,IAChC,SAAS,YAAY;AACnB,MAAA,MAAM,GAAA,GAAM,MAAMC,6BAAA,CAAY,IAAK,CAAA;AACnC,MAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,GAAA,CAAI,OAAA,IAAW,wBAAwB,CAAA;AAAA,MACzD;AACA,MAAA,OAAO,GAAA,CAAI,IAAA;AAAA,IACb,CAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,KAAA,GAAQN,aAAAA;AAAA,IACZ,MAAO,KAAA,CAAM,IAAA,GAAOO,2BAAA,CAAU,KAAA,CAAM,IAAI,CAAA,GAAI,CAAA;AAAA,IAC5C,CAAC,MAAM,IAAI;AAAA,GACb;AAEA,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,WAAW,KAAA,CAAM,SAAA;AAAA,IACjB,YAAY,KAAA,CAAM,UAAA;AAAA,IAClB,SAAS,KAAA,CAAM,OAAA;AAAA,IACf,OAAA,EAAS;AAAA,GACX;AACF","file":"chunk-M2F3K6UX.cjs","sourcesContent":["import React, {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nexport interface MessengerUiState {\n open: boolean;\n threadId: number | null;\n openInbox: () => void;\n openThread: (threadId: number) => void;\n close: () => void;\n setOpen: (open: boolean) => void;\n}\n\nconst MessengerUiContext = createContext<MessengerUiState | null>(null);\n\nexport function MessengerUiProvider({ children }: { children: ReactNode }) {\n const [open, setOpen] = useState(false);\n const [threadId, setThreadId] = useState<number | null>(null);\n\n const openInbox = useCallback(() => {\n setThreadId(null);\n setOpen(true);\n }, []);\n\n const openThread = useCallback((id: number) => {\n setThreadId(id);\n setOpen(true);\n }, []);\n\n const close = useCallback(() => {\n setOpen(false);\n }, []);\n\n const value = useMemo(\n () => ({\n open,\n threadId,\n openInbox,\n openThread,\n close,\n setOpen,\n }),\n [open, threadId, openInbox, openThread, close]\n );\n\n return (\n <MessengerUiContext.Provider value={value}>\n {children}\n </MessengerUiContext.Provider>\n );\n}\n\nexport function useMessengerUi(): MessengerUiState {\n const ctx = useContext(MessengerUiContext);\n if (!ctx) {\n throw new Error(\n \"@gymlium/messenger: useMessengerUi must be used within MessengerUiProvider\"\n );\n }\n return ctx;\n}\n\nexport function useOptionalMessengerUi(): MessengerUiState | null {\n return useContext(MessengerUiContext);\n}\n","import { useMemo } from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { listThreads, sumUnread } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useOptionalMessengerConfig } from \"../provider\";\n\n/**\n * Total unread across threads. Works outside MessengerProvider (returns 0).\n * Prefer mounting MessengerProvider above host chrome that needs this.\n */\nexport function useUnreadTotal(options?: { enabled?: boolean }) {\n const cfg = useOptionalMessengerConfig();\n const featureOn = cfg?.enabled === true;\n const http = cfg?.http;\n const enabled = (options?.enabled ?? true) && featureOn && !!http;\n\n const query = useQuery({\n queryKey: messengerKeys.threads(),\n queryFn: async () => {\n const res = await listThreads(http!);\n if (!res.success) {\n throw new Error(res.message ?? \"Failed to load threads\");\n }\n return res.data;\n },\n enabled,\n });\n\n const total = useMemo(\n () => (query.data ? sumUnread(query.data) : 0),\n [query.data]\n );\n\n return {\n total,\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n refetch: query.refetch,\n enabled: featureOn,\n };\n}\n"]}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
|
|
4
|
+
|
|
5
|
+
// src/api.ts
|
|
6
|
+
async function listThreads(http) {
|
|
7
|
+
const res = await http.getJson(
|
|
8
|
+
"/api/messages/threads"
|
|
9
|
+
);
|
|
10
|
+
return { ...res, data: res.data?.threads ?? [] };
|
|
11
|
+
}
|
|
12
|
+
async function openThread(http, clientUserId) {
|
|
13
|
+
return http.postJson("/api/messages/threads", {
|
|
14
|
+
...clientUserId ? { client_user_id: clientUserId } : {}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async function getThread(http, threadId, limit = 50, beforeId) {
|
|
18
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
19
|
+
if (beforeId != null) params.set("before_id", String(beforeId));
|
|
20
|
+
return http.getJson(`/api/messages/threads/${threadId}?${params.toString()}`);
|
|
21
|
+
}
|
|
22
|
+
async function sendMessage(http, threadId, body, image) {
|
|
23
|
+
const path = `/api/messages/threads/${threadId}/messages`;
|
|
24
|
+
if (image?.uri || image?.file) {
|
|
25
|
+
const form = new FormData();
|
|
26
|
+
if (body.trim()) form.append("body", body.trim());
|
|
27
|
+
const filename = image.name ?? image.uri?.split("/").pop() ?? "photo.jpg";
|
|
28
|
+
const match = /\.(\w+)$/.exec(filename);
|
|
29
|
+
const type = image.type ?? (match ? `image/${match[1]}` : "image/jpeg");
|
|
30
|
+
if (image.file) {
|
|
31
|
+
form.append("image", image.file, filename);
|
|
32
|
+
} else if (image.uri.startsWith("blob:") || image.uri.startsWith("data:")) {
|
|
33
|
+
const blob = await fetch(image.uri).then((r) => r.blob());
|
|
34
|
+
form.append("image", blob, filename);
|
|
35
|
+
} else {
|
|
36
|
+
form.append("image", {
|
|
37
|
+
uri: image.uri,
|
|
38
|
+
name: filename,
|
|
39
|
+
type
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return http.postForm(path, form);
|
|
43
|
+
}
|
|
44
|
+
return http.postJson(path, { body });
|
|
45
|
+
}
|
|
46
|
+
async function markThreadRead(http, threadId) {
|
|
47
|
+
return http.patchJson(`/api/messages/threads/${threadId}/read`, {});
|
|
48
|
+
}
|
|
49
|
+
function sumUnread(threads) {
|
|
50
|
+
return threads.reduce((sum, th) => sum + Number(th.unread_count ?? 0), 0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/queryKeys.ts
|
|
54
|
+
var messengerKeys = {
|
|
55
|
+
all: ["messenger"],
|
|
56
|
+
threads: () => [...messengerKeys.all, "threads"],
|
|
57
|
+
thread: (id) => [...messengerKeys.all, "thread", id]
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/i18nKeys.ts
|
|
61
|
+
var MessengerI18n = {
|
|
62
|
+
off: {
|
|
63
|
+
key: "mobile.messenger.off",
|
|
64
|
+
fallback: "Messaging is not enabled for this account."
|
|
65
|
+
},
|
|
66
|
+
inbox: { key: "mobile.messenger.inbox", fallback: "Chat" },
|
|
67
|
+
chat: { key: "mobile.messenger.chat", fallback: "Chat" },
|
|
68
|
+
/** Dock / list title — prefer over inbox for web UI copy. */
|
|
69
|
+
messages: { key: "mobile.messenger.messages", fallback: "Chat" },
|
|
70
|
+
messagesHint: {
|
|
71
|
+
key: "mobile.messenger.messagesHint",
|
|
72
|
+
fallback: "Message your coach anytime"
|
|
73
|
+
},
|
|
74
|
+
messageCoach: {
|
|
75
|
+
key: "mobile.messenger.messageCoach",
|
|
76
|
+
fallback: "Message your coach"
|
|
77
|
+
},
|
|
78
|
+
empty: {
|
|
79
|
+
key: "mobile.messenger.empty",
|
|
80
|
+
fallback: "No conversations yet"
|
|
81
|
+
},
|
|
82
|
+
yourCoach: {
|
|
83
|
+
key: "mobile.messenger.yourCoach",
|
|
84
|
+
fallback: "Your coach"
|
|
85
|
+
},
|
|
86
|
+
photo: { key: "mobile.messenger.photo", fallback: "Photo" },
|
|
87
|
+
noMessages: {
|
|
88
|
+
key: "mobile.messenger.noMessages",
|
|
89
|
+
fallback: "No messages yet"
|
|
90
|
+
},
|
|
91
|
+
startConversation: {
|
|
92
|
+
key: "mobile.messenger.startConversation",
|
|
93
|
+
fallback: "Say hello \u2014 send the first message."
|
|
94
|
+
},
|
|
95
|
+
typeMessage: {
|
|
96
|
+
key: "mobile.messenger.typeMessage",
|
|
97
|
+
fallback: "Type a message\u2026"
|
|
98
|
+
},
|
|
99
|
+
client: { key: "mobile.trainer.client", fallback: "Client" },
|
|
100
|
+
back: { key: "common.back", fallback: "Back" }
|
|
101
|
+
};
|
|
102
|
+
var MessengerContext = createContext(null);
|
|
103
|
+
function MessengerProvider({
|
|
104
|
+
config,
|
|
105
|
+
children
|
|
106
|
+
}) {
|
|
107
|
+
return /* @__PURE__ */ jsx(MessengerContext.Provider, { value: config, children });
|
|
108
|
+
}
|
|
109
|
+
function useMessengerConfig() {
|
|
110
|
+
const ctx = useContext(MessengerContext);
|
|
111
|
+
if (!ctx) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"@gymlium/messenger: useMessengerConfig must be used within MessengerProvider"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return ctx;
|
|
117
|
+
}
|
|
118
|
+
function useOptionalMessengerConfig() {
|
|
119
|
+
return useContext(MessengerContext);
|
|
120
|
+
}
|
|
121
|
+
function useThreads(options) {
|
|
122
|
+
const { http, enabled: featureOn } = useMessengerConfig();
|
|
123
|
+
const enabled = (options?.enabled ?? true) && featureOn;
|
|
124
|
+
return useQuery({
|
|
125
|
+
queryKey: messengerKeys.threads(),
|
|
126
|
+
queryFn: async () => {
|
|
127
|
+
const res = await listThreads(http);
|
|
128
|
+
if (!res.success) {
|
|
129
|
+
throw new Error(res.message ?? "Failed to load threads");
|
|
130
|
+
}
|
|
131
|
+
return res.data;
|
|
132
|
+
},
|
|
133
|
+
enabled
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
var POLL_MS = 18e3;
|
|
137
|
+
function useThread(threadId, options) {
|
|
138
|
+
const { http, enabled: featureOn } = useMessengerConfig();
|
|
139
|
+
const enabled = (options?.enabled ?? true) && featureOn && Number.isFinite(threadId) && threadId > 0;
|
|
140
|
+
const poll = options?.poll ?? true;
|
|
141
|
+
return useQuery({
|
|
142
|
+
queryKey: messengerKeys.thread(threadId),
|
|
143
|
+
queryFn: async () => {
|
|
144
|
+
const res = await getThread(http, threadId);
|
|
145
|
+
if (!res.success) {
|
|
146
|
+
throw new Error(res.message ?? "Failed to load thread");
|
|
147
|
+
}
|
|
148
|
+
return res.data;
|
|
149
|
+
},
|
|
150
|
+
enabled,
|
|
151
|
+
refetchInterval: enabled && poll ? POLL_MS : false
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function useSendMessage(threadId) {
|
|
155
|
+
const { http } = useMessengerConfig();
|
|
156
|
+
const qc = useQueryClient();
|
|
157
|
+
return useMutation({
|
|
158
|
+
mutationFn: async (input) => {
|
|
159
|
+
const res = await sendMessage(http, threadId, input.body, input.image);
|
|
160
|
+
if (!res.success || !res.data?.message) {
|
|
161
|
+
throw new Error(res.message ?? "Send failed");
|
|
162
|
+
}
|
|
163
|
+
return res.data.message;
|
|
164
|
+
},
|
|
165
|
+
onSuccess: (message) => {
|
|
166
|
+
qc.setQueryData(
|
|
167
|
+
messengerKeys.thread(threadId),
|
|
168
|
+
(prev) => {
|
|
169
|
+
if (!prev) return prev;
|
|
170
|
+
if (prev.messages.some((m) => m.id === message.id)) return prev;
|
|
171
|
+
return { ...prev, messages: [...prev.messages, message] };
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function useOpenThread() {
|
|
179
|
+
const { http, navigation } = useMessengerConfig();
|
|
180
|
+
const qc = useQueryClient();
|
|
181
|
+
return useMutation({
|
|
182
|
+
mutationFn: async (clientUserId) => {
|
|
183
|
+
const res = await openThread(http, clientUserId);
|
|
184
|
+
if (!res.success || !res.data?.thread?.id) {
|
|
185
|
+
throw new Error(res.message ?? "Could not open thread");
|
|
186
|
+
}
|
|
187
|
+
return res.data.thread;
|
|
188
|
+
},
|
|
189
|
+
onSuccess: (thread) => {
|
|
190
|
+
void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
|
|
191
|
+
navigation.openThread(thread.id);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { MessengerI18n, MessengerProvider, getThread, listThreads, markThreadRead, messengerKeys, openThread, sendMessage, sumUnread, useMessengerConfig, useOpenThread, useOptionalMessengerConfig, useSendMessage, useThread, useThreads };
|
|
197
|
+
//# sourceMappingURL=chunk-O5XNJHGZ.js.map
|
|
198
|
+
//# sourceMappingURL=chunk-O5XNJHGZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api.ts","../src/queryKeys.ts","../src/i18nKeys.ts","../src/provider.tsx","../src/hooks/useThreads.ts","../src/hooks/useThread.ts","../src/hooks/useSendMessage.ts","../src/hooks/useOpenThread.ts"],"names":["useQuery","useQueryClient","useMutation"],"mappings":"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","file":"chunk-O5XNJHGZ.js","sourcesContent":["import type {\n ApiResult,\n ChatMessage,\n MessageThread,\n MessengerHttp,\n PickedImage,\n} from \"./types\";\n\nexport async function listThreads(\n http: MessengerHttp\n): Promise<ApiResult<MessageThread[]>> {\n const res = await http.getJson<{ threads: MessageThread[] }>(\n \"/api/messages/threads\"\n );\n return { ...res, data: res.data?.threads ?? [] };\n}\n\nexport async function openThread(\n http: MessengerHttp,\n clientUserId?: number\n): Promise<ApiResult<{ thread: MessageThread }>> {\n return http.postJson<{ thread: MessageThread }>(\"/api/messages/threads\", {\n ...(clientUserId ? { client_user_id: clientUserId } : {}),\n });\n}\n\nexport async function getThread(\n http: MessengerHttp,\n threadId: number,\n limit = 50,\n beforeId?: number\n): Promise<ApiResult<{ thread: MessageThread; messages: ChatMessage[] }>> {\n const params = new URLSearchParams({ limit: String(limit) });\n if (beforeId != null) params.set(\"before_id\", String(beforeId));\n return http.getJson(`/api/messages/threads/${threadId}?${params.toString()}`);\n}\n\nexport async function sendMessage(\n http: MessengerHttp,\n threadId: number,\n body: string,\n image?: PickedImage | null\n): Promise<ApiResult<{ message: ChatMessage }>> {\n const path = `/api/messages/threads/${threadId}/messages`;\n if (image?.uri || image?.file) {\n const form = new FormData();\n if (body.trim()) form.append(\"body\", body.trim());\n const filename =\n image.name ??\n image.uri?.split(\"/\").pop() ??\n \"photo.jpg\";\n const match = /\\.(\\w+)$/.exec(filename);\n const type =\n image.type ?? (match ? `image/${match[1]}` : \"image/jpeg\");\n\n if (image.file) {\n form.append(\"image\", image.file, filename);\n } else if (image.uri.startsWith(\"blob:\") || image.uri.startsWith(\"data:\")) {\n const blob = await fetch(image.uri).then((r) => r.blob());\n form.append(\"image\", blob, filename);\n } else {\n // React Native FormData file shape\n form.append(\"image\", {\n uri: image.uri,\n name: filename,\n type,\n } as unknown as Blob);\n }\n return http.postForm<{ message: ChatMessage }>(path, form);\n }\n return http.postJson<{ message: ChatMessage }>(path, { body });\n}\n\nexport async function markThreadRead(\n http: MessengerHttp,\n threadId: number\n): Promise<ApiResult<{ success: boolean }>> {\n return http.patchJson(`/api/messages/threads/${threadId}/read`, {});\n}\n\nexport function sumUnread(threads: MessageThread[]): number {\n return threads.reduce((sum, th) => sum + Number(th.unread_count ?? 0), 0);\n}\n","export const messengerKeys = {\n all: [\"messenger\"] as const,\n threads: () => [...messengerKeys.all, \"threads\"] as const,\n thread: (id: number) => [...messengerKeys.all, \"thread\", id] as const,\n};\n","/** Stable i18n keys + English fallbacks used by the package UI. */\nexport const MessengerI18n = {\n off: {\n key: \"mobile.messenger.off\",\n fallback: \"Messaging is not enabled for this account.\",\n },\n inbox: { key: \"mobile.messenger.inbox\", fallback: \"Chat\" },\n chat: { key: \"mobile.messenger.chat\", fallback: \"Chat\" },\n /** Dock / list title — prefer over inbox for web UI copy. */\n messages: { key: \"mobile.messenger.messages\", fallback: \"Chat\" },\n messagesHint: {\n key: \"mobile.messenger.messagesHint\",\n fallback: \"Message your coach anytime\",\n },\n messageCoach: {\n key: \"mobile.messenger.messageCoach\",\n fallback: \"Message your coach\",\n },\n empty: {\n key: \"mobile.messenger.empty\",\n fallback: \"No conversations yet\",\n },\n yourCoach: {\n key: \"mobile.messenger.yourCoach\",\n fallback: \"Your coach\",\n },\n photo: { key: \"mobile.messenger.photo\", fallback: \"Photo\" },\n noMessages: {\n key: \"mobile.messenger.noMessages\",\n fallback: \"No messages yet\",\n },\n startConversation: {\n key: \"mobile.messenger.startConversation\",\n fallback: \"Say hello — send the first message.\",\n },\n typeMessage: {\n key: \"mobile.messenger.typeMessage\",\n fallback: \"Type a message…\",\n },\n client: { key: \"mobile.trainer.client\", fallback: \"Client\" },\n back: { key: \"common.back\", fallback: \"Back\" },\n} as const;\n","import React, { createContext, useContext, type ReactNode } from \"react\";\nimport type { MessengerConfig } from \"./types\";\n\nconst MessengerContext = createContext<MessengerConfig | null>(null);\n\nexport function MessengerProvider({\n config,\n children,\n}: {\n config: MessengerConfig;\n children: ReactNode;\n}) {\n return (\n <MessengerContext.Provider value={config}>\n {children}\n </MessengerContext.Provider>\n );\n}\n\nexport function useMessengerConfig(): MessengerConfig {\n const ctx = useContext(MessengerContext);\n if (!ctx) {\n throw new Error(\n \"@gymlium/messenger: useMessengerConfig must be used within MessengerProvider\"\n );\n }\n return ctx;\n}\n\n/** Safe variant for optional host chrome (returns null outside provider). */\nexport function useOptionalMessengerConfig(): MessengerConfig | null {\n return useContext(MessengerContext);\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport { listThreads } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useMessengerConfig } from \"../provider\";\n\nexport function useThreads(options?: { enabled?: boolean }) {\n const { http, enabled: featureOn } = useMessengerConfig();\n const enabled = (options?.enabled ?? true) && featureOn;\n\n return useQuery({\n queryKey: messengerKeys.threads(),\n queryFn: async () => {\n const res = await listThreads(http);\n if (!res.success) {\n throw new Error(res.message ?? \"Failed to load threads\");\n }\n return res.data;\n },\n enabled,\n });\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport { getThread } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useMessengerConfig } from \"../provider\";\n\nconst POLL_MS = 18_000;\n\nexport function useThread(\n threadId: number,\n options?: { enabled?: boolean; poll?: boolean }\n) {\n const { http, enabled: featureOn } = useMessengerConfig();\n const enabled =\n (options?.enabled ?? true) && featureOn && Number.isFinite(threadId) && threadId > 0;\n const poll = options?.poll ?? true;\n\n return useQuery({\n queryKey: messengerKeys.thread(threadId),\n queryFn: async () => {\n const res = await getThread(http, threadId);\n if (!res.success) {\n throw new Error(res.message ?? \"Failed to load thread\");\n }\n return res.data;\n },\n enabled,\n refetchInterval: enabled && poll ? POLL_MS : false,\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { sendMessage } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useMessengerConfig } from \"../provider\";\nimport type { ChatMessage, PickedImage } from \"../types\";\n\nexport function useSendMessage(threadId: number) {\n const { http } = useMessengerConfig();\n const qc = useQueryClient();\n\n return useMutation({\n mutationFn: async (input: { body: string; image?: PickedImage | null }) => {\n const res = await sendMessage(http, threadId, input.body, input.image);\n if (!res.success || !res.data?.message) {\n throw new Error(res.message ?? \"Send failed\");\n }\n return res.data.message as ChatMessage;\n },\n onSuccess: (message: ChatMessage) => {\n qc.setQueryData(\n messengerKeys.thread(threadId),\n (prev: { thread: unknown; messages: ChatMessage[] } | undefined) => {\n if (!prev) return prev;\n if (prev.messages.some((m) => m.id === message.id)) return prev;\n return { ...prev, messages: [...prev.messages, message] };\n }\n );\n void qc.invalidateQueries({ queryKey: messengerKeys.threads() });\n },\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { openThread } from \"../api\";\nimport { messengerKeys } from \"../queryKeys\";\nimport { useMessengerConfig } from \"../provider\";\nimport type { MessageThread } from \"../types\";\n\nexport function useOpenThread() {\n const { http, navigation } = useMessengerConfig();\n const qc = useQueryClient();\n\n return useMutation({\n mutationFn: async (clientUserId?: number) => {\n const res = await openThread(http, clientUserId);\n if (!res.success || !res.data?.thread?.id) {\n throw new Error(res.message ?? \"Could not open thread\");\n }\n return res.data.thread as MessageThread;\n },\n onSuccess: (thread: MessageThread) => {\n void qc.invalidateQueries({ queryKey: messengerKeys.threads() });\n navigation.openThread(thread.id);\n },\n });\n}\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useOptionalMessengerConfig, listThreads, messengerKeys, sumUnread } from './chunk-O5XNJHGZ.js';
|
|
2
|
+
import { createContext, useState, useCallback, useMemo, useContext } from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { useQuery } from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
var MessengerUiContext = createContext(null);
|
|
7
|
+
function MessengerUiProvider({ children }) {
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [threadId, setThreadId] = useState(null);
|
|
10
|
+
const openInbox = useCallback(() => {
|
|
11
|
+
setThreadId(null);
|
|
12
|
+
setOpen(true);
|
|
13
|
+
}, []);
|
|
14
|
+
const openThread = useCallback((id) => {
|
|
15
|
+
setThreadId(id);
|
|
16
|
+
setOpen(true);
|
|
17
|
+
}, []);
|
|
18
|
+
const close = useCallback(() => {
|
|
19
|
+
setOpen(false);
|
|
20
|
+
}, []);
|
|
21
|
+
const value = useMemo(
|
|
22
|
+
() => ({
|
|
23
|
+
open,
|
|
24
|
+
threadId,
|
|
25
|
+
openInbox,
|
|
26
|
+
openThread,
|
|
27
|
+
close,
|
|
28
|
+
setOpen
|
|
29
|
+
}),
|
|
30
|
+
[open, threadId, openInbox, openThread, close]
|
|
31
|
+
);
|
|
32
|
+
return /* @__PURE__ */ jsx(MessengerUiContext.Provider, { value, children });
|
|
33
|
+
}
|
|
34
|
+
function useMessengerUi() {
|
|
35
|
+
const ctx = useContext(MessengerUiContext);
|
|
36
|
+
if (!ctx) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"@gymlium/messenger: useMessengerUi must be used within MessengerUiProvider"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return ctx;
|
|
42
|
+
}
|
|
43
|
+
function useOptionalMessengerUi() {
|
|
44
|
+
return useContext(MessengerUiContext);
|
|
45
|
+
}
|
|
46
|
+
function useUnreadTotal(options) {
|
|
47
|
+
const cfg = useOptionalMessengerConfig();
|
|
48
|
+
const featureOn = cfg?.enabled === true;
|
|
49
|
+
const http = cfg?.http;
|
|
50
|
+
const enabled = (options?.enabled ?? true) && featureOn && !!http;
|
|
51
|
+
const query = useQuery({
|
|
52
|
+
queryKey: messengerKeys.threads(),
|
|
53
|
+
queryFn: async () => {
|
|
54
|
+
const res = await listThreads(http);
|
|
55
|
+
if (!res.success) {
|
|
56
|
+
throw new Error(res.message ?? "Failed to load threads");
|
|
57
|
+
}
|
|
58
|
+
return res.data;
|
|
59
|
+
},
|
|
60
|
+
enabled
|
|
61
|
+
});
|
|
62
|
+
const total = useMemo(
|
|
63
|
+
() => query.data ? sumUnread(query.data) : 0,
|
|
64
|
+
[query.data]
|
|
65
|
+
);
|
|
66
|
+
return {
|
|
67
|
+
total,
|
|
68
|
+
isLoading: query.isLoading,
|
|
69
|
+
isFetching: query.isFetching,
|
|
70
|
+
refetch: query.refetch,
|
|
71
|
+
enabled: featureOn
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { MessengerUiProvider, useMessengerUi, useOptionalMessengerUi, useUnreadTotal };
|
|
76
|
+
//# sourceMappingURL=chunk-OTJZXVON.js.map
|
|
77
|
+
//# sourceMappingURL=chunk-OTJZXVON.js.map
|