Refactor EditTenantModal into EditTenant page with multi-step form for tenant details, contact information, and settings. Enhance validation schemas and integrate file upload functionality for branding assets. Update routing to navigate to the new EditTenant page instead of using a modal. Remove legacy modal handling from Tenants component.

This commit is contained in:
Yashwin 2026-01-28 18:43:50 +05:30
parent 2ced49373c
commit c6ee8c7032
6 changed files with 2140 additions and 265 deletions

File diff suppressed because it is too large Load Diff

1148
src/pages/EditTenant.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ import {
ActionDropdown, ActionDropdown,
// NewTenantModal, // Commented out - using wizard instead // NewTenantModal, // Commented out - using wizard instead
// ViewTenantModal, // Commented out - using details page instead // ViewTenantModal, // Commented out - using details page instead
EditTenantModal, // EditTenantModal, // Commented out - using edit page instead
DeleteConfirmationModal, DeleteConfirmationModal,
DataTable, DataTable,
Pagination, Pagination,
@ -18,7 +18,6 @@ import { Plus, Download, ArrowUpDown } from 'lucide-react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { tenantService } from '@/services/tenant-service'; import { tenantService } from '@/services/tenant-service';
import type { Tenant } from '@/types/tenant'; import type { Tenant } from '@/types/tenant';
import { showToast } from '@/utils/toast';
// Helper function to get tenant initials // Helper function to get tenant initials
const getTenantInitials = (name: string): string => { const getTenantInitials = (name: string): string => {
@ -86,11 +85,10 @@ const Tenants = (): ReactElement => {
// View, Edit, Delete modals // View, Edit, Delete modals
// const [viewModalOpen, setViewModalOpen] = useState<boolean>(false); // Commented out - using details page instead // const [viewModalOpen, setViewModalOpen] = useState<boolean>(false); // Commented out - using details page instead
const [editModalOpen, setEditModalOpen] = useState<boolean>(false); // const [editModalOpen, setEditModalOpen] = useState<boolean>(false); // Commented out - using edit page instead
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false); const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
const [selectedTenantId, setSelectedTenantId] = useState<string | null>(null); const [selectedTenantId, setSelectedTenantId] = useState<string | null>(null);
const [selectedTenantName, setSelectedTenantName] = useState<string>(''); const [selectedTenantName, setSelectedTenantName] = useState<string>('');
const [isUpdating, setIsUpdating] = useState<boolean>(false);
const [isDeleting, setIsDeleting] = useState<boolean>(false); const [isDeleting, setIsDeleting] = useState<boolean>(false);
const fetchTenants = async ( const fetchTenants = async (
@ -152,40 +150,11 @@ const Tenants = (): ReactElement => {
}; };
// Edit tenant handler // Edit tenant handler
const handleEditTenant = (tenantId: string, tenantName: string): void => { const handleEditTenant = (tenantId: string): void => {
setSelectedTenantId(tenantId); navigate(`/tenants/${tenantId}/edit`);
setSelectedTenantName(tenantName);
setEditModalOpen(true);
}; };
// Update tenant handler // Update tenant handler - removed, now handled in EditTenant page
const handleUpdateTenant = async (
id: string,
data: {
name: string;
slug: string;
status: 'active' | 'suspended' | 'deleted';
settings?: Record<string, unknown> | null;
subscription_tier?: string | null;
max_users?: number | null;
max_modules?: number | null;
}
): Promise<void> => {
try {
setIsUpdating(true);
const response = await tenantService.update(id, data);
const message = response.message || `Tenant updated successfully`;
const description = response.message ? undefined : `${data.name} has been updated`;
showToast.success(message, description);
setEditModalOpen(false);
setSelectedTenantId(null);
await fetchTenants(currentPage, limit, statusFilter, orderBy);
} catch (err: any) {
throw err; // Let the modal handle the error display
} finally {
setIsUpdating(false);
}
};
// Delete tenant handler // Delete tenant handler
const handleDeleteTenant = (tenantId: string, tenantName: string): void => { const handleDeleteTenant = (tenantId: string, tenantName: string): void => {
@ -212,12 +181,6 @@ const Tenants = (): ReactElement => {
} }
}; };
// Load tenant for view/edit
const loadTenant = async (id: string): Promise<Tenant> => {
const response = await tenantService.getById(id);
return response.data;
};
// Define table columns // Define table columns
const columns: Column<Tenant>[] = [ const columns: Column<Tenant>[] = [
{ {
@ -291,7 +254,7 @@ const Tenants = (): ReactElement => {
<div className="flex justify-end"> <div className="flex justify-end">
<ActionDropdown <ActionDropdown
onView={() => handleViewTenant(tenant.id)} onView={() => handleViewTenant(tenant.id)}
onEdit={() => handleEditTenant(tenant.id, tenant.name)} onEdit={() => handleEditTenant(tenant.id)}
onDelete={() => handleDeleteTenant(tenant.id, tenant.name)} onDelete={() => handleDeleteTenant(tenant.id, tenant.name)}
/> />
</div> </div>
@ -320,7 +283,7 @@ const Tenants = (): ReactElement => {
</div> </div>
<ActionDropdown <ActionDropdown
onView={() => handleViewTenant(tenant.id)} onView={() => handleViewTenant(tenant.id)}
onEdit={() => handleEditTenant(tenant.id, tenant.name)} onEdit={() => handleEditTenant(tenant.id)}
onDelete={() => handleDeleteTenant(tenant.id, tenant.name)} onDelete={() => handleDeleteTenant(tenant.id, tenant.name)}
/> />
</div> </div>
@ -489,19 +452,7 @@ const Tenants = (): ReactElement => {
onLoadTenant={loadTenant} onLoadTenant={loadTenant}
/> */} /> */}
{/* Edit Tenant Modal */} {/* Edit Tenant Modal - Removed, using edit page instead */}
<EditTenantModal
isOpen={editModalOpen}
onClose={() => {
setEditModalOpen(false);
setSelectedTenantId(null);
setSelectedTenantName('');
}}
tenantId={selectedTenantId}
onLoadTenant={loadTenant}
onSubmit={handleUpdateTenant}
isLoading={isUpdating}
/>
{/* Delete Confirmation Modal */} {/* Delete Confirmation Modal */}
<DeleteConfirmationModal <DeleteConfirmationModal

View File

@ -1,6 +1,7 @@
import Dashboard from '@/pages/Dashboard'; import Dashboard from '@/pages/Dashboard';
import Tenants from '@/pages/Tenants'; import Tenants from '@/pages/Tenants';
import CreateTenantWizard from '@/pages/CreateTenantWizard'; import CreateTenantWizard from '@/pages/CreateTenantWizard';
import EditTenant from '@/pages/EditTenant';
import TenantDetails from '@/pages/TenantDetails'; import TenantDetails from '@/pages/TenantDetails';
import Users from '@/pages/Users'; import Users from '@/pages/Users';
import Roles from '@/pages/Roles'; import Roles from '@/pages/Roles';
@ -27,6 +28,10 @@ export const superAdminRoutes: RouteConfig[] = [
path: '/tenants/create-wizard', path: '/tenants/create-wizard',
element: <CreateTenantWizard />, element: <CreateTenantWizard />,
}, },
{
path: '/tenants/:id/edit',
element: <EditTenant />,
},
{ {
path: '/tenants/:id', path: '/tenants/:id',
element: <TenantDetails />, element: <TenantDetails />,

View File

@ -42,10 +42,12 @@ export interface UpdateTenantRequest {
name: string; name: string;
slug: string; slug: string;
status: 'active' | 'suspended' | 'deleted'; status: 'active' | 'suspended' | 'deleted';
domain?: string | null;
settings?: Record<string, unknown> | null; settings?: Record<string, unknown> | null;
subscription_tier?: string | null; subscription_tier?: string | null;
max_users?: number | null; max_users?: number | null;
max_modules?: number | null; max_modules?: number | null;
module_ids?: string[] | null;
} }
export interface UpdateTenantResponse { export interface UpdateTenantResponse {

View File

@ -19,6 +19,28 @@ export interface TenantUser {
status: string; status: string;
} }
export interface TenantAdmin {
id: string;
email: string;
first_name: string;
last_name: string;
contact_phone?: string | null;
address_line1?: string | null;
address_line2?: string | null;
city?: string | null;
state?: string | null;
postal_code?: string | null;
country?: string | null;
}
export interface TenantBranding {
logo_file_path?: string | null;
favicon_file_path?: string | null;
primary_color?: string | null;
secondary_color?: string | null;
accent_color?: string | null;
}
export interface Tenant { export interface Tenant {
id: string; id: string;
name: string; name: string;
@ -31,9 +53,15 @@ export interface Tenant {
domain?: string | null; domain?: string | null;
enable_sso?: boolean; enable_sso?: boolean;
enable_2fa?: boolean; enable_2fa?: boolean;
logo_file_path?: string | null;
favicon_file_path?: string | null;
primary_color?: string | null;
secondary_color?: string | null;
accent_color?: string | null;
modules?: string[]; // Array of module IDs (legacy, for backward compatibility) modules?: string[]; // Array of module IDs (legacy, for backward compatibility)
assignedModules?: AssignedModule[]; // Array of assigned modules with full details assignedModules?: AssignedModule[]; // Array of assigned modules with full details
users?: TenantUser[]; // Array of tenant users users?: TenantUser[]; // Array of tenant users
tenant_admin?: TenantAdmin; // Tenant admin user details
created_at: string; created_at: string;
updated_at: string; updated_at: string;
} }