@fayz-ai/plugin-conversations 0.8.0 → 0.9.0-next.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.
Files changed (60) hide show
  1. package/dist/ConversationsContext.d.ts +18 -1
  2. package/dist/ConversationsContext.d.ts.map +1 -1
  3. package/dist/ConversationsPage.d.ts +3 -1
  4. package/dist/ConversationsPage.d.ts.map +1 -1
  5. package/dist/data/accents.d.ts +3 -0
  6. package/dist/data/accents.d.ts.map +1 -0
  7. package/dist/data/mock.d.ts +7 -1
  8. package/dist/data/mock.d.ts.map +1 -1
  9. package/dist/data/mock.test.d.ts +2 -0
  10. package/dist/data/mock.test.d.ts.map +1 -0
  11. package/dist/data/supabase.d.ts.map +1 -1
  12. package/dist/data/tables.d.ts +5 -0
  13. package/dist/data/tables.d.ts.map +1 -0
  14. package/dist/data/types.d.ts +2 -1
  15. package/dist/data/types.d.ts.map +1 -1
  16. package/dist/index.d.ts +16 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +667 -73
  19. package/dist/index.js.map +1 -1
  20. package/dist/locales/en.d.ts.map +1 -1
  21. package/dist/locales/index.d.ts.map +1 -1
  22. package/dist/locales/pt-BR.d.ts +2 -0
  23. package/dist/locales/pt-BR.d.ts.map +1 -0
  24. package/dist/migrations/index.d.ts +7 -0
  25. package/dist/migrations/index.d.ts.map +1 -0
  26. package/dist/store.d.ts +2 -1
  27. package/dist/store.d.ts.map +1 -1
  28. package/dist/types.d.ts +18 -0
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/views/ContactPanel.d.ts.map +1 -1
  31. package/dist/views/ConversationList.d.ts.map +1 -1
  32. package/dist/views/InboxView.d.ts.map +1 -1
  33. package/dist/views/MessageThread.d.ts.map +1 -1
  34. package/dist/views/NewConversationModal.d.ts +6 -0
  35. package/dist/views/NewConversationModal.d.ts.map +1 -0
  36. package/package.json +15 -11
  37. package/src/ConversationsContext.tsx +31 -1
  38. package/src/ConversationsPage.tsx +6 -3
  39. package/src/data/accents.ts +12 -0
  40. package/src/data/mock.test.ts +90 -0
  41. package/src/data/mock.ts +131 -12
  42. package/src/data/supabase.ts +69 -7
  43. package/src/data/tables.ts +7 -0
  44. package/src/data/types.ts +2 -0
  45. package/src/index.ts +69 -11
  46. package/src/locales/en.ts +64 -0
  47. package/src/locales/index.ts +2 -0
  48. package/src/locales/pt-BR.ts +68 -0
  49. package/src/migrations/001_conversations.sql +74 -0
  50. package/src/migrations/002_contact_person.sql +22 -0
  51. package/src/migrations/index.ts +108 -0
  52. package/src/store.ts +44 -1
  53. package/src/types.ts +19 -0
  54. package/src/views/ContactPanel.tsx +14 -12
  55. package/src/views/ConversationList.tsx +48 -21
  56. package/src/views/InboxView.tsx +3 -1
  57. package/src/views/MessageThread.tsx +14 -16
  58. package/src/views/NewConversationModal.tsx +204 -0
  59. package/dist/index.cjs +0 -904
  60. package/dist/index.cjs.map +0 -1
package/dist/index.cjs DELETED
@@ -1,904 +0,0 @@
1
- 'use strict';
2
-
3
- var React3 = require('react');
4
- var core = require('@fayz-ai/core');
5
- var zustand = require('zustand');
6
- var jsxRuntime = require('react/jsx-runtime');
7
- var lucideReact = require('lucide-react');
8
- var ui = require('@fayz-ai/ui');
9
- var vanilla = require('zustand/vanilla');
10
-
11
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
-
13
- var React3__default = /*#__PURE__*/_interopDefault(React3);
14
-
15
- // src/index.ts
16
- var StoreContext = React3__default.default.createContext(null);
17
- function ConversationsContextProvider({
18
- store,
19
- children
20
- }) {
21
- return /* @__PURE__ */ jsxRuntime.jsx(StoreContext.Provider, { value: store, children });
22
- }
23
- function useConversationsStore(selector) {
24
- const store = React3__default.default.useContext(StoreContext);
25
- if (!store) throw new Error("useConversationsStore must be used within ConversationsPage");
26
- return zustand.useStore(store, selector);
27
- }
28
-
29
- // src/types.ts
30
- var CHANNEL_LABELS = {
31
- sms: "SMS",
32
- whatsapp: "WhatsApp",
33
- instagram: "Instagram",
34
- email: "Email",
35
- webchat: "Web Chat"
36
- };
37
-
38
- // src/channel.ts
39
- var CHANNEL_ICON = {
40
- sms: lucideReact.Phone,
41
- whatsapp: lucideReact.MessageSquare,
42
- instagram: lucideReact.Instagram,
43
- email: lucideReact.Mail,
44
- webchat: lucideReact.Globe
45
- };
46
- var CHANNEL_ACCENT = {
47
- whatsapp: { color: "#22c55e", badge: "bg-[#22c55e]/12 text-[#15803d] dark:text-[#4ade80]" },
48
- sms: { color: "#6366f1", badge: "bg-[#6366f1]/12 text-[#4338ca] dark:text-[#a5b4fc]" },
49
- instagram: { color: "#ec4899", badge: "bg-[#ec4899]/12 text-[#be185d] dark:text-[#f9a8d4]" },
50
- email: { color: "#0ea5e9", badge: "bg-[#0ea5e9]/12 text-[#0369a1] dark:text-[#7dd3fc]" },
51
- webchat: { color: "#f59e0b", badge: "bg-[#f59e0b]/12 text-[#b45309] dark:text-[#fcd34d]" }
52
- };
53
- function useMediaQuery(query) {
54
- const [matches, setMatches] = React3__default.default.useState(
55
- () => typeof window !== "undefined" && window.matchMedia(query).matches
56
- );
57
- React3__default.default.useEffect(() => {
58
- if (typeof window === "undefined") return;
59
- const mql = window.matchMedia(query);
60
- const onChange = () => setMatches(mql.matches);
61
- onChange();
62
- mql.addEventListener("change", onChange);
63
- return () => mql.removeEventListener("change", onChange);
64
- }, [query]);
65
- return matches;
66
- }
67
- function initialsOf(name) {
68
- return name.replace("@", "").split(/\s+/).filter(Boolean).map((w) => w[0]).slice(0, 2).join("").toUpperCase();
69
- }
70
- function Avatar({ name, accent, size = "md", channel }) {
71
- const dims = size === "lg" ? "h-14 w-14 text-lg" : size === "sm" ? "h-9 w-9 text-xs" : "h-10 w-10 text-sm";
72
- const Icon = channel ? CHANNEL_ICON[channel] : null;
73
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative shrink-0", children: [
74
- /* @__PURE__ */ jsxRuntime.jsx(
75
- "div",
76
- {
77
- className: ui.cn("flex items-center justify-center rounded-full font-semibold text-white", dims),
78
- style: { backgroundColor: accent },
79
- children: initialsOf(name)
80
- }
81
- ),
82
- Icon && /* @__PURE__ */ jsxRuntime.jsx(
83
- "span",
84
- {
85
- className: "absolute -bottom-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full ring-2 ring-card",
86
- style: { backgroundColor: CHANNEL_ACCENT[channel].color },
87
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-2.5 w-2.5 text-white" })
88
- }
89
- )
90
- ] });
91
- }
92
- function ChannelBadge({ channel, className }) {
93
- const Icon = CHANNEL_ICON[channel];
94
- return /* @__PURE__ */ jsxRuntime.jsxs(
95
- "span",
96
- {
97
- className: ui.cn(
98
- "inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium",
99
- CHANNEL_ACCENT[channel].badge,
100
- className
101
- ),
102
- children: [
103
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-2.5 w-2.5" }),
104
- CHANNEL_LABELS[channel]
105
- ]
106
- }
107
- );
108
- }
109
- function relativeTime(iso) {
110
- const diff = Date.now() - new Date(iso).getTime();
111
- const mins = Math.round(diff / 6e4);
112
- if (mins < 1) return "now";
113
- if (mins < 60) return `${mins}m`;
114
- const hours = Math.round(mins / 60);
115
- if (hours < 24) return `${hours}h`;
116
- const days = Math.round(hours / 24);
117
- if (days < 7) return `${days}d`;
118
- return `${Math.round(days / 7)}w`;
119
- }
120
- function dayLabel(iso) {
121
- const d = new Date(iso);
122
- const now = /* @__PURE__ */ new Date();
123
- const startOf = (x) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();
124
- const dayDiff = Math.round((startOf(now) - startOf(d)) / 864e5);
125
- if (dayDiff <= 0) return "Today";
126
- if (dayDiff === 1) return "Yesterday";
127
- if (dayDiff < 7) return d.toLocaleDateString([], { weekday: "long" });
128
- return d.toLocaleDateString([], { month: "short", day: "numeric", year: now.getFullYear() === d.getFullYear() ? void 0 : "numeric" });
129
- }
130
- var FILTERS = [
131
- { id: "all", label: "All" },
132
- { id: "whatsapp", label: "WhatsApp" },
133
- { id: "sms", label: "SMS" },
134
- { id: "instagram", label: "Instagram" },
135
- { id: "email", label: "Email" },
136
- { id: "webchat", label: "Web" }
137
- ];
138
- function ConversationList({ className }) {
139
- const {
140
- conversations,
141
- selectedId,
142
- channelFilter,
143
- search,
144
- loading,
145
- select,
146
- setChannelFilter,
147
- setSearch
148
- } = useConversationsStore((s) => s);
149
- return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: ui.cn("w-full shrink-0 flex-col border-r border-border bg-card lg:w-[320px]", className), children: [
150
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-border px-3 py-3", children: [
151
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
152
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
153
- /* @__PURE__ */ jsxRuntime.jsx(
154
- ui.Input,
155
- {
156
- value: search,
157
- onChange: (e) => setSearch(e.target.value),
158
- placeholder: "Search conversations",
159
- className: "pl-8"
160
- }
161
- )
162
- ] }),
163
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex flex-wrap gap-1", children: FILTERS.map((f) => /* @__PURE__ */ jsxRuntime.jsx(
164
- "button",
165
- {
166
- onClick: () => setChannelFilter(f.id),
167
- className: ui.cn(
168
- "rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
169
- channelFilter === f.id ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/70"
170
- ),
171
- children: f.label
172
- },
173
- f.id
174
- )) })
175
- ] }),
176
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto", children: [
177
- loading && conversations.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Loading\u2026" }),
178
- !loading && conversations.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 p-8 text-center text-muted-foreground", children: [
179
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Inbox, { className: "h-6 w-6" }),
180
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "No conversations" })
181
- ] }),
182
- conversations.map((c) => {
183
- const active = c.id === selectedId;
184
- const unread = c.unreadCount > 0;
185
- return /* @__PURE__ */ jsxRuntime.jsxs(
186
- "button",
187
- {
188
- onClick: () => select(c.id),
189
- className: ui.cn(
190
- "flex w-full items-start gap-3 border-b border-border/50 px-3 py-3 text-left transition-colors",
191
- active ? "bg-accent" : "hover:bg-muted/50"
192
- ),
193
- children: [
194
- /* @__PURE__ */ jsxRuntime.jsx(Avatar, { name: c.contactName, accent: c.accent, channel: c.channel }),
195
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
196
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
197
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn("truncate text-sm text-foreground", unread ? "font-semibold" : "font-medium"), children: c.contactName }),
198
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn("shrink-0 text-[11px]", unread ? "font-semibold text-primary" : "text-muted-foreground"), children: relativeTime(c.lastMessageAt) })
199
- ] }),
200
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
201
- /* @__PURE__ */ jsxRuntime.jsx(ChannelBadge, { channel: c.channel }),
202
- c.status !== "open" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground/70", children: c.status })
203
- ] }),
204
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex items-center justify-between gap-2", children: [
205
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn("truncate text-xs", unread ? "text-foreground" : "text-muted-foreground"), children: c.lastMessagePreview }),
206
- unread && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full bg-primary px-1.5 text-[10px] font-semibold text-primary-foreground", children: c.unreadCount })
207
- ] })
208
- ] })
209
- ]
210
- },
211
- c.id
212
- );
213
- })
214
- ] })
215
- ] });
216
- }
217
- function buildRows(messages) {
218
- const rows = [];
219
- let lastDay = "";
220
- messages.forEach((m, i) => {
221
- const day = new Date(m.at).toDateString();
222
- if (day !== lastDay) {
223
- rows.push({ kind: "day", id: `day-${day}`, label: dayLabel(m.at) });
224
- lastDay = day;
225
- }
226
- const prev = messages[i - 1];
227
- const next = messages[i + 1];
228
- const samePrev = prev && prev.direction === m.direction && new Date(prev.at).toDateString() === day;
229
- const sameNext = next && next.direction === m.direction && new Date(next.at).toDateString() === day;
230
- rows.push({ kind: "msg", id: m.id, message: m, startsRun: !samePrev, endsRun: !sameNext });
231
- });
232
- return rows;
233
- }
234
- function MessageThread({ selected, onTogglePanel, panelOpen, onBack, className }) {
235
- const { messages, sending, send, setStatus } = useConversationsStore((s) => s);
236
- const [draft, setDraft] = React3__default.default.useState("");
237
- const threadRef = React3__default.default.useRef(null);
238
- React3__default.default.useEffect(() => {
239
- threadRef.current?.scrollTo({ top: threadRef.current.scrollHeight, behavior: "smooth" });
240
- }, [messages.length, selected.id]);
241
- async function handleSend() {
242
- if (!draft.trim()) return;
243
- const body = draft;
244
- setDraft("");
245
- await send(body);
246
- }
247
- const accent = CHANNEL_ACCENT[selected.channel];
248
- const rows = React3__default.default.useMemo(() => buildRows(messages), [messages]);
249
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: ui.cn("flex min-w-0 flex-1 flex-col bg-muted/20", className), children: [
250
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-card px-3 py-2.5 md:px-5", children: [
251
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2 md:gap-3", children: [
252
- onBack && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "icon", className: "-ml-1 shrink-0 lg:hidden", onClick: onBack, "aria-label": "Back to conversations", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-5 w-5" }) }),
253
- /* @__PURE__ */ jsxRuntime.jsx(Avatar, { name: selected.contactName, accent: selected.accent, size: "sm", channel: selected.channel }),
254
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
255
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: selected.contactName }),
256
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
257
- /* @__PURE__ */ jsxRuntime.jsx(ChannelBadge, { channel: selected.channel }),
258
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: selected.contactHandle })
259
- ] })
260
- ] })
261
- ] }),
262
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [
263
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: () => setStatus("snoozed"), "aria-label": "Snooze", children: [
264
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3.5 w-3.5 sm:mr-1" }),
265
- " ",
266
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Snooze" })
267
- ] }),
268
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: () => setStatus("closed"), "aria-label": "Close conversation", children: [
269
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Archive, { className: "h-3.5 w-3.5 sm:mr-1" }),
270
- " ",
271
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Close" })
272
- ] }),
273
- /* @__PURE__ */ jsxRuntime.jsx(
274
- ui.Button,
275
- {
276
- variant: panelOpen ? "secondary" : "ghost",
277
- size: "icon",
278
- onClick: onTogglePanel,
279
- "aria-label": "Toggle contact details",
280
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelRight, { className: "h-4 w-4" })
281
- }
282
- )
283
- ] })
284
- ] }),
285
- /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: threadRef, className: "min-h-0 flex-1 overflow-y-auto px-5 py-4", children: [
286
- rows.map((row) => {
287
- if (row.kind === "day") {
288
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-3 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-muted px-3 py-0.5 text-[11px] font-medium text-muted-foreground", children: row.label }) }, row.id);
289
- }
290
- const m = row.message;
291
- const outbound = m.direction === "outbound";
292
- return /* @__PURE__ */ jsxRuntime.jsx(
293
- "div",
294
- {
295
- className: ui.cn("flex", outbound ? "justify-end" : "justify-start", row.startsRun ? "mt-2.5" : "mt-0.5"),
296
- children: /* @__PURE__ */ jsxRuntime.jsxs(
297
- "div",
298
- {
299
- className: ui.cn(
300
- "max-w-[68%] px-3.5 py-2 text-sm shadow-sm",
301
- outbound ? "rounded-2xl text-white" : "rounded-2xl bg-card text-foreground",
302
- // Tail only on the last bubble of a run, on the sender's side.
303
- outbound && row.endsRun && "rounded-br-sm",
304
- !outbound && row.endsRun && "rounded-bl-sm"
305
- ),
306
- style: outbound ? { backgroundColor: accent.color } : void 0,
307
- children: [
308
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap break-words", children: m.body }),
309
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("mt-0.5 text-right text-[10px]", outbound ? "text-white/70" : "text-muted-foreground"), children: new Date(m.at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) })
310
- ]
311
- }
312
- )
313
- },
314
- row.id
315
- );
316
- }),
317
- rows.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col items-center justify-center text-muted-foreground", children: [
318
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-7 w-7" }),
319
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm", children: "No messages yet" })
320
- ] })
321
- ] }),
322
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border bg-card px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2", children: [
323
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "icon", className: "mb-0.5 text-muted-foreground", "aria-label": "Add emoji", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Smile, { className: "h-4 w-4" }) }),
324
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "icon", className: "mb-0.5 text-muted-foreground", "aria-label": "Attach file", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-4 w-4" }) }),
325
- /* @__PURE__ */ jsxRuntime.jsx(
326
- "textarea",
327
- {
328
- value: draft,
329
- onChange: (e) => setDraft(e.target.value),
330
- onKeyDown: (e) => {
331
- if (e.key === "Enter" && !e.shiftKey) {
332
- e.preventDefault();
333
- void handleSend();
334
- }
335
- },
336
- rows: 1,
337
- placeholder: `Reply via ${CHANNEL_LABELS[selected.channel]}\u2026`,
338
- className: "max-h-32 min-h-[40px] flex-1 resize-none rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary"
339
- }
340
- ),
341
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => void handleSend(), disabled: sending || !draft.trim(), "aria-label": "Send", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "h-4 w-4" }) })
342
- ] }) })
343
- ] });
344
- }
345
- function Section({ icon: Icon, title, children }) {
346
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border px-4 py-3", children: [
347
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: [
348
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3 w-3" }),
349
- " ",
350
- title
351
- ] }),
352
- children
353
- ] });
354
- }
355
- function ContactPanel({ contact, onClose, className }) {
356
- return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: ui.cn("flex shrink-0 flex-col overflow-y-auto border-l border-border bg-card", className), children: [
357
- onClose && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2 xl:hidden", children: [
358
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-foreground", children: "Details" }),
359
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "icon", onClick: onClose, "aria-label": "Close details", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) })
360
- ] }),
361
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 px-4 py-5 text-center", children: [
362
- /* @__PURE__ */ jsxRuntime.jsx(Avatar, { name: contact.contactName, accent: contact.accent, size: "lg", channel: contact.channel }),
363
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
364
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-foreground", children: contact.contactName }),
365
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: contact.contactHandle })
366
- ] }),
367
- /* @__PURE__ */ jsxRuntime.jsx(ChannelBadge, { channel: contact.channel })
368
- ] }),
369
- /* @__PURE__ */ jsxRuntime.jsx(Section, { icon: lucideReact.User, title: "Details", children: /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "space-y-1.5 text-sm", children: [
370
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
371
- /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: "Channel" }),
372
- /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "text-foreground", children: CHANNEL_LABELS[contact.channel] })
373
- ] }),
374
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
375
- /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: "Status" }),
376
- /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "capitalize text-foreground", children: contact.status })
377
- ] }),
378
- contact.assignedTo && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
379
- /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: "Assigned to" }),
380
- /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "text-foreground", children: contact.assignedTo })
381
- ] }),
382
- contact.location && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
383
- /* @__PURE__ */ jsxRuntime.jsxs("dt", { className: "flex items-center gap-1 text-muted-foreground", children: [
384
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { className: "h-3 w-3" }),
385
- " Location"
386
- ] }),
387
- /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "text-foreground", children: contact.location })
388
- ] })
389
- ] }) }),
390
- contact.tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Section, { icon: lucideReact.Tag, title: "Tags", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: contact.tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-foreground", children: tag }, tag)) }) }),
391
- contact.note && /* @__PURE__ */ jsxRuntime.jsx(Section, { icon: lucideReact.StickyNote, title: "Note", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground", children: contact.note }) }),
392
- /* @__PURE__ */ jsxRuntime.jsx(Section, { icon: lucideReact.Link2, title: "Linked records", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No linked records yet." }) })
393
- ] });
394
- }
395
- function InboxView() {
396
- const { conversations, selectedId, deselect } = useConversationsStore((s) => s);
397
- const isWidePanel = useMediaQuery("(min-width: 1280px)");
398
- const [panelOpen, setPanelOpen] = React3__default.default.useState(false);
399
- const selected = conversations.find((c) => c.id === selectedId) ?? null;
400
- React3__default.default.useEffect(() => {
401
- setPanelOpen(isWidePanel);
402
- }, [isWidePanel]);
403
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-full min-h-0 overflow-hidden bg-background", children: [
404
- /* @__PURE__ */ jsxRuntime.jsx(ConversationList, { className: ui.cn(selected ? "hidden lg:flex" : "flex") }),
405
- selected ? /* @__PURE__ */ jsxRuntime.jsx(
406
- MessageThread,
407
- {
408
- selected,
409
- panelOpen,
410
- onTogglePanel: () => setPanelOpen((v) => !v),
411
- onBack: deselect,
412
- className: "flex"
413
- }
414
- ) : /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "hidden min-w-0 flex-1 flex-col items-center justify-center bg-muted/20 text-muted-foreground lg:flex", children: [
415
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-9 w-9" }),
416
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm", children: "Select a conversation to start chatting" })
417
- ] }),
418
- selected && panelOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
419
- /* @__PURE__ */ jsxRuntime.jsx(
420
- "button",
421
- {
422
- className: "absolute inset-0 z-10 bg-black/40 xl:hidden",
423
- "aria-label": "Close details",
424
- onClick: () => setPanelOpen(false)
425
- }
426
- ),
427
- /* @__PURE__ */ jsxRuntime.jsx(
428
- ContactPanel,
429
- {
430
- contact: selected,
431
- onClose: () => setPanelOpen(false),
432
- className: "absolute inset-y-0 right-0 z-20 w-[340px] max-w-[88%] shadow-2xl xl:static xl:z-auto xl:w-[300px] xl:max-w-none xl:shadow-none"
433
- }
434
- )
435
- ] })
436
- ] });
437
- }
438
- function ConversationsPage({ store }) {
439
- React3__default.default.useEffect(() => {
440
- void store.getState().load();
441
- }, []);
442
- return /* @__PURE__ */ jsxRuntime.jsx(ConversationsContextProvider, { store, children: /* @__PURE__ */ jsxRuntime.jsx(InboxView, {}) });
443
- }
444
-
445
- // src/data/mock.ts
446
- function minutesAgo(base, mins) {
447
- return new Date(base - mins * 6e4).toISOString();
448
- }
449
- function seed() {
450
- const base = (/* @__PURE__ */ new Date("2026-06-16T14:00:00Z")).getTime();
451
- const conversations = [
452
- {
453
- id: "c1",
454
- contactName: "Marina Alves",
455
- contactHandle: "+55 11 99876-1020",
456
- channel: "whatsapp",
457
- lastMessagePreview: "Perfect, can we book for Friday at 3pm?",
458
- lastMessageAt: minutesAgo(base, 4),
459
- unreadCount: 2,
460
- status: "open",
461
- assignedTo: "You",
462
- accent: "#22c55e",
463
- tags: ["Hot lead"],
464
- location: "S\xE3o Paulo, BR",
465
- note: "Referred by Instagram ad \u2014 interested in full color + cut."
466
- },
467
- {
468
- id: "c2",
469
- contactName: "Jordan Pierce",
470
- contactHandle: "+1 (415) 555-0142",
471
- channel: "sms",
472
- lastMessagePreview: "Got it \u2014 sending the deposit now.",
473
- lastMessageAt: minutesAgo(base, 22),
474
- unreadCount: 0,
475
- status: "open",
476
- assignedTo: "You",
477
- accent: "#6366f1",
478
- tags: ["Customer"],
479
- location: "San Francisco, US"
480
- },
481
- {
482
- id: "c3",
483
- contactName: "@thehairloft",
484
- contactHandle: "thehairloft",
485
- channel: "instagram",
486
- lastMessagePreview: "Do you offer balayage on weekends?",
487
- lastMessageAt: minutesAgo(base, 51),
488
- unreadCount: 1,
489
- status: "open",
490
- accent: "#ec4899",
491
- tags: ["New"]
492
- },
493
- {
494
- id: "c4",
495
- contactName: "David Whitman",
496
- contactHandle: "david@whitman.co",
497
- channel: "email",
498
- lastMessagePreview: "Re: Proposal \u2014 looks great, one question on pricing\u2026",
499
- lastMessageAt: minutesAgo(base, 95),
500
- unreadCount: 0,
501
- status: "open",
502
- assignedTo: "Sofia",
503
- accent: "#0ea5e9",
504
- tags: ["Proposal"],
505
- location: "Austin, US",
506
- note: "Evaluating the retainer tier. Decision expected this week."
507
- },
508
- {
509
- id: "c5",
510
- contactName: "Website visitor",
511
- contactHandle: "live chat \xB7 acme.com",
512
- channel: "webchat",
513
- lastMessagePreview: "Is anyone available to chat?",
514
- lastMessageAt: minutesAgo(base, 140),
515
- unreadCount: 0,
516
- status: "snoozed",
517
- accent: "#f59e0b",
518
- tags: []
519
- },
520
- {
521
- id: "c6",
522
- contactName: "Priya Nair",
523
- contactHandle: "+44 7700 900123",
524
- channel: "whatsapp",
525
- lastMessagePreview: "Thank you! See you next week \u{1F64C}",
526
- lastMessageAt: minutesAgo(base, 1440),
527
- unreadCount: 0,
528
- status: "closed",
529
- assignedTo: "You",
530
- accent: "#14b8a6",
531
- tags: ["Customer"],
532
- location: "London, UK"
533
- }
534
- ];
535
- const messages = [
536
- msg("m1", "c1", "whatsapp", "inbound", "Hi! I saw your ad \u2014 do you have availability this week?", "Marina Alves", minutesAgo(base, 18)),
537
- msg("m2", "c1", "whatsapp", "outbound", "Hi Marina! Yes, we do. What service are you interested in?", "You", minutesAgo(base, 15)),
538
- msg("m3", "c1", "whatsapp", "inbound", "A full color + cut.", "Marina Alves", minutesAgo(base, 9)),
539
- msg("m4", "c1", "whatsapp", "inbound", "Perfect, can we book for Friday at 3pm?", "Marina Alves", minutesAgo(base, 4)),
540
- msg("m5", "c2", "sms", "outbound", "Your appointment is confirmed for tomorrow at 10am.", "You", minutesAgo(base, 40)),
541
- msg("m6", "c2", "sms", "inbound", "Got it \u2014 sending the deposit now.", "Jordan Pierce", minutesAgo(base, 22)),
542
- msg("m7", "c3", "instagram", "inbound", "Do you offer balayage on weekends?", "@thehairloft", minutesAgo(base, 51)),
543
- msg("m8", "c4", "email", "inbound", "Re: Proposal \u2014 looks great, one question on pricing for the retainer tier.", "David Whitman", minutesAgo(base, 95)),
544
- msg("m9", "c4", "email", "outbound", "Happy to walk you through it \u2014 are you free for a quick call tomorrow?", "Sofia", minutesAgo(base, 80)),
545
- msg("m10", "c5", "webchat", "inbound", "Is anyone available to chat?", "Website visitor", minutesAgo(base, 140)),
546
- msg("m11", "c6", "whatsapp", "outbound", "You are all set for next Tuesday. Anything else?", "You", minutesAgo(base, 1500)),
547
- msg("m12", "c6", "whatsapp", "inbound", "Thank you! See you next week \u{1F64C}", "Priya Nair", minutesAgo(base, 1440))
548
- ];
549
- return { conversations, messages };
550
- }
551
- function msg(id, conversationId, channel, direction, body, author, at) {
552
- return { id, conversationId, channel, direction, body, author, at };
553
- }
554
- function createMockConversationsProvider() {
555
- const { conversations, messages } = seed();
556
- let counter = 100;
557
- return {
558
- async listConversations(query) {
559
- let list = [...conversations];
560
- if (query?.channel && query.channel !== "all") list = list.filter((c) => c.channel === query.channel);
561
- if (query?.status && query.status !== "all") list = list.filter((c) => c.status === query.status);
562
- if (query?.search) {
563
- const q = query.search.toLowerCase();
564
- list = list.filter(
565
- (c) => c.contactName.toLowerCase().includes(q) || c.lastMessagePreview.toLowerCase().includes(q)
566
- );
567
- }
568
- return list.sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt));
569
- },
570
- async getMessages(conversationId) {
571
- return messages.filter((m) => m.conversationId === conversationId).sort((a, b) => a.at.localeCompare(b.at));
572
- },
573
- async sendMessage(input) {
574
- const conv = conversations.find((c) => c.id === input.conversationId);
575
- const created = {
576
- id: `m${++counter}`,
577
- conversationId: input.conversationId,
578
- channel: conv?.channel ?? "sms",
579
- direction: "outbound",
580
- body: input.body,
581
- author: "You",
582
- at: (/* @__PURE__ */ new Date()).toISOString()
583
- };
584
- messages.push(created);
585
- if (conv) {
586
- conv.lastMessagePreview = input.body;
587
- conv.lastMessageAt = created.at;
588
- conv.unreadCount = 0;
589
- if (conv.status === "closed") conv.status = "open";
590
- }
591
- return created;
592
- },
593
- async markRead(conversationId) {
594
- const conv = conversations.find((c) => c.id === conversationId);
595
- if (conv) conv.unreadCount = 0;
596
- },
597
- async setStatus(conversationId, status) {
598
- const conv = conversations.find((c) => c.id === conversationId);
599
- if (!conv) throw new Error("Conversation not found");
600
- conv.status = status;
601
- return conv;
602
- }
603
- };
604
- }
605
- function mapConversation(r) {
606
- return {
607
- id: String(r.id),
608
- contactName: r.contact_name ?? "",
609
- contactHandle: r.contact_handle ?? "",
610
- channel: r.channel ?? "sms",
611
- lastMessagePreview: r.last_message_preview ?? "",
612
- lastMessageAt: r.last_message_at ?? "",
613
- unreadCount: Number(r.unread_count ?? 0),
614
- status: r.status ?? "open",
615
- assignedTo: r.assigned_to ?? void 0,
616
- accent: r.accent ?? "#6366f1",
617
- tags: r.tags ?? [],
618
- location: r.location ?? void 0,
619
- note: r.note ?? void 0
620
- };
621
- }
622
- function mapMessage(r) {
623
- return {
624
- id: String(r.id),
625
- conversationId: String(r.conversation_id),
626
- channel: r.channel ?? "sms",
627
- direction: r.direction ?? "inbound",
628
- body: r.body ?? "",
629
- at: r.at ?? "",
630
- author: r.author ?? ""
631
- };
632
- }
633
- function createSupabaseConversationsProvider(config) {
634
- const selfAuthor = config?.selfAuthor ?? "You";
635
- function resolveTenantId() {
636
- if (!config?.tenantId) return void 0;
637
- return typeof config.tenantId === "function" ? config.tenantId() : config.tenantId;
638
- }
639
- function client() {
640
- const supabase = config?.supabaseClient ?? core.getSupabaseClientOptional();
641
- if (!supabase) {
642
- throw new Error(
643
- "[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp."
644
- );
645
- }
646
- return supabase;
647
- }
648
- return {
649
- async listConversations(query) {
650
- let q = client().from("conversations").select("*");
651
- const tenantId = resolveTenantId();
652
- if (tenantId) q = q.eq("tenant_id", tenantId);
653
- if (query?.channel && query.channel !== "all") {
654
- q = q.eq("channel", query.channel);
655
- }
656
- if (query?.status && query.status !== "all") {
657
- q = q.eq("status", query.status);
658
- }
659
- if (query?.search) {
660
- const term = `%${query.search}%`;
661
- q = q.or(
662
- `contact_name.ilike.${term},last_message_preview.ilike.${term}`
663
- );
664
- }
665
- q = q.order("last_message_at", {
666
- ascending: false
667
- });
668
- const { data, error } = await q;
669
- if (error) throw error;
670
- return (data ?? []).map(mapConversation);
671
- },
672
- async getMessages(conversationId) {
673
- const selected = client().from("conversation_messages").select("*");
674
- const filtered = selected.eq(
675
- "conversation_id",
676
- conversationId
677
- );
678
- const ordered = filtered.order("at", {
679
- ascending: true
680
- });
681
- const { data, error } = await ordered;
682
- if (error) throw error;
683
- return (data ?? []).map(mapMessage);
684
- },
685
- async sendMessage(input) {
686
- const tenantId = resolveTenantId();
687
- const convSelected = client().from("conversations").select(
688
- "channel"
689
- );
690
- const convFiltered = convSelected.eq(
691
- "id",
692
- input.conversationId
693
- );
694
- const { data: conv } = await convFiltered.maybeSingle();
695
- const channel = conv?.channel ?? "sms";
696
- const at = (/* @__PURE__ */ new Date()).toISOString();
697
- const row = {
698
- conversation_id: input.conversationId,
699
- channel,
700
- direction: "outbound",
701
- body: input.body,
702
- author: selfAuthor,
703
- at
704
- };
705
- if (tenantId) row.tenant_id = tenantId;
706
- const { data: created, error } = await client().from("conversation_messages").insert(row).select().single();
707
- if (error) throw error;
708
- await client().from("conversations").update({
709
- last_message_preview: input.body,
710
- last_message_at: at,
711
- unread_count: 0,
712
- status: "open"
713
- }).eq("id", input.conversationId);
714
- return mapMessage(created ?? row);
715
- },
716
- async markRead(conversationId) {
717
- const { error } = await client().from("conversations").update({
718
- unread_count: 0
719
- }).eq("id", conversationId);
720
- if (error) throw error;
721
- },
722
- async setStatus(conversationId, status) {
723
- const updated = client().from("conversations").update({
724
- status
725
- });
726
- const filtered = updated.eq("id", conversationId);
727
- const selected = filtered.select();
728
- const { data, error } = await selected.single();
729
- if (error) throw error;
730
- if (!data) throw new Error("Conversation not found");
731
- return mapConversation(data);
732
- }
733
- };
734
- }
735
- function createConversationsStore(provider) {
736
- return vanilla.createStore((set, get) => ({
737
- conversations: [],
738
- messages: [],
739
- selectedId: null,
740
- channelFilter: "all",
741
- search: "",
742
- loading: false,
743
- sending: false,
744
- async load() {
745
- set({ loading: true });
746
- const conversations = await provider.listConversations({
747
- channel: get().channelFilter,
748
- search: get().search || void 0
749
- });
750
- const selectedId = get().selectedId ?? conversations[0]?.id ?? null;
751
- set({ conversations, loading: false, selectedId });
752
- if (selectedId) await get().select(selectedId);
753
- },
754
- async select(id) {
755
- set({ selectedId: id });
756
- const messages = await provider.getMessages(id);
757
- set({ messages });
758
- await provider.markRead(id);
759
- set((s) => ({
760
- conversations: s.conversations.map((c) => c.id === id ? { ...c, unreadCount: 0 } : c)
761
- }));
762
- },
763
- deselect() {
764
- set({ selectedId: null, messages: [] });
765
- },
766
- async setChannelFilter(channel) {
767
- set({ channelFilter: channel });
768
- await get().load();
769
- },
770
- async setSearch(search) {
771
- set({ search });
772
- await get().load();
773
- },
774
- async send(body) {
775
- const id = get().selectedId;
776
- if (!id || !body.trim()) return;
777
- set({ sending: true });
778
- const created = await provider.sendMessage({ conversationId: id, body: body.trim() });
779
- set((s) => ({
780
- sending: false,
781
- messages: [...s.messages, created],
782
- conversations: s.conversations.map(
783
- (c) => c.id === id ? { ...c, lastMessagePreview: created.body, lastMessageAt: created.at } : c
784
- )
785
- }));
786
- },
787
- async setStatus(status) {
788
- const id = get().selectedId;
789
- if (!id) return;
790
- const updated = await provider.setStatus(id, status);
791
- set((s) => ({
792
- conversations: s.conversations.map((c) => c.id === id ? updated : c)
793
- }));
794
- }
795
- }));
796
- }
797
-
798
- // src/locales/en.ts
799
- var en = {
800
- "conversations.title": "Conversations",
801
- "conversations.subtitle": "Unified inbox across every channel"
802
- };
803
-
804
- // src/locales/index.ts
805
- var conversationsLocales = {
806
- en
807
- };
808
-
809
- // src/index.ts
810
- function createSafeProvider() {
811
- if (core.getSupabaseClientOptional()) {
812
- return createSupabaseConversationsProvider({ tenantId: () => core.getActiveTenantId() });
813
- }
814
- return createMockConversationsProvider();
815
- }
816
- function createConversationsPlugin(options) {
817
- core.registerTranslations(conversationsLocales);
818
- const provider = options?.dataProvider ?? createSafeProvider();
819
- const store = createConversationsStore(provider);
820
- const PageComponent = () => React3__default.default.createElement(ConversationsPage, { store });
821
- PageComponent.displayName = "ConversationsPage";
822
- return {
823
- id: "conversations",
824
- name: options?.navLabel ?? "Conversations",
825
- icon: "MessageCircle",
826
- version: "1.0.0",
827
- scope: options?.scope ?? "universal",
828
- verticalId: options?.verticalId,
829
- defaultEnabled: true,
830
- dependencies: [],
831
- declaredFeatures: [{ id: "conversations", label: "Conversations", group: "Engage" }],
832
- navigation: [
833
- {
834
- section: options?.navSection ?? "main",
835
- position: options?.navPosition ?? 1,
836
- label: options?.navLabel ?? "Conversations",
837
- route: "/conversations",
838
- icon: "MessageCircle",
839
- permission: { feature: "conversations", action: "read" }
840
- }
841
- ],
842
- routes: [
843
- {
844
- path: "/conversations",
845
- component: PageComponent,
846
- fullBleed: true,
847
- permission: { feature: "conversations", action: "read" }
848
- }
849
- ],
850
- widgets: [],
851
- events: [
852
- { name: "conversations.message.received", description: "An inbound message arrived on any channel" },
853
- { name: "conversations.message.sent", description: "An outbound message was sent" }
854
- ],
855
- aiTools: [
856
- {
857
- id: "conversations.list-threads",
858
- name: "listConversations",
859
- description: "Lists open conversations across all channels, optionally filtered by channel.",
860
- icon: "MessageCircle",
861
- mode: "read",
862
- category: "Conversations",
863
- parameters: {
864
- type: "object",
865
- properties: {
866
- channel: {
867
- type: "string",
868
- enum: ["all", "sms", "whatsapp", "instagram", "email", "webchat"]
869
- }
870
- }
871
- },
872
- suggestions: [
873
- { label: "Show unread conversations" },
874
- { label: "Any new WhatsApp messages?" }
875
- ],
876
- permission: { feature: "conversations", action: "read" }
877
- },
878
- {
879
- id: "conversations.send-message",
880
- name: "sendMessage",
881
- description: "Sends a reply in a conversation thread.",
882
- icon: "Send",
883
- mode: "persist",
884
- category: "Conversations",
885
- parameters: {
886
- type: "object",
887
- properties: {
888
- conversationId: { type: "string", description: "Conversation id" },
889
- body: { type: "string", description: "Message body" }
890
- },
891
- required: ["conversationId", "body"]
892
- },
893
- permission: { feature: "conversations", action: "create" }
894
- }
895
- ],
896
- locales: conversationsLocales
897
- };
898
- }
899
-
900
- exports.createConversationsPlugin = createConversationsPlugin;
901
- exports.createMockConversationsProvider = createMockConversationsProvider;
902
- exports.createSupabaseConversationsProvider = createSupabaseConversationsProvider;
903
- //# sourceMappingURL=index.cjs.map
904
- //# sourceMappingURL=index.cjs.map