import '../stream-custom-data' import type { StoryFn } from '@storybook/react' import React, { useEffect } from 'react' import { Channel as ChannelType, QueryChannelAPIResponse, StreamChat, } from 'stream-chat' import { Channel, Chat, MessageList, MessageUIComponentProps, Window, } from 'stream-chat-react' import { CustomMessage } from '../components/CustomMessage' import { OutboundContext, buildOutboundsByRecordId, } from '../components/CustomMessage/OutboundContext' import CustomTypingIndicator from '../components/CustomTypingIndicator' import { createMockStreamChatClient } from '../testing/createMockStreamChatClient' import type { OutboundResolver } from '../types' import { minutesAgo } from './decorators/storyTime' import { StoryUser, storyUsers } from './decorators/storyUser' export const createMockChannel = async ( client: StreamChat, messages: TemplateProps['messages'] ) => { const mockMessages = messages.map((msg, index) => ({ ...msg, type: msg.type ?? ('regular' as const), created_at: minutesAgo(messages.length - index), updated_at: minutesAgo(messages.length - index), attachments: msg.attachments ?? [], latest_reactions: [], own_reactions: [], reaction_counts: {}, reaction_scores: {}, reply_count: 0, status: 'received', cid: 'messaging:storybook-channel-1', mentioned_users: msg.mentioned_users ?? [], })) const channelData = { members: [storyUsers.creator.id, storyUsers.visitor.id], // Required for useUserRole: canDelete needs delete-own-message, canFlag needs flag-message own_capabilities: ['delete-own-message', 'flag-message'], } const channel = client.channel( 'messaging', 'storybook-channel-1', channelData ) channel.watch = async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any channel.state.messages = mockMessages as unknown as any[] channel.state.members = { [storyUsers.creator.id]: { user: storyUsers.creator, user_id: storyUsers.creator.id, }, [storyUsers.visitor.id]: { user: storyUsers.visitor, user_id: storyUsers.visitor.id, }, } return { channel: channelData, members: [], messages: mockMessages, watchers: [], pinned_messages: [], duration: '0ms', } as unknown as QueryChannelAPIResponse } try { await channel.watch() } catch { // Ignore errors in mock mode } return channel } export interface TemplateProps { currentUser: StoryUser typingUser?: StoryUser resolveOutbound?: OutboundResolver messages: Array<{ id: string text: string user: StoryUser mentioned_users?: StoryUser[] type?: 'regular' | 'system' attachments?: Array> metadata?: { custom_type?: | 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT' | 'MESSAGE_ATTACHMENT' | 'MESSAGE_OUTBOUND' payment_status?: 'pending' | 'paid' | 'failed' | 'refunded' amount_text?: string attachment_title?: string attachment_mime_type?: string attachment_thumbnail?: string attachment_detail?: string attachment_content_type?: 'text' | 'media' outbound_id?: string } }> } const TemplateInner: React.FC<{ currentUser: StoryUser typingUser?: StoryUser messages: TemplateProps['messages'] resolveOutbound?: OutboundResolver }> = ({ currentUser, typingUser, messages, resolveOutbound }) => { const [client] = React.useState(() => createMockStreamChatClient(currentUser)) const [channel, setChannel] = React.useState(null) useEffect(() => { createMockChannel(client, messages).then(setChannel) }, [client, messages]) useEffect(() => { if (!channel || !typingUser) { return } // Stream Channel UI only updates typing context from typing.start/typing.stop events. const timer = setTimeout(() => { client.dispatchEvent({ type: 'typing.start', cid: channel.cid, user: typingUser, }) }, 0) return () => clearTimeout(timer) }, [channel, client, typingUser]) const outboundsByRecordId = React.useMemo( () => buildOutboundsByRecordId(messages, resolveOutbound), [messages, resolveOutbound] ) const MessageComponent = React.useMemo(() => { return function CustomMessageComponent(props: MessageUIComponentProps) { return undefined} /> } }, []) if (!channel) { return
Loading...
} return (
) } export const Template: StoryFn = ({ currentUser = storyUsers.creator, typingUser, messages, resolveOutbound, }) => ( )