good forst commit

This commit is contained in:
2026-06-09 02:02:40 +05:30
parent 801c1d7121
commit 249d759e6a
215 changed files with 15425 additions and 1240 deletions
+78 -13
View File
@@ -1,9 +1,19 @@
import { RouteManager } from './RouteManager';
import { GroupsTabs } from './GroupsTabs';
import { apiFetch } from '../_lib/api';
interface Group {
id: string;
name: string;
platform: string;
platformId: string;
isActive: boolean;
accountId: string | null;
tenantId: string | null;
}
interface SharedGroup extends Group {
sharedByTenantName: string;
}
interface Route {
@@ -14,27 +24,82 @@ interface Route {
targetGroup: { name: string };
}
async function fetchJson<T>(url: string): Promise<T | null> {
type FetchResult<T> = { ok: true; data: T } | { ok: false; status: number; error: string };
async function fetchJson<T>(path: string): Promise<FetchResult<T>> {
let res: Response;
try {
const res = await fetch(url, { cache: 'no-store' });
if (!res.ok) return null;
return res.json();
} catch {
return null;
res = await apiFetch(path);
} catch (err) {
return { ok: false, status: 0, error: `API unreachable: ${(err as Error).message}` };
}
if (!res.ok) {
const body = await res.text().catch(() => '');
return { ok: false, status: res.status, error: body.slice(0, 200) || res.statusText };
}
return { ok: true, data: (await res.json()) as T };
}
export default async function GroupsPage() {
const apiUrl = process.env.API_URL ?? 'http://localhost:3001';
const [groups, routes] = await Promise.all([
fetchJson<Group[]>(`${apiUrl}/groups`),
fetchJson<Route[]>(`${apiUrl}/routes`),
export default async function GroupsPage({
searchParams,
}: {
searchParams: Promise<{ tab?: 'mine' | 'shared' }>;
}) {
const { tab: rawTab } = await searchParams;
const tab: 'mine' | 'shared' = rawTab === 'shared' ? 'shared' : 'mine';
const [groupsR, sharedR, routesR] = await Promise.all([
fetchJson<Group[]>('/groups'),
tab === 'shared' ? fetchJson<SharedGroup[]>('/groups/shared') : Promise.resolve(null),
fetchJson<Route[]>('/routes'),
]);
const groups = groupsR?.ok ? groupsR.data : [];
const shared = sharedR && sharedR.ok ? sharedR.data : [];
const routes = routesR?.ok ? routesR.data : [];
const errors = [groupsR, sharedR, routesR]
.filter((r): r is { ok: false; status: number; error: string } => !!r && !r.ok && r.status !== 401)
.map((e) => `${e.status === 0 ? 'API unreachable' : `API ${e.status}`}: ${e.error}`);
return (
<div className="max-w-2xl">
<div className="max-w-3xl">
<h1 className="text-xl font-semibold mb-6">Groups &amp; Routes</h1>
<RouteManager groups={groups ?? []} initialRoutes={routes ?? []} />
{errors.length > 0 && (
<div className="mb-4 rounded border border-red-200 bg-red-50 p-3 text-sm text-red-800">
<div className="font-medium mb-1">Failed to load some data</div>
<ul className="list-disc pl-5 space-y-0.5">
{errors.map((e) => <li key={e}>{e}</li>)}
</ul>
</div>
)}
<GroupsTabs current={tab} counts={{ mine: groups.length, shared: shared.length }} />
{tab === 'shared' && (
<div className="mt-4">
<h2 className="text-sm font-medium mb-2">Shared with me</h2>
<p className="text-xs text-gray-500 mb-3">
Groups other tenants have shared with you. You can use them as TARGET groups in your routes.
</p>
{shared.length === 0 ? (
<p className="text-sm text-gray-400 italic">No groups shared with you yet.</p>
) : (
<ul className="space-y-2">
{shared.map((g) => (
<li key={g.id} className="border border-gray-200 rounded bg-white px-4 py-3 flex items-center justify-between">
<div>
<span className="text-sm font-medium">{g.name}</span>
{!g.isActive && <span className="ml-2 text-xs text-red-500 font-medium">(Bot removed)</span>}
<span className="text-xs text-gray-400 ml-2">shared by {g.sharedByTenantName}</span>
</div>
</li>
))}
</ul>
)}
</div>
)}
{tab === 'mine' && (
<div className="mt-4">
<RouteManager groups={groups} initialRoutes={routes} />
</div>
)}
</div>
);
}