Add Site Builder help docs and fix FloatingHelpButton paths
- Add HelpSiteBuilder.tsx with comprehensive documentation for the drag-and-drop page editor (components, publishing, settings) - Fix FloatingHelpButton to use /dashboard/help/* paths on tenant sites - Update HelpComprehensive and HelpAutomations to rename plugins to automations - Add site-crawler utility with cross-subdomain redirect detection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,76 +10,85 @@ import { Link, useLocation } from 'react-router-dom';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// Map routes to their help paths
|
||||
const routeToHelpPath: Record<string, string> = {
|
||||
'/': '/help/dashboard',
|
||||
'/dashboard': '/help/dashboard',
|
||||
'/scheduler': '/help/scheduler',
|
||||
'/tasks': '/help/tasks',
|
||||
'/customers': '/help/customers',
|
||||
'/services': '/help/services',
|
||||
'/resources': '/help/resources',
|
||||
'/staff': '/help/staff',
|
||||
'/time-blocks': '/help/time-blocks',
|
||||
'/my-availability': '/help/time-blocks',
|
||||
'/messages': '/help/messages',
|
||||
'/tickets': '/help/ticketing',
|
||||
'/payments': '/help/payments',
|
||||
'/contracts': '/help/contracts',
|
||||
'/contracts/templates': '/help/contracts',
|
||||
'/automations': '/help/automations',
|
||||
'/automations/marketplace': '/help/automations',
|
||||
'/automations/my-automations': '/help/automations',
|
||||
'/automations/create': '/help/automations/docs',
|
||||
'/settings': '/help/settings/general',
|
||||
'/settings/general': '/help/settings/general',
|
||||
'/settings/resource-types': '/help/settings/resource-types',
|
||||
'/settings/booking': '/help/settings/booking',
|
||||
'/settings/appearance': '/help/settings/appearance',
|
||||
'/settings/email': '/help/settings/email',
|
||||
'/settings/domains': '/help/settings/domains',
|
||||
'/settings/api': '/help/settings/api',
|
||||
'/settings/auth': '/help/settings/auth',
|
||||
'/settings/billing': '/help/settings/billing',
|
||||
'/settings/quota': '/help/settings/quota',
|
||||
// Platform routes
|
||||
'/platform/dashboard': '/help/dashboard',
|
||||
'/platform/businesses': '/help/dashboard',
|
||||
'/platform/users': '/help/staff',
|
||||
'/platform/tickets': '/help/ticketing',
|
||||
// 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',
|
||||
'/tasks': 'tasks',
|
||||
'/customers': 'customers',
|
||||
'/services': 'services',
|
||||
'/resources': 'resources',
|
||||
'/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/email': 'settings/email',
|
||||
'/settings/domains': 'settings/domains',
|
||||
'/settings/api': 'settings/api',
|
||||
'/settings/auth': 'settings/auth',
|
||||
'/settings/billing': 'settings/billing',
|
||||
'/settings/quota': 'settings/quota',
|
||||
};
|
||||
|
||||
const FloatingHelpButton: 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 (routeToHelpPath[location.pathname]) {
|
||||
return routeToHelpPath[location.pathname];
|
||||
if (routeToHelpSuffix[lookupPath]) {
|
||||
return `${helpBase}/${routeToHelpSuffix[lookupPath]}`;
|
||||
}
|
||||
|
||||
// Try matching with a prefix (for dynamic routes like /customers/:id)
|
||||
const pathSegments = location.pathname.split('/').filter(Boolean);
|
||||
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 (routeToHelpPath[testPath]) {
|
||||
return routeToHelpPath[testPath];
|
||||
if (routeToHelpSuffix[testPath]) {
|
||||
return `${helpBase}/${routeToHelpSuffix[testPath]}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to the main help guide
|
||||
return '/help';
|
||||
// Default to the main help page
|
||||
return helpBase;
|
||||
};
|
||||
|
||||
const helpPath = getHelpPath();
|
||||
|
||||
// Don't show on help pages themselves
|
||||
if (location.pathname.startsWith('/help')) {
|
||||
if (location.pathname.includes('/help')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,68 +19,127 @@ describe('FloatingHelpButton', () => {
|
||||
);
|
||||
};
|
||||
|
||||
it('renders help link on dashboard', () => {
|
||||
renderWithRouter('/dashboard');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toBeInTheDocument();
|
||||
describe('tenant dashboard routes (prefixed with /dashboard)', () => {
|
||||
it('renders help link on tenant dashboard', () => {
|
||||
renderWithRouter('/dashboard');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('links to /dashboard/help/dashboard for /dashboard', () => {
|
||||
renderWithRouter('/dashboard');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help/dashboard');
|
||||
});
|
||||
|
||||
it('links to /dashboard/help/scheduler for /dashboard/scheduler', () => {
|
||||
renderWithRouter('/dashboard/scheduler');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help/scheduler');
|
||||
});
|
||||
|
||||
it('links to /dashboard/help/services for /dashboard/services', () => {
|
||||
renderWithRouter('/dashboard/services');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help/services');
|
||||
});
|
||||
|
||||
it('links to /dashboard/help/resources for /dashboard/resources', () => {
|
||||
renderWithRouter('/dashboard/resources');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help/resources');
|
||||
});
|
||||
|
||||
it('links to /dashboard/help/settings/general for /dashboard/settings/general', () => {
|
||||
renderWithRouter('/dashboard/settings/general');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help/settings/general');
|
||||
});
|
||||
|
||||
it('links to /dashboard/help/customers for /dashboard/customers/123', () => {
|
||||
renderWithRouter('/dashboard/customers/123');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help/customers');
|
||||
});
|
||||
|
||||
it('returns null on /dashboard/help pages', () => {
|
||||
const { container } = renderWithRouter('/dashboard/help/dashboard');
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('links to /dashboard/help for unknown dashboard routes', () => {
|
||||
renderWithRouter('/dashboard/unknown-route');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help');
|
||||
});
|
||||
|
||||
it('links to /dashboard/help/site-builder for /dashboard/site-editor', () => {
|
||||
renderWithRouter('/dashboard/site-editor');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help/site-builder');
|
||||
});
|
||||
|
||||
it('links to /dashboard/help/site-builder for /dashboard/gallery', () => {
|
||||
renderWithRouter('/dashboard/gallery');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/dashboard/help/site-builder');
|
||||
});
|
||||
});
|
||||
|
||||
it('links to correct help page for dashboard', () => {
|
||||
renderWithRouter('/dashboard');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/dashboard');
|
||||
describe('non-dashboard routes (public/platform)', () => {
|
||||
it('links to /help/scheduler for /scheduler', () => {
|
||||
renderWithRouter('/scheduler');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/scheduler');
|
||||
});
|
||||
|
||||
it('links to /help/services for /services', () => {
|
||||
renderWithRouter('/services');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/services');
|
||||
});
|
||||
|
||||
it('links to /help/resources for /resources', () => {
|
||||
renderWithRouter('/resources');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/resources');
|
||||
});
|
||||
|
||||
it('links to /help/settings/general for /settings/general', () => {
|
||||
renderWithRouter('/settings/general');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/settings/general');
|
||||
});
|
||||
|
||||
it('returns null on /help pages', () => {
|
||||
const { container } = renderWithRouter('/help/dashboard');
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('links to /help for unknown routes', () => {
|
||||
renderWithRouter('/unknown-route');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help');
|
||||
});
|
||||
|
||||
it('handles dynamic routes by matching prefix', () => {
|
||||
renderWithRouter('/customers/123');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/customers');
|
||||
});
|
||||
});
|
||||
|
||||
it('links to correct help page for scheduler', () => {
|
||||
renderWithRouter('/scheduler');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/scheduler');
|
||||
});
|
||||
describe('accessibility', () => {
|
||||
it('has aria-label', () => {
|
||||
renderWithRouter('/dashboard');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('aria-label', 'Help');
|
||||
});
|
||||
|
||||
it('links to correct help page for services', () => {
|
||||
renderWithRouter('/services');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/services');
|
||||
});
|
||||
|
||||
it('links to correct help page for resources', () => {
|
||||
renderWithRouter('/resources');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/resources');
|
||||
});
|
||||
|
||||
it('links to correct help page for settings', () => {
|
||||
renderWithRouter('/settings/general');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/settings/general');
|
||||
});
|
||||
|
||||
it('returns null on help pages', () => {
|
||||
const { container } = renderWithRouter('/help/dashboard');
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('has aria-label', () => {
|
||||
renderWithRouter('/dashboard');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('aria-label', 'Help');
|
||||
});
|
||||
|
||||
it('has title attribute', () => {
|
||||
renderWithRouter('/dashboard');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('title', 'Help');
|
||||
});
|
||||
|
||||
it('links to default help for unknown routes', () => {
|
||||
renderWithRouter('/unknown-route');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help');
|
||||
});
|
||||
|
||||
it('handles dynamic routes by matching prefix', () => {
|
||||
renderWithRouter('/customers/123');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', '/help/customers');
|
||||
it('has title attribute', () => {
|
||||
renderWithRouter('/dashboard');
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('title', 'Help');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user