Типы ответов хаба, клиент с разбором ошибок (AuthError, HubDownError, ApiError), хранилище токена с подпиской, экран входа с проверкой токена до сохранения, QueryClient с выходом на экран входа при 401, хуки запросов. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
47 lines
2.3 KiB
TypeScript
47 lines
2.3 KiB
TypeScript
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { api } from "./client.ts";
|
|
import type { BoardSnapshot, HubSettings, IssueDetail, IssueRow, Note, NoteFields } from "./types.ts";
|
|
|
|
export const ISSUE_FILTER_NAMES = ["sprint", "assignee", "work_kind", "fact_status", "status", "label", "epic", "neighbor", "q"] as const;
|
|
export type IssueFilters = Partial<Record<(typeof ISSUE_FILTER_NAMES)[number], string>>;
|
|
export const ISSUE_PAGE = 50;
|
|
|
|
export const qk = {
|
|
settings: ["settings"] as const,
|
|
board: ["board"] as const,
|
|
issues: (f: IssueFilters) => ["issues", f] as const,
|
|
issue: (key: string) => ["issue", key] as const,
|
|
};
|
|
|
|
export const useSettings = () =>
|
|
useQuery({ queryKey: qk.settings, queryFn: () => api.get<HubSettings>("/settings"), staleTime: Infinity });
|
|
|
|
export const useBoard = () => useQuery({ queryKey: qk.board, queryFn: () => api.get<BoardSnapshot>("/board") });
|
|
|
|
export const useIssue = (key: string) =>
|
|
useQuery({ queryKey: qk.issue(key), queryFn: () => api.get<IssueDetail>(`/issues/${encodeURIComponent(key)}`) });
|
|
|
|
// список порциями по ISSUE_PAGE: следующая порция есть, пока последняя полная
|
|
export const useIssues = (filters: IssueFilters) =>
|
|
useInfiniteQuery({
|
|
queryKey: qk.issues(filters),
|
|
initialPageParam: 0,
|
|
queryFn: ({ pageParam }) =>
|
|
api.get<{ issues: IssueRow[] }>("/issues", { ...filters, limit: ISSUE_PAGE, offset: pageParam }).then((r) => r.issues),
|
|
getNextPageParam: (last, all) => (last.length < ISSUE_PAGE ? undefined : all.length * ISSUE_PAGE),
|
|
});
|
|
|
|
// сохранение заметки: key фиксируется при создании мутации — ответ пишется в карточку той задачи, для которой отправлен
|
|
export function useSaveNote(key: string) {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (fields: NoteFields) =>
|
|
api.put<Note>(`/issues/${encodeURIComponent(key)}/note`, { ...fields, updated_by: "web" }),
|
|
onSuccess: (note) => {
|
|
qc.setQueryData<IssueDetail>(qk.issue(key), (d) => (d ? { ...d, note } : d));
|
|
void qc.invalidateQueries({ queryKey: ["issues"] });
|
|
void qc.invalidateQueries({ queryKey: qk.board });
|
|
},
|
|
});
|
|
}
|