@paramms/chat-widget 1.0.6 → 1.0.7
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/chatlist.d.ts +62 -0
- package/dist/chatlist.js +124 -0
- package/dist/chatlist.js.map +1 -0
- package/dist/index.d.ts +1 -6
- package/dist/index.js +607 -863
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +49 -8
- package/dist/react.js +121 -92
- package/dist/react.js.map +1 -1
- package/dist/renderer.d.ts +1 -46
- package/dist/uid.d.ts +3 -0
- package/package.json +9 -1
package/dist/react.d.ts
CHANGED
|
@@ -34,13 +34,7 @@ export interface ChatWidgetProps extends BaseProps {
|
|
|
34
34
|
contextTitle?: string;
|
|
35
35
|
contextSubtitle?: string;
|
|
36
36
|
contextStatus?: string;
|
|
37
|
-
/**
|
|
38
|
-
* Use when users can have more than one thread on this profile
|
|
39
|
-
* (e.g. one thread per support ticket, one per booking). */
|
|
40
|
-
showChatList?: boolean;
|
|
41
|
-
/** Stable item ID — pins this conversation to a specific item/thread.
|
|
42
|
-
* When showChatList is also true, the widget opens this thread immediately
|
|
43
|
-
* but the user can navigate back to the full list. */
|
|
37
|
+
/** Stable item ID — pins this conversation to a specific item/thread. */
|
|
44
38
|
subjectId?: string;
|
|
45
39
|
}
|
|
46
40
|
/**
|
|
@@ -70,7 +64,7 @@ export interface ChatWidgetProps extends BaseProps {
|
|
|
70
64
|
* />
|
|
71
65
|
* ```
|
|
72
66
|
*/
|
|
73
|
-
export declare function ChatWidget({ url, profileId, userId, userName, userEmail, userAvatar, contextTitle, contextSubtitle, contextStatus,
|
|
67
|
+
export declare function ChatWidget({ url, profileId, userId, userName, userEmail, userAvatar, contextTitle, contextSubtitle, contextStatus, subjectId, accent, launcher, position, quickReplies, height, i18n, translateLang, }: ChatWidgetProps): JSX.Element;
|
|
74
68
|
export interface MarketplaceChatProps extends BaseProps {
|
|
75
69
|
/** Unique ID for this listing — each listing gets its own thread.
|
|
76
70
|
* Omit for a general (non-item-specific) conversation. */
|
|
@@ -121,4 +115,51 @@ export interface MarketplaceChatProps extends BaseProps {
|
|
|
121
115
|
* Only `url` and `profileId` are required. All other props are optional.
|
|
122
116
|
*/
|
|
123
117
|
export declare function MarketplaceChat({ url, profileId, listingId, listingTitle, listingMeta, listingPrice, listingStatus, userId, userName, userEmail, userAvatar, accent, launcher, position, quickReplies, height, i18n, translateLang, }: MarketplaceChatProps): JSX.Element;
|
|
118
|
+
export interface ChatListProps {
|
|
119
|
+
/** Relay WebSocket URL — required */
|
|
120
|
+
url: string;
|
|
121
|
+
/** Your Relay profile ID — required */
|
|
122
|
+
profileId: string;
|
|
123
|
+
/** Your logged-in user's stable ID. Omit for anonymous (uses localStorage UID) */
|
|
124
|
+
userId?: string;
|
|
125
|
+
/** Called when the user taps a conversation row.
|
|
126
|
+
* Use this to navigate to the listing page or open a ChatWidget. */
|
|
127
|
+
onSelect: (entry: import('./chatlist.js').ChatListEntry) => void;
|
|
128
|
+
/** Brand colour hex — default '#f5713c' */
|
|
129
|
+
accent?: string;
|
|
130
|
+
/** Container height when rendered inline. Default: '100%' */
|
|
131
|
+
height?: string;
|
|
132
|
+
/** i18n overrides */
|
|
133
|
+
i18n?: import('./chatlist.js').ChatListOptions['i18n'];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Standalone conversation list — separate from the chat widget.
|
|
137
|
+
* Use this on a dedicated /messages or /inbox page.
|
|
138
|
+
* Tap a row → onSelect fires → you navigate to the listing or open a ChatWidget.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* 'use client'
|
|
143
|
+
* import { ChatList } from '@paramms/chat-widget/react'
|
|
144
|
+
* import { useRouter } from 'next/navigation'
|
|
145
|
+
*
|
|
146
|
+
* export default function MessagesPage({ session }) {
|
|
147
|
+
* const router = useRouter()
|
|
148
|
+
* return (
|
|
149
|
+
* <div style={{ height: '600px' }}>
|
|
150
|
+
* <ChatList
|
|
151
|
+
* url={process.env.NEXT_PUBLIC_RELAY_WS_URL}
|
|
152
|
+
* profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}
|
|
153
|
+
* userId={session?.user.id}
|
|
154
|
+
* onSelect={(entry) => {
|
|
155
|
+
* // Navigate to the listing page where the ChatWidget lives
|
|
156
|
+
* router.push(`/listings/${entry.subjectId}`)
|
|
157
|
+
* }}
|
|
158
|
+
* />
|
|
159
|
+
* </div>
|
|
160
|
+
* )
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export declare function ChatList({ url, profileId, userId, onSelect, accent, height, i18n, }: ChatListProps): JSX.Element;
|
|
124
165
|
export {};
|
package/dist/react.js
CHANGED
|
@@ -1,143 +1,172 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { useRef as
|
|
3
|
-
import { mount as
|
|
4
|
-
function
|
|
5
|
-
url:
|
|
6
|
-
profileId:
|
|
1
|
+
import { jsx as k } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as b, useEffect as H } from "react";
|
|
3
|
+
import { mount as _ } from "./index.js";
|
|
4
|
+
function W({
|
|
5
|
+
url: c,
|
|
6
|
+
profileId: l,
|
|
7
7
|
userId: t,
|
|
8
|
-
userName:
|
|
9
|
-
userEmail:
|
|
10
|
-
userAvatar:
|
|
11
|
-
contextTitle:
|
|
8
|
+
userName: u,
|
|
9
|
+
userEmail: r,
|
|
10
|
+
userAvatar: i,
|
|
11
|
+
contextTitle: f,
|
|
12
12
|
contextSubtitle: o,
|
|
13
|
-
contextStatus:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
height: v = "100%",
|
|
13
|
+
contextStatus: e,
|
|
14
|
+
subjectId: n,
|
|
15
|
+
accent: m,
|
|
16
|
+
launcher: p,
|
|
17
|
+
position: R,
|
|
18
|
+
quickReplies: s,
|
|
19
|
+
height: y = "100%",
|
|
21
20
|
i18n: C,
|
|
22
|
-
translateLang:
|
|
21
|
+
translateLang: d
|
|
23
22
|
}) {
|
|
24
|
-
const
|
|
25
|
-
return
|
|
26
|
-
if (
|
|
27
|
-
return
|
|
28
|
-
el:
|
|
29
|
-
url:
|
|
30
|
-
profileId:
|
|
31
|
-
...
|
|
23
|
+
const v = b(null), h = b(null);
|
|
24
|
+
return H(() => {
|
|
25
|
+
if (v.current)
|
|
26
|
+
return h.current = _({
|
|
27
|
+
el: v.current,
|
|
28
|
+
url: c,
|
|
29
|
+
profileId: l,
|
|
30
|
+
...n ? { subjectId: n } : {},
|
|
32
31
|
...t ? { token: t } : {},
|
|
33
|
-
...t ||
|
|
32
|
+
...t || u || r || i ? {
|
|
34
33
|
user: {
|
|
35
|
-
...
|
|
36
|
-
...
|
|
37
|
-
...
|
|
34
|
+
...u ? { name: u } : {},
|
|
35
|
+
...r ? { email: r } : {},
|
|
36
|
+
...i ? { avatar: i } : {}
|
|
38
37
|
}
|
|
39
38
|
} : {},
|
|
40
|
-
...
|
|
39
|
+
...f ? {
|
|
41
40
|
subject: {
|
|
42
|
-
title:
|
|
41
|
+
title: f,
|
|
43
42
|
...o ? { subtitle: o } : {},
|
|
44
|
-
...
|
|
43
|
+
...e ? { status: e } : {}
|
|
45
44
|
}
|
|
46
45
|
} : {},
|
|
47
|
-
...
|
|
48
|
-
...
|
|
49
|
-
...
|
|
50
|
-
...s ? { translateLang: s } : {},
|
|
46
|
+
...s ? { quickReplies: s } : {},
|
|
47
|
+
...m ? { accent: m } : {},
|
|
48
|
+
...d ? { translateLang: d } : {},
|
|
51
49
|
...C ? { i18n: C } : {},
|
|
52
|
-
...
|
|
50
|
+
...p ? { launcher: p, position: R ?? "bottom-right" } : {}
|
|
53
51
|
}), () => {
|
|
54
|
-
var
|
|
55
|
-
(
|
|
52
|
+
var w;
|
|
53
|
+
(w = h.current) == null || w.close(), h.current = null;
|
|
56
54
|
};
|
|
57
|
-
}, [
|
|
55
|
+
}, [c, l, n, t]), /* @__PURE__ */ k(
|
|
58
56
|
"div",
|
|
59
57
|
{
|
|
60
|
-
ref:
|
|
61
|
-
style:
|
|
58
|
+
ref: v,
|
|
59
|
+
style: p ? { width: 0, height: 0, overflow: "hidden" } : { width: "100%", height: y, minHeight: y === "100%" ? "480px" : void 0 }
|
|
62
60
|
}
|
|
63
61
|
);
|
|
64
62
|
}
|
|
65
|
-
const
|
|
63
|
+
const $ = [
|
|
66
64
|
"Is this still available?",
|
|
67
65
|
"Can I schedule a viewing?",
|
|
68
66
|
"What is your best price?"
|
|
69
67
|
];
|
|
70
68
|
function D({
|
|
71
|
-
url:
|
|
72
|
-
profileId:
|
|
69
|
+
url: c,
|
|
70
|
+
profileId: l,
|
|
73
71
|
listingId: t,
|
|
74
|
-
listingTitle:
|
|
75
|
-
listingMeta:
|
|
76
|
-
listingPrice:
|
|
77
|
-
listingStatus:
|
|
72
|
+
listingTitle: u,
|
|
73
|
+
listingMeta: r,
|
|
74
|
+
listingPrice: i,
|
|
75
|
+
listingStatus: f,
|
|
78
76
|
userId: o,
|
|
79
|
-
userName:
|
|
80
|
-
userEmail:
|
|
81
|
-
userAvatar:
|
|
82
|
-
accent:
|
|
83
|
-
launcher:
|
|
84
|
-
position:
|
|
85
|
-
quickReplies:
|
|
86
|
-
height:
|
|
87
|
-
i18n:
|
|
88
|
-
translateLang:
|
|
77
|
+
userName: e,
|
|
78
|
+
userEmail: n,
|
|
79
|
+
userAvatar: m,
|
|
80
|
+
accent: p,
|
|
81
|
+
launcher: R,
|
|
82
|
+
position: s,
|
|
83
|
+
quickReplies: y,
|
|
84
|
+
height: C = "100%",
|
|
85
|
+
i18n: d,
|
|
86
|
+
translateLang: v
|
|
89
87
|
}) {
|
|
90
|
-
const
|
|
91
|
-
return
|
|
92
|
-
if (
|
|
93
|
-
return
|
|
94
|
-
el:
|
|
95
|
-
url:
|
|
96
|
-
profileId:
|
|
88
|
+
const h = b(null), w = b(null);
|
|
89
|
+
return H(() => {
|
|
90
|
+
if (h.current)
|
|
91
|
+
return w.current = _({
|
|
92
|
+
el: h.current,
|
|
93
|
+
url: c,
|
|
94
|
+
profileId: l,
|
|
97
95
|
// ── Listing context ────────────────────────────────────────────────────
|
|
98
96
|
// When listingId is present: open that listing's chat directly (no list).
|
|
99
97
|
// When listingId is absent: show the thread list (dedicated inbox page).
|
|
100
98
|
...t ? { subjectId: `listing_${t}` } : {},
|
|
101
99
|
...o ? { token: o } : {},
|
|
102
|
-
...o ||
|
|
100
|
+
...o || e || n || m ? {
|
|
103
101
|
user: {
|
|
104
|
-
...
|
|
105
|
-
...
|
|
106
|
-
...
|
|
102
|
+
...e ? { name: e } : {},
|
|
103
|
+
...n ? { email: n } : {},
|
|
104
|
+
...m ? { avatar: m } : {},
|
|
107
105
|
...t ? { meta: { listingId: t } } : {}
|
|
108
106
|
}
|
|
109
107
|
} : {},
|
|
110
|
-
...
|
|
108
|
+
...u ? {
|
|
111
109
|
subject: {
|
|
112
|
-
title:
|
|
113
|
-
...
|
|
114
|
-
...
|
|
115
|
-
...
|
|
110
|
+
title: u,
|
|
111
|
+
...r ? { subtitle: r } : {},
|
|
112
|
+
...i ? { tags: [`$${i.toLocaleString()}`] } : {},
|
|
113
|
+
...f ? { status: f } : {}
|
|
116
114
|
}
|
|
117
115
|
} : {},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
116
|
+
quickReplies: y ?? $,
|
|
117
|
+
...p ? { accent: p } : {},
|
|
118
|
+
...v ? { translateLang: v } : {},
|
|
119
|
+
...d ? { i18n: d } : {},
|
|
120
|
+
...R ? { launcher: R, position: s ?? "bottom-right" } : {}
|
|
121
|
+
}), () => {
|
|
122
|
+
var L;
|
|
123
|
+
(L = w.current) == null || L.close(), w.current = null;
|
|
124
|
+
};
|
|
125
|
+
}, [c, l, t, o]), /* @__PURE__ */ k(
|
|
126
|
+
"div",
|
|
127
|
+
{
|
|
128
|
+
ref: h,
|
|
129
|
+
style: R ? { width: 0, height: 0, overflow: "hidden" } : { width: "100%", height: C, minHeight: C === "100%" ? "480px" : void 0 }
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
function F({
|
|
134
|
+
url: c,
|
|
135
|
+
profileId: l,
|
|
136
|
+
userId: t,
|
|
137
|
+
onSelect: u,
|
|
138
|
+
accent: r,
|
|
139
|
+
height: i = "100%",
|
|
140
|
+
i18n: f
|
|
141
|
+
}) {
|
|
142
|
+
const o = b(null), e = b(null);
|
|
143
|
+
return H(() => {
|
|
144
|
+
if (o.current)
|
|
145
|
+
return import("./chatlist.js").then(({ mountChatList: n }) => {
|
|
146
|
+
e.current = n({
|
|
147
|
+
el: o.current,
|
|
148
|
+
url: c,
|
|
149
|
+
profileId: l,
|
|
150
|
+
onSelect: u,
|
|
151
|
+
...t ? { userId: t } : {},
|
|
152
|
+
...r ? { accent: r } : {},
|
|
153
|
+
...f ? { i18n: f } : {}
|
|
154
|
+
});
|
|
127
155
|
}), () => {
|
|
128
|
-
var
|
|
129
|
-
(
|
|
156
|
+
var n;
|
|
157
|
+
(n = e.current) == null || n.close(), e.current = null;
|
|
130
158
|
};
|
|
131
|
-
}, [
|
|
159
|
+
}, [c, l, t]), /* @__PURE__ */ k(
|
|
132
160
|
"div",
|
|
133
161
|
{
|
|
134
|
-
ref:
|
|
135
|
-
style:
|
|
162
|
+
ref: o,
|
|
163
|
+
style: { width: "100%", height: i, minHeight: i === "100%" ? "400px" : void 0 }
|
|
136
164
|
}
|
|
137
165
|
);
|
|
138
166
|
}
|
|
139
167
|
export {
|
|
140
|
-
|
|
168
|
+
F as ChatList,
|
|
169
|
+
W as ChatWidget,
|
|
141
170
|
D as MarketplaceChat
|
|
142
171
|
};
|
|
143
172
|
//# sourceMappingURL=react.js.map
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sources":["../src/react.tsx"],"sourcesContent":["// react.tsx — React wrapper around mount().\n//\n// Next.js App Router: add 'use client' to whichever file imports these.\n// The widget needs WebSocket + DOM — it cannot render on the server.\n//\n// Import path: '@paramms/chat-widget/react'\n//\n// Two components:\n// ChatWidget — general support chat (hotel, SaaS, helpdesk, etc.)\n// MarketplaceChat — one thread per listing (used cars, rentals, etc.)\n//\n// Both: only url + profileId are required. userId is always optional —\n// anonymous guests are handled automatically via localStorage.\n\nimport { useEffect, useRef } from 'react'\nimport { mount, type MountOptions, type WidgetHandle } from './index.js'\n\n// ── Shared base props ─────────────────────────────────────────────────────────\n\ninterface BaseProps {\n /** Relay WebSocket URL — set as NEXT_PUBLIC_RELAY_WS_URL in .env — required */\n url: string\n /** Your Relay profile ID — set as NEXT_PUBLIC_RELAY_PROFILE_ID in .env — required */\n profileId: string\n\n /** Your logged-in user's stable ID. When omitted the widget automatically\n * assigns a persistent anonymous ID from localStorage — no login required. */\n userId?: string\n /** Shown to agents instead of the raw user ID */\n userName?: string\n /** Shown to agents so they can follow up by email */\n userEmail?: string\n /** Avatar URL shown in the widget header and dashboard */\n userAvatar?: string\n\n /** Brand colour hex, e.g. \"#1a56db\". Defaults to the profile theme colour. */\n accent?: string\n /** Render as a floating launcher button instead of inline */\n launcher?: boolean\n /** Launcher position — default 'bottom-right' */\n position?: 'bottom-right' | 'bottom-left'\n /** Pre-set reply chips shown above the input */\n quickReplies?: string[]\n /** Container height when rendered inline. Default: '100%' */\n height?: string\n /** i18n string overrides for non-English sites */\n i18n?: MountOptions['i18n']\n /** ISO language code for auto-translating incoming messages */\n translateLang?: string\n}\n\n// ── ChatWidget ────────────────────────────────────────────────────────────────\n\nexport interface ChatWidgetProps extends BaseProps {\n /** Optional context card shown at the top of the chat\n * (e.g. the support ticket, booking, or order being discussed) */\n contextTitle?: string\n contextSubtitle?: string\n contextStatus?: string\n\n /** Show the WhatsApp-style thread list as the home screen.\n * Use when users can have more than one thread on this profile\n * (e.g. one thread per support ticket, one per booking). */\n showChatList?: boolean\n\n /** Stable item ID — pins this conversation to a specific item/thread.\n * When showChatList is also true, the widget opens this thread immediately\n * but the user can navigate back to the full list. */\n subjectId?: string\n}\n\n/**\n * General-purpose support chat widget. Only `url` and `profileId` are required.\n * All other props are optional — anonymous guests work without any configuration.\n *\n * @example Basic support chat\n * ```tsx\n * <ChatWidget\n * url={process.env.NEXT_PUBLIC_RELAY_WS_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * ```\n *\n * @example Multi-thread (tickets, bookings, orders)\n * ```tsx\n * <ChatWidget\n * url={...} profileId={...}\n * showChatList\n * subjectId={`ticket_${ticket.id}`}\n * contextTitle={ticket.title}\n * contextStatus={ticket.status}\n * userId={session?.user.id}\n * />\n * ```\n */\nexport function ChatWidget({\n url, profileId,\n userId, userName, userEmail, userAvatar,\n contextTitle, contextSubtitle, contextStatus,\n showChatList, subjectId,\n accent, launcher, position, quickReplies, height = '100%',\n i18n, translateLang,\n}: ChatWidgetProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<WidgetHandle | null>(null)\n\n useEffect(() => {\n if (!ref.current) return\n handleRef.current = mount({\n el: ref.current,\n url,\n profileId,\n ...(subjectId ? { subjectId } : {}),\n ...(userId ? { token: userId } : {}),\n ...(userId || userName || userEmail || userAvatar ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n ...(userAvatar ? { avatar: userAvatar } : {}),\n },\n } : {}),\n ...(contextTitle ? {\n subject: {\n title: contextTitle,\n ...(contextSubtitle ? { subtitle: contextSubtitle } : {}),\n ...(contextStatus ? { status: contextStatus } : {}),\n },\n } : {}),\n ...(showChatList ? { showChatList: true } : {}),\n ...(quickReplies ? { quickReplies } : {}),\n ...(accent ? { accent } : {}),\n ...(translateLang ? { translateLang } : {}),\n ...(i18n ? { i18n } : {}),\n ...(launcher ? { launcher, position: position ?? 'bottom-right' } : {}),\n })\n return () => { handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, profileId, subjectId, userId])\n\n // When launcher=true, still render the div — mount() moves it inside the\n // fixed panel above the bubble. Zero size so it doesn't affect page layout.\n return (\n <div\n ref={ref}\n style={launcher ? { width: 0, height: 0, overflow: 'hidden' } : { width: '100%', height, minHeight: height === '100%' ? '480px' : undefined }}\n />\n )\n}\n\n// ── MarketplaceChat ───────────────────────────────────────────────────────────\n\nexport interface MarketplaceChatProps extends BaseProps {\n /** Unique ID for this listing — each listing gets its own thread.\n * Omit for a general (non-item-specific) conversation. */\n listingId?: string\n /** Shown at the top of the chat — e.g. \"2019 Toyota Camry SE\" */\n listingTitle?: string\n /** One-line detail — e.g. \"45,000 km · Automatic\" */\n listingMeta?: string\n /** Price shown as a tag — e.g. 12500 → \"$12,500\" */\n listingPrice?: number\n /** Status badge — e.g. \"Available\", \"Sold\", \"Pending\" */\n listingStatus?: string\n}\n\nconst DEFAULT_MARKETPLACE_REPLIES = [\n 'Is this still available?',\n 'Can I schedule a viewing?',\n 'What is your best price?',\n]\n\n/**\n * Marketplace chat widget. Two modes depending on whether `listingId` is provided:\n *\n * **With `listingId` (listing page)** — opens that listing's chat immediately.\n * No list screen. The buyer is already looking at the item so context is clear.\n * ```tsx\n * // On your listing detail page:\n * <MarketplaceChat\n * url={process.env.NEXT_PUBLIC_RELAY_WS_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * listingId={car.id}\n * listingTitle={car.title}\n * listingPrice={car.price}\n * listingMeta={`${car.mileage.toLocaleString()} km · ${car.gearbox}`}\n * listingStatus=\"Available\"\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * ```\n *\n * **Without `listingId` (My Messages / inbox page)** — shows the WhatsApp-style\n * thread list as the home screen. No conversation is opened until the user taps one.\n * Use this on a dedicated `/messages` or `/inbox` page.\n * ```tsx\n * // On your /messages page:\n * <MarketplaceChat\n * url={process.env.NEXT_PUBLIC_RELAY_WS_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * userName={session?.user.name}\n * />\n * ```\n *\n * Only `url` and `profileId` are required. All other props are optional.\n */\nexport function MarketplaceChat({\n url, profileId,\n listingId, listingTitle, listingMeta, listingPrice, listingStatus,\n userId, userName, userEmail, userAvatar,\n accent, launcher, position, quickReplies, height = '100%',\n i18n, translateLang,\n}: MarketplaceChatProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<WidgetHandle | null>(null)\n\n useEffect(() => {\n if (!ref.current) return\n handleRef.current = mount({\n el: ref.current,\n url,\n profileId,\n\n // ── Listing context ────────────────────────────────────────────────────\n // When listingId is present: open that listing's chat directly (no list).\n // When listingId is absent: show the thread list (dedicated inbox page).\n ...(listingId ? { subjectId: `listing_${listingId}` } : {}),\n\n ...(userId ? { token: userId } : {}),\n ...(userId || userName || userEmail || userAvatar ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n ...(userAvatar ? { avatar: userAvatar } : {}),\n ...(listingId ? { meta: { listingId } } : {}),\n },\n } : {}),\n\n ...(listingTitle ? {\n subject: {\n title: listingTitle,\n ...(listingMeta ? { subtitle: listingMeta } : {}),\n ...(listingPrice ? { tags: [`$${listingPrice.toLocaleString()}`] } : {}),\n ...(listingStatus ? { status: listingStatus } : {}),\n },\n } : {}),\n\n // showChatList is true in both cases but behaves differently:\n // • With subjectId → chat screen shown immediately on open (in index.ts)\n // • Without subjectId → list screen is home (the dedicated inbox case)\n showChatList: true,\n quickReplies: quickReplies ?? DEFAULT_MARKETPLACE_REPLIES,\n ...(accent ? { accent } : {}),\n ...(translateLang ? { translateLang } : {}),\n ...(i18n ? { i18n } : {}),\n ...(launcher ? { launcher, position: position ?? 'bottom-right' } : {}),\n })\n return () => { handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, profileId, listingId, userId])\n\n // When launcher=true, still render the div — mount() moves it inside the\n // fixed panel above the bubble. Zero size so it doesn't affect page layout.\n return (\n <div\n ref={ref}\n style={launcher ? { width: 0, height: 0, overflow: 'hidden' } : { width: '100%', height, minHeight: height === '100%' ? '480px' : undefined }}\n />\n )\n}\n"],"names":["ChatWidget","url","profileId","userId","userName","userEmail","userAvatar","contextTitle","contextSubtitle","contextStatus","showChatList","subjectId","accent","launcher","position","quickReplies","height","i18n","translateLang","ref","useRef","handleRef","useEffect","mount","_a","jsx","DEFAULT_MARKETPLACE_REPLIES","MarketplaceChat","listingId","listingTitle","listingMeta","listingPrice","listingStatus"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"react.js","sources":["../src/react.tsx"],"sourcesContent":["// react.tsx — React wrapper around mount().\n//\n// Next.js App Router: add 'use client' to whichever file imports these.\n// The widget needs WebSocket + DOM — it cannot render on the server.\n//\n// Import path: '@paramms/chat-widget/react'\n//\n// Two components:\n// ChatWidget — general support chat (hotel, SaaS, helpdesk, etc.)\n// MarketplaceChat — one thread per listing (used cars, rentals, etc.)\n//\n// Both: only url + profileId are required. userId is always optional —\n// anonymous guests are handled automatically via localStorage.\n\nimport { useEffect, useRef } from 'react'\nimport { mount, type MountOptions, type WidgetHandle } from './index.js'\n\n// ── Shared base props ─────────────────────────────────────────────────────────\n\ninterface BaseProps {\n /** Relay WebSocket URL — set as NEXT_PUBLIC_RELAY_WS_URL in .env — required */\n url: string\n /** Your Relay profile ID — set as NEXT_PUBLIC_RELAY_PROFILE_ID in .env — required */\n profileId: string\n\n /** Your logged-in user's stable ID. When omitted the widget automatically\n * assigns a persistent anonymous ID from localStorage — no login required. */\n userId?: string\n /** Shown to agents instead of the raw user ID */\n userName?: string\n /** Shown to agents so they can follow up by email */\n userEmail?: string\n /** Avatar URL shown in the widget header and dashboard */\n userAvatar?: string\n\n /** Brand colour hex, e.g. \"#1a56db\". Defaults to the profile theme colour. */\n accent?: string\n /** Render as a floating launcher button instead of inline */\n launcher?: boolean\n /** Launcher position — default 'bottom-right' */\n position?: 'bottom-right' | 'bottom-left'\n /** Pre-set reply chips shown above the input */\n quickReplies?: string[]\n /** Container height when rendered inline. Default: '100%' */\n height?: string\n /** i18n string overrides for non-English sites */\n i18n?: MountOptions['i18n']\n /** ISO language code for auto-translating incoming messages */\n translateLang?: string\n}\n\n// ── ChatWidget ────────────────────────────────────────────────────────────────\n\nexport interface ChatWidgetProps extends BaseProps {\n /** Optional context card shown at the top of the chat\n * (e.g. the support ticket, booking, or order being discussed) */\n contextTitle?: string\n contextSubtitle?: string\n contextStatus?: string\n\n /** Stable item ID — pins this conversation to a specific item/thread. */\n subjectId?: string\n}\n\n/**\n * General-purpose support chat widget. Only `url` and `profileId` are required.\n * All other props are optional — anonymous guests work without any configuration.\n *\n * @example Basic support chat\n * ```tsx\n * <ChatWidget\n * url={process.env.NEXT_PUBLIC_RELAY_WS_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * ```\n *\n * @example Multi-thread (tickets, bookings, orders)\n * ```tsx\n * <ChatWidget\n * url={...} profileId={...}\n * showChatList\n * subjectId={`ticket_${ticket.id}`}\n * contextTitle={ticket.title}\n * contextStatus={ticket.status}\n * userId={session?.user.id}\n * />\n * ```\n */\nexport function ChatWidget({\n url, profileId,\n userId, userName, userEmail, userAvatar,\n contextTitle, contextSubtitle, contextStatus,\n subjectId,\n accent, launcher, position, quickReplies, height = '100%',\n i18n, translateLang,\n}: ChatWidgetProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<WidgetHandle | null>(null)\n\n useEffect(() => {\n if (!ref.current) return\n handleRef.current = mount({\n el: ref.current,\n url,\n profileId,\n ...(subjectId ? { subjectId } : {}),\n ...(userId ? { token: userId } : {}),\n ...(userId || userName || userEmail || userAvatar ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n ...(userAvatar ? { avatar: userAvatar } : {}),\n },\n } : {}),\n ...(contextTitle ? {\n subject: {\n title: contextTitle,\n ...(contextSubtitle ? { subtitle: contextSubtitle } : {}),\n ...(contextStatus ? { status: contextStatus } : {}),\n },\n } : {}),\n ...(quickReplies ? { quickReplies } : {}),\n ...(accent ? { accent } : {}),\n ...(translateLang ? { translateLang } : {}),\n ...(i18n ? { i18n } : {}),\n ...(launcher ? { launcher, position: position ?? 'bottom-right' } : {}),\n })\n return () => { handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, profileId, subjectId, userId])\n\n // When launcher=true, still render the div — mount() moves it inside the\n // fixed panel above the bubble. Zero size so it doesn't affect page layout.\n return (\n <div\n ref={ref}\n style={launcher ? { width: 0, height: 0, overflow: 'hidden' } : { width: '100%', height, minHeight: height === '100%' ? '480px' : undefined }}\n />\n )\n}\n\n// ── MarketplaceChat ───────────────────────────────────────────────────────────\n\nexport interface MarketplaceChatProps extends BaseProps {\n /** Unique ID for this listing — each listing gets its own thread.\n * Omit for a general (non-item-specific) conversation. */\n listingId?: string\n /** Shown at the top of the chat — e.g. \"2019 Toyota Camry SE\" */\n listingTitle?: string\n /** One-line detail — e.g. \"45,000 km · Automatic\" */\n listingMeta?: string\n /** Price shown as a tag — e.g. 12500 → \"$12,500\" */\n listingPrice?: number\n /** Status badge — e.g. \"Available\", \"Sold\", \"Pending\" */\n listingStatus?: string\n}\n\nconst DEFAULT_MARKETPLACE_REPLIES = [\n 'Is this still available?',\n 'Can I schedule a viewing?',\n 'What is your best price?',\n]\n\n/**\n * Marketplace chat widget. Two modes depending on whether `listingId` is provided:\n *\n * **With `listingId` (listing page)** — opens that listing's chat immediately.\n * No list screen. The buyer is already looking at the item so context is clear.\n * ```tsx\n * // On your listing detail page:\n * <MarketplaceChat\n * url={process.env.NEXT_PUBLIC_RELAY_WS_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * listingId={car.id}\n * listingTitle={car.title}\n * listingPrice={car.price}\n * listingMeta={`${car.mileage.toLocaleString()} km · ${car.gearbox}`}\n * listingStatus=\"Available\"\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * ```\n *\n * **Without `listingId` (My Messages / inbox page)** — shows the WhatsApp-style\n * thread list as the home screen. No conversation is opened until the user taps one.\n * Use this on a dedicated `/messages` or `/inbox` page.\n * ```tsx\n * // On your /messages page:\n * <MarketplaceChat\n * url={process.env.NEXT_PUBLIC_RELAY_WS_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * userName={session?.user.name}\n * />\n * ```\n *\n * Only `url` and `profileId` are required. All other props are optional.\n */\nexport function MarketplaceChat({\n url, profileId,\n listingId, listingTitle, listingMeta, listingPrice, listingStatus,\n userId, userName, userEmail, userAvatar,\n accent, launcher, position, quickReplies, height = '100%',\n i18n, translateLang,\n}: MarketplaceChatProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<WidgetHandle | null>(null)\n\n useEffect(() => {\n if (!ref.current) return\n handleRef.current = mount({\n el: ref.current,\n url,\n profileId,\n\n // ── Listing context ────────────────────────────────────────────────────\n // When listingId is present: open that listing's chat directly (no list).\n // When listingId is absent: show the thread list (dedicated inbox page).\n ...(listingId ? { subjectId: `listing_${listingId}` } : {}),\n\n ...(userId ? { token: userId } : {}),\n ...(userId || userName || userEmail || userAvatar ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n ...(userAvatar ? { avatar: userAvatar } : {}),\n ...(listingId ? { meta: { listingId } } : {}),\n },\n } : {}),\n\n ...(listingTitle ? {\n subject: {\n title: listingTitle,\n ...(listingMeta ? { subtitle: listingMeta } : {}),\n ...(listingPrice ? { tags: [`$${listingPrice.toLocaleString()}`] } : {}),\n ...(listingStatus ? { status: listingStatus } : {}),\n },\n } : {}),\n\n\n quickReplies: quickReplies ?? DEFAULT_MARKETPLACE_REPLIES,\n ...(accent ? { accent } : {}),\n ...(translateLang ? { translateLang } : {}),\n ...(i18n ? { i18n } : {}),\n ...(launcher ? { launcher, position: position ?? 'bottom-right' } : {}),\n })\n return () => { handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, profileId, listingId, userId])\n\n // When launcher=true, still render the div — mount() moves it inside the\n // fixed panel above the bubble. Zero size so it doesn't affect page layout.\n return (\n <div\n ref={ref}\n style={launcher ? { width: 0, height: 0, overflow: 'hidden' } : { width: '100%', height, minHeight: height === '100%' ? '480px' : undefined }}\n />\n )\n}\n\n// ── ChatList ──────────────────────────────────────────────────────────────────\n\nexport interface ChatListProps {\n /** Relay WebSocket URL — required */\n url: string\n /** Your Relay profile ID — required */\n profileId: string\n /** Your logged-in user's stable ID. Omit for anonymous (uses localStorage UID) */\n userId?: string\n /** Called when the user taps a conversation row.\n * Use this to navigate to the listing page or open a ChatWidget. */\n onSelect: (entry: import('./chatlist.js').ChatListEntry) => void\n /** Brand colour hex — default '#f5713c' */\n accent?: string\n /** Container height when rendered inline. Default: '100%' */\n height?: string\n /** i18n overrides */\n i18n?: import('./chatlist.js').ChatListOptions['i18n']\n}\n\n/**\n * Standalone conversation list — separate from the chat widget.\n * Use this on a dedicated /messages or /inbox page.\n * Tap a row → onSelect fires → you navigate to the listing or open a ChatWidget.\n *\n * @example\n * ```tsx\n * 'use client'\n * import { ChatList } from '@paramms/chat-widget/react'\n * import { useRouter } from 'next/navigation'\n *\n * export default function MessagesPage({ session }) {\n * const router = useRouter()\n * return (\n * <div style={{ height: '600px' }}>\n * <ChatList\n * url={process.env.NEXT_PUBLIC_RELAY_WS_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * onSelect={(entry) => {\n * // Navigate to the listing page where the ChatWidget lives\n * router.push(`/listings/${entry.subjectId}`)\n * }}\n * />\n * </div>\n * )\n * }\n * ```\n */\nexport function ChatList({\n url, profileId, userId, onSelect, accent, height = '100%', i18n,\n}: ChatListProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<import('./chatlist.js').ChatListHandle | null>(null)\n\n useEffect(() => {\n if (!ref.current) return\n import('./chatlist.js').then(({ mountChatList }) => {\n handleRef.current = mountChatList({\n el: ref.current!,\n url,\n profileId,\n onSelect,\n ...(userId ? { userId } : {}),\n ...(accent ? { accent } : {}),\n ...(i18n ? { i18n } : {}),\n })\n })\n return () => { handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, profileId, userId])\n\n return (\n <div\n ref={ref}\n style={{ width: '100%', height, minHeight: height === '100%' ? '400px' : undefined }}\n />\n )\n}\n"],"names":["ChatWidget","url","profileId","userId","userName","userEmail","userAvatar","contextTitle","contextSubtitle","contextStatus","subjectId","accent","launcher","position","quickReplies","height","i18n","translateLang","ref","useRef","handleRef","useEffect","mount","_a","jsx","DEFAULT_MARKETPLACE_REPLIES","MarketplaceChat","listingId","listingTitle","listingMeta","listingPrice","listingStatus","ChatList","onSelect","mountChatList"],"mappings":"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"}
|
package/dist/renderer.d.ts
CHANGED
|
@@ -22,10 +22,6 @@ export interface WidgetConfig {
|
|
|
22
22
|
offline?: string;
|
|
23
23
|
poweredBy?: string;
|
|
24
24
|
};
|
|
25
|
-
/** When showChatList=true, start on the chat screen instead of the list screen.
|
|
26
|
-
* Set when a specific conversation/listing is already known (e.g. MarketplaceChat
|
|
27
|
-
* with listingId). The list is still accessible via the back button. */
|
|
28
|
-
startOnChat?: boolean;
|
|
29
25
|
}
|
|
30
26
|
export interface RendererHandlers {
|
|
31
27
|
onSend(text: string): void;
|
|
@@ -45,24 +41,6 @@ export interface RendererHandlers {
|
|
|
45
41
|
/** Translate a message's text for display. Return null if unavailable —
|
|
46
42
|
* the renderer shows a brief "unavailable" hint and leaves the original. */
|
|
47
43
|
onTranslate?(text: string): Promise<string | null>;
|
|
48
|
-
/** Multi-subject chat list (e.g. a marketplace with one thread per item):
|
|
49
|
-
* fetch the guest's other conversations. Omit to hide the list button
|
|
50
|
-
* entirely — single-conversation embeds don't need this. */
|
|
51
|
-
onListChats?(): Promise<ChatListEntry[]>;
|
|
52
|
-
/** Switch the active conversation to a different one from the list —
|
|
53
|
-
* effectively a re-open with a different subjectId. */
|
|
54
|
-
onSwitchChat?(entry: ChatListEntry): void;
|
|
55
|
-
}
|
|
56
|
-
export interface ChatListEntry {
|
|
57
|
-
id: string;
|
|
58
|
-
subjectId?: string;
|
|
59
|
-
subjectTitle?: string;
|
|
60
|
-
state: string;
|
|
61
|
-
updatedAt: number;
|
|
62
|
-
/** Server-assigned highest message seq — used to compute unread count. */
|
|
63
|
-
lastSeq?: number;
|
|
64
|
-
/** Snippet of the last message text — shown as preview in the list row. */
|
|
65
|
-
lastMessage?: string;
|
|
66
44
|
}
|
|
67
45
|
/** Renders a ChatStore into a host element in the Image-1 layout: header →
|
|
68
46
|
* subject card → action chips → chat → quick replies → input. Self-injects its
|
|
@@ -104,38 +82,15 @@ export declare class Renderer {
|
|
|
104
82
|
setScrollCleanup(fn: () => void): void;
|
|
105
83
|
private readonly translationCache;
|
|
106
84
|
private readonly showingTranslation;
|
|
107
|
-
private listScreen;
|
|
108
|
-
private chatScreen;
|
|
109
|
-
private listBody;
|
|
110
|
-
private listSearch;
|
|
111
|
-
private backBtn;
|
|
112
|
-
private activeChatId;
|
|
113
|
-
private inChatScreen;
|
|
114
|
-
private allEntries;
|
|
115
85
|
/** Last seq the guest has seen per conversationId — used to compute unread badges. */
|
|
116
|
-
private readonly seenSeq;
|
|
117
|
-
private seenSeqKey;
|
|
118
|
-
/** Load seenSeq from localStorage (keyed by userId+profileId). */
|
|
119
|
-
initSeenSeq(key: string): void;
|
|
120
|
-
setSeenSeq(conversationId: string, seq: number): void;
|
|
121
86
|
constructor(root: HTMLElement, me: string, h: RendererHandlers, cfg?: WidgetConfig);
|
|
122
|
-
/**
|
|
123
|
-
private destroyed;
|
|
124
|
-
/** Call when the widget is unmounted. Disconnects scroll listeners and
|
|
125
|
-
* prevents in-flight fetches from writing to dead DOM. */
|
|
87
|
+
/** Call when the widget is unmounted. Disconnects scroll listeners. */
|
|
126
88
|
destroy(): void;
|
|
127
|
-
refreshList(): void;
|
|
128
89
|
/** Render list rows, optionally filtered by search query. */
|
|
129
|
-
private renderListRows;
|
|
130
|
-
private isUnread;
|
|
131
90
|
/** Build a single WhatsApp-style conversation row. */
|
|
132
|
-
private buildRow;
|
|
133
91
|
/** Returns true when the chat screen is visible (not the list). */
|
|
134
|
-
isInChatScreen(): boolean;
|
|
135
92
|
/** Navigate to the chat screen (slide list left, slide chat in from right). */
|
|
136
|
-
showChatScreen(): void;
|
|
137
93
|
/** Navigate back to the list screen. */
|
|
138
|
-
showListScreen(): void;
|
|
139
94
|
private flushSend;
|
|
140
95
|
private signalTyping;
|
|
141
96
|
render(store: ChatStore): void;
|
package/dist/uid.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paramms/chat-widget",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Embeddable real-time chat widget for the Relay platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
"dist/react.js",
|
|
22
22
|
"dist/react.js.map",
|
|
23
23
|
"dist/react.d.ts",
|
|
24
|
+
"dist/chatlist.js",
|
|
25
|
+
"dist/chatlist.js.map",
|
|
26
|
+
"dist/chatlist.d.ts",
|
|
24
27
|
"dist/store.d.ts",
|
|
25
28
|
"dist/connection.d.ts",
|
|
26
29
|
"dist/history.d.ts",
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
"dist/renderer.d.ts",
|
|
29
32
|
"dist/crypto.d.ts",
|
|
30
33
|
"dist/e2e.d.ts",
|
|
34
|
+
"dist/uid.d.ts",
|
|
31
35
|
"dist/protocol"
|
|
32
36
|
],
|
|
33
37
|
"type": "module",
|
|
@@ -60,6 +64,10 @@
|
|
|
60
64
|
"./react": {
|
|
61
65
|
"types": "./dist/react.d.ts",
|
|
62
66
|
"default": "./dist/react.js"
|
|
67
|
+
},
|
|
68
|
+
"./chatlist": {
|
|
69
|
+
"types": "./dist/chatlist.d.ts",
|
|
70
|
+
"default": "./dist/chatlist.js"
|
|
63
71
|
}
|
|
64
72
|
},
|
|
65
73
|
"peerDependencies": {
|