import React, { useEffect, useState, useMemo } from "react"; import type { SpecConfiguration, ResourceConfig, ValidationMessage } from "../types"; import { AppContext } from "./AppContext"; import { loadSpec } from "../spec-loader"; import { validateSpec } from "../spec-validator"; import { buildResourceConfigs } from "../transformers/resource-config"; import { initApi } from "../hooks/useApi"; interface AppProviderProps { specConfiguration: SpecConfiguration; children: React.ReactNode; } export function AppProvider({ specConfiguration, children }: AppProviderProps) { const [loading, setLoading] = useState(true); const [resources, setResources] = useState([]); const [schemas, setSchemas] = useState>({}); const [errors, setErrors] = useState([]); const [warnings, setWarnings] = useState([]); useEffect(() => { let cancelled = false; async function init() { try { setLoading(true); const spec = await loadSpec(specConfiguration.specUrl); const allMessages = validateSpec(spec); const errs = allMessages.filter((m) => m.type === "error"); const warns = allMessages.filter((m) => m.type === "warning"); if (!cancelled) { setErrors(errs); setWarnings(warns); setSchemas(spec.components?.schemas ?? {}); } if (errs.length === 0) { const configs = buildResourceConfigs(spec); if (!cancelled) { setResources(configs); } const baseUrl = specConfiguration.baseApiUrl ?? spec.servers?.[0]?.url ?? ""; if (baseUrl) { initApi(baseUrl, specConfiguration.getToken); } } } catch (e: any) { if (!cancelled) { setErrors([{ type: "error", message: e.message ?? "Failed to load spec" }]); } } finally { if (!cancelled) { setLoading(false); } } } init(); return () => { cancelled = true; }; }, [specConfiguration.specUrl]); const value = useMemo( () => ({ config: specConfiguration, resources, schemas, loading, errors, warnings, }), [specConfiguration, resources, schemas, loading, errors, warnings] ); return React.createElement(AppContext.Provider, { value }, children); }