9c6e2c76e8
Root cause of 'waiting for escalations': no queue/membership meant tickets were never assigned. Now: createTicket find-or-creates a default queue; new joinDefault endpoint + useGoOnline hook; agent-demo joins+goes-AVAILABLE on load (assignPending picks up any waiting ticket). smoke-support self-seeds (no seed script needed). Also: vitest singleFork to end cross-file DB races (45 tests deterministic). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
import { MessageProvider } from '@insignia/iios-message-web';
|
|
import { RestClient, type Ticket, type CallbackRequest, type TicketState } from '@insignia/iios-kernel-client';
|
|
|
|
const SupportContext = createContext<RestClient | null>(null);
|
|
|
|
/** Wraps MessageProvider (chat) and provides a support REST client. */
|
|
export function SupportProvider({
|
|
serviceUrl,
|
|
token,
|
|
children,
|
|
}: {
|
|
serviceUrl: string;
|
|
token: string;
|
|
children: React.ReactNode;
|
|
}): React.ReactElement {
|
|
const client = useMemo(() => new RestClient({ serviceUrl, token }), [serviceUrl, token]);
|
|
return (
|
|
<SupportContext.Provider value={client}>
|
|
<MessageProvider serviceUrl={serviceUrl} token={token}>
|
|
{children}
|
|
</MessageProvider>
|
|
</SupportContext.Provider>
|
|
);
|
|
}
|
|
|
|
function useSupportClient(): RestClient {
|
|
const c = useContext(SupportContext);
|
|
if (!c) throw new Error('support hooks must be used within <SupportProvider>');
|
|
return c;
|
|
}
|
|
|
|
/** Escalate a chat thread to support (creates + queues a ticket). */
|
|
export function useEscalate(): (threadId: string, subject?: string) => Promise<Ticket> {
|
|
const client = useSupportClient();
|
|
return (threadId, subject) => client.escalate(threadId, subject);
|
|
}
|
|
|
|
/** The caller's tickets (scope 'mine') or an agent's assigned tickets ('assigned'). */
|
|
export function useTickets(scope: 'mine' | 'assigned' = 'mine', pollMs = 3000): {
|
|
tickets: Ticket[];
|
|
refresh: () => Promise<void>;
|
|
createTicket: (subject: string, opts?: { priority?: string; threadId?: string }) => Promise<Ticket>;
|
|
patch: (id: string, state: TicketState) => Promise<void>;
|
|
} {
|
|
const client = useSupportClient();
|
|
const [tickets, setTickets] = useState<Ticket[]>([]);
|
|
const refresh = useCallback(async () => {
|
|
try {
|
|
setTickets(await client.listTickets(scope));
|
|
} catch {
|
|
/* transient */
|
|
}
|
|
}, [client, scope]);
|
|
useEffect(() => {
|
|
void refresh();
|
|
const t = setInterval(() => void refresh(), pollMs);
|
|
return () => clearInterval(t);
|
|
}, [refresh, pollMs]);
|
|
const createTicket = async (subject: string, opts?: { priority?: string; threadId?: string }) => {
|
|
const t = await client.createTicket({ subject, ...opts });
|
|
await refresh();
|
|
return t;
|
|
};
|
|
const patch = async (id: string, state: TicketState) => {
|
|
await client.patchTicket(id, state);
|
|
await refresh();
|
|
};
|
|
return { tickets, refresh, createTicket, patch };
|
|
}
|
|
|
|
/** Agent convenience: their assigned tickets. */
|
|
export function useAssignedTickets(pollMs = 3000) {
|
|
return useTickets('assigned', pollMs);
|
|
}
|
|
|
|
export function useCallbackRequest(): (input: {
|
|
preferChannel: string;
|
|
preferredTime?: string;
|
|
notes?: string;
|
|
ticketId?: string;
|
|
}) => Promise<CallbackRequest> {
|
|
const client = useSupportClient();
|
|
return (input) => client.requestCallback(input);
|
|
}
|
|
|
|
/** Agent presence: set AVAILABLE/OFFLINE. */
|
|
export function useAvailability(): (state: string) => Promise<unknown> {
|
|
const client = useSupportClient();
|
|
return (state) => client.setAvailability(state);
|
|
}
|
|
|
|
/** Agent onboarding: join the default queue + go AVAILABLE (self-seed). */
|
|
export function useGoOnline(): () => Promise<void> {
|
|
const client = useSupportClient();
|
|
return async () => {
|
|
await client.joinDefaultQueue();
|
|
await client.setAvailability('AVAILABLE');
|
|
};
|
|
}
|