Conversations
Conversations
Section titled “Conversations”Summary
Section titled “Summary”The Conversations feature provides agents with a unified view of all customer conversations, enabling efficient conversation management, filtering, and prioritization.
User Story: As a support agent, I want to view and filter my assigned conversations so that I can prioritize and respond to customer inquiries efficiently.
Acceptance Criteria:
- ✅ Display list of conversations with key metadata
- ✅ Filter by status, assignee, inbox, and team
- ✅ Sort by last activity or priority
- ✅ Pull-to-refresh to sync latest conversations
- ✅ Infinite scroll pagination
- ✅ Real-time updates via WebSocket
- ✅ Swipe actions for quick operations
- ✅ Navigate to chat screen on conversation tap
Inputs:
- Filter criteria (status, assignee type, inbox, team, sort order)
- Page number for pagination
- User permissions
Outputs:
- Filtered and sorted conversation list
- Conversation metadata (unread count, last message, assignee, status)
- Loading and error states
Initial Load Flow
Section titled “Initial Load Flow”User Opens Conversations Tab ↓Check Cached Conversations in Redux ↓├─ Cache Available│ ↓│ Display Cached Conversations│ ↓│ Fetch Fresh Data in Background│ ↓│ Update UI with New Data│└─ No Cache ↓ Show Loading Indicator ↓ Fetch Conversations from API ↓ Display ConversationsFilter Flow
Section titled “Filter Flow”User Changes Filter (Status/Assignee/Inbox) ↓Update Redux Filter State ↓Clear Existing Conversations ↓Reset Page Number to 1 ↓Dispatch fetchConversations with New Filters ↓API Request with Filter Params ↓Transform Response Data ↓Update Redux Store ↓Re-render Conversation ListInfinite Scroll Flow
Section titled “Infinite Scroll Flow”User Scrolls to End of List ↓Check isAllConversationsFetched ↓├─ More Conversations Available│ ↓│ Increment Page Number│ ↓│ Fetch Next Page│ ↓│ Append to Existing Conversations│ ↓│ Update UI│└─ All Loaded ↓ Show End of List MessageReal-time Update Flow
Section titled “Real-time Update Flow”WebSocket Event: conversation.created ↓ActionCable Connector Receives Event ↓Transform Event Data ↓Dispatch: addConversation(conversation) ↓Reducer Adds to conversations.byId ↓Update conversations.allIds ↓Re-render List with New ConversationEdge Cases:
- Empty state when no conversations match filters
- Network error during fetch
- Stale data after app backgrounded for extended period
- Duplicate conversations from race conditions
- Conversation deleted while viewing list
API/Contracts
Section titled “API/Contracts”Fetch Conversations Endpoint
Section titled “Fetch Conversations Endpoint”Request:
GET /api/v1/accounts/{accountId}/conversations
Query Parameters:{ assignee_type: 'me' | 'unassigned' | 'all', status: 'open' | 'resolved' | 'pending' | 'snoozed', inbox_id?: number, team_id?: number, sort_by: 'last_activity_at' | 'priority', page: number}Response:
{ data: { meta: { count: number, current_page: number, all_count: number, mine_count: number, unassigned_count: number, }, payload: Conversation[] }}
interface Conversation { id: number; account_id: number; inbox_id: number; status: 'open' | 'resolved' | 'pending' | 'snoozed'; assignee: Agent | null; messages: Message[]; meta: { sender: Contact; channel: string; }; labels: Label[]; created_at: string; timestamp: number; last_activity_at: number; unread_count: number; priority: number | null;}Redux Actions
Section titled “Redux Actions”Fetch Conversations:
conversationActions.fetchConversations({ assigneeType: 'me', status: 'open', inboxId?: number, teamId?: number, sortBy: 'last_activity_at', page: 1,})Mark as Read:
conversationActions.markMessagesAsRead({ conversationId: number,})Toggle Status:
conversationActions.toggleStatus({ conversationId: number, status: 'open' | 'resolved',})Assign Conversation:
conversationActions.assignConversation({ conversationId: number, assigneeId: number,})Redux Selectors
Section titled “Redux Selectors”// Get all filtered conversationsconst conversations = useAppSelector(getFilteredConversations);
// Get conversation by IDconst conversation = useAppSelector(state => selectConversationById(state, conversationId));
// Get loading stateconst isLoading = useAppSelector(selectConversationsLoading);
// Get current filtersconst filters = useAppSelector(selectFilters);
// Get conversation countsconst counts = useAppSelector(selectConversationCounts);Error Handling and Retries
Section titled “Error Handling and Retries”Network Errors
Section titled “Network Errors”try { await dispatch(conversationActions.fetchConversations(params)).unwrap();} catch (error) { if (error.code === 'NETWORK_ERROR') { showToast({ message: I18n.t('ERRORS.NETWORK_ERROR') }); // Auto-retry after 3 seconds setTimeout(() => { dispatch(conversationActions.fetchConversations(params)); }, 3000); }}API Errors
Section titled “API Errors”- 401 Unauthorized: Auto-logout and redirect to login
- 403 Forbidden: Show permission error
- 404 Not Found: Remove conversation from list
- 500 Server Error: Show generic error, allow manual retry
Optimistic Updates
Section titled “Optimistic Updates”// Optimistically update UIdispatch(updateConversation({ id: conversationId, status: 'resolved'}));
try { await conversationActions.toggleStatus({ conversationId, status: 'resolved' });} catch (error) { // Revert on failure dispatch(updateConversation({ id: conversationId, status: 'open' })); showToast({ message: I18n.t('ERRORS.UPDATE_FAILED') });}Telemetry
Section titled “Telemetry”Tracked Events
Section titled “Tracked Events”// Conversation viewedAnalyticsHelper.track('conversation_viewed', { conversation_id: conversationId, source: 'conversation_list',});
// Filter appliedAnalyticsHelper.track('conversation_filter_applied', { assignee_type: filter.assigneeType, status: filter.status, inbox_id: filter.inboxId,});
// Sort changedAnalyticsHelper.track('conversation_sort_changed', { sort_by: sortBy,});Performance Metrics
Section titled “Performance Metrics”- Time to first render
- Time to interactive
- Infinite scroll performance
- Filter application latency
Example
Section titled “Example”Basic Usage
Section titled “Basic Usage”import React, { useEffect } from 'react';import { View, FlatList } from 'react-native';import { useAppDispatch, useAppSelector } from '@/hooks';import { conversationActions } from '@/store/conversation/conversationActions';import { getFilteredConversations } from '@/store/conversation/conversationSelectors';import { ConversationItem } from './ConversationItem';
export const ConversationList = () => { const dispatch = useAppDispatch(); const conversations = useAppSelector(getFilteredConversations); const isLoading = useAppSelector(state => state.conversations.isFetching);
useEffect(() => { dispatch(conversationActions.fetchConversations({ assigneeType: 'me', status: 'open', page: 1, })); }, [dispatch]);
const handleRefresh = () => { dispatch(conversationActions.fetchConversations({ assigneeType: 'me', status: 'open', page: 1, })); };
const handleLoadMore = () => { if (!isLoading) { dispatch(conversationActions.fetchConversations({ assigneeType: 'me', status: 'open', page: pageNumber + 1, })); } };
return ( <FlatList data={conversations} renderItem={({ item }) => <ConversationItem conversation={item} />} keyExtractor={item => item.id.toString()} onRefresh={handleRefresh} refreshing={isLoading} onEndReached={handleLoadMore} onEndReachedThreshold={0.5} /> );};With Filters
Section titled “With Filters”import { StatusFilters, AssigneeTypeFilters } from './components';
export const ConversationScreen = () => { const dispatch = useAppDispatch(); const filters = useAppSelector(selectFilters);
const handleFilterChange = (newFilters: Partial<FilterState>) => { dispatch(updateFilters(newFilters)); dispatch(clearAllConversations()); dispatch(conversationActions.fetchConversations({ ...filters, ...newFilters, page: 1, })); };
return ( <View> <StatusFilters selectedStatus={filters.status} onStatusChange={(status) => handleFilterChange({ status })} /> <AssigneeTypeFilters selectedType={filters.assigneeType} onTypeChange={(assigneeType) => handleFilterChange({ assigneeType })} /> <ConversationList /> </View> );};Rollout
Section titled “Rollout”Feature Flags
Section titled “Feature Flags”if (featureFlags.conversationPriority) { // Show priority sorting option}Migrations
Section titled “Migrations”When updating conversation schema:
- Add new fields with defaults in reducer
- Update API transformation layer
- Handle both old and new response formats
- Monitor error rates
Further Reading
Section titled “Further Reading”- Messaging - Chat interface and messages
- State Management - Redux architecture
- Real-time Communication - WebSocket updates
- Conversation Components - UI components