refactor: migrate from @hey-api/openapi-ts to openapi-react-query

- Replace @hey-api/openapi-ts with openapi-typescript and openapi-react-query
- Generate TypeScript types from OpenAPI spec
- Set up React Query infrastructure with QueryClientProvider
- Migrate all API hooks to use React Query patterns
- Maintain backward compatibility for existing components
- Remove old API infrastructure and dependencies
This commit is contained in:
2025-08-27 23:49:27 -06:00
parent 6f0c7c1a5e
commit e8afe82acd
30 changed files with 3116 additions and 4889 deletions

View File

@@ -1,8 +1,6 @@
// this hook is not great, we want to substitute it with a proper state management solution that is also not re-invention
import { useEffect, useRef, useState } from "react";
// Wrapper for backward compatibility
import { SearchResult, SourceKind } from "../../api";
import useApi from "../../lib/useApi";
import { useTranscriptsSearch } from "../../lib/api-hooks";
import {
PaginationPage,
paginationPageTo0Based,
@@ -13,11 +11,6 @@ interface SearchFilters {
sourceKind: SourceKind | null;
}
const EMPTY_SEARCH_FILTERS: SearchFilters = {
roomIds: null,
sourceKind: null,
};
type UseSearchTranscriptsOptions = {
pageSize: number;
page: PaginationPage;
@@ -31,13 +24,9 @@ interface UseSearchTranscriptsReturn {
reload: () => void;
}
function hashEffectFilters(filters: SearchFilters): string {
return JSON.stringify(filters);
}
export function useSearchTranscripts(
query: string = "",
filters: SearchFilters = EMPTY_SEARCH_FILTERS,
filters: SearchFilters = { roomIds: null, sourceKind: null },
options: UseSearchTranscriptsOptions = {
pageSize: 20,
page: PaginationPage(1),
@@ -45,79 +34,18 @@ export function useSearchTranscripts(
): UseSearchTranscriptsReturn {
const { pageSize, page } = options;
const [reloadCount, setReloadCount] = useState(0);
const api = useApi();
const abortControllerRef = useRef<AbortController>();
const [data, setData] = useState<{ results: SearchResult[]; total: number }>({
results: [],
total: 0,
const { data, isLoading, error, refetch } = useTranscriptsSearch(query, {
limit: pageSize,
offset: paginationPageTo0Based(page) * pageSize,
room_id: filters.roomIds?.[0],
source_kind: filters.sourceKind || undefined,
});
const [error, setError] = useState<any>();
const [isLoading, setIsLoading] = useState(false);
const filterHash = hashEffectFilters(filters);
useEffect(() => {
if (!api) {
setData({ results: [], total: 0 });
setError(undefined);
setIsLoading(false);
return;
}
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
const abortController = new AbortController();
abortControllerRef.current = abortController;
const performSearch = async () => {
setIsLoading(true);
try {
const response = await api.v1TranscriptsSearch({
q: query || "",
limit: pageSize,
offset: paginationPageTo0Based(page) * pageSize,
roomId: filters.roomIds?.[0],
sourceKind: filters.sourceKind || undefined,
});
if (abortController.signal.aborted) return;
setData(response);
setError(undefined);
} catch (err: unknown) {
if ((err as Error).name === "AbortError") {
return;
}
if (abortController.signal.aborted) {
console.error("Aborted search but error", err);
return;
}
setError(err);
} finally {
if (!abortController.signal.aborted) {
setIsLoading(false);
}
}
};
performSearch().then(() => {});
return () => {
abortController.abort();
};
}, [api, query, page, filterHash, pageSize, reloadCount]);
return {
results: data.results,
totalCount: data.total,
results: data?.results || [],
totalCount: data?.total || 0,
isLoading,
error,
reload: () => setReloadCount(reloadCount + 1),
reload: refetch,
};
}

View File

@@ -1,8 +1,5 @@
import { useEffect, useState } from "react";
import { GetTranscript } from "../../api";
import { useError } from "../../(errors)/errorContext";
import { shouldShowError } from "../../lib/errorUtils";
import useApi from "../../lib/useApi";
import { useTranscriptGet } from "../../lib/api-hooks";
type ErrorTranscript = {
error: Error;
@@ -28,43 +25,33 @@ type SuccessTranscript = {
const useTranscript = (
id: string | null,
): ErrorTranscript | LoadingTranscript | SuccessTranscript => {
const [response, setResponse] = useState<GetTranscript | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const [reload, setReload] = useState(0);
const { setError } = useError();
const api = useApi();
const reloadHandler = () => setReload((prev) => prev + 1);
const { data, isLoading, error, refetch } = useTranscriptGet(id);
useEffect(() => {
if (!id || !api) return;
// Map to the expected return format
if (isLoading) {
return {
response: null,
loading: true,
error: false,
reload: refetch,
};
}
if (!response) {
setLoading(true);
}
if (error) {
return {
error: error as Error,
loading: false,
response: null,
reload: refetch,
};
}
api
.v1TranscriptGet({ transcriptId: id })
.then((result) => {
setResponse(result);
setLoading(false);
console.debug("Transcript Loaded:", result);
})
.catch((error) => {
const shouldShowHuman = shouldShowError(error);
if (shouldShowHuman) {
setError(error, "There was an error loading the transcript");
} else {
setError(error);
}
setErrorState(error);
});
}, [id, !api, reload]);
return { response, loading, error, reload: reloadHandler } as
| ErrorTranscript
| LoadingTranscript
| SuccessTranscript;
return {
response: data as GetTranscript,
loading: false,
error: null,
reload: refetch,
};
};
export default useTranscript;