feat: Email templates, bulk delete, communication credits, plan features
- Add email template presets for Browse Templates tab (12 templates) - Add bulk selection and deletion for My Templates tab - Add communication credits system with Twilio integration - Add payment views for credit purchases and auto-reload - Add SMS reminder and masked calling plan permissions - Fix appointment status mapping (frontend/backend mismatch) - Clear masquerade stack on login/logout for session hygiene - Update platform settings with credit configuration - Add new migrations for Twilio and Stripe payment fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -783,6 +783,7 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
can_create_plugins: false,
|
||||
can_white_label: false,
|
||||
can_api_access: false,
|
||||
can_use_masked_phone_numbers: false,
|
||||
},
|
||||
transaction_fee_percent: plan?.transaction_fee_percent
|
||||
? parseFloat(plan.transaction_fee_percent)
|
||||
@@ -790,6 +791,17 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
transaction_fee_fixed: plan?.transaction_fee_fixed
|
||||
? parseFloat(plan.transaction_fee_fixed)
|
||||
: 0,
|
||||
// Communication pricing
|
||||
sms_enabled: plan?.sms_enabled ?? false,
|
||||
sms_price_per_message_cents: plan?.sms_price_per_message_cents ?? 3,
|
||||
masked_calling_enabled: plan?.masked_calling_enabled ?? false,
|
||||
masked_calling_price_per_minute_cents: plan?.masked_calling_price_per_minute_cents ?? 5,
|
||||
proxy_number_enabled: plan?.proxy_number_enabled ?? false,
|
||||
proxy_number_monthly_fee_cents: plan?.proxy_number_monthly_fee_cents ?? 200,
|
||||
// Default credit settings
|
||||
default_auto_reload_enabled: plan?.default_auto_reload_enabled ?? false,
|
||||
default_auto_reload_threshold_cents: plan?.default_auto_reload_threshold_cents ?? 1000,
|
||||
default_auto_reload_amount_cents: plan?.default_auto_reload_amount_cents ?? 2500,
|
||||
is_active: plan?.is_active ?? true,
|
||||
is_public: plan?.is_public ?? true,
|
||||
is_most_popular: plan?.is_most_popular ?? false,
|
||||
@@ -888,8 +900,8 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
>
|
||||
<option value="base">Base Tier</option>
|
||||
<option value="addon">Add-on</option>
|
||||
<option value="base" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">Base Tier</option>
|
||||
<option value="addon" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">Add-on</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -955,14 +967,51 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
onChange={(e) => setFormData((prev) => ({ ...prev, business_tier: e.target.value }))}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
>
|
||||
<option value="">None</option>
|
||||
<option value="Free">Free</option>
|
||||
<option value="Professional">Professional</option>
|
||||
<option value="Business">Business</option>
|
||||
<option value="Enterprise">Enterprise</option>
|
||||
<option value="" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">None (Add-on)</option>
|
||||
<option value="Free" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">Free</option>
|
||||
<option value="Starter" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">Starter</option>
|
||||
<option value="Professional" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">Professional</option>
|
||||
<option value="Business" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">Business</option>
|
||||
<option value="Enterprise" className="bg-white dark:bg-gray-700 text-gray-900 dark:text-white">Enterprise</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Trial Days
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={formData.limits?.trial_days ?? 0}
|
||||
onChange={(e) => setFormData((prev) => ({
|
||||
...prev,
|
||||
limits: { ...prev.limits, trial_days: parseInt(e.target.value) || 0 }
|
||||
}))}
|
||||
placeholder="0"
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">Days of free trial</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Display Order
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={formData.limits?.display_order ?? 0}
|
||||
onChange={(e) => setFormData((prev) => ({
|
||||
...prev,
|
||||
limits: { ...prev.limits, display_order: parseInt(e.target.value) || 0 }
|
||||
}))}
|
||||
placeholder="0"
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">Order on pricing page</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
@@ -1001,20 +1050,220 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Communication Pricing */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white border-b pb-2 dark:border-gray-700">
|
||||
Communication Pricing
|
||||
</h3>
|
||||
|
||||
{/* SMS Settings */}
|
||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-white">SMS Reminders</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Allow businesses on this tier to send SMS reminders</p>
|
||||
</div>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.sms_enabled || false}
|
||||
onChange={(e) => setFormData((prev) => ({ ...prev, sms_enabled: e.target.checked }))}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
{formData.sms_enabled && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Price per SMS (cents)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={formData.sms_price_per_message_cents || 0}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
sms_price_per_message_cents: parseInt(e.target.value) || 0,
|
||||
}))
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Current: ${((formData.sms_price_per_message_cents || 0) / 100).toFixed(2)} per message
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Masked Calling Settings */}
|
||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-white">Masked Calling</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Allow anonymous calls between customers and staff</p>
|
||||
</div>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.masked_calling_enabled || false}
|
||||
onChange={(e) => setFormData((prev) => ({ ...prev, masked_calling_enabled: e.target.checked }))}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
{formData.masked_calling_enabled && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Price per minute (cents)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={formData.masked_calling_price_per_minute_cents || 0}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
masked_calling_price_per_minute_cents: parseInt(e.target.value) || 0,
|
||||
}))
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Current: ${((formData.masked_calling_price_per_minute_cents || 0) / 100).toFixed(2)} per minute
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Proxy Phone Number Settings */}
|
||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-white">Proxy Phone Numbers</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Dedicated phone numbers for masked communication</p>
|
||||
</div>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.proxy_number_enabled || false}
|
||||
onChange={(e) => setFormData((prev) => ({ ...prev, proxy_number_enabled: e.target.checked }))}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
{formData.proxy_number_enabled && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Monthly fee per number (cents)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={formData.proxy_number_monthly_fee_cents || 0}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
proxy_number_monthly_fee_cents: parseInt(e.target.value) || 0,
|
||||
}))
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Current: ${((formData.proxy_number_monthly_fee_cents || 0) / 100).toFixed(2)} per month
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Default Credit Settings */}
|
||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-white mb-3">Default Credit Settings</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4">
|
||||
Default auto-reload settings for new businesses on this tier
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.default_auto_reload_enabled || false}
|
||||
onChange={(e) => setFormData((prev) => ({ ...prev, default_auto_reload_enabled: e.target.checked }))}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Auto-reload enabled by default</span>
|
||||
</label>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Reload threshold (cents)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="100"
|
||||
value={formData.default_auto_reload_threshold_cents || 0}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
default_auto_reload_threshold_cents: parseInt(e.target.value) || 0,
|
||||
}))
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Reload when balance falls below ${((formData.default_auto_reload_threshold_cents || 0) / 100).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Reload amount (cents)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="100"
|
||||
value={formData.default_auto_reload_amount_cents || 0}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
default_auto_reload_amount_cents: parseInt(e.target.value) || 0,
|
||||
}))
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Add ${((formData.default_auto_reload_amount_cents || 0) / 100).toFixed(2)} to balance
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Limits Configuration */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white border-b pb-2 dark:border-gray-700">
|
||||
Limits Configuration
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Use -1 for unlimited. These limits control what businesses on this plan can create.
|
||||
</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Max Users
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={formData.limits?.max_users || 0}
|
||||
min="-1"
|
||||
value={formData.limits?.max_users ?? 0}
|
||||
onChange={(e) => handleLimitChange('max_users', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
@@ -1025,12 +1274,24 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={formData.limits?.max_resources || 0}
|
||||
min="-1"
|
||||
value={formData.limits?.max_resources ?? 0}
|
||||
onChange={(e) => handleLimitChange('max_resources', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Max Services
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="-1"
|
||||
value={formData.limits?.max_services ?? 0}
|
||||
onChange={(e) => handleLimitChange('max_services', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Max Appointments / Month
|
||||
@@ -1038,19 +1299,31 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
<input
|
||||
type="number"
|
||||
min="-1"
|
||||
value={formData.limits?.max_appointments || 0}
|
||||
value={formData.limits?.max_appointments ?? 0}
|
||||
onChange={(e) => handleLimitChange('max_appointments', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Max Email Templates
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="-1"
|
||||
value={formData.limits?.max_email_templates ?? 0}
|
||||
onChange={(e) => handleLimitChange('max_email_templates', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Max Automated Tasks
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={formData.limits?.max_automated_tasks || 0}
|
||||
min="-1"
|
||||
value={formData.limits?.max_automated_tasks ?? 0}
|
||||
onChange={(e) => handleLimitChange('max_automated_tasks', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
@@ -1063,79 +1336,205 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white border-b pb-2 dark:border-gray-700">
|
||||
Features & Permissions
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_accept_payments || false}
|
||||
onChange={(e) => handlePermissionChange('can_accept_payments', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Stripe Payments</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.sms_reminders || false}
|
||||
onChange={(e) => handlePermissionChange('sms_reminders', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">SMS Reminders</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.advanced_reporting || false}
|
||||
onChange={(e) => handlePermissionChange('advanced_reporting', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Advanced Reporting</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.priority_support || false}
|
||||
onChange={(e) => handlePermissionChange('priority_support', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Priority Email Support</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_use_custom_domain || false}
|
||||
onChange={(e) => handlePermissionChange('can_use_custom_domain', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Custom Domains</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_create_plugins || false}
|
||||
onChange={(e) => handlePermissionChange('can_create_plugins', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Create Plugins</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_white_label || false}
|
||||
onChange={(e) => handlePermissionChange('can_white_label', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">White Labelling</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_api_access || false}
|
||||
onChange={(e) => handlePermissionChange('can_api_access', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">API Access</span>
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Control which features are available to businesses on this plan.
|
||||
</p>
|
||||
|
||||
{/* Payments & Revenue */}
|
||||
<div>
|
||||
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">Payments & Revenue</h4>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_accept_payments || false}
|
||||
onChange={(e) => handlePermissionChange('can_accept_payments', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Online Payments</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_process_refunds || false}
|
||||
onChange={(e) => handlePermissionChange('can_process_refunds', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Process Refunds</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_create_packages || false}
|
||||
onChange={(e) => handlePermissionChange('can_create_packages', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Service Packages</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Communication */}
|
||||
<div>
|
||||
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">Communication</h4>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.sms_reminders || false}
|
||||
onChange={(e) => handlePermissionChange('sms_reminders', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">SMS Reminders</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_use_masked_phone_numbers || false}
|
||||
onChange={(e) => handlePermissionChange('can_use_masked_phone_numbers', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Masked Calling</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_use_email_templates || false}
|
||||
onChange={(e) => handlePermissionChange('can_use_email_templates', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Email Templates</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Customization */}
|
||||
<div>
|
||||
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">Customization</h4>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_customize_booking_page || false}
|
||||
onChange={(e) => handlePermissionChange('can_customize_booking_page', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Custom Booking Page</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_use_custom_domain || false}
|
||||
onChange={(e) => handlePermissionChange('can_use_custom_domain', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Custom Domains</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_white_label || false}
|
||||
onChange={(e) => handlePermissionChange('can_white_label', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">White Labelling</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Advanced Features */}
|
||||
<div>
|
||||
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">Advanced Features</h4>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.advanced_reporting || false}
|
||||
onChange={(e) => handlePermissionChange('advanced_reporting', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Advanced Analytics</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_api_access || false}
|
||||
onChange={(e) => handlePermissionChange('can_api_access', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">API Access</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_create_plugins || false}
|
||||
onChange={(e) => handlePermissionChange('can_create_plugins', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Create Plugins</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_export_data || false}
|
||||
onChange={(e) => handlePermissionChange('can_export_data', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Data Export</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_use_webhooks || false}
|
||||
onChange={(e) => handlePermissionChange('can_use_webhooks', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Webhooks</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.calendar_sync || false}
|
||||
onChange={(e) => handlePermissionChange('calendar_sync', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Calendar Sync</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Support & Enterprise */}
|
||||
<div>
|
||||
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">Support & Enterprise</h4>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.priority_support || false}
|
||||
onChange={(e) => handlePermissionChange('priority_support', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Priority Support</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.dedicated_support || false}
|
||||
onChange={(e) => handlePermissionChange('dedicated_support', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Dedicated Support</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.sso_enabled || false}
|
||||
onChange={(e) => handlePermissionChange('sso_enabled', e.target.checked)}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">SSO / SAML</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user