import { createContext, useCallback, useContext, useMemo } from 'react'; import type { ReactNode } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import type { DashboardPayload } from '../../features/skills/state'; import { fetchDashboardState, refreshDashboardState } from '../api'; import { buildSkills, getErrorMessage } from './utils'; import type { BrowserSkill, DashboardActionsValue, DashboardDataValue } from './types'; const DashboardDataContext = createContext(null); const DashboardActionsContext = createContext(null); export const dashboardQueryKey = ['dashboard', 'state'] as const; export function DashboardDataProvider({ children }: { children: ReactNode }) { const queryClient = useQueryClient(); const { data: payload = null, error: loadError, isPending: isInitialLoading, isRefetching, refetch, } = useQuery({ queryKey: dashboardQueryKey, queryFn: fetchDashboardState, }); const { error: refreshError, isPending: isRefreshPending, mutateAsync: refreshDashboard, reset: resetRefresh, } = useMutation({ mutationFn: async () => { const currentPayload = queryClient.getQueryData(dashboardQueryKey) ?? payload; return currentPayload ? refreshDashboardState(currentPayload.installedState) : fetchDashboardState(); }, onSuccess: (nextPayload) => { queryClient.setQueryData(dashboardQueryKey, nextPayload); }, }); const load = useCallback(async () => { resetRefresh(); await refetch(); }, [refetch, resetRefresh]); const refresh = useCallback(async () => { if (isRefreshPending) { return; } await refreshDashboard(); }, [isRefreshPending, refreshDashboard]); const errorMessage = useMemo(() => { if (refreshError) { return `Refresh request failed: ${getErrorMessage(refreshError)}`; } if (!payload && loadError) { return `Dashboard load failed: ${getErrorMessage(loadError)}`; } return null; }, [loadError, payload, refreshError]); const skills = useMemo(() => { if (!payload) { return []; } return buildSkills(payload.installedState); }, [payload]); const skillsById = useMemo(() => { return new Map(skills.map((skill) => [skill.id, skill])); }, [skills]); const getSkillById = useCallback( (skillId: string): BrowserSkill | undefined => { return skillsById.get(skillId); }, [skillsById] ); const dataValue: DashboardDataValue = useMemo( () => ({ payload, skills, isInitialLoading, errorMessage, getSkillById, }), [errorMessage, getSkillById, isInitialLoading, payload, skills] ); const actionsValue: DashboardActionsValue = useMemo( () => ({ isRefreshing: isRefreshPending || isRefetching, reload: load, refresh, }), [isRefreshPending, isRefetching, load, refresh] ); return ( {children} ); } export function useDashboardData(): DashboardDataValue { const context = useContext(DashboardDataContext); if (!context) { throw new Error('useDashboardData must be used within DashboardDataProvider.'); } return context; } export function useDashboardActions(): DashboardActionsValue { const context = useContext(DashboardActionsContext); if (!context) { throw new Error('useDashboardActions must be used within DashboardDataProvider.'); } return context; }