import { useEffect, useMemo, useState } from "react"; import { Alert, Autocomplete, Box, Button, Card, CardContent, CircularProgress, Container, FormControl, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; import { api } from "./api"; import type { SelectionState } from "./types"; const EMPTY_SELECTION: SelectionState = { location: "", publisher: "", offer: "", sku: "", version: "" }; const App = () => { const [selection, setSelection] = useState(EMPTY_SELECTION); const [templateFile, setTemplateFile] = useState(""); const [renderedUsage, setRenderedUsage] = useState(""); const [skuExport, setSkuExport] = useState(""); const [renderError, setRenderError] = useState(""); const healthQuery = useQuery({ queryKey: ["health"], queryFn: api.health }); const locationsQuery = useQuery({ queryKey: ["locations"], queryFn: api.locations }); const publishersQuery = useQuery({ queryKey: ["publishers", selection.location], queryFn: () => api.publishers(selection.location), enabled: Boolean(selection.location) }); const offersQuery = useQuery({ queryKey: ["offers", selection.location, selection.publisher], queryFn: () => api.offers(selection.location, selection.publisher), enabled: Boolean(selection.location && selection.publisher) }); const skusQuery = useQuery({ queryKey: ["skus", selection.location, selection.publisher, selection.offer], queryFn: () => api.skus(selection.location, selection.publisher, selection.offer), enabled: Boolean(selection.location && selection.publisher && selection.offer) }); const versionsQuery = useQuery({ queryKey: ["versions", selection.location, selection.publisher, selection.offer, selection.sku], queryFn: () => api.versions(selection.location, selection.publisher, selection.offer, selection.sku), enabled: Boolean(selection.location && selection.publisher && selection.offer && selection.sku) }); const templatesQuery = useQuery({ queryKey: ["templates"], queryFn: api.templates }); useEffect(() => { setSelection((previous) => ({ ...previous, publisher: "", offer: "", sku: "", version: "" })); setRenderedUsage(""); setSkuExport(""); }, [selection.location]); useEffect(() => { setSelection((previous) => ({ ...previous, offer: "", sku: "", version: "" })); setRenderedUsage(""); setSkuExport(""); }, [selection.publisher]); useEffect(() => { setSelection((previous) => ({ ...previous, sku: "", version: "" })); setRenderedUsage(""); }, [selection.offer]); useEffect(() => { setSelection((previous) => ({ ...previous, version: "" })); setRenderedUsage(""); }, [selection.sku]); const canRender = useMemo( () => Boolean(selection.location && selection.publisher && selection.offer && selection.sku && selection.version && templateFile), [selection, templateFile] ); const publishers = useMemo( () => [...(publishersQuery.data ?? [])].sort((a, b) => a.localeCompare(b)), [publishersQuery.data] ); const offers = useMemo( () => [...(offersQuery.data ?? [])].sort((a, b) => a.localeCompare(b)), [offersQuery.data] ); const skus = useMemo( () => [...(skusQuery.data ?? [])].sort((a, b) => a.localeCompare(b)), [skusQuery.data] ); const versions = useMemo( () => [...(versionsQuery.data ?? [])].sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" })), [versionsQuery.data] ); const containsFilter = (options: string[], inputValue: string) => { const query = inputValue.trim().toLowerCase(); if (!query) { return options; } return options.filter((option) => option.toLowerCase().includes(query)); }; const onRender = async () => { setRenderError(""); try { const [usage, skuBlock] = await Promise.all([ api.render(templateFile, selection), api.skuExport(selection.location, selection.publisher, selection.offer) ]); setRenderedUsage(usage); setSkuExport(skuBlock); } catch (error) { setRenderError(error instanceof Error ? error.message : "Unexpected render error"); } }; const loading = locationsQuery.isLoading || templatesQuery.isLoading; const healthError = healthQuery.error instanceof Error ? healthQuery.error.message : ""; const appNotConfigured = healthError.includes("Missing AZURE_SUBSCRIPTION_ID"); const locationsError = locationsQuery.error instanceof Error ? locationsQuery.error.message : "Failed to load locations"; return ( Azure Image Chooser Select a marketplace image and generate reusable snippets. {appNotConfigured ? ( App is not configured. Set AZURE_SUBSCRIPTION_ID (and Azure credentials) in the container start environment, then restart the app. ) : null} {loading ? ( ) : locationsQuery.isError ? ( {locationsError} ) : ( Location setSelection((prev) => ({ ...prev, publisher: value ?? "" }))} filterOptions={(options, state) => containsFilter(options, state.inputValue)} renderInput={(params) => } /> setSelection((prev) => ({ ...prev, offer: value ?? "" }))} filterOptions={(options, state) => containsFilter(options, state.inputValue)} renderInput={(params) => } /> setSelection((prev) => ({ ...prev, sku: value ?? "" }))} filterOptions={(options, state) => containsFilter(options, state.inputValue)} renderInput={(params) => } /> Version Usage scenario )} {renderError ? {renderError} : null} {renderedUsage ? ( Usage snippet {renderedUsage} ) : null} {skuExport ? ( Available SKUs {skuExport} ) : null} ); }; export default App;