39948-vm/frontend/src/hooks/useConstructorData.ts

151 lines
4.0 KiB
TypeScript

/**
* useConstructorData Hook
*
* Orchestrates all data fetching for the constructor page using React Query.
* Replaces the manual loadData function with cached, deduplicated queries.
*/
import { useMemo } from 'react';
import { useProjectQuery } from './queries/useProjectQuery';
import { usePagesQuery } from './queries/usePagesQuery';
import { useAssetsQuery } from './queries/useAssetsQuery';
import { useElementDefaultsQuery } from './queries/useElementDefaultsQuery';
import { extractPageLinksAndElements } from '../lib/extractPageLinks';
import type { TourPage, Asset } from '../types/entities';
import type { CanvasElementType, CanvasElement } from '../types/constructor';
import type { PreloadPageLink, PreloadElement } from '../types/preload';
interface UseConstructorDataParams {
projectId: string | undefined;
isAuthReady: boolean;
}
// Stable empty references to prevent infinite loops from identity changes
const EMPTY_ELEMENT_DEFAULTS: Partial<
Record<CanvasElementType, Partial<CanvasElement>>
> = {};
const EMPTY_PAGES: TourPage[] = [];
const EMPTY_ASSETS: Asset[] = [];
interface UseConstructorDataResult {
// Project
project: { name: string } | null;
projectName: string;
// Pages
pages: TourPage[];
pageLinks: PreloadPageLink[];
allPagesPreloadElements: PreloadElement[];
// Assets
assets: Asset[];
// Element Defaults
uiElementDefaultsByType: Partial<
Record<CanvasElementType, Partial<CanvasElement>>
>;
// Loading state
isLoading: boolean;
isError: boolean;
error: Error | null;
// Refetch function
refetch: () => Promise<void>;
}
export function useConstructorData({
projectId,
isAuthReady,
}: UseConstructorDataParams): UseConstructorDataResult {
// Enable queries only when we have projectId and auth is ready
const enabled = Boolean(projectId) && isAuthReady;
// Fetch project
const projectQuery = useProjectQuery(enabled ? projectId : undefined);
// Fetch pages (dev environment for constructor)
const pagesQuery = usePagesQuery(enabled ? projectId : undefined, 'dev');
// Fetch assets
const assetsQuery = useAssetsQuery(enabled ? projectId : undefined, {
limit: 500,
});
// Fetch element defaults
const elementDefaultsQuery = useElementDefaultsQuery(
enabled ? projectId : undefined,
);
// Extract page links and preload elements from pages
const { pageLinks, allPagesPreloadElements } = useMemo(() => {
if (!pagesQuery.data || pagesQuery.data.length === 0) {
return {
pageLinks: [] as PreloadPageLink[],
allPagesPreloadElements: [] as PreloadElement[],
};
}
const { pageLinks: links, preloadElements: elements } =
extractPageLinksAndElements(pagesQuery.data as TourPage[]);
return { pageLinks: links, allPagesPreloadElements: elements };
}, [pagesQuery.data]);
// Combine loading states
const isLoading =
projectQuery.isLoading ||
pagesQuery.isLoading ||
assetsQuery.isLoading ||
elementDefaultsQuery.isLoading;
// Combine error states
const isError =
projectQuery.isError ||
pagesQuery.isError ||
assetsQuery.isError ||
elementDefaultsQuery.isError;
const error =
projectQuery.error ||
pagesQuery.error ||
assetsQuery.error ||
elementDefaultsQuery.error;
// Refetch all queries
const refetch = async () => {
await Promise.all([
projectQuery.refetch(),
pagesQuery.refetch(),
assetsQuery.refetch(),
elementDefaultsQuery.refetch(),
]);
};
return {
// Project
project: projectQuery.data || null,
projectName: projectQuery.data?.name || '',
// Pages
pages: (pagesQuery.data as TourPage[]) || EMPTY_PAGES,
pageLinks,
allPagesPreloadElements,
// Assets
assets: (assetsQuery.data as Asset[]) || EMPTY_ASSETS,
// Element Defaults
uiElementDefaultsByType:
elementDefaultsQuery.data || EMPTY_ELEMENT_DEFAULTS,
// Loading state
isLoading,
isError,
error: error instanceof Error ? error : null,
// Refetch
refetch,
};
}
export default useConstructorData;