Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | 2x 31x 31x 31x 31x 31x 31x 1x 31x 29x 29x 3x 3x 1x 31x 1x 31x 31x | import React, { useState, useEffect, useRef } from 'react';
import { Outlet, Link, useNavigate } from 'react-router-dom';
import { User, Business } from '../types';
import { LayoutDashboard, CalendarPlus, CreditCard, HelpCircle, Sun, Moon } from 'lucide-react';
import MasqueradeBanner from '../components/MasqueradeBanner';
import UserProfileDropdown from '../components/UserProfileDropdown';
import NotificationDropdown from '../components/NotificationDropdown';
import { useStopMasquerade } from '../hooks/useAuth';
import { MasqueradeStackEntry } from '../api/auth';
import { useScrollToTop } from '../hooks/useScrollToTop';
interface CustomerLayoutProps {
business: Business;
user: User;
darkMode: boolean;
toggleTheme: () => void;
}
const CustomerLayout: React.FC<CustomerLayoutProps> = ({ business, user, darkMode, toggleTheme }) => {
const navigate = useNavigate();
const mainContentRef = useRef<HTMLElement>(null);
useScrollToTop(mainContentRef);
// Masquerade logic
const [masqueradeStack, setMasqueradeStack] = useState<MasqueradeStackEntry[]>([]);
const stopMasqueradeMutation = useStopMasquerade();
// Handle ticket notification click - navigate to support page
const handleTicketClick = (ticketId: string) => {
// Navigate to support page - the CustomerSupport component will handle showing tickets
navigate('/support');
};
useEffect(() => {
const stackJson = localStorage.getItem('masquerade_stack');
if (stackJson) {
try {
setMasqueradeStack(JSON.parse(stackJson));
} catch (e) {
console.error('Failed to parse masquerade stack data', e);
}
}
}, []);
const handleStopMasquerade = () => {
stopMasqueradeMutation.mutate();
};
// Get the original user (first in the stack)
const originalUser = masqueradeStack.length > 0
? {
id: masqueradeStack[0].user_id,
username: masqueradeStack[0].username,
name: masqueradeStack[0].username,
role: masqueradeStack[0].role,
email: '',
is_staff: false,
is_superuser: false,
} as User
: null;
return (
<div className="h-full flex flex-col bg-gray-50 dark:bg-gray-900">
{originalUser && (
<MasqueradeBanner
effectiveUser={user}
originalUser={originalUser}
previousUser={null}
onStop={handleStopMasquerade}
/>
)}
<header
className="text-white shadow-md"
style={{ backgroundColor: business.primaryColor }}
>
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo and Business Name */}
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-8 h-8 bg-white rounded-lg font-bold text-lg" style={{ color: business.primaryColor }}>
{business.name.charAt(0)}
</div>
<span className="font-bold text-lg">{business.name}</span>
</div>
{/* Navigation and User Menu */}
<div className="flex items-center gap-6">
<nav className="hidden md:flex gap-1">
<Link to="/" className="text-sm font-medium text-white/80 hover:text-white transition-colors flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/10">
<LayoutDashboard size={16} /> Dashboard
</Link>
<Link to="/book" className="text-sm font-medium text-white/80 hover:text-white transition-colors flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/10">
<CalendarPlus size={16} /> Book Appointment
</Link>
<Link to="/payments" className="text-sm font-medium text-white/80 hover:text-white transition-colors flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/10">
<CreditCard size={16} /> Billing
</Link>
<Link to="/support" className="text-sm font-medium text-white/80 hover:text-white transition-colors flex items-center gap-2 px-3 py-2 rounded-md hover:bg-white/10">
<HelpCircle size={16} /> Support
</Link>
</nav>
{/* Notifications */}
<NotificationDropdown variant="light" onTicketClick={handleTicketClick} />
{/* Dark Mode Toggle */}
<button
onClick={toggleTheme}
className="p-2 rounded-md text-white/80 hover:text-white hover:bg-white/10 transition-colors"
aria-label={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}
>
{darkMode ? <Sun size={20} /> : <Moon size={20} />}
</button>
<UserProfileDropdown user={user} variant="light" />
</div>
</div>
</div>
</header>
<main ref={mainContentRef} className="flex-1 overflow-y-auto">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Outlet context={{ business, user }} />
</div>
</main>
</div>
);
};
export default CustomerLayout; |