feat(dashboard): GuruConnect v2 Support Codes view
Generate, list, and cancel attended-support codes (XXX-XXX-XXX), built on the v2 codes API and existing UI primitives. - Codes table: code in mono, status badge (pending+pulse/connected/ completed/cancelled), bound client/machine, created-by, created (relative + absolute tooltip). Sticky header, skeleton load, actionable empty/error states. - Generate opens a focused reveal modal showing the code large in JetBrains Mono with copy and a read-aloud instruction; the code is announced character-by-character for screen readers. Mint is ref- guarded so it creates exactly one code per open (no StrictMode dupe). - Cancel via confirm dialog (POST /api/codes/:code/cancel), disabled for non-cancellable statuses; invalidates the codes query. List polls 7s. - Shared API client now tolerates non-JSON 200 bodies, so the cancel endpoint's plain-text "Code cancelled" success no longer surfaces as a failure. Error-envelope handling unchanged. Passed Code Review (no blockers after fixes) and local gates (tsc/lint/build green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
55
dashboard/src/features/codes/hooks.ts
Normal file
55
dashboard/src/features/codes/hooks.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import * as codesApi from "../../api/codes";
|
||||
import type { CreateCodeRequest } from "../../api/types";
|
||||
|
||||
const CODES_KEY = ["codes"] as const;
|
||||
|
||||
/**
|
||||
* List the active support codes. Polls on a short interval because codes are
|
||||
* short-lived: a `pending` code can be redeemed (-> `connected`) or expire out
|
||||
* of the active set at any moment, and a tech who just read a code aloud is
|
||||
* watching for exactly that transition. The interval is tight (like the
|
||||
* sessions poll) so the redeem shows up on its own without a manual refresh.
|
||||
*/
|
||||
export function useSupportCodes() {
|
||||
return useQuery({
|
||||
queryKey: CODES_KEY,
|
||||
queryFn: ({ signal }) => codesApi.listCodes(signal),
|
||||
refetchInterval: 7_000,
|
||||
staleTime: 3_500,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new support code, then invalidate the list so the new `pending`
|
||||
* code appears in the table. The created code is returned to the caller so the
|
||||
* generate flow can surface it prominently (it is read to the end user).
|
||||
*/
|
||||
export function useGenerateCode() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (body: CreateCodeRequest) => codesApi.createCode(body),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: CODES_KEY });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel (revoke) a support code, then invalidate the list so the row drops out
|
||||
* of the active set. Cancelling an un-redeemed code is irreversible, so the UI
|
||||
* confirms first; this hook is the action behind that confirmation.
|
||||
*/
|
||||
export function useCancelCode() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (code: string) => codesApi.cancelCode(code),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: CODES_KEY });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user