@linktr.ee/messaging-react 1.0.2 → 1.1.0-rc-1760927977

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 (39) hide show
  1. package/dist/assets/index.css +1 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +836 -1079
  4. package/dist/index.js.map +1 -1
  5. package/package.json +4 -3
  6. package/src/components/ActionButton/ActionButton.stories.tsx +2 -1
  7. package/src/components/ActionButton/ActionButton.test.tsx +2 -0
  8. package/src/components/Avatar/Avatar.stories.tsx +2 -1
  9. package/src/components/Avatar/index.tsx +2 -1
  10. package/src/components/ChannelList/ChannelList.stories.tsx +4 -2
  11. package/src/components/ChannelList/CustomChannelPreview.stories.tsx +31 -27
  12. package/src/components/ChannelList/CustomChannelPreview.tsx +5 -11
  13. package/src/components/ChannelList/index.tsx +43 -35
  14. package/src/components/ChannelView.tsx +150 -127
  15. package/src/components/CloseButton/index.tsx +4 -5
  16. package/src/components/IconButton/IconButton.stories.tsx +3 -3
  17. package/src/components/Loading/Loading.stories.tsx +2 -1
  18. package/src/components/Loading/index.tsx +7 -9
  19. package/src/components/MessagingShell/EmptyState.stories.tsx +2 -1
  20. package/src/components/MessagingShell/ErrorState.stories.tsx +2 -1
  21. package/src/components/MessagingShell/LoadingState.stories.tsx +2 -1
  22. package/src/components/MessagingShell/LoadingState.tsx +3 -5
  23. package/src/components/MessagingShell/index.tsx +159 -135
  24. package/src/components/ParticipantPicker/ParticipantItem.stories.tsx +4 -2
  25. package/src/components/ParticipantPicker/ParticipantItem.tsx +25 -21
  26. package/src/components/ParticipantPicker/ParticipantPicker.stories.tsx +4 -2
  27. package/src/components/ParticipantPicker/ParticipantPicker.tsx +104 -76
  28. package/src/components/ParticipantPicker/index.tsx +93 -72
  29. package/src/components/SearchInput/SearchInput.stories.tsx +2 -1
  30. package/src/components/SearchInput/SearchInput.test.tsx +4 -2
  31. package/src/components/SearchInput/index.tsx +14 -15
  32. package/src/hooks/useParticipants.ts +1 -0
  33. package/src/index.ts +3 -0
  34. package/src/providers/MessagingProvider.tsx +213 -135
  35. package/src/stories/mocks.tsx +18 -19
  36. package/src/styles.css +75 -0
  37. package/src/test/setup.ts +11 -12
  38. package/src/test/utils.tsx +6 -7
  39. package/src/types.ts +1 -1
@@ -1,65 +1,78 @@
1
- import { FlagIcon } from "@phosphor-icons/react/dist/csr/Flag";
2
- import { ProhibitInsetIcon } from "@phosphor-icons/react/dist/csr/ProhibitInset";
3
- import { SignOutIcon } from "@phosphor-icons/react/dist/csr/SignOut";
4
- import { SpinnerGapIcon } from "@phosphor-icons/react/dist/csr/SpinnerGap";
5
-
6
- import { ArrowLeftIcon } from "@phosphor-icons/react/dist/csr/ArrowLeft";
7
- import { DotsThreeIcon } from "@phosphor-icons/react/dist/csr/DotsThree";
8
-
9
- import React, { useState, useCallback, useRef, useEffect } from 'react';
10
- import classNames from 'classnames';
1
+ import {
2
+ ArrowLeftIcon,
3
+ DotsThreeIcon,
4
+ FlagIcon,
5
+ ProhibitInsetIcon,
6
+ SignOutIcon,
7
+ SpinnerGapIcon,
8
+ } from '@phosphor-icons/react'
9
+ import classNames from 'classnames'
10
+ import React, { useState, useCallback, useRef, useEffect } from 'react'
11
+ import { Channel as ChannelType, ChannelMemberResponse } from 'stream-chat'
11
12
  import {
12
13
  Channel,
13
14
  Window,
14
15
  MessageList,
15
16
  MessageInput,
16
17
  useChannelStateContext,
17
- } from 'stream-chat-react';
18
- import type { ChannelViewProps } from '../types';
19
- import { useMessagingContext } from '../providers/MessagingProvider';
20
- import { CloseButton } from './CloseButton';
21
- import { IconButton } from './IconButton';
22
- import ActionButton from './ActionButton';
23
- import { Avatar } from './Avatar';
18
+ } from 'stream-chat-react'
19
+
20
+ import { useMessagingContext } from '../providers/MessagingProvider'
21
+ import type { ChannelViewProps } from '../types'
22
+
23
+ import ActionButton from './ActionButton'
24
+ import { Avatar } from './Avatar'
25
+ import { CloseButton } from './CloseButton'
26
+ import { IconButton } from './IconButton'
27
+
28
+ // Custom user type with email and username
29
+ type CustomUser = {
30
+ email?: string
31
+ username?: string
32
+ }
33
+
34
+ // Blocked user from Stream Chat API
35
+ type BlockedUser = {
36
+ blocked_user_id: string
37
+ }
24
38
 
25
39
  /**
26
40
  * Custom message input component with render prop for actions
27
41
  */
28
42
  const CustomMessageInput: React.FC<{
29
- renderActions?: () => React.ReactNode;
43
+ renderActions?: () => React.ReactNode
30
44
  }> = ({ renderActions }) => (
31
45
  <div className="message-input flex items-center gap-2 p-4">
32
46
  {renderActions && renderActions()}
33
-
47
+
34
48
  <div className="flex-1">
35
49
  <MessageInput focus maxRows={4} />
36
50
  </div>
37
51
  </div>
38
- );
52
+ )
39
53
 
40
54
  /**
41
55
  * Custom channel header component
42
56
  */
43
57
  const CustomChannelHeader: React.FC<{
44
- onBack?: () => void;
45
- showBackButton: boolean;
46
- onShowInfo?: () => void;
47
- canShowInfo: boolean;
58
+ onBack?: () => void
59
+ showBackButton: boolean
60
+ onShowInfo?: () => void
61
+ canShowInfo: boolean
48
62
  }> = ({ onBack, showBackButton, onShowInfo, canShowInfo }) => {
49
- const { channel } = useChannelStateContext();
50
-
63
+ const { channel } = useChannelStateContext()
64
+
51
65
  // Get participant info (excluding current user)
52
66
  const participant = React.useMemo(() => {
53
- const members = Object.values(channel.state.members || {});
54
- return members.find(member =>
55
- member.user?.id && member.user.id !== channel._client.userID
56
- );
57
- }, [channel._client.userID, channel.state.members]);
67
+ const members = Object.values(channel.state.members || {})
68
+ return members.find(
69
+ (member) => member.user?.id && member.user.id !== channel._client.userID
70
+ )
71
+ }, [channel._client.userID, channel.state.members])
58
72
 
59
- const participantName = participant?.user?.name ||
60
- participant?.user?.id ||
61
- 'Unknown member';
62
- const participantImage = participant?.user?.image;
73
+ const participantName =
74
+ participant?.user?.name || participant?.user?.id || 'Unknown member'
75
+ const participantImage = participant?.user?.image
63
76
 
64
77
  return (
65
78
  <div className="flex items-center justify-between gap-3 min-h-12">
@@ -74,7 +87,7 @@ const CustomChannelHeader: React.FC<{
74
87
  <ArrowLeftIcon className="h-5 w-5 text-stone" weight="bold" />
75
88
  </button>
76
89
  )}
77
-
90
+
78
91
  {/* Avatar */}
79
92
  <Avatar
80
93
  id={participant?.user?.id || channel.id || 'unknown'}
@@ -82,7 +95,7 @@ const CustomChannelHeader: React.FC<{
82
95
  image={participantImage}
83
96
  size={40}
84
97
  />
85
-
98
+
86
99
  <div className="min-w-0">
87
100
  <h1 className="text-lg font-semibold text-charcoal truncate">
88
101
  {participantName}
@@ -91,168 +104,178 @@ const CustomChannelHeader: React.FC<{
91
104
  </div>
92
105
 
93
106
  {canShowInfo && onShowInfo && (
94
- <IconButton
95
- label="Chat info"
96
- onClick={onShowInfo}
97
- >
107
+ <IconButton label="Chat info" onClick={onShowInfo}>
98
108
  <DotsThreeIcon className="h-6 w-6 text-charcoal" weight="bold" />
99
109
  </IconButton>
100
110
  )}
101
111
  </div>
102
- );
103
- };
112
+ )
113
+ }
104
114
 
105
115
  /**
106
116
  * Channel info dialog (matching original implementation)
107
117
  */
108
118
  const ChannelInfoDialog: React.FC<{
109
- isOpen: boolean;
110
- onClose: () => void;
111
- participant: any;
112
- channel: any;
113
- followerStatusLabel?: string;
114
- onLeaveConversation?: (channel: any) => void;
115
- onBlockParticipant?: (participantId?: string) => void;
116
- }> = ({ isOpen, onClose, participant, channel, followerStatusLabel, onLeaveConversation, onBlockParticipant }) => {
117
- const { service, debug } = useMessagingContext();
118
- const dialogRef = useRef<HTMLDialogElement>(null);
119
- const [isParticipantBlocked, setIsParticipantBlocked] = useState(false);
120
- const [isLeaving, setIsLeaving] = useState(false);
121
- const [isUpdatingBlockStatus, setIsUpdatingBlockStatus] = useState(false);
119
+ isOpen: boolean
120
+ onClose: () => void
121
+ participant: ChannelMemberResponse | undefined
122
+ channel: ChannelType
123
+ followerStatusLabel?: string
124
+ onLeaveConversation?: (channel: ChannelType) => void
125
+ onBlockParticipant?: (participantId?: string) => void
126
+ }> = ({
127
+ isOpen,
128
+ onClose,
129
+ participant,
130
+ channel,
131
+ followerStatusLabel,
132
+ onLeaveConversation,
133
+ onBlockParticipant,
134
+ }) => {
135
+ const { service, debug } = useMessagingContext()
136
+ const dialogRef = useRef<HTMLDialogElement>(null)
137
+ const [isParticipantBlocked, setIsParticipantBlocked] = useState(false)
138
+ const [isLeaving, setIsLeaving] = useState(false)
139
+ const [isUpdatingBlockStatus, setIsUpdatingBlockStatus] = useState(false)
122
140
 
123
141
  // Sync dialog open state with prop
124
142
  useEffect(() => {
125
- const dialog = dialogRef.current;
126
- if (!dialog) return;
143
+ const dialog = dialogRef.current
144
+ if (!dialog) return
127
145
 
128
146
  if (isOpen) {
129
- dialog.showModal();
147
+ dialog.showModal()
130
148
  } else {
131
- dialog.close();
149
+ dialog.close()
132
150
  }
133
- }, [isOpen]);
151
+ }, [isOpen])
134
152
 
135
153
  // Check if participant is blocked
136
154
  const checkIsParticipantBlocked = useCallback(async () => {
137
- if (!service || !participant?.user?.id) return;
138
-
155
+ if (!service || !participant?.user?.id) return
156
+
139
157
  try {
140
- const blockedUsers = await service.getBlockedUsers();
158
+ const blockedUsers = await service.getBlockedUsers()
141
159
  const isBlocked = blockedUsers.some(
142
- (user: any) => user.blocked_user_id === participant.user.id,
143
- );
144
- setIsParticipantBlocked(isBlocked);
160
+ (user: BlockedUser) => user.blocked_user_id === participant?.user?.id
161
+ )
162
+ setIsParticipantBlocked(isBlocked)
145
163
  } catch (error) {
146
- console.error('[ChannelInfoDialog] Failed to check blocked status:', error);
164
+ console.error(
165
+ '[ChannelInfoDialog] Failed to check blocked status:',
166
+ error
167
+ )
147
168
  }
148
- }, [service, participant?.user?.id]);
169
+ }, [service, participant?.user?.id])
149
170
 
150
171
  useEffect(() => {
151
172
  if (isOpen) {
152
- checkIsParticipantBlocked();
173
+ checkIsParticipantBlocked()
153
174
  }
154
- }, [isOpen, checkIsParticipantBlocked]);
175
+ }, [isOpen, checkIsParticipantBlocked])
155
176
 
156
177
  const handleLeaveConversation = async () => {
157
- if (isLeaving) return;
178
+ if (isLeaving) return
158
179
 
159
180
  if (debug) {
160
- console.log('[ChannelInfoDialog] Leave conversation', channel.cid);
181
+ console.log('[ChannelInfoDialog] Leave conversation', channel.cid)
161
182
  }
162
- setIsLeaving(true);
183
+ setIsLeaving(true)
163
184
 
164
185
  try {
165
- const actingUserId = channel._client?.userID ?? null;
166
- await channel.hide(actingUserId, false);
186
+ const actingUserId = channel._client?.userID ?? null
187
+ await channel.hide(actingUserId, false)
167
188
 
168
189
  if (onLeaveConversation) {
169
- await onLeaveConversation(channel);
190
+ await onLeaveConversation(channel)
170
191
  }
171
192
 
172
- onClose();
193
+ onClose()
173
194
  } catch (error) {
174
- console.error('[ChannelInfoDialog] Failed to leave conversation', error);
195
+ console.error('[ChannelInfoDialog] Failed to leave conversation', error)
175
196
  } finally {
176
- setIsLeaving(false);
197
+ setIsLeaving(false)
177
198
  }
178
- };
199
+ }
179
200
 
180
201
  const handleBlockUser = async () => {
181
- if (isUpdatingBlockStatus || !service) return;
202
+ if (isUpdatingBlockStatus || !service) return
182
203
 
183
204
  if (debug) {
184
- console.log('[ChannelInfoDialog] Block member', participant?.user?.id);
205
+ console.log('[ChannelInfoDialog] Block member', participant?.user?.id)
185
206
  }
186
- setIsUpdatingBlockStatus(true);
207
+ setIsUpdatingBlockStatus(true)
187
208
 
188
209
  try {
189
- await service.blockUser(participant?.user?.id);
210
+ await service.blockUser(participant?.user?.id)
190
211
 
191
212
  if (onBlockParticipant) {
192
- await onBlockParticipant(participant?.user?.id);
213
+ await onBlockParticipant(participant?.user?.id)
193
214
  }
194
215
 
195
- onClose();
216
+ onClose()
196
217
  } catch (error) {
197
- console.error('[ChannelInfoDialog] Failed to block member', error);
218
+ console.error('[ChannelInfoDialog] Failed to block member', error)
198
219
  } finally {
199
- setIsUpdatingBlockStatus(false);
220
+ setIsUpdatingBlockStatus(false)
200
221
  }
201
- };
222
+ }
202
223
 
203
224
  const handleUnblockUser = async () => {
204
- if (isUpdatingBlockStatus || !service) return;
225
+ if (isUpdatingBlockStatus || !service) return
205
226
 
206
227
  if (debug) {
207
- console.log('[ChannelInfoDialog] Unblock member', participant?.user?.id);
228
+ console.log('[ChannelInfoDialog] Unblock member', participant?.user?.id)
208
229
  }
209
- setIsUpdatingBlockStatus(true);
230
+ setIsUpdatingBlockStatus(true)
210
231
 
211
232
  try {
212
- await service.unBlockUser(participant?.user?.id);
233
+ await service.unBlockUser(participant?.user?.id)
213
234
 
214
235
  if (onBlockParticipant) {
215
- await onBlockParticipant(participant?.user?.id);
236
+ await onBlockParticipant(participant?.user?.id)
216
237
  }
217
238
 
218
- onClose();
239
+ onClose()
219
240
  } catch (error) {
220
- console.error('[ChannelInfoDialog] Failed to unblock member', error);
241
+ console.error('[ChannelInfoDialog] Failed to unblock member', error)
221
242
  } finally {
222
- setIsUpdatingBlockStatus(false);
243
+ setIsUpdatingBlockStatus(false)
223
244
  }
224
- };
245
+ }
225
246
 
226
247
  const handleReportUser = () => {
227
- onClose();
248
+ onClose()
228
249
  window.open(
229
250
  'https://linktr.ee/s/about/trust-center/report',
230
251
  '_blank',
231
- 'noopener,noreferrer',
232
- );
233
- };
252
+ 'noopener,noreferrer'
253
+ )
254
+ }
234
255
 
235
- if (!participant) return null;
256
+ if (!participant) return null
236
257
 
237
- const participantName = participant.user?.name || participant.user?.id || 'Unknown member';
238
- const participantImage = participant.user?.image;
239
- const participantEmail = participant.user?.email;
240
- const participantUsername = participant.user?.username;
258
+ const participantName =
259
+ participant.user?.name || participant.user?.id || 'Unknown member'
260
+ const participantImage = participant.user?.image
261
+ const participantEmail = (participant.user as CustomUser)?.email
262
+ const participantUsername = (participant.user as CustomUser)?.username
241
263
  const participantSecondary = participantEmail
242
264
  ? participantEmail
243
265
  : participantUsername
244
266
  ? `linktr.ee/${participantUsername}`
245
- : undefined;
246
- const participantId = participant.user?.id || 'unknown';
267
+ : undefined
268
+ const participantId = participant.user?.id || 'unknown'
247
269
 
248
270
  return (
271
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
249
272
  <dialog
250
273
  ref={dialogRef}
251
274
  className="mes-dialog"
252
275
  onClose={onClose}
253
276
  onClick={(e) => {
254
277
  if (e.target === dialogRef.current) {
255
- onClose();
278
+ onClose()
256
279
  }
257
280
  }}
258
281
  >
@@ -261,7 +284,7 @@ const ChannelInfoDialog: React.FC<{
261
284
  <h2 className="text-base font-semibold text-charcoal">Chat info</h2>
262
285
  <CloseButton onClick={onClose} />
263
286
  </div>
264
-
287
+
265
288
  <div className="flex-1 overflow-y-auto px-6 py-6">
266
289
  <div className="rounded-2xl bg-chalk p-4">
267
290
  <div className="flex items-center gap-4">
@@ -343,8 +366,8 @@ const ChannelInfoDialog: React.FC<{
343
366
  </div>
344
367
  </div>
345
368
  </dialog>
346
- );
347
- };
369
+ )
370
+ }
348
371
 
349
372
  /**
350
373
  * Channel view component with message list and input
@@ -358,28 +381,28 @@ export const ChannelView: React.FC<ChannelViewProps> = ({
358
381
  onBlockParticipant,
359
382
  className,
360
383
  }) => {
361
- const [showInfo, setShowInfo] = useState(false);
384
+ const [showInfo, setShowInfo] = useState(false)
362
385
 
363
386
  // Get participant info for info dialog
364
387
  const participant = React.useMemo(() => {
365
- const members = Object.values(channel.state.members || {});
366
- return members.find(member =>
367
- member.user?.id && member.user.id !== channel._client.userID
368
- );
369
- }, [channel._client.userID, channel.state.members]);
388
+ const members = Object.values(channel.state.members || {})
389
+ return members.find(
390
+ (member) => member.user?.id && member.user.id !== channel._client.userID
391
+ )
392
+ }, [channel._client.userID, channel.state.members])
370
393
 
371
394
  // Get follower status label from channel data
372
395
  const followerStatusLabel = React.useMemo(() => {
373
396
  const channelExtraData = (channel.data ?? {}) as {
374
- followerStatus?: string;
375
- isFollower?: boolean;
376
- };
397
+ followerStatus?: string
398
+ isFollower?: boolean
399
+ }
377
400
  return channelExtraData.followerStatus
378
401
  ? String(channelExtraData.followerStatus)
379
402
  : channelExtraData.isFollower
380
403
  ? 'Subscribed to you'
381
- : undefined;
382
- }, [channel.data]);
404
+ : undefined
405
+ }, [channel.data])
383
406
 
384
407
  return (
385
408
  <div className={classNames('h-full flex flex-col', className)}>
@@ -418,5 +441,5 @@ export const ChannelView: React.FC<ChannelViewProps> = ({
418
441
  onBlockParticipant={onBlockParticipant}
419
442
  />
420
443
  </div>
421
- );
422
- };
444
+ )
445
+ }
@@ -1,10 +1,9 @@
1
- import { XIcon } from "@phosphor-icons/react/dist/csr/X";
2
- import React from "react";
1
+ import { XIcon } from '@phosphor-icons/react'
3
2
 
4
- import { IconButton } from "../IconButton";
3
+ import { IconButton } from '../IconButton'
5
4
 
6
5
  interface CloseButtonProps {
7
- onClick: () => void;
6
+ onClick: () => void
8
7
  }
9
8
 
10
9
  export function CloseButton({ onClick }: CloseButtonProps) {
@@ -12,5 +11,5 @@ export function CloseButton({ onClick }: CloseButtonProps) {
12
11
  <IconButton label="Close" onClick={onClick} className="p-1">
13
12
  <XIcon className="h-5 w-5 text-stone" weight="bold" />
14
13
  </IconButton>
15
- );
14
+ )
16
15
  }
@@ -1,8 +1,9 @@
1
+ import { XIcon } from '@phosphor-icons/react'
1
2
  import type { Meta, StoryFn } from '@storybook/react'
2
- import { IconButton } from '.'
3
- import { XIcon } from '@phosphor-icons/react/dist/csr/X'
4
3
  import React from 'react'
5
4
 
5
+ import { IconButton } from '.'
6
+
6
7
  type ComponentProps = React.ComponentProps<typeof IconButton>
7
8
 
8
9
  const meta: Meta<ComponentProps> = {
@@ -37,4 +38,3 @@ WithCustomClassName.args = {
37
38
  children: <XIcon className="h-6 w-6" />,
38
39
  className: 'bg-primary text-white size-12',
39
40
  }
40
-
@@ -1,7 +1,8 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
- import Loading from '.'
3
2
  import React from 'react'
4
3
 
4
+ import Loading from '.'
5
+
5
6
  type ComponentProps = React.ComponentProps<typeof Loading>
6
7
 
7
8
  const meta: Meta<ComponentProps> = {
@@ -1,14 +1,13 @@
1
- import classNames from "classnames";
2
- import React from "react";
1
+ import classNames from 'classnames'
3
2
 
4
3
  type LoadingProps = {
5
- className?: string;
6
- message?: string;
7
- };
4
+ className?: string
5
+ message?: string
6
+ }
8
7
 
9
8
  const Loading = ({ className, message }: LoadingProps) => (
10
9
  <div
11
- className={classNames("flex items-center justify-center h-full", className)}
10
+ className={classNames('flex items-center justify-center h-full', className)}
12
11
  >
13
12
  <svg viewBox="0 0 100 100" className="size-8 fill-pebble" stroke="none">
14
13
  <circle cx="6" cy="50" r="6">
@@ -44,7 +43,6 @@ const Loading = ({ className, message }: LoadingProps) => (
44
43
  </svg>
45
44
  {message && <span className="text-stone">{message}</span>}
46
45
  </div>
47
- );
48
-
49
- export default Loading;
46
+ )
50
47
 
48
+ export default Loading
@@ -1,7 +1,8 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
- import { EmptyState } from './EmptyState'
3
2
  import React from 'react'
4
3
 
4
+ import { EmptyState } from './EmptyState'
5
+
5
6
 
6
7
  type ComponentProps = React.ComponentProps<typeof EmptyState>
7
8
 
@@ -1,7 +1,8 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
- import { ErrorState } from './ErrorState'
3
2
  import React from 'react'
4
3
 
4
+ import { ErrorState } from './ErrorState'
5
+
5
6
  type ComponentProps = React.ComponentProps<typeof ErrorState>
6
7
 
7
8
  const meta: Meta<ComponentProps> = {
@@ -1,7 +1,8 @@
1
1
  import type { Meta, StoryFn } from '@storybook/react'
2
- import { LoadingState } from './LoadingState'
3
2
  import React from 'react'
4
3
 
4
+ import { LoadingState } from './LoadingState'
5
+
5
6
  type ComponentProps = React.ComponentProps<typeof LoadingState>
6
7
 
7
8
  const meta: Meta<ComponentProps> = {
@@ -1,5 +1,4 @@
1
- import React from 'react';
2
- import Loading from '../Loading';
1
+ import Loading from '../Loading'
3
2
 
4
3
  /**
5
4
  * Loading state component
@@ -7,9 +6,8 @@ import Loading from '../Loading';
7
6
  export const LoadingState = () => (
8
7
  <div className="flex items-center justify-center h-full">
9
8
  <div className="flex items-center">
10
- <Loading className='w-6 h-6' />
9
+ <Loading className="w-6 h-6" />
11
10
  <span className="text-sm text-stone">Loading messages</span>
12
11
  </div>
13
12
  </div>
14
- );
15
-
13
+ )