feat: Add comprehensive permissions to BusinessEditModal
Frontend Changes: - Add all 5 platform permissions to BusinessEditModal (matching TenantInviteModal) - Manage OAuth Credentials - Accept Online Payments (Stripe Connect) - Use Custom Domain - Remove Branding (White Label) - API Access - Add "Coming Soon" feature limits section with 11 future capabilities - Video conferencing - Event types limits (unlimited or custom) - Calendar connections limits (unlimited or custom) - External API connections - Repeated/recurring events - 2FA requirement - System logs download - Data deletion - Masked phone numbers - POS system integration - Mobile app access - Update TypeScript interfaces to include all permission fields - PlatformBusiness: Add 4 new required boolean fields - PlatformBusinessUpdate: Add 4 new optional boolean fields Backend Changes: - Update TenantUpdateSerializer to accept all 5 permission fields - can_manage_oauth_credentials - can_accept_payments - can_use_custom_domain - can_white_label - can_api_access UI Improvements: - All permissions displayed with toggle switches and descriptions - Purple theme for permission toggles - Gray card backgrounds for visual separation - "Coming Soon" badge with yellow styling - Disabled state (opacity-50) for future features - Proper spacing and layout consistency Result: - BusinessEditModal now has complete feature parity with TenantInviteModal - Platform admins can view and modify all current permissions - Clear visibility into planned features 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,10 @@ export interface PlatformBusiness {
|
||||
phone?: string;
|
||||
// Platform permissions
|
||||
can_manage_oauth_credentials: boolean;
|
||||
can_accept_payments: boolean;
|
||||
can_use_custom_domain: boolean;
|
||||
can_white_label: boolean;
|
||||
can_api_access: boolean;
|
||||
}
|
||||
|
||||
export interface PlatformBusinessUpdate {
|
||||
@@ -37,6 +41,10 @@ export interface PlatformBusinessUpdate {
|
||||
max_users?: number;
|
||||
max_resources?: number;
|
||||
can_manage_oauth_credentials?: boolean;
|
||||
can_accept_payments?: boolean;
|
||||
can_use_custom_domain?: boolean;
|
||||
can_white_label?: boolean;
|
||||
can_api_access?: boolean;
|
||||
}
|
||||
|
||||
export interface PlatformBusinessCreate {
|
||||
|
||||
@@ -19,6 +19,24 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
max_users: 5,
|
||||
max_resources: 10,
|
||||
can_manage_oauth_credentials: false,
|
||||
can_accept_payments: false,
|
||||
can_use_custom_domain: false,
|
||||
can_white_label: false,
|
||||
can_api_access: false,
|
||||
// New feature limits (not yet implemented)
|
||||
limits: {
|
||||
can_add_video_conferencing: false,
|
||||
max_event_types: null as number | null,
|
||||
max_calendars_connected: null as number | null,
|
||||
can_connect_to_api: false,
|
||||
can_book_repeated_events: false,
|
||||
can_require_2fa: false,
|
||||
can_download_logs: false,
|
||||
can_delete_data: false,
|
||||
can_use_masked_phone_numbers: false,
|
||||
can_use_pos: false,
|
||||
can_use_mobile_app: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Update form when business changes
|
||||
@@ -31,6 +49,23 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
max_users: business.max_users || 5,
|
||||
max_resources: business.max_resources || 10,
|
||||
can_manage_oauth_credentials: business.can_manage_oauth_credentials || false,
|
||||
can_accept_payments: business.can_accept_payments || false,
|
||||
can_use_custom_domain: business.can_use_custom_domain || false,
|
||||
can_white_label: business.can_white_label || false,
|
||||
can_api_access: business.can_api_access || false,
|
||||
limits: {
|
||||
can_add_video_conferencing: false,
|
||||
max_event_types: null,
|
||||
max_calendars_connected: null,
|
||||
can_connect_to_api: false,
|
||||
can_book_repeated_events: false,
|
||||
can_require_2fa: false,
|
||||
can_download_logs: false,
|
||||
can_delete_data: false,
|
||||
can_use_masked_phone_numbers: false,
|
||||
can_use_pos: false,
|
||||
can_use_mobile_app: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [business]);
|
||||
@@ -156,6 +191,7 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
Platform Permissions
|
||||
</h4>
|
||||
|
||||
<div className="space-y-3">
|
||||
{/* Can Manage OAuth Credentials */}
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg">
|
||||
<div>
|
||||
@@ -175,6 +211,245 @@ const BusinessEditModal: React.FC<BusinessEditModalProps> = ({ business, isOpen,
|
||||
<span className={`${editForm.can_manage_oauth_credentials ? 'translate-x-5' : 'translate-x-0'} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Can Accept Payments */}
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Accept Online Payments
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Enable Stripe Connect for payment processing
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditForm({ ...editForm, can_accept_payments: !editForm.can_accept_payments })}
|
||||
className={`${editForm.can_accept_payments ? 'bg-purple-600' : 'bg-gray-300 dark:bg-gray-600'} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-500`}
|
||||
role="switch"
|
||||
>
|
||||
<span className={`${editForm.can_accept_payments ? 'translate-x-5' : 'translate-x-0'} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Can Use Custom Domain */}
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Use Custom Domain
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Allow custom domain configuration
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditForm({ ...editForm, can_use_custom_domain: !editForm.can_use_custom_domain })}
|
||||
className={`${editForm.can_use_custom_domain ? 'bg-purple-600' : 'bg-gray-300 dark:bg-gray-600'} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-500`}
|
||||
role="switch"
|
||||
>
|
||||
<span className={`${editForm.can_use_custom_domain ? 'translate-x-5' : 'translate-x-0'} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Can White Label */}
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Remove Branding
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Allow removal of SmoothSchedule branding
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditForm({ ...editForm, can_white_label: !editForm.can_white_label })}
|
||||
className={`${editForm.can_white_label ? 'bg-purple-600' : 'bg-gray-300 dark:bg-gray-600'} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-500`}
|
||||
role="switch"
|
||||
>
|
||||
<span className={`${editForm.can_white_label ? 'translate-x-5' : 'translate-x-0'} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Can API Access */}
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
API Access
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Enable API access for integrations
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditForm({ ...editForm, can_api_access: !editForm.can_api_access })}
|
||||
className={`${editForm.can_api_access ? 'bg-purple-600' : 'bg-gray-300 dark:bg-gray-600'} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-500`}
|
||||
role="switch"
|
||||
>
|
||||
<span className={`${editForm.can_api_access ? 'translate-x-5' : 'translate-x-0'} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out`} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature Limits (Not Yet Implemented) */}
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Feature Limits & Capabilities
|
||||
</label>
|
||||
<span className="text-xs px-2 py-1 bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 rounded-full">
|
||||
Coming Soon
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-3 opacity-50">
|
||||
{/* Video Conferencing */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_add_video_conferencing}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can add video conferencing to events
|
||||
</label>
|
||||
|
||||
{/* Event Types Limit */}
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.max_event_types === null}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 mt-1 cursor-not-allowed"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Unlimited event types</span>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
disabled
|
||||
value={editForm.limits.max_event_types || ''}
|
||||
placeholder="Or set a limit"
|
||||
className="mt-1 w-32 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Calendars Connected Limit */}
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.max_calendars_connected === null}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 mt-1 cursor-not-allowed"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Unlimited calendar connections</span>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
disabled
|
||||
value={editForm.limits.max_calendars_connected || ''}
|
||||
placeholder="Or set a limit"
|
||||
className="mt-1 w-32 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* API Access */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_connect_to_api}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can connect to external APIs
|
||||
</label>
|
||||
|
||||
{/* Repeated Events */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_book_repeated_events}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can book repeated/recurring events
|
||||
</label>
|
||||
|
||||
{/* 2FA */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_require_2fa}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can require 2FA for users
|
||||
</label>
|
||||
|
||||
{/* Download Logs */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_download_logs}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can download system logs
|
||||
</label>
|
||||
|
||||
{/* Delete Data */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_delete_data}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can permanently delete data
|
||||
</label>
|
||||
|
||||
{/* Masked Phone Numbers */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_use_masked_phone_numbers}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can use masked phone numbers for privacy
|
||||
</label>
|
||||
|
||||
{/* POS Integration */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_use_pos}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can use Point of Sale (POS) system
|
||||
</label>
|
||||
|
||||
{/* Mobile App */}
|
||||
<label className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editForm.limits.can_use_mobile_app}
|
||||
disabled
|
||||
className="rounded border-gray-300 dark:border-gray-600 cursor-not-allowed"
|
||||
/>
|
||||
Can use mobile app
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ class TenantUpdateSerializer(serializers.ModelSerializer):
|
||||
'max_users', 'max_resources',
|
||||
# Platform permissions
|
||||
'can_manage_oauth_credentials',
|
||||
'can_accept_payments',
|
||||
'can_use_custom_domain',
|
||||
'can_white_label',
|
||||
'can_api_access',
|
||||
]
|
||||
read_only_fields = ['id']
|
||||
|
||||
|
||||
Reference in New Issue
Block a user