geocrop-platform./apps/nextgen/superadmin/client/src/App.tsx

133 lines
4.5 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { Sidebar } from './components/Sidebar';
import { ToastContainer } from './components/ui/Toast';
import { TelemetryLogsView } from './components/TelemetryLogsView';
import { InstancesPanel } from './components/InstancesPanel';
import { SUPERADMIN_API_BASE, useSuperAdminAuth } from './store/auth';
import { useToasts } from './hooks/useToasts';
import { useDashboardData } from './hooks/useDashboardData';
import { Dashboard } from './pages/Dashboard';
import { TenantManagement } from './pages/TenantManagement';
import { LicensingEngine } from './pages/LicensingEngine';
import { SupabaseCloudLink } from './pages/SupabaseCloudLink';
const api = axios.create({ baseURL: SUPERADMIN_API_BASE });
api.interceptors.request.use((config) => {
let token: string | null = useSuperAdminAuth.getState().token;
if (!token && typeof window !== 'undefined') {
try {
const raw = window.localStorage.getItem('superadmin_auth');
if (raw) {
const parsed = JSON.parse(raw);
token = parsed?.state?.token || null;
}
} catch {
token = null;
}
}
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
api.interceptors.response.use(
(response) => response,
(error) => {
const status = error?.response?.status;
const url = error?.config?.url || '';
const isPublic = url.includes('/auth/login') || url.includes('/api/health') || url.includes('/v1/auth/jwks');
if (status === 401 && !isPublic) {
try {
const current = useSuperAdminAuth.getState().token;
if (current) {
useSuperAdminAuth.getState().logout();
if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
window.location.assign('/login');
}
}
} catch {
/* ignore */
}
}
return Promise.reject(error);
}
);
export default function App() {
const navigate = useNavigate();
const token = useSuperAdminAuth((state) => state.token);
const logout = useSuperAdminAuth((state) => state.logout);
const [currentTab, setCurrentTab] = useState('dashboard');
const { toasts, addToast, removeToast } = useToasts();
const { metrics, tenants, instances, loading, refetch } = useDashboardData(api, {
onError: (msg) => addToast('error', 'Telemetry Error', msg),
});
useEffect(() => {
if (!token) {
navigate('/login', { replace: true });
}
}, [token, navigate]);
const handleLogout = () => {
logout();
navigate('/login', { replace: true });
};
if (!token) return null;
const navigateToInstances = () => setCurrentTab('instances');
return (
<div className="flex h-screen bg-background text-text overflow-hidden">
<ToastContainer toasts={toasts} onClose={removeToast} />
<Sidebar
currentTab={currentTab}
onTabChange={setCurrentTab}
onLogout={handleLogout}
/>
<main className="flex-1 flex flex-col min-w-0 overflow-y-auto custom-scrollbar p-6 sm:p-10 pt-20 lg:pt-10">
<div className="h-1.5 w-full ng-gradient rounded-xl mb-6 opacity-90" />
{currentTab === 'telemetry' ? (
<TelemetryLogsView api={api} />
) : currentTab === 'instances' ? (
<InstancesPanel
api={api}
instances={instances}
onRefresh={refetch}
onSpawnSuccess={(msg) => addToast('success', 'School Instance Spawned', msg)}
onSpawnError={(msg) => addToast('error', 'Instance Spawning Failed', msg)}
onStopSuccess={(msg) => addToast('info', 'Instance Stopped', msg)}
onStopError={(msg) => addToast('error', 'Stop Failed', msg)}
/>
) : currentTab === 'licenses' ? (
<LicensingEngine tenants={tenants} metrics={metrics} onRefresh={refetch} />
) : currentTab === 'supabase' ? (
<SupabaseCloudLink api={api} tenants={tenants} />
) : currentTab === 'dashboard' ? (
<Dashboard
metrics={metrics}
tenants={tenants}
instances={instances}
loading={loading}
onRefresh={refetch}
onNavigateToInstances={navigateToInstances}
/>
) : (
<TenantManagement
api={api}
tenants={tenants}
onRefresh={refetch}
addToast={addToast}
onSpawnInstanceFromProfile={() => setCurrentTab('instances')}
/>
)}
</main>
</div>
);
}