@linktr.ee/messaging-react 1.3.0 → 1.4.0-rc-1761046174
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/index.d.ts +9 -0
- package/dist/index.js +741 -681
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelList/index.tsx +1 -1
- package/src/components/ChannelView.tsx +7 -2
- package/src/components/MessagingShell/EmptyState.tsx +1 -1
- package/src/components/MessagingShell/ErrorState.tsx +18 -20
- package/src/components/MessagingShell/LoadingState.tsx +1 -1
- package/src/components/MessagingShell/index.tsx +103 -12
- package/src/components/ParticipantPicker/ParticipantPicker.tsx +6 -1
- package/src/types.ts +10 -0
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React from 'react'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Error state component
|
|
4
|
+
* Error state component shown when something goes wrong
|
|
5
5
|
*/
|
|
6
|
-
export const ErrorState: React.FC<{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
export const ErrorState: React.FC<{
|
|
7
|
+
message: string
|
|
8
|
+
onBack?: () => void
|
|
9
|
+
}> = ({ message, onBack }) => (
|
|
10
|
+
<div className="messaging-error-state flex items-center justify-center h-full p-8">
|
|
11
|
+
<div className="text-center max-w-sm">
|
|
12
|
+
<div className="w-24 h-24 bg-danger-alt/20 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
10
13
|
<span className="text-4xl">⚠️</span>
|
|
11
14
|
</div>
|
|
12
|
-
|
|
13
|
-
<h2 className="text-xl font-semibold text-charcoal mb-3">
|
|
14
|
-
Connection Error
|
|
15
|
-
</h2>
|
|
16
|
-
|
|
17
|
-
<p className="text-stone text-sm mb-6">
|
|
18
|
-
{error}
|
|
19
|
-
</p>
|
|
20
15
|
|
|
21
|
-
|
|
16
|
+
<h2 className="font-semibold text-charcoal mb-2">Oops!</h2>
|
|
17
|
+
|
|
18
|
+
<p className="text-stone text-sm mb-6">{message}</p>
|
|
19
|
+
|
|
20
|
+
{onBack && (
|
|
22
21
|
<button
|
|
23
22
|
type="button"
|
|
24
|
-
onClick={
|
|
25
|
-
className="inline-flex items-center px-4 py-2
|
|
23
|
+
onClick={onBack}
|
|
24
|
+
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-primary hover:bg-primary-alt rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition-colors"
|
|
26
25
|
>
|
|
27
|
-
|
|
26
|
+
Go Back
|
|
28
27
|
</button>
|
|
29
28
|
)}
|
|
30
29
|
</div>
|
|
31
30
|
</div>
|
|
32
|
-
)
|
|
33
|
-
|
|
31
|
+
)
|
|
@@ -4,7 +4,7 @@ import Loading from '../Loading'
|
|
|
4
4
|
* Loading state component
|
|
5
5
|
*/
|
|
6
6
|
export const LoadingState = () => (
|
|
7
|
-
<div className="flex items-center justify-center h-full">
|
|
7
|
+
<div className="messaging-loading-state flex items-center justify-center h-full">
|
|
8
8
|
<div className="flex items-center">
|
|
9
9
|
<Loading className="w-6 h-6" />
|
|
10
10
|
<span className="text-sm text-stone">Loading messages</span>
|
|
@@ -21,6 +21,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
21
21
|
renderMessageInputActions,
|
|
22
22
|
onChannelSelect,
|
|
23
23
|
onParticipantSelect,
|
|
24
|
+
initialParticipantFilter,
|
|
24
25
|
}) => {
|
|
25
26
|
const {
|
|
26
27
|
service,
|
|
@@ -39,6 +40,10 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
39
40
|
Set<string>
|
|
40
41
|
>(new Set())
|
|
41
42
|
const [pickerKey, setPickerKey] = useState(0) // Key to force remount of ParticipantPicker
|
|
43
|
+
const [directConversationMode, setDirectConversationMode] = useState(false)
|
|
44
|
+
const [directConversationError, setDirectConversationError] = useState<
|
|
45
|
+
string | null
|
|
46
|
+
>(null)
|
|
42
47
|
|
|
43
48
|
const participantPickerRef = useRef<HTMLDialogElement>(null)
|
|
44
49
|
|
|
@@ -112,6 +117,64 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
112
117
|
syncChannels()
|
|
113
118
|
}, [client, isConnected, syncChannels])
|
|
114
119
|
|
|
120
|
+
// Load initial channel for direct conversation mode
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!initialParticipantFilter || !client || !isConnected) return
|
|
123
|
+
|
|
124
|
+
const loadInitialChannel = async () => {
|
|
125
|
+
const userId = client.userID
|
|
126
|
+
if (!userId) return
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
if (debug) {
|
|
130
|
+
console.log(
|
|
131
|
+
'[MessagingShell] Loading initial conversation with:',
|
|
132
|
+
initialParticipantFilter
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const channels = await client.queryChannels(
|
|
137
|
+
{
|
|
138
|
+
type: 'messaging',
|
|
139
|
+
members: { $in: [userId, initialParticipantFilter] },
|
|
140
|
+
},
|
|
141
|
+
{},
|
|
142
|
+
{ limit: 1 }
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if (channels.length > 0) {
|
|
146
|
+
setSelectedChannel(channels[0])
|
|
147
|
+
setDirectConversationMode(true)
|
|
148
|
+
setDirectConversationError(null)
|
|
149
|
+
|
|
150
|
+
if (debug) {
|
|
151
|
+
console.log(
|
|
152
|
+
'[MessagingShell] Initial conversation loaded:',
|
|
153
|
+
channels[0].id
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
setDirectConversationError('No conversation found with this account')
|
|
158
|
+
|
|
159
|
+
if (debug) {
|
|
160
|
+
console.log(
|
|
161
|
+
'[MessagingShell] No conversation found for:',
|
|
162
|
+
initialParticipantFilter
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(
|
|
168
|
+
'[MessagingShell] Failed to load initial conversation:',
|
|
169
|
+
err
|
|
170
|
+
)
|
|
171
|
+
setDirectConversationError('Failed to load conversation')
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
loadInitialChannel()
|
|
176
|
+
}, [initialParticipantFilter, client, isConnected, debug])
|
|
177
|
+
|
|
115
178
|
const handleChannelSelect = useCallback(
|
|
116
179
|
(channel: Channel) => {
|
|
117
180
|
setSelectedChannel(channel)
|
|
@@ -121,8 +184,12 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
121
184
|
)
|
|
122
185
|
|
|
123
186
|
const handleBackToChannelList = useCallback(() => {
|
|
187
|
+
// In direct conversation mode, don't allow going back to channel list
|
|
188
|
+
// The parent component should handle navigation
|
|
189
|
+
if (directConversationMode) return
|
|
190
|
+
|
|
124
191
|
setSelectedChannel(null)
|
|
125
|
-
}, [])
|
|
192
|
+
}, [directConversationMode])
|
|
126
193
|
|
|
127
194
|
const handleStartConversation = useCallback(() => {
|
|
128
195
|
if (participantSource) {
|
|
@@ -218,7 +285,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
218
285
|
if (error) {
|
|
219
286
|
return (
|
|
220
287
|
<div className={classNames('h-full', className)}>
|
|
221
|
-
<ErrorState
|
|
288
|
+
<ErrorState message={error} onBack={refreshConnection} />
|
|
222
289
|
</div>
|
|
223
290
|
)
|
|
224
291
|
}
|
|
@@ -228,24 +295,43 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
228
295
|
return (
|
|
229
296
|
<div className={classNames('h-full', className)}>
|
|
230
297
|
<ErrorState
|
|
231
|
-
|
|
232
|
-
|
|
298
|
+
message="Not connected to messaging service"
|
|
299
|
+
onBack={refreshConnection}
|
|
233
300
|
/>
|
|
234
301
|
</div>
|
|
235
302
|
)
|
|
236
303
|
}
|
|
237
304
|
|
|
305
|
+
// Show direct conversation error state
|
|
306
|
+
if (directConversationError) {
|
|
307
|
+
return (
|
|
308
|
+
<div className={classNames('h-full', className)}>
|
|
309
|
+
<ErrorState message={directConversationError} />
|
|
310
|
+
</div>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
238
314
|
return (
|
|
239
|
-
<div
|
|
315
|
+
<div
|
|
316
|
+
className={classNames(
|
|
317
|
+
'messaging-shell h-full bg-white overflow-hidden',
|
|
318
|
+
className
|
|
319
|
+
)}
|
|
320
|
+
>
|
|
240
321
|
<div className="flex h-full min-h-0">
|
|
241
322
|
{/* Channel List Sidebar */}
|
|
242
323
|
<div
|
|
243
324
|
className={classNames(
|
|
244
|
-
'min-h-0 min-w-0 bg-white lg:bg-chalk lg:flex lg:flex-col lg:border-r lg:border-sand',
|
|
325
|
+
'messaging-channel-list-sidebar min-h-0 min-w-0 bg-white lg:bg-chalk lg:flex lg:flex-col lg:border-r lg:border-sand',
|
|
245
326
|
{
|
|
327
|
+
// In direct conversation mode, always hide the channel list
|
|
328
|
+
hidden: directConversationMode,
|
|
329
|
+
// Normal mode: hide on mobile when channel selected, show on desktop
|
|
246
330
|
'hidden lg:flex lg:w-80 lg:min-w-[280px] lg:max-w-[360px]':
|
|
247
|
-
isChannelSelected,
|
|
248
|
-
|
|
331
|
+
!directConversationMode && isChannelSelected,
|
|
332
|
+
// Normal mode: show when no channel selected
|
|
333
|
+
'flex flex-col w-full lg:flex-1 lg:max-w-2xl':
|
|
334
|
+
!directConversationMode && !isChannelSelected,
|
|
249
335
|
}
|
|
250
336
|
)}
|
|
251
337
|
>
|
|
@@ -262,10 +348,15 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
262
348
|
|
|
263
349
|
{/* Channel View */}
|
|
264
350
|
<div
|
|
265
|
-
className={classNames(
|
|
266
|
-
'
|
|
267
|
-
|
|
268
|
-
|
|
351
|
+
className={classNames(
|
|
352
|
+
'messaging-conversation-view flex-1 flex-col min-w-0 min-h-0',
|
|
353
|
+
{
|
|
354
|
+
// In direct conversation mode, always show (full width)
|
|
355
|
+
flex: directConversationMode || isChannelSelected,
|
|
356
|
+
// Normal mode: hide on mobile when no channel selected
|
|
357
|
+
'hidden lg:flex': !directConversationMode && !isChannelSelected,
|
|
358
|
+
}
|
|
359
|
+
)}
|
|
269
360
|
>
|
|
270
361
|
{selectedChannel ? (
|
|
271
362
|
<div className="flex-1 min-h-0 flex flex-col">
|
|
@@ -123,7 +123,12 @@ export const ParticipantPicker: React.FC<ParticipantPickerProps> = ({
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
return (
|
|
126
|
-
<div
|
|
126
|
+
<div
|
|
127
|
+
className={classNames(
|
|
128
|
+
'messaging-participant-picker flex flex-col h-full',
|
|
129
|
+
className
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
127
132
|
{/* Header */}
|
|
128
133
|
<div className="px-4 py-4 border-b border-sand bg-chalk">
|
|
129
134
|
<div className="flex items-center justify-between mb-3">
|
package/src/types.ts
CHANGED
|
@@ -59,6 +59,16 @@ export interface MessagingShellProps {
|
|
|
59
59
|
renderMessageInputActions?: (channel: Channel) => React.ReactNode;
|
|
60
60
|
onChannelSelect?: (channel: Channel) => void;
|
|
61
61
|
onParticipantSelect?: (participant: Participant) => void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Auto-select a conversation with this participant on mount.
|
|
65
|
+
* Useful for deep-linking to a specific conversation (e.g., /messages/[accountUuid])
|
|
66
|
+
*
|
|
67
|
+
* If a channel with this participant exists, it will be auto-selected.
|
|
68
|
+
* If no channel exists and participantSource is provided,
|
|
69
|
+
* the participant picker can be shown to create a new conversation.
|
|
70
|
+
*/
|
|
71
|
+
initialParticipantFilter?: string;
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
/**
|