import React, { useEffect, useState } from "react"; import { Routes, Route, Navigate } from "react-router-dom"; import { Box, CircularProgress, Alert } from "@mui/material"; import { useAppContext } from "../context/AppContext"; import { Layout } from "./Layout"; import { ResourceList } from "./ResourceList"; import { ResourceForm } from "./ResourceForm"; import { ResourceDetail } from "./ResourceDetail"; import { ValidationAlert } from "./ValidationAlert"; import { getApi } from "../hooks/useApi"; export interface AdminProps { basePath: string; } export function Admin({ basePath }: AdminProps) { const { resources, loading, errors, warnings } = useAppContext(); if (loading) { return ( ); } if (errors.length > 0) { return ; } if (resources.length === 0) { return ( No resources found in the OpenAPI spec. ); } const topLevel = resources.filter((r) => !r.parent); return ( <> {warnings.length > 0 && } } /> {topLevel.map((r) => ( } /> {!r.streaming && ( <> } /> } /> } /> )} ))} ); } export interface ProfileRoute { path: string; method: string; operationId: string; summary?: string; } /** Derive a friendly profile route path from the operation's spec path. * e.g. "/users/me" → "me", "/users/me/" → "me" */ function friendlyProfilePath(specPath: string): string { return specPath.replace(/^\/[^/]+\/?/, "").replace(/\/+$/, ""); } export function ProfileRoutes() { const { profileOperations, profileComponents } = useAppContext(); const ops = profileOperations; const getOp = ops.find((o) => o.method === "GET"); return ( } /> {ops.map((op) => { const basePath = friendlyProfilePath(op.path); let Component: React.ComponentType | undefined; let mode: "view" | "create" | "edit" | undefined; if (op.method === "GET" && profileComponents.view) { Component = profileComponents.view; mode = "view"; } else if (op.method === "POST" && profileComponents.create) { Component = profileComponents.create; mode = "create"; } else if ((op.method === "PUT" || op.method === "PATCH") && profileComponents.edit) { Component = profileComponents.edit; mode = "edit"; } if (!Component || !mode) return null; const routePath = mode === "view" ? basePath : `${basePath}/edit`; return ( } /> ); })} ); } function ProfileComponentWrapper({ Component, mode, operation, getOperation, }: { Component: React.ComponentType; mode: "view" | "create" | "edit"; operation: { path: string; method: string }; getOperation?: { path: string }; }) { const [data, setData] = useState(null); const [loading, setLoading] = useState(mode !== "create"); const [error, setError] = useState(null); const fetchPath = getOperation?.path ?? operation.path; const fetchData = async () => { setLoading(true); setError(null); try { const res = await getApi().get(fetchPath); setData(res.data); } catch (e: any) { setError(e.response?.data?.detail ?? e.message ?? "Failed to load profile"); } finally { setLoading(false); } }; useEffect(() => { if (mode !== "create") { fetchData(); } }, [fetchPath, mode]); const handleSubmit = async (formData: Record) => { setLoading(true); setError(null); try { if (mode === "create") { await getApi().post(operation.path, formData); } else { await getApi()({ method: operation.method.toLowerCase(), url: operation.path, data: formData }); } // Navigate back to the base profile view path const base = friendlyProfilePath(operation.path); window.history.pushState(null, "", `/${base}`); window.location.reload(); } catch (e: any) { setError(e.response?.data?.detail ?? e.message ?? "Operation failed"); } finally { setLoading(false); } }; if (loading && !data && mode !== "create") { return ( ); } if (error && !data && mode !== "create") { return ( {error} ); } const props: Record = {}; if (mode === "view") { if (data) { props.name = data.name; props.username = data.username; props.email = data.email; } props.onEdit = () => { window.history.pushState(null, "", `/profile/me/edit`); window.location.reload(); }; props.loading = loading; } if (mode === "create") { props.defaultUsername = data?.username; props.defaultEmail = data?.email; props.onBack = () => window.history.back(); props.onSubmit = handleSubmit; props.loading = loading; props.error = error; } if (mode === "edit") { if (data) { props.name = data.name; props.username = data.username; props.email = data.email; } props.onBack = () => window.history.back(); props.onSubmit = handleSubmit; props.loading = loading; props.error = error; } return ; }