Add staff email client with WebSocket real-time updates

Implements a complete email client for platform staff members:

Backend:
- Add routing_mode field to PlatformEmailAddress (PLATFORM/STAFF)
- Create staff_email app with models for folders, emails, attachments, labels
- IMAP service for fetching emails with folder mapping
- SMTP service for sending emails with attachment support
- Celery tasks for periodic sync and full sync operations
- WebSocket consumer for real-time notifications
- Comprehensive API viewsets with filtering and actions

Frontend:
- Thunderbird-style three-pane email interface
- Multi-account support with drag-and-drop ordering
- Email composer with rich text editor
- Email viewer with thread support
- Real-time WebSocket updates for new emails and sync status
- 94 unit tests covering models, serializers, views, services, and consumers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 01:50:40 -05:00
parent 9848268d34
commit 3ab0306191
63 changed files with 8944 additions and 411 deletions

View File

@@ -62,8 +62,8 @@ vi.mock('../../components/TicketModal', () => ({
),
}));
vi.mock('../../components/FloatingHelpButton', () => ({
default: () => <div data-testid="floating-help-button">Help</div>,
vi.mock('../../components/HelpButton', () => ({
default: () => <div data-testid="help-button">Help</div>,
}));
// Mock hooks - create a mocked function that can be reassigned
@@ -150,7 +150,7 @@ describe('PlatformLayout', () => {
expect(screen.getByTestId('user-profile-dropdown')).toBeInTheDocument();
expect(screen.getByTestId('notification-dropdown')).toBeInTheDocument();
expect(screen.getByTestId('language-selector')).toBeInTheDocument();
expect(screen.getByTestId('floating-help-button')).toBeInTheDocument();
expect(screen.getByTestId('help-button')).toBeInTheDocument();
});
it('should render children content via Outlet', () => {
@@ -412,7 +412,7 @@ describe('PlatformLayout', () => {
it('should render floating help button', () => {
renderLayout();
expect(screen.getByTestId('floating-help-button')).toBeInTheDocument();
expect(screen.getByTestId('help-button')).toBeInTheDocument();
});
});