From f6194d87b9ffbd07240d6f1ab1551858946b4e80 Mon Sep 17 00:00:00 2001 From: CarterPerez-dev Date: Sun, 17 May 2026 12:56:03 -0400 Subject: [PATCH] feat(canary): TanStack Query hooks for token lifecycle Four hooks wired through the canary api/client envelope helpers. Module-augmented defaultError surfaces ApiError everywhere. - useTokenTypes: GET /tokens/types, parsed via typeDescriptorListSchema. Uses QUERY_STRATEGIES.static (Infinity stale, no refetch on focus) since the discoverable types list is build-time stable. - useCreateToken: POST /tokens (CreateTokenInput -> CreateTokenResponse). On success invalidates ['canary','manage', manage_id] so a freshly created token's manage page hydrates with the new row on first hit. - useManageToken: GET /m/{id} with cursor pagination. URLSearchParams builds ?cursor=&limit= only for defined values; manageId is URI-encoded. QUERY_STRATEGIES.frequent enables 30s polling so the events list feels live. enabled=false when manageId is empty. - useDeleteToken: DELETE /m/{id}. On success removes the cached manage data entirely (no point keeping it after a delete). --- .../frontend/src/api/hooks/index.ts | 5 +++ .../frontend/src/api/hooks/useCreateToken.ts | 28 ++++++++++++ .../frontend/src/api/hooks/useDeleteToken.ts | 23 ++++++++++ .../frontend/src/api/hooks/useManageToken.ts | 43 +++++++++++++++++++ .../frontend/src/api/hooks/useTokenTypes.ts | 20 +++++++++ 5 files changed, 119 insertions(+) create mode 100644 PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useCreateToken.ts create mode 100644 PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useDeleteToken.ts create mode 100644 PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useManageToken.ts create mode 100644 PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useTokenTypes.ts diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/index.ts b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/index.ts index 88da7857..90327613 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/index.ts +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/index.ts @@ -2,3 +2,8 @@ // ©AngelaMos | 2026 // index.ts // =================== + +export * from './useCreateToken' +export * from './useDeleteToken' +export * from './useManageToken' +export * from './useTokenTypes' diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useCreateToken.ts b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useCreateToken.ts new file mode 100644 index 00000000..3d4f67fd --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useCreateToken.ts @@ -0,0 +1,28 @@ +// =================== +// ©AngelaMos | 2026 +// useCreateToken.ts +// =================== + +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { apiPost } from '../client' +import { + type CreateTokenInput, + type CreateTokenResponse, + createTokenResponseSchema, +} from '../types/token' + +const CREATE_TOKEN_PATH = '/tokens' +const MANAGE_KEY_PREFIX = ['canary', 'manage'] as const + +export function useCreateToken() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (body) => + apiPost(CREATE_TOKEN_PATH, body, createTokenResponseSchema), + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: [...MANAGE_KEY_PREFIX, data.token.manage_id], + }) + }, + }) +} diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useDeleteToken.ts b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useDeleteToken.ts new file mode 100644 index 00000000..8858a529 --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useDeleteToken.ts @@ -0,0 +1,23 @@ +// =================== +// ©AngelaMos | 2026 +// useDeleteToken.ts +// =================== + +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { apiDelete } from '../client' + +const MANAGE_PATH_PREFIX = '/m/' +const MANAGE_KEY_PREFIX = ['canary', 'manage'] as const + +export function useDeleteToken() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (manageId) => + apiDelete(`${MANAGE_PATH_PREFIX}${encodeURIComponent(manageId)}`), + onSuccess: (_data, manageId) => { + queryClient.removeQueries({ + queryKey: [...MANAGE_KEY_PREFIX, manageId], + }) + }, + }) +} diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useManageToken.ts b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useManageToken.ts new file mode 100644 index 00000000..559a7634 --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useManageToken.ts @@ -0,0 +1,43 @@ +// =================== +// ©AngelaMos | 2026 +// useManageToken.ts +// =================== + +import { useQuery } from '@tanstack/react-query' +import { QUERY_STRATEGIES } from '@/core/api' +import { apiGet } from '../client' +import { manageResponseSchema } from '../types/event' + +const MANAGE_PATH_PREFIX = '/m/' +const MANAGE_KEY_PREFIX = ['canary', 'manage'] as const + +export type UseManageTokenParams = { + cursor?: string + limit?: number +} + +function buildSearch(params: UseManageTokenParams | undefined): string { + if (!params) { + return '' + } + const sp = new URLSearchParams() + if (params.cursor !== undefined && params.cursor.length > 0) { + sp.set('cursor', params.cursor) + } + if (params.limit !== undefined) { + sp.set('limit', String(params.limit)) + } + const query = sp.toString() + return query.length > 0 ? `?${query}` : '' +} + +export function useManageToken(manageId: string, params?: UseManageTokenParams) { + const search = buildSearch(params) + const path = `${MANAGE_PATH_PREFIX}${encodeURIComponent(manageId)}${search}` + return useQuery({ + queryKey: [...MANAGE_KEY_PREFIX, manageId, params ?? null], + queryFn: () => apiGet(path, manageResponseSchema), + enabled: manageId.length > 0, + ...QUERY_STRATEGIES.frequent, + }) +} diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useTokenTypes.ts b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useTokenTypes.ts new file mode 100644 index 00000000..a36500dd --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/api/hooks/useTokenTypes.ts @@ -0,0 +1,20 @@ +// =================== +// ©AngelaMos | 2026 +// useTokenTypes.ts +// =================== + +import { useQuery } from '@tanstack/react-query' +import { QUERY_STRATEGIES } from '@/core/api' +import { apiGet } from '../client' +import { typeDescriptorListSchema } from '../types/token' + +const TOKEN_TYPES_PATH = '/tokens/types' +const TOKEN_TYPES_KEY = ['canary', 'token-types'] as const + +export function useTokenTypes() { + return useQuery({ + queryKey: TOKEN_TYPES_KEY, + queryFn: () => apiGet(TOKEN_TYPES_PATH, typeDescriptorListSchema), + ...QUERY_STRATEGIES.static, + }) +}