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>
116 lines
3.7 KiB
TypeScript
116 lines
3.7 KiB
TypeScript
/**
|
|
* HelpButton Component
|
|
*
|
|
* 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, useLocation } from 'react-router-dom';
|
|
import { HelpCircle } from 'lucide-react';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
// 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 = () => {
|
|
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="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={20} />
|
|
</Link>
|
|
);
|
|
};
|
|
|
|
export default HelpButton;
|