Initial commit: SmoothSchedule multi-tenant scheduling platform

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>
This commit is contained in:
poduck
2025-11-27 01:43:20 -05:00
commit 2e111364a2
567 changed files with 96410 additions and 0 deletions

View File

@@ -0,0 +1,174 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useLocation } from 'react-router-dom';
import {
LayoutDashboard,
CalendarDays,
Settings,
Users,
CreditCard,
MessageSquare,
LogOut,
ClipboardList,
Briefcase
} from 'lucide-react';
import { Business, User } from '../types';
import { useLogout } from '../hooks/useAuth';
import SmoothScheduleLogo from './SmoothScheduleLogo';
interface SidebarProps {
business: Business;
user: User;
isCollapsed: boolean;
toggleCollapse: () => void;
}
const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCollapse }) => {
const { t } = useTranslation();
const location = useLocation();
const { role } = user;
const logoutMutation = useLogout();
const getNavClass = (path: string, exact: boolean = false, disabled: boolean = false) => {
const isActive = exact
? location.pathname === path
: location.pathname.startsWith(path);
const baseClasses = `flex items-center gap-3 py-3 text-sm font-medium rounded-lg transition-colors`;
const collapsedClasses = isCollapsed ? 'px-3 justify-center' : 'px-4';
const activeClasses = 'bg-opacity-10 text-white bg-white';
const inactiveClasses = 'text-white/70 hover:text-white hover:bg-white/5';
const disabledClasses = 'text-white/30 cursor-not-allowed';
if (disabled) {
return `${baseClasses} ${collapsedClasses} ${disabledClasses}`;
}
return `${baseClasses} ${collapsedClasses} ${isActive ? activeClasses : inactiveClasses}`;
};
const canViewAdminPages = role === 'owner' || role === 'manager';
const canViewManagementPages = role === 'owner' || role === 'manager' || role === 'staff';
const canViewSettings = role === 'owner';
const getDashboardLink = () => {
if (role === 'resource') return '/';
return '/';
};
const handleSignOut = () => {
logoutMutation.mutate();
};
return (
<div
className={`flex flex-col h-full text-white shrink-0 transition-all duration-300 ${isCollapsed ? 'w-20' : 'w-64'}`}
style={{ backgroundColor: business.primaryColor }}
>
<button
onClick={toggleCollapse}
className={`flex items-center gap-3 w-full text-left px-6 py-8 ${isCollapsed ? 'justify-center' : ''} hover:bg-white/5 transition-colors focus:outline-none`}
aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
>
<div className="flex items-center justify-center w-10 h-10 bg-white rounded-lg text-brand-600 font-bold text-xl shrink-0" style={{ color: business.primaryColor }}>
{business.name.substring(0, 2).toUpperCase()}
</div>
{!isCollapsed && (
<div className="overflow-hidden">
<h1 className="font-bold leading-tight truncate">{business.name}</h1>
<p className="text-xs text-white/60 truncate">{business.subdomain}.smoothschedule.com</p>
</div>
)}
</button>
<nav className="flex-1 px-4 space-y-1 overflow-y-auto">
<Link to={getDashboardLink()} className={getNavClass('/', true)} title={t('nav.dashboard')}>
<LayoutDashboard size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.dashboard')}</span>}
</Link>
<Link to="/scheduler" className={getNavClass('/scheduler')} title={t('nav.scheduler')}>
<CalendarDays size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.scheduler')}</span>}
</Link>
{canViewManagementPages && (
<>
<Link to="/customers" className={getNavClass('/customers')} title={t('nav.customers')}>
<Users size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.customers')}</span>}
</Link>
<Link to="/services" className={getNavClass('/services')} title={t('nav.services', 'Services')}>
<Briefcase size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.services', 'Services')}</span>}
</Link>
<Link to="/resources" className={getNavClass('/resources')} title={t('nav.resources')}>
<ClipboardList size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.resources')}</span>}
</Link>
</>
)}
{canViewAdminPages && (
<>
{business.paymentsEnabled ? (
<Link to="/payments" className={getNavClass('/payments')} title={t('nav.payments')}>
<CreditCard size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.payments')}</span>}
</Link>
) : (
<div
className={getNavClass('/payments', false, true)}
title={t('nav.paymentsDisabledTooltip')}
>
<CreditCard size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.payments')}</span>}
</div>
)}
<Link to="/messages" className={getNavClass('/messages')} title={t('nav.messages')}>
<MessageSquare size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.messages')}</span>}
</Link>
<Link to="/staff" className={getNavClass('/staff')} title={t('nav.staff')}>
<Users size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.staff')}</span>}
</Link>
</>
)}
{canViewSettings && (
<div className="pt-8 mt-8 border-t border-white/10">
{canViewSettings && (
<Link to="/settings" className={getNavClass('/settings', true)} title={t('nav.businessSettings')}>
<Settings size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.businessSettings')}</span>}
</Link>
)}
</div>
)}
</nav>
<div className="p-4 border-t border-white/10">
<div className={`flex items-center gap-2 text-xs text-white/60 mb-4 ${isCollapsed ? 'justify-center' : ''}`}>
<SmoothScheduleLogo className="w-6 h-6 text-white" />
{!isCollapsed && (
<div>
<span className="block">{t('common.poweredBy')}</span>
<span className="font-semibold text-white/80">Smooth Schedule</span>
</div>
)}
</div>
<button
onClick={handleSignOut}
disabled={logoutMutation.isPending}
className={`flex items-center gap-3 px-4 py-2 text-sm font-medium text-white/70 hover:text-white w-full transition-colors rounded-lg ${isCollapsed ? 'justify-center' : ''} disabled:opacity-50`}
>
<LogOut size={20} className="shrink-0" />
{!isCollapsed && <span>{t('auth.signOut')}</span>}
</button>
</div>
</div>
);
};
export default Sidebar;