This commit includes: - Django backend with multi-tenancy (django-tenants) - React + TypeScript frontend with Vite - Platform administration API with role-based access control - Authentication system with token-based auth - Quick login dev tools for testing different user roles - CORS and CSRF configuration for local development - Docker development environment setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
103 lines
3.8 KiB
TypeScript
103 lines
3.8 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Outlet, Link } from 'react-router-dom';
|
|
import { User, Business } from '../types';
|
|
import { LayoutDashboard, CalendarPlus, CreditCard } from 'lucide-react';
|
|
import MasqueradeBanner from '../components/MasqueradeBanner';
|
|
import UserProfileDropdown from '../components/UserProfileDropdown';
|
|
import { useStopMasquerade } from '../hooks/useAuth';
|
|
import { MasqueradeStackEntry } from '../api/auth';
|
|
import { useScrollToTop } from '../hooks/useScrollToTop';
|
|
|
|
interface CustomerLayoutProps {
|
|
business: Business;
|
|
user: User;
|
|
}
|
|
|
|
const CustomerLayout: React.FC<CustomerLayoutProps> = ({ business, user }) => {
|
|
useScrollToTop();
|
|
|
|
// Masquerade logic
|
|
const [masqueradeStack, setMasqueradeStack] = useState<MasqueradeStackEntry[]>([]);
|
|
const stopMasqueradeMutation = useStopMasquerade();
|
|
|
|
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>
|
|
</nav>
|
|
|
|
<UserProfileDropdown user={user} variant="light" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<main 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; |