feat(help): Add floating help button to all pages

Replaced inline HelpButton components with a global FloatingHelpButton
that appears fixed in the top-right corner of all pages. The button:
- Automatically detects the current route and links to the appropriate help page
- Uses a consistent position across all pages (fixed, top-right)
- Is hidden on help pages themselves
- Works on both business and platform layouts

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-03 02:23:28 -05:00
parent 11bb83a85d
commit 5aa49399d0
7 changed files with 125 additions and 42 deletions

View File

@@ -0,0 +1,94 @@
/**
* FloatingHelpButton Component
*
* A floating help button fixed in the top-right corner of the screen.
* 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 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',
'/messages': '/help/messages',
'/tickets': '/help/ticketing',
'/payments': '/help/payments',
'/plugins': '/help/plugins',
'/plugins/marketplace': '/help/plugins',
'/plugins/my-plugins': '/help/plugins',
'/plugins/create': '/help/plugins/create',
'/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',
};
const FloatingHelpButton: React.FC = () => {
const { t } = useTranslation();
const location = useLocation();
// Get the help path for the current route
const getHelpPath = (): string => {
// Exact match first
if (routeToHelpPath[location.pathname]) {
return routeToHelpPath[location.pathname];
}
// Try matching with a prefix (for dynamic routes like /customers/:id)
const pathSegments = location.pathname.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];
}
}
}
// Default to the main help guide
return '/help';
};
const helpPath = getHelpPath();
// Don't show on help pages themselves
if (location.pathname.startsWith('/help')) {
return null;
}
return (
<Link
to={helpPath}
className="fixed top-20 right-4 z-50 inline-flex items-center justify-center w-10 h-10 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 hover:text-brand-600 dark:hover:text-brand-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 transition-all duration-200 hover:scale-110"
title={t('common.help', 'Help')}
aria-label={t('common.help', 'Help')}
>
<HelpCircle size={20} />
</Link>
);
};
export default FloatingHelpButton;

View File

@@ -9,6 +9,7 @@ import { Business, User } from '../types';
import MasqueradeBanner from '../components/MasqueradeBanner';
import OnboardingWizard from '../components/OnboardingWizard';
import TicketModal from '../components/TicketModal';
import FloatingHelpButton from '../components/FloatingHelpButton';
import { useStopMasquerade } from '../hooks/useAuth';
import { useNotificationWebSocket } from '../hooks/useNotificationWebSocket';
import { useTicket } from '../hooks/useTickets';
@@ -167,6 +168,9 @@ const BusinessLayoutContent: React.FC<BusinessLayoutProps> = ({ business, user,
return (
<div className="flex h-full bg-gray-50 dark:bg-gray-900 transition-colors duration-200">
{/* Floating Help Button */}
<FloatingHelpButton />
<div className={`fixed inset-y-0 left-0 z-40 transform ${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'} transition-transform duration-300 ease-in-out md:hidden`}>
<Sidebar business={business} user={user} isCollapsed={false} toggleCollapse={() => { }} />
</div>

View File

@@ -6,6 +6,7 @@ import PlatformSidebar from '../components/PlatformSidebar';
import UserProfileDropdown from '../components/UserProfileDropdown';
import NotificationDropdown from '../components/NotificationDropdown';
import TicketModal from '../components/TicketModal';
import FloatingHelpButton from '../components/FloatingHelpButton';
import { useTicket } from '../hooks/useTickets';
import { useScrollToTop } from '../hooks/useScrollToTop';
@@ -36,6 +37,9 @@ const PlatformLayout: React.FC<PlatformLayoutProps> = ({ user, darkMode, toggleT
return (
<div className="flex h-screen bg-gray-100 dark:bg-gray-900">
{/* Floating Help Button */}
<FloatingHelpButton />
{/* Mobile menu */}
<div className={`fixed inset-y-0 left-0 z-40 transform ${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'} transition-transform duration-300 ease-in-out md:hidden`}>
<PlatformSidebar user={user} isCollapsed={false} toggleCollapse={() => { }} />

View File

@@ -16,7 +16,6 @@ import {
Eye
} from 'lucide-react';
import Portal from '../components/Portal';
import HelpButton from '../components/HelpButton';
interface CustomersProps {
onMasquerade: (user: User) => void;
@@ -126,12 +125,9 @@ const Customers: React.FC<CustomersProps> = ({ onMasquerade, effectiveUser }) =>
return (
<div className="p-8 max-w-7xl mx-auto space-y-6">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex items-start gap-4">
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('customers.title')}</h2>
<p className="text-gray-500 dark:text-gray-400">{t('customers.description')}</p>
</div>
<HelpButton helpPath="/help/customers" />
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('customers.title')}</h2>
<p className="text-gray-500 dark:text-gray-400">{t('customers.description')}</p>
</div>
<button
onClick={() => setIsAddModalOpen(true)}

View File

@@ -12,7 +12,6 @@ import {
Line
} from 'recharts';
import { TrendingUp, TrendingDown, Minus } from 'lucide-react';
import HelpButton from '../components/HelpButton';
import { useServices } from '../hooks/useServices';
import { useResources } from '../hooks/useResources';
import { useAppointments } from '../hooks/useAppointments';
@@ -104,12 +103,9 @@ const Dashboard: React.FC = () => {
if (isLoading) {
return (
<div className="p-8 space-y-8">
<div className="flex items-start justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('dashboard.title')}</h2>
<p className="text-gray-500 dark:text-gray-400">{t('common.loading')}</p>
</div>
<HelpButton helpPath="/help/dashboard" />
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('dashboard.title')}</h2>
<p className="text-gray-500 dark:text-gray-400">{t('common.loading')}</p>
</div>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
{[1, 2, 3, 4].map((i) => (
@@ -125,12 +121,9 @@ const Dashboard: React.FC = () => {
return (
<div className="p-8 space-y-8">
<div className="flex items-start justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('dashboard.title')}</h2>
<p className="text-gray-500 dark:text-gray-400">{t('dashboard.todayOverview')}</p>
</div>
<HelpButton helpPath="/help/dashboard" />
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('dashboard.title')}</h2>
<p className="text-gray-500 dark:text-gray-400">{t('dashboard.todayOverview')}</p>
</div>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">

View File

@@ -1,7 +1,6 @@
import React, { useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Plus, Pencil, Trash2, Clock, DollarSign, X, Loader2, GripVertical, Eye, ChevronRight, Upload, ImagePlus, Image } from 'lucide-react';
import HelpButton from '../components/HelpButton';
import { useServices, useCreateService, useUpdateService, useDeleteService, useReorderServices } from '../hooks/useServices';
import { Service } from '../types';
@@ -267,16 +266,13 @@ const Services: React.FC = () => {
return (
<div className="p-8 space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-start gap-4">
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
{t('services.title', 'Services')}
</h2>
<p className="text-gray-500 dark:text-gray-400">
{t('services.description', 'Manage the services your business offers')}
</p>
</div>
<HelpButton helpPath="/help/services" />
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
{t('services.title', 'Services')}
</h2>
<p className="text-gray-500 dark:text-gray-400">
{t('services.description', 'Manage the services your business offers')}
</p>
</div>
<button
onClick={openCreateModal}

View File

@@ -2,7 +2,6 @@ import React, { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import axios from '../api/client';
import HelpButton from '../components/HelpButton';
import {
Plus,
Play,
@@ -259,16 +258,13 @@ const Tasks: React.FC = () => {
<div className="p-6 max-w-7xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-start gap-4">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
{t('Tasks')}
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Schedule and manage automated plugin executions
</p>
</div>
<HelpButton helpPath="/help/tasks" />
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
{t('Tasks')}
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Schedule and manage automated plugin executions
</p>
</div>
<button
onClick={() => setShowCreateModal(true)}