/** * React Query hooks for layout management */ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import type { UseQueryResult, UseMutationResult } from "@tanstack/react-query"; import type { UserLayout, WidgetPlacement } from "@mosaic/shared"; const LAYOUTS_KEY = ["layouts"]; interface CreateLayoutData { name: string; layout: WidgetPlacement[]; isDefault?: boolean; metadata?: Record; } interface UpdateLayoutData { id: string; name?: string; layout?: WidgetPlacement[]; isDefault?: boolean; metadata?: Record; } /** * Fetch all layouts for the current user */ export function useLayouts(): UseQueryResult { return useQuery({ queryKey: LAYOUTS_KEY, queryFn: async (): Promise => { const response = await fetch("/api/layouts"); if (!response.ok) { throw new Error("Failed to fetch layouts"); } return response.json() as Promise; }, }); } /** * Fetch a single layout by ID */ export function useLayout(id: string): UseQueryResult { return useQuery({ queryKey: [...LAYOUTS_KEY, id], queryFn: async (): Promise => { const response = await fetch(`/api/layouts/${id}`); if (!response.ok) { throw new Error("Failed to fetch layout"); } return response.json() as Promise; }, enabled: !!id, }); } /** * Fetch the default layout */ export function useDefaultLayout(): UseQueryResult { return useQuery({ queryKey: [...LAYOUTS_KEY, "default"], queryFn: async (): Promise => { const response = await fetch("/api/layouts/default"); if (!response.ok) { throw new Error("Failed to fetch default layout"); } return response.json() as Promise; }, }); } /** * Create a new layout */ export function useCreateLayout(): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (data: CreateLayoutData): Promise => { const response = await fetch("/api/layouts", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data), }); if (!response.ok) { throw new Error("Failed to create layout"); } return response.json() as Promise; }, onSuccess: (): void => { // Invalidate layouts cache to refetch void queryClient.invalidateQueries({ queryKey: LAYOUTS_KEY }); }, }); } /** * Update an existing layout */ export function useUpdateLayout(): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ id, ...data }: UpdateLayoutData): Promise => { const response = await fetch(`/api/layouts/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data), }); if (!response.ok) { throw new Error("Failed to update layout"); } return response.json() as Promise; }, onSuccess: (_data, variables): void => { // Invalidate affected queries void queryClient.invalidateQueries({ queryKey: LAYOUTS_KEY }); void queryClient.invalidateQueries({ queryKey: [...LAYOUTS_KEY, variables.id] }); }, }); } /** * Delete a layout */ export function useDeleteLayout(): UseMutationResult { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (id: string): Promise => { const response = await fetch(`/api/layouts/${id}`, { method: "DELETE", }); if (!response.ok) { throw new Error("Failed to delete layout"); } await response.json(); }, onSuccess: (): void => { // Invalidate layouts cache to refetch void queryClient.invalidateQueries({ queryKey: LAYOUTS_KEY }); }, }); } interface UseSaveLayoutReturn { saveLayout: (layout: WidgetPlacement[]) => void; isSaving: boolean; error: Error | null; } /** * Helper hook to save layout changes with debouncing */ export function useSaveLayout(layoutId: string): UseSaveLayoutReturn { const updateLayout = useUpdateLayout(); const saveLayout = (layout: WidgetPlacement[]): void => { updateLayout.mutate({ id: layoutId, layout, }); }; return { saveLayout, isSaving: updateLayout.isPending, error: updateLayout.error, }; }