feat: Dashboard redesign, plan permissions, and help docs improvements
Major updates including: - Customizable dashboard with drag-and-drop widget grid layout - Plan-based feature locking for plugins and tasks - Comprehensive help documentation updates across all pages - Plugin seeding in deployment process for all tenants - Permission synchronization system for subscription plans - QuotaOverageModal component and enhanced UX flows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ import {
|
||||
useUpdateSubscriptionPlan,
|
||||
useDeleteSubscriptionPlan,
|
||||
useSyncPlansWithStripe,
|
||||
useSyncPlanToTenants,
|
||||
SubscriptionPlan,
|
||||
SubscriptionPlanCreate,
|
||||
} from '../../hooks/usePlatformSettings';
|
||||
@@ -527,9 +528,12 @@ const TiersSettingsTab: React.FC = () => {
|
||||
const updatePlanMutation = useUpdateSubscriptionPlan();
|
||||
const deletePlanMutation = useDeleteSubscriptionPlan();
|
||||
const syncMutation = useSyncPlansWithStripe();
|
||||
const syncTenantsMutation = useSyncPlanToTenants();
|
||||
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [editingPlan, setEditingPlan] = useState<SubscriptionPlan | null>(null);
|
||||
const [showSyncConfirmModal, setShowSyncConfirmModal] = useState(false);
|
||||
const [savedPlanForSync, setSavedPlanForSync] = useState<SubscriptionPlan | null>(null);
|
||||
|
||||
const handleCreatePlan = () => {
|
||||
setEditingPlan(null);
|
||||
@@ -550,6 +554,9 @@ const TiersSettingsTab: React.FC = () => {
|
||||
const handleSavePlan = async (data: SubscriptionPlanCreate) => {
|
||||
if (editingPlan) {
|
||||
await updatePlanMutation.mutateAsync({ id: editingPlan.id, ...data });
|
||||
// After updating an existing plan, ask if they want to sync to tenants
|
||||
setSavedPlanForSync(editingPlan);
|
||||
setShowSyncConfirmModal(true);
|
||||
} else {
|
||||
await createPlanMutation.mutateAsync(data);
|
||||
}
|
||||
@@ -557,6 +564,19 @@ const TiersSettingsTab: React.FC = () => {
|
||||
setEditingPlan(null);
|
||||
};
|
||||
|
||||
const handleSyncConfirm = async () => {
|
||||
if (savedPlanForSync) {
|
||||
await syncTenantsMutation.mutateAsync(savedPlanForSync.id);
|
||||
}
|
||||
setShowSyncConfirmModal(false);
|
||||
setSavedPlanForSync(null);
|
||||
};
|
||||
|
||||
const handleSyncCancel = () => {
|
||||
setShowSyncConfirmModal(false);
|
||||
setSavedPlanForSync(null);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
@@ -672,6 +692,48 @@ const TiersSettingsTab: React.FC = () => {
|
||||
isLoading={createPlanMutation.isPending || updatePlanMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sync Confirmation Modal */}
|
||||
{showSyncConfirmModal && savedPlanForSync && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md mx-4">
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Update All Tenants?
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
Do you want to sync the updated settings to all tenants currently on the "{savedPlanForSync.name}" plan?
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-500 mt-2">
|
||||
This will update permissions and limits for all businesses on this tier.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 px-6 py-4 bg-gray-50 dark:bg-gray-700/50 rounded-b-lg">
|
||||
<button
|
||||
onClick={handleSyncCancel}
|
||||
disabled={syncTenantsMutation.isPending}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50"
|
||||
>
|
||||
No, Skip
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSyncConfirm}
|
||||
disabled={syncTenantsMutation.isPending}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{syncTenantsMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Syncing...
|
||||
</>
|
||||
) : (
|
||||
'Yes, Update All Tenants'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -780,6 +842,8 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
advanced_reporting: false,
|
||||
priority_support: false,
|
||||
can_use_custom_domain: false,
|
||||
can_use_plugins: false,
|
||||
can_use_tasks: false,
|
||||
can_create_plugins: false,
|
||||
can_white_label: false,
|
||||
can_api_access: false,
|
||||
@@ -1470,10 +1534,37 @@ const PlanModal: React.FC<PlanModalProps> = ({ plan, onSave, onClose, isLoading
|
||||
<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_use_plugins || false}
|
||||
onChange={(e) => {
|
||||
handlePermissionChange('can_use_plugins', e.target.checked);
|
||||
// If disabling plugins, also disable dependent permissions
|
||||
if (!e.target.checked) {
|
||||
handlePermissionChange('can_use_tasks', false);
|
||||
handlePermissionChange('can_create_plugins', false);
|
||||
}
|
||||
}}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Use Plugins</span>
|
||||
</label>
|
||||
<label className={`flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer ${formData.permissions?.can_use_plugins ? 'hover:bg-gray-50 dark:hover:bg-gray-700/50' : 'opacity-50 cursor-not-allowed'}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_use_tasks || false}
|
||||
onChange={(e) => handlePermissionChange('can_use_tasks', e.target.checked)}
|
||||
disabled={!formData.permissions?.can_use_plugins}
|
||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Scheduled Tasks</span>
|
||||
</label>
|
||||
<label className={`flex items-center gap-2 p-2 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer ${formData.permissions?.can_use_plugins ? 'hover:bg-gray-50 dark:hover:bg-gray-700/50' : 'opacity-50 cursor-not-allowed'}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.permissions?.can_create_plugins || false}
|
||||
onChange={(e) => handlePermissionChange('can_create_plugins', e.target.checked)}
|
||||
disabled={!formData.permissions?.can_use_plugins}
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user