Files
saas_frontend/src/application/modules/admin/components/ModuleEnvironments.tsx
T

200 lines
8.6 KiB
TypeScript
Raw Normal View History

import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { ArrowLeft, Plus, Edit, Trash2, Check, X } from 'lucide-react';
import { adminModuleApi } from '../AdminModuleApi';
import type { ModuleEnvironment, Module } from '../AdminModuleTypes';
import EnvironmentForm from './EnvironmentForm';
2026-04-09 11:26:50 +05:30
import { CustomConfirmationModal } from '../../../../components/custom';
const ModuleEnvironments = () => {
const { moduleId } = useParams<{ moduleId: string }>();
const navigate = useNavigate();
const [module, setModule] = useState<Module | null>(null);
const [environments, setEnvironments] = useState<ModuleEnvironment[]>([]);
const [loading, setLoading] = useState(true);
const [showForm, setShowForm] = useState(false);
const [selectedEnv, setSelectedEnv] = useState<ModuleEnvironment | null>(null);
2026-04-09 11:26:50 +05:30
const [envToDelete, setEnvToDelete] = useState<ModuleEnvironment | null>(null);
const fetchData = async () => {
if (!moduleId) return;
2026-04-25 08:23:16 +03:00
try {
const [moduleData, envsData] = await Promise.all([
adminModuleApi.getModule(moduleId),
adminModuleApi.listEnvironments(moduleId)
]);
setModule(moduleData);
setEnvironments(envsData);
} catch (error) {
console.error('Failed to fetch data', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [moduleId]);
const handleSetDefault = async (envId: string) => {
if (!moduleId) return;
2026-04-25 08:23:16 +03:00
try {
await adminModuleApi.setDefaultEnvironment(moduleId, envId);
fetchData();
} catch (error) {
console.error('Failed to set default', error);
}
};
const handleDelete = async (env: ModuleEnvironment) => {
2026-04-09 11:26:50 +05:30
setEnvToDelete(env);
};
2026-04-09 11:26:50 +05:30
const confirmDelete = async () => {
if (!moduleId || !envToDelete) return;
try {
2026-04-09 11:26:50 +05:30
await adminModuleApi.deleteEnvironment(moduleId, envToDelete.id);
setEnvToDelete(null);
fetchData();
} catch (error: any) {
2026-04-09 11:26:50 +05:30
console.error('Failed to delete environment', error);
}
};
if (loading) {
return <div className="p-6">Loading...</div>;
}
return (
<div className="p-6 space-y-6">
<div className="flex items-center gap-4">
<button
onClick={() => navigate('/admin/modules')}
className="p-2 hover:bg-gray-100 rounded-lg"
>
<ArrowLeft size={20} />
</button>
<div className="flex-1">
<h1 className="text-3xl font-bold text-(--text-primary)">
{module?.module_name} - Environments
</h1>
<p className="text-(--text-secondary) mt-1">Configure environment-specific settings</p>
</div>
<button
onClick={() => { setSelectedEnv(null); setShowForm(true); }}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
<Plus size={20} />
Add Environment
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{environments.map((env) => (
<div key={env.id} className="bg-(--card-bg) border border-(--card-border) rounded-lg p-6 space-y-4">
<div className="flex items-start justify-between">
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold">{env.slug}</h3>
{env.is_default && (
<span className="px-2 py-1 bg-blue-100 text-blue-700 text-xs font-medium rounded">
Default
</span>
)}
{env.is_active ? (
<Check className="w-5 h-5 text-green-600" />
) : (
<X className="w-5 h-5 text-gray-400" />
)}
</div>
</div>
</div>
<div className="space-y-2 text-sm">
<div>
<span className="text-gray-500">Frontend:</span>
<p className="text-(--text-primary) font-mono text-xs break-all">{env.frontend_base_url}</p>
</div>
<div>
<span className="text-gray-500">Backend:</span>
<p className="text-(--text-primary) font-mono text-xs break-all">{env.backend_base_url}</p>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<span className="text-gray-500">Trust:</span>
<p className="text-(--text-primary)">{env.trust_type}</p>
</div>
<div>
<span className="text-gray-500">SSO Path:</span>
<p className="text-(--text-primary) font-mono text-xs">{env.sso_entry_path}</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 pt-2 border-t">
{!env.is_default && (
<button
onClick={() => handleSetDefault(env.id)}
className="flex-1 px-3 py-2 bg-green-50 text-green-600 rounded-lg hover:bg-green-100 text-sm"
>
Set Default
</button>
)}
<button
onClick={() => { setSelectedEnv(env); setShowForm(true); }}
className="flex-1 px-3 py-2 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 text-sm flex items-center justify-center gap-2"
>
<Edit size={16} />
Edit
</button>
<button
onClick={() => handleDelete(env)}
className="flex-1 px-3 py-2 bg-red-50 text-red-600 rounded-lg hover:bg-red-100 text-sm flex items-center justify-center gap-2"
>
<Trash2 size={16} />
Delete
</button>
</div>
</div>
))}
</div>
{environments.length === 0 && (
<div className="bg-(--card-bg) border border-(--card-border) rounded-lg p-12 text-center">
<p className="text-(--text-secondary) mb-4">No environments configured</p>
<button
onClick={() => { setSelectedEnv(null); setShowForm(true); }}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Add First Environment
</button>
</div>
)}
2026-04-09 11:26:50 +05:30
<CustomConfirmationModal
isOpen={!!envToDelete}
onClose={() => setEnvToDelete(null)}
onConfirm={confirmDelete}
title="Delete Environment"
description={`Are you sure you want to delete ${envToDelete?.slug} environment?`}
variant="danger"
/>
{showForm && moduleId && (
<EnvironmentForm
moduleId={moduleId}
environment={selectedEnv}
onClose={(saved) => {
setShowForm(false);
setSelectedEnv(null);
if (saved) fetchData();
}}
/>
)}
</div>
);
};
export default ModuleEnvironments;