1ee737c025
Task 6.5: add createBinding/listBindings/simulateRoute/listRouteDecisions/ approveDecision/denyDecision to RestClient + RouteBinding/RouteDecision types. New @insignia/iios-community-web package (CommunityProvider, useBindings, useRoutePreview, useRouteDecisions). Boundary allowlist: community-web -> contracts + kernel-client. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
import { RestClient, type RouteBinding, type RouteDecision } from '@insignia/iios-kernel-client';
|
|
|
|
const CommunityContext = createContext<RestClient | null>(null);
|
|
|
|
export function CommunityProvider({
|
|
serviceUrl,
|
|
token,
|
|
children,
|
|
}: {
|
|
serviceUrl: string;
|
|
token: string;
|
|
children: React.ReactNode;
|
|
}): React.ReactElement {
|
|
const client = useMemo(() => new RestClient({ serviceUrl, token }), [serviceUrl, token]);
|
|
return <CommunityContext.Provider value={client}>{children}</CommunityContext.Provider>;
|
|
}
|
|
|
|
function useClient(): RestClient {
|
|
const c = useContext(CommunityContext);
|
|
if (!c) throw new Error('community hooks must be used within <CommunityProvider>');
|
|
return c;
|
|
}
|
|
|
|
/** Manage route bindings (origin→destination rules). */
|
|
export function useBindings(): {
|
|
bindings: RouteBinding[];
|
|
refresh: () => Promise<void>;
|
|
createBinding: (input: Partial<RouteBinding>) => Promise<RouteBinding>;
|
|
} {
|
|
const client = useClient();
|
|
const [bindings, setBindings] = useState<RouteBinding[]>([]);
|
|
const refresh = useCallback(async () => {
|
|
try {
|
|
setBindings(await client.listBindings());
|
|
} catch {
|
|
/* transient */
|
|
}
|
|
}, [client]);
|
|
useEffect(() => {
|
|
void refresh();
|
|
}, [refresh]);
|
|
const createBinding = async (input: Partial<RouteBinding>) => {
|
|
const b = await client.createBinding(input);
|
|
await refresh();
|
|
return b;
|
|
};
|
|
return { bindings, refresh, createBinding };
|
|
}
|
|
|
|
/** Preview-first: simulate an interaction against the bindings (no send). */
|
|
export function useRoutePreview(): (interactionId: string, originChannelType: string, originRef?: string) => Promise<RouteDecision[]> {
|
|
const client = useClient();
|
|
return async (interactionId, originChannelType, originRef) =>
|
|
(await client.simulateRoute({ interactionId, originChannelType, originRef })).decisions;
|
|
}
|
|
|
|
/** Pending decisions + admin approve/deny. */
|
|
export function useRouteDecisions(opts?: { state?: string; pollMs?: number }): {
|
|
decisions: RouteDecision[];
|
|
refresh: () => Promise<void>;
|
|
approve: (id: string) => Promise<void>;
|
|
deny: (id: string) => Promise<void>;
|
|
} {
|
|
const client = useClient();
|
|
const [decisions, setDecisions] = useState<RouteDecision[]>([]);
|
|
const refresh = useCallback(async () => {
|
|
try {
|
|
setDecisions(await client.listRouteDecisions(opts?.state));
|
|
} catch {
|
|
/* transient */
|
|
}
|
|
}, [client, opts?.state]);
|
|
useEffect(() => {
|
|
void refresh();
|
|
const t = setInterval(() => void refresh(), opts?.pollMs ?? 3000);
|
|
return () => clearInterval(t);
|
|
}, [refresh, opts?.pollMs]);
|
|
const approve = async (id: string) => {
|
|
await client.approveDecision(id);
|
|
await refresh();
|
|
};
|
|
const deny = async (id: string) => {
|
|
await client.denyDecision(id);
|
|
await refresh();
|
|
};
|
|
return { decisions, refresh, approve, deny };
|
|
}
|