> ## Documentation Index
> Fetch the complete documentation index at: https://twigz.arqqin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat System Implementation

> Complete guide to implementing the real-time chat system

## Overview

The Twigz chat system provides real-time messaging between customers, stores, and admin support. This guide covers the complete implementation from basic messaging to advanced features like conversation transfers and typing indicators.

## Architecture

The chat system consists of three main components:

<CardGroup cols={3}>
  <Card title="Customer Support" icon="users">
    Direct messaging between customers and stores
  </Card>

  <Card title="Admin Support" icon="headphones">
    Admin intervention and conversation management
  </Card>

  <Card title="Transfer System" icon="arrow-right">
    Seamless conversation transfers between support levels
  </Card>
</CardGroup>

## Core Concepts

### Conversation Types

<AccordionGroup>
  <Accordion title="Customer to Store">
    **Purpose**: Direct communication between customers and store owners

    **Requirements**:

    * Customer must have at least one non-cancelled order with the store
    * Direct messaging must be enabled globally and for the specific store
    * Store must have direct messaging enabled in their settings

    **Use Cases**:

    * Order-related questions
    * Product inquiries
    * Delivery issues
    * General store communication
  </Accordion>

  <Accordion title="Customer to Admin">
    **Purpose**: Customer support through platform administrators

    **Requirements**:

    * Admin support must be enabled in platform settings
    * Available as fallback when direct store messaging is not possible

    **Use Cases**:

    * Platform-related issues
    * Escalated store problems
    * General support requests
    * Technical difficulties
  </Accordion>
</AccordionGroup>

### Message Types

<AccordionGroup>
  <Accordion title="Text Messages">
    Standard text communication with full UTF-8 support
  </Accordion>

  <Accordion title="Image Messages">
    Images with automatic validation and compression (max 5MB)
  </Accordion>

  <Accordion title="System Messages">
    Automated notifications and status updates
  </Accordion>

  <Accordion title="Transfer Notices">
    Messages indicating conversation transfers between support levels
  </Accordion>
</AccordionGroup>

## Implementation Guide

### 1. Basic Chat Setup

<CodeGroup>
  ```typescript React Hook theme={null}
  import { useQuery, useMutation } from 'convex/react';
  import { api } from './convex/_generated/api';

  export function useChat() {
    // Get user's conversations
    const conversations = useQuery(api.shared.chat.getConversations, {
      status: "active",
      limit: 50
    });
    
    // Get unread count
    const unreadCount = useQuery(api.shared.chat.getUnreadCount, {});
    
    // Mutations
    const createConversation = useMutation(api.shared.chat.createOrGetConversation);
    const sendMessage = useMutation(api.shared.chat.sendMessage);
    const markAsRead = useMutation(api.shared.chat.markMessagesAsRead);
    
    return {
      conversations,
      unreadCount,
      createConversation,
      sendMessage,
      markAsRead
    };
  }
  ```

  ```typescript Vue Composition API theme={null}
  import { useConvexQuery, useConvexMutation } from '@convex-vue/core';

  export function useChat() {
    const conversations = useConvexQuery('shared.chat.getConversations', {
      status: "active",
      limit: 50
    });
    
    const unreadCount = useConvexQuery('shared.chat.getUnreadCount', {});
    
    const createConversation = useConvexMutation('shared.chat.createOrGetConversation');
    const sendMessage = useConvexMutation('shared.chat.sendMessage');
    const markAsRead = useConvexMutation('shared.chat.markMessagesAsRead');
    
    return {
      conversations,
      unreadCount,
      createConversation,
      sendMessage,
      markAsRead
    };
  }
  ```
</CodeGroup>

### 2. Real-time Message Display

<CodeGroup>
  ```typescript React Component theme={null}
  import { useQuery, useMutation } from 'convex/react';
  import { api } from './convex/_generated/api';

  interface ChatMessagesProps {
    conversationId: string;
  }

  export function ChatMessages({ conversationId }: ChatMessagesProps) {
    const messages = useQuery(api.shared.chat.getMessages, {
      conversationId,
      limit: 50
    });
    
    const typingIndicators = useQuery(api.shared.chat.getTypingIndicators, {
      conversationId
    });
    
    const updateTyping = useMutation(api.shared.chat.updateTypingStatus);
    const markAsRead = useMutation(api.shared.chat.markMessagesAsRead);
    
    // Mark messages as read when component mounts
    useEffect(() => {
      if (messages?.messages.length) {
        markAsRead({ conversationId });
      }
    }, [conversationId, messages?.messages.length]);
    
    return (
      <div className="chat-container">
        <div className="messages">
          {messages?.messages.map((message) => (
            <MessageBubble key={message._id} message={message} />
          ))}
        </div>
        
        {typingIndicators?.length > 0 && (
          <div className="typing-indicators">
            {typingIndicators.map((indicator) => (
              <div key={indicator.userId} className="typing-indicator">
                {indicator.userName} is typing...
              </div>
            ))}
          </div>
        )}
      </div>
    );
  }
  ```

  ```typescript Vue Component theme={null}
  <template>
    <div class="chat-container">
      <div class="messages">
        <MessageBubble 
          v-for="message in messages?.messages" 
          :key="message._id"
          :message="message"
        />
      </div>
      
      <div v-if="typingIndicators?.length > 0" class="typing-indicators">
        <div 
          v-for="indicator in typingIndicators" 
          :key="indicator.userId"
          class="typing-indicator"
        >
          {{ indicator.userName }} is typing...
        </div>
      </div>
    </div>
  </template>

  <script setup lang="ts">
  import { useConvexQuery, useConvexMutation } from '@convex-vue/core';

  interface Props {
    conversationId: string;
  }

  const props = defineProps<Props>();

  const messages = useConvexQuery('shared.chat.getMessages', () => ({
    conversationId: props.conversationId,
    limit: 50
  }));

  const typingIndicators = useConvexQuery('shared.chat.getTypingIndicators', () => ({
    conversationId: props.conversationId
  }));

  const updateTyping = useConvexMutation('shared.chat.updateTypingStatus');
  const markAsRead = useConvexMutation('shared.chat.markMessagesAsRead');

  // Mark messages as read when component mounts
  onMounted(() => {
    if (messages.value?.messages.length) {
      markAsRead({ conversationId: props.conversationId });
    }
  });
  </script>
  ```
</CodeGroup>

### 3. Message Input with Typing Indicators

<CodeGroup>
  ```typescript React Component theme={null}
  import { useState, useEffect, useCallback } from 'react';
  import { useMutation } from 'convex/react';
  import { api } from './convex/_generated/api';

  interface MessageInputProps {
    conversationId: string;
  }

  export function MessageInput({ conversationId }: MessageInputProps) {
    const [message, setMessage] = useState('');
    const [isTyping, setIsTyping] = useState(false);
    
    const sendMessage = useMutation(api.shared.chat.sendMessage);
    const updateTyping = useMutation(api.shared.chat.updateTypingStatus);
    
    // Debounced typing indicator
    const debouncedTypingUpdate = useCallback(
      debounce((typing: boolean) => {
        updateTyping({ conversationId, isTyping: typing });
        setIsTyping(typing);
      }, 500),
      [conversationId]
    );
    
    const handleInputChange = (value: string) => {
      setMessage(value);
      
      // Update typing status
      if (value.length > 0 && !isTyping) {
        debouncedTypingUpdate(true);
      } else if (value.length === 0 && isTyping) {
        debouncedTypingUpdate(false);
      }
    };
    
    const handleSend = async () => {
      if (!message.trim()) return;
      
      try {
        await sendMessage({
          conversationId,
          content: message.trim(),
          messageType: "text"
        });
        
        setMessage('');
        debouncedTypingUpdate(false);
      } catch (error) {
        console.error('Failed to send message:', error);
      }
    };
    
    // Cleanup typing indicator when component unmounts
    useEffect(() => {
      return () => {
        if (isTyping) {
          updateTyping({ conversationId, isTyping: false });
        }
      };
    }, [conversationId, isTyping]);
    
    return (
      <div className="message-input">
        <input
          type="text"
          value={message}
          onChange={(e) => handleInputChange(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleSend()}
          placeholder="Type a message..."
        />
        <button onClick={handleSend} disabled={!message.trim()}>
          Send
        </button>
      </div>
    );
  }

  // Debounce utility
  function debounce<T extends (...args: any[]) => void>(
    func: T,
    wait: number
  ): (...args: Parameters<T>) => void {
    let timeout: NodeJS.Timeout;
    return (...args: Parameters<T>) => {
      clearTimeout(timeout);
      timeout = setTimeout(() => func(...args), wait);
    };
  }
  ```

  ```typescript Vue Component theme={null}
  <template>
    <div class="message-input">
      <input
        v-model="message"
        @input="handleInputChange"
        @keypress.enter="handleSend"
        placeholder="Type a message..."
      />
      <button @click="handleSend" :disabled="!message.trim()">
        Send
      </button>
    </div>
  </template>

  <script setup lang="ts">
  import { ref, onMounted, onUnmounted } from 'vue';
  import { useConvexMutation } from '@convex-vue/core';

  interface Props {
    conversationId: string;
  }

  const props = defineProps<Props>();

  const message = ref('');
  const isTyping = ref(false);

  const sendMessage = useConvexMutation('shared.chat.sendMessage');
  const updateTyping = useConvexMutation('shared.chat.updateTypingStatus');

  let typingTimeout: NodeJS.Timeout;

  const handleInputChange = () => {
    // Update typing status with debounce
    if (message.value.length > 0 && !isTyping.value) {
      updateTyping({ conversationId: props.conversationId, isTyping: true });
      isTyping.value = true;
    }
    
    clearTimeout(typingTimeout);
    typingTimeout = setTimeout(() => {
      if (message.value.length === 0 || isTyping.value) {
        updateTyping({ conversationId: props.conversationId, isTyping: false });
        isTyping.value = false;
      }
    }, 1000);
  };

  const handleSend = async () => {
    if (!message.value.trim()) return;
    
    try {
      await sendMessage({
        conversationId: props.conversationId,
        content: message.value.trim(),
        messageType: "text"
      });
      
      message.value = '';
      updateTyping({ conversationId: props.conversationId, isTyping: false });
      isTyping.value = false;
    } catch (error) {
      console.error('Failed to send message:', error);
    }
  };

  // Cleanup typing indicator when component unmounts
  onUnmounted(() => {
    if (isTyping.value) {
      updateTyping({ conversationId: props.conversationId, isTyping: false });
    }
    clearTimeout(typingTimeout);
  });
  </script>
  ```
</CodeGroup>

### 4. Image Message Support

<CodeGroup>
  ```typescript React Hook theme={null}
  import { useAction } from 'convex/react';
  import { api } from './convex/_generated/api';

  export function useImageUpload() {
    const processImage = useAction(api.shared.imageProcessing.processImage);
    const validateImage = useAction(api.shared.imageProcessing.validateImageUpload);
    const sendMessage = useMutation(api.shared.chat.sendMessage);
    
    const sendImageMessage = async (
      conversationId: string,
      imageFile: File
    ) => {
      try {
        // 1. Upload to storage
        const storageId = await convex.storage.store(imageFile);
        
        // 2. Validate image
        const validation = await validateImage({
          storageId,
          imageType: "chatImage"
        });
        
        if (!validation.valid) {
          throw new Error(validation.error);
        }
        
        // 3. Process image
        const processed = await processImage({
          storageId,
          imageType: "chatImage",
          quality: 80
        });
        
        // 4. Send message
        const messageId = await sendMessage({
          conversationId,
          content: "📷 Image",
          messageType: "image",
          imageAttachment: {
            fileId: processed.newStorageId,
            fileName: imageFile.name,
            fileType: imageFile.type,
            fileSize: processed.compressedSize,
            width: processed.width,
            height: processed.height
          }
        });
        
        return messageId;
      } catch (error) {
        console.error('Failed to send image:', error);
        throw error;
      }
    };
    
    return { sendImageMessage };
  }
  ```

  ```typescript Vue Composition API theme={null}
  import { useConvexAction, useConvexMutation } from '@convex-vue/core';

  export function useImageUpload() {
    const processImage = useConvexAction('shared.imageProcessing.processImage');
    const validateImage = useConvexAction('shared.imageProcessing.validateImageUpload');
    const sendMessage = useConvexMutation('shared.chat.sendMessage');
    
    const sendImageMessage = async (conversationId: string, imageFile: File) => {
      try {
        // Upload and process image
        const storageId = await convex.storage.store(imageFile);
        
        const validation = await validateImage({
          storageId,
          imageType: "chatImage"
        });
        
        if (!validation.valid) {
          throw new Error(validation.error);
        }
        
        const processed = await processImage({
          storageId,
          imageType: "chatImage",
          quality: 80
        });
        
        // Send message
        const messageId = await sendMessage({
          conversationId,
          content: "📷 Image",
          messageType: "image",
          imageAttachment: {
            fileId: processed.newStorageId,
            fileName: imageFile.name,
            fileType: imageFile.type,
            fileSize: processed.compressedSize,
            width: processed.width,
            height: processed.height
          }
        });
        
        return messageId;
      } catch (error) {
        console.error('Failed to send image:', error);
        throw error;
      }
    };
    
    return { sendImageMessage };
  }
  ```
</CodeGroup>

### 5. Admin Chat Management

<CodeGroup>
  ```typescript Admin Dashboard theme={null}
  import { useQuery, useMutation } from 'convex/react';
  import { api } from './convex/_generated/api';

  export function AdminChatDashboard() {
    const conversations = useQuery(api.admins.chat.getAdminConversations, {
      status: "active",
      limit: 50
    });
    
    const chatStats = useQuery(api.admins.chat.getChatStats, {});
    
    const transferToStore = useMutation(api.admins.chat.transferConversationToStore);
    const blockConversation = useMutation(api.admins.chat.blockConversation);
    
    const handleTransferToStore = async (conversationId: string, storeId: string) => {
      try {
        const result = await transferToStore({
          conversationId,
          storeId,
          includeHistory: true
        });
        
        console.log('Conversation transferred:', result.newConversationId);
      } catch (error) {
        console.error('Transfer failed:', error);
      }
    };
    
    return (
      <div className="admin-chat-dashboard">
        <div className="stats">
          <div>Total Conversations: {chatStats?.totalConversations}</div>
          <div>Active Conversations: {chatStats?.activeConversations}</div>
          <div>Blocked Conversations: {chatStats?.blockedConversations}</div>
        </div>
        
        <div className="conversations">
          {conversations?.map((conv) => (
            <ConversationCard 
              key={conv._id}
              conversation={conv}
              onTransfer={handleTransferToStore}
              onBlock={() => blockConversation({ conversationId: conv._id })}
            />
          ))}
        </div>
      </div>
    );
  }
  ```
</CodeGroup>

## Advanced Features

### Conversation Transfers

The system supports seamless conversation transfers between different support levels:

<AccordionGroup>
  <Accordion title="Customer to Store Transfer">
    **When**: Customer wants to communicate directly with store
    **Requirements**: Customer must have orders with the store
    **Process**: Creates new store conversation, archives admin conversation
  </Accordion>

  <Accordion title="Store to Admin Transfer">
    **When**: Store needs admin intervention
    **Requirements**: Store owner or admin can initiate
    **Process**: Creates new admin conversation, archives store conversation
  </Accordion>

  <Accordion title="Message History Transfer">
    **Option**: Include or exclude message history
    **Default**: Admin transfers include history, store transfers exclude
    **Benefit**: Maintains context while allowing fresh start
  </Accordion>
</AccordionGroup>

### Chat Settings Management

<CodeGroup>
  ```typescript Settings Management theme={null}
  import { useQuery, useMutation } from 'convex/react';
  import { api } from './convex/_generated/api';

  export function ChatSettingsManager() {
    const chatSettings = useQuery(api.shared.chat.getChatSettings, {});
    const updateSettings = useMutation(api.admins.chat.updateChatSettings);
    
    const handleUpdateSettings = async (newSettings: any) => {
      try {
        await updateSettings(newSettings);
        console.log('Settings updated successfully');
      } catch (error) {
        console.error('Failed to update settings:', error);
      }
    };
    
    return (
      <div className="chat-settings">
        <h3>Chat System Settings</h3>
        
        <div className="setting">
          <label>
            <input
              type="checkbox"
              checked={chatSettings?.directMessagingEnabled}
              onChange={(e) => handleUpdateSettings({
                directMessagingEnabled: e.target.checked
              })}
            />
            Enable Direct Messaging
          </label>
        </div>
        
        <div className="setting">
          <label>
            Max Image Size (MB):
            <input
              type="number"
              value={chatSettings?.maxImageSizeMB}
              onChange={(e) => handleUpdateSettings({
                maxImageSizeMB: parseInt(e.target.value)
              })}
            />
          </label>
        </div>
        
        <div className="setting">
          <label>
            <input
              type="checkbox"
              checked={chatSettings?.adminSupportEnabled}
              onChange={(e) => handleUpdateSettings({
                adminSupportEnabled: e.target.checked
              })}
            />
            Enable Admin Support
          </label>
        </div>
      </div>
    );
  }
  ```
</CodeGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Performance" icon="zap">
    * Debounce typing indicators to avoid excessive updates
    * Use pagination for message loading
    * Implement proper cleanup for real-time subscriptions
  </Card>

  <Card title="User Experience" icon="user">
    * Show typing indicators for better engagement
    * Provide immediate feedback for message status
    * Handle offline scenarios gracefully
  </Card>

  <Card title="Security" icon="shield">
    * Validate all message content
    * Implement proper authorization checks
    * Sanitize user input before display
  </Card>

  <Card title="Scalability" icon="trending-up">
    * Use efficient database queries with indexes
    * Implement proper caching strategies
    * Monitor chat system performance
  </Card>
</CardGroup>

## Error Handling

<AccordionGroup>
  <Accordion title="Network Issues">
    **Problem**: Connection lost during message send
    **Solution**: Implement retry logic with exponential backoff
    **User Feedback**: Show message as "pending" until confirmed
  </Accordion>

  <Accordion title="Permission Errors">
    **Problem**: User tries to access unauthorized conversation
    **Solution**: Check permissions before allowing access
    **User Feedback**: Show clear error message with guidance
  </Accordion>

  <Accordion title="Image Upload Failures">
    **Problem**: Image too large or invalid format
    **Solution**: Validate before upload and provide clear limits
    **User Feedback**: Show specific error message with requirements
  </Accordion>
</AccordionGroup>

<Note>
  The chat system is designed to handle high-volume messaging with real-time updates. Always test thoroughly with multiple concurrent users to ensure performance.
</Note>

<Tip>
  Consider implementing message queuing for offline scenarios and message status indicators (sent, delivered, read) for better user experience.
</Tip>
