Add demo tenant reseed, staff roles, and fix masquerade redirect
Demo Tenant: - Add block_emails field to Tenant model for demo accounts - Add is_email_blocked() and wrapper functions in email_service - Create reseed_demo management command with salon/spa theme - Add Celery beat task for daily reseed at midnight UTC - Create 100 appointments, 20 customers, 13 services, 12 resources Staff Roles: - Add StaffRole model with permission toggles - Create default roles: Full Access, Front Desk, Limited Staff - Add StaffRolesSettings page and hooks - Integrate role assignment in Staff management Bug Fixes: - Fix masquerade redirect using wrong role names (tenant_owner vs owner) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,12 +45,26 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
const logoutMutation = useLogout();
|
||||
const { canUse } = usePlanFeatures();
|
||||
|
||||
// Helper to check if user has a specific staff permission
|
||||
// Owners and managers always have all permissions
|
||||
// Staff members check their effective_permissions (role + user overrides)
|
||||
const hasPermission = (permissionKey: string): boolean => {
|
||||
if (role === 'owner' || role === 'manager') {
|
||||
return true;
|
||||
}
|
||||
if (role === 'staff') {
|
||||
// Check effective_permissions which combines user overrides and staff role
|
||||
return user.effective_permissions?.[permissionKey] === true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const canViewAdminPages = role === 'owner' || role === 'manager';
|
||||
const canViewManagementPages = role === 'owner' || role === 'manager';
|
||||
const isStaff = role === 'staff';
|
||||
const canViewSettings = role === 'owner';
|
||||
const canViewTickets = role === 'owner' || role === 'manager' || (role === 'staff' && user.can_access_tickets);
|
||||
const canSendMessages = user.can_send_messages === true;
|
||||
const canViewTickets = hasPermission('can_access_tickets');
|
||||
const canSendMessages = hasPermission('can_access_messages') || user.can_send_messages === true;
|
||||
|
||||
const handleSignOut = () => {
|
||||
logoutMutation.mutate();
|
||||
@@ -116,7 +130,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
isCollapsed={isCollapsed}
|
||||
exact
|
||||
/>
|
||||
{!isStaff && (
|
||||
{hasPermission('can_access_scheduler') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/scheduler"
|
||||
icon={CalendarDays}
|
||||
@@ -124,7 +138,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
{!isStaff && (
|
||||
{hasPermission('can_access_tasks') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/tasks"
|
||||
icon={Clock}
|
||||
@@ -134,7 +148,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
)}
|
||||
{isStaff && (
|
||||
{(isStaff && hasPermission('can_access_my_schedule')) && (
|
||||
<SidebarItem
|
||||
to="/dashboard/my-schedule"
|
||||
icon={CalendarDays}
|
||||
@@ -142,7 +156,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
{(role === 'staff' || role === 'resource') && (
|
||||
{(role === 'staff' || role === 'resource') && hasPermission('can_access_my_availability') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/my-availability"
|
||||
icon={CalendarOff}
|
||||
@@ -152,72 +166,94 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
)}
|
||||
</SidebarSection>
|
||||
|
||||
{/* Manage Section - Staff+ */}
|
||||
{canViewManagementPages && (
|
||||
{/* Manage Section - Show if user has any manage-related permission */}
|
||||
{(canViewManagementPages ||
|
||||
hasPermission('can_access_site_builder') ||
|
||||
hasPermission('can_access_gallery') ||
|
||||
hasPermission('can_access_customers') ||
|
||||
hasPermission('can_access_services') ||
|
||||
hasPermission('can_access_resources') ||
|
||||
hasPermission('can_access_staff') ||
|
||||
hasPermission('can_access_contracts') ||
|
||||
hasPermission('can_access_time_blocks') ||
|
||||
hasPermission('can_access_locations')
|
||||
) && (
|
||||
<SidebarSection title={t('nav.sections.manage', 'Manage')} isCollapsed={isCollapsed}>
|
||||
<SidebarItem
|
||||
to="/dashboard/site-editor"
|
||||
icon={LayoutTemplate}
|
||||
label={t('nav.siteBuilder', 'Site Builder')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
<SidebarItem
|
||||
to="/dashboard/gallery"
|
||||
icon={Image}
|
||||
label={t('nav.gallery', 'Media Gallery')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
<SidebarItem
|
||||
to="/dashboard/customers"
|
||||
icon={Users}
|
||||
label={t('nav.customers')}
|
||||
isCollapsed={isCollapsed}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
<SidebarItem
|
||||
to="/dashboard/services"
|
||||
icon={Briefcase}
|
||||
label={t('nav.services', 'Services')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
<SidebarItem
|
||||
to="/dashboard/resources"
|
||||
icon={ClipboardList}
|
||||
label={t('nav.resources')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
{canViewAdminPages && (
|
||||
<>
|
||||
<SidebarItem
|
||||
to="/dashboard/staff"
|
||||
icon={Users}
|
||||
label={t('nav.staff')}
|
||||
isCollapsed={isCollapsed}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
{canUse('contracts') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/contracts"
|
||||
icon={FileSignature}
|
||||
label={t('nav.contracts', 'Contracts')}
|
||||
isCollapsed={isCollapsed}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
)}
|
||||
<SidebarItem
|
||||
to="/dashboard/time-blocks"
|
||||
icon={CalendarOff}
|
||||
label={t('nav.timeBlocks', 'Time Blocks')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
<SidebarItem
|
||||
to="/dashboard/locations"
|
||||
icon={MapPin}
|
||||
label={t('nav.locations', 'Locations')}
|
||||
isCollapsed={isCollapsed}
|
||||
locked={!canUse('multi_location')}
|
||||
/>
|
||||
</>
|
||||
{hasPermission('can_access_site_builder') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/site-editor"
|
||||
icon={LayoutTemplate}
|
||||
label={t('nav.siteBuilder', 'Site Builder')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_gallery') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/gallery"
|
||||
icon={Image}
|
||||
label={t('nav.gallery', 'Media Gallery')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_customers') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/customers"
|
||||
icon={Users}
|
||||
label={t('nav.customers')}
|
||||
isCollapsed={isCollapsed}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_services') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/services"
|
||||
icon={Briefcase}
|
||||
label={t('nav.services', 'Services')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_resources') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/resources"
|
||||
icon={ClipboardList}
|
||||
label={t('nav.resources')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_staff') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/staff"
|
||||
icon={Users}
|
||||
label={t('nav.staff')}
|
||||
isCollapsed={isCollapsed}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_contracts') && canUse('contracts') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/contracts"
|
||||
icon={FileSignature}
|
||||
label={t('nav.contracts', 'Contracts')}
|
||||
isCollapsed={isCollapsed}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_time_blocks') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/time-blocks"
|
||||
icon={CalendarOff}
|
||||
label={t('nav.timeBlocks', 'Time Blocks')}
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_locations') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/locations"
|
||||
icon={MapPin}
|
||||
label={t('nav.locations', 'Locations')}
|
||||
isCollapsed={isCollapsed}
|
||||
locked={!canUse('multi_location')}
|
||||
/>
|
||||
)}
|
||||
</SidebarSection>
|
||||
)}
|
||||
@@ -245,7 +281,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
)}
|
||||
|
||||
{/* Money Section - Payments */}
|
||||
{canViewAdminPages && (
|
||||
{hasPermission('can_access_payments') && (
|
||||
<SidebarSection title={t('nav.sections.money', 'Money')} isCollapsed={isCollapsed}>
|
||||
<SidebarItem
|
||||
to="/dashboard/payments"
|
||||
@@ -258,7 +294,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
)}
|
||||
|
||||
{/* Extend Section - Automations */}
|
||||
{canViewAdminPages && (
|
||||
{hasPermission('can_access_automations') && (
|
||||
<SidebarSection title={t('nav.sections.extend', 'Extend')} isCollapsed={isCollapsed}>
|
||||
<SidebarItem
|
||||
to="/dashboard/automations/my-automations"
|
||||
|
||||
Reference in New Issue
Block a user