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:
@@ -1,31 +1,113 @@
|
||||
/**
|
||||
* HelpButton Component
|
||||
*
|
||||
* A contextual help button that appears at the top-right of pages
|
||||
* and links to the relevant help documentation.
|
||||
* A help button for the top bar that navigates to context-aware help pages.
|
||||
* Automatically determines the help path based on the current route.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface HelpButtonProps {
|
||||
helpPath: string;
|
||||
className?: string;
|
||||
}
|
||||
// Map route suffixes to their help page suffixes
|
||||
// These get prefixed appropriately based on context (tenant dashboard or public)
|
||||
const routeToHelpSuffix: Record<string, string> = {
|
||||
'/': 'dashboard',
|
||||
'/dashboard': 'dashboard',
|
||||
'/scheduler': 'scheduler',
|
||||
'/my-schedule': 'scheduler',
|
||||
'/tasks': 'tasks',
|
||||
'/customers': 'customers',
|
||||
'/services': 'services',
|
||||
'/resources': 'resources',
|
||||
'/locations': 'locations',
|
||||
'/staff': 'staff',
|
||||
'/time-blocks': 'time-blocks',
|
||||
'/my-availability': 'time-blocks',
|
||||
'/messages': 'messages',
|
||||
'/tickets': 'ticketing',
|
||||
'/payments': 'payments',
|
||||
'/contracts': 'contracts',
|
||||
'/contracts/templates': 'contracts',
|
||||
'/automations': 'automations',
|
||||
'/automations/marketplace': 'automations',
|
||||
'/automations/my-automations': 'automations',
|
||||
'/automations/create': 'automations/docs',
|
||||
'/site-editor': 'site-builder',
|
||||
'/gallery': 'site-builder',
|
||||
'/settings': 'settings/general',
|
||||
'/settings/general': 'settings/general',
|
||||
'/settings/resource-types': 'settings/resource-types',
|
||||
'/settings/booking': 'settings/booking',
|
||||
'/settings/appearance': 'settings/appearance',
|
||||
'/settings/branding': 'settings/appearance',
|
||||
'/settings/business-hours': 'settings/business-hours',
|
||||
'/settings/email': 'settings/email',
|
||||
'/settings/email-templates': 'settings/email-templates',
|
||||
'/settings/embed-widget': 'settings/embed-widget',
|
||||
'/settings/staff-roles': 'settings/staff-roles',
|
||||
'/settings/sms-calling': 'settings/communication',
|
||||
'/settings/domains': 'settings/domains',
|
||||
'/settings/api': 'settings/api',
|
||||
'/settings/auth': 'settings/auth',
|
||||
'/settings/billing': 'settings/billing',
|
||||
'/settings/quota': 'settings/quota',
|
||||
};
|
||||
|
||||
const HelpButton: React.FC<HelpButtonProps> = ({ helpPath, className = '' }) => {
|
||||
const HelpButton: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
// Check if we're on a tenant dashboard route
|
||||
const isOnDashboard = location.pathname.startsWith('/dashboard');
|
||||
|
||||
// Get the help path for the current route
|
||||
const getHelpPath = (): string => {
|
||||
// Determine the base help path based on context
|
||||
const helpBase = isOnDashboard ? '/dashboard/help' : '/help';
|
||||
|
||||
// Get the route to look up (strip /dashboard prefix if present)
|
||||
const lookupPath = isOnDashboard
|
||||
? location.pathname.replace(/^\/dashboard/, '') || '/'
|
||||
: location.pathname;
|
||||
|
||||
// Exact match first
|
||||
if (routeToHelpSuffix[lookupPath]) {
|
||||
return `${helpBase}/${routeToHelpSuffix[lookupPath]}`;
|
||||
}
|
||||
|
||||
// Try matching with a prefix (for dynamic routes like /customers/:id)
|
||||
const pathSegments = lookupPath.split('/').filter(Boolean);
|
||||
if (pathSegments.length > 0) {
|
||||
// Try progressively shorter paths
|
||||
for (let i = pathSegments.length; i > 0; i--) {
|
||||
const testPath = '/' + pathSegments.slice(0, i).join('/');
|
||||
if (routeToHelpSuffix[testPath]) {
|
||||
return `${helpBase}/${routeToHelpSuffix[testPath]}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to the main help page
|
||||
return helpBase;
|
||||
};
|
||||
|
||||
const helpPath = getHelpPath();
|
||||
|
||||
// Don't show on help pages themselves
|
||||
if (location.pathname.includes('/help')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={helpPath}
|
||||
className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-gray-500 dark:text-gray-400 hover:text-brand-600 dark:hover:text-brand-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors ${className}`}
|
||||
className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
||||
title={t('common.help', 'Help')}
|
||||
aria-label={t('common.help', 'Help')}
|
||||
>
|
||||
<HelpCircle size={18} />
|
||||
<span className="hidden sm:inline">{t('common.help', 'Help')}</span>
|
||||
<HelpCircle size={20} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user