AI scaffolded NodeJS version of the App.

This commit is contained in:
2026-04-19 18:54:31 +02:00
parent 4ff0a7205f
commit aca4998da7
27 changed files with 8733 additions and 0 deletions

View File

@@ -0,0 +1,267 @@
import { useEffect, useMemo, useState } from "react";
import {
Alert,
Box,
Button,
Card,
CardContent,
CircularProgress,
Container,
FormControl,
Grid,
InputLabel,
MenuItem,
Select,
Stack,
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<SelectionState>(EMPTY_SELECTION);
const [templateFile, setTemplateFile] = useState("");
const [renderedUsage, setRenderedUsage] = useState("");
const [skuExport, setSkuExport] = useState("");
const [renderError, setRenderError] = useState("");
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 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;
return (
<Box sx={{ minHeight: "100vh", py: 6 }}>
<Container maxWidth="lg">
<Stack spacing={3}>
<Typography variant="h3" sx={{ fontWeight: 700 }}>
Azure Image Chooser
</Typography>
<Typography color="text.secondary">
Select a marketplace image and generate reusable snippets.
</Typography>
{loading ? (
<CircularProgress />
) : (
<Card>
<CardContent>
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 6 }}>
<FormControl fullWidth>
<InputLabel id="location-label">Location</InputLabel>
<Select
labelId="location-label"
label="Location"
value={selection.location}
onChange={(event) => setSelection((prev) => ({ ...prev, location: event.target.value }))}
>
{(locationsQuery.data ?? []).map((location) => (
<MenuItem key={location.name} value={location.name}>
{location.displayName}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<FormControl fullWidth disabled={!selection.location}>
<InputLabel id="publisher-label">Publisher</InputLabel>
<Select
labelId="publisher-label"
label="Publisher"
value={selection.publisher}
onChange={(event) => setSelection((prev) => ({ ...prev, publisher: event.target.value }))}
>
{(publishersQuery.data ?? []).map((publisher) => (
<MenuItem key={publisher} value={publisher}>
{publisher}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<FormControl fullWidth disabled={!selection.publisher}>
<InputLabel id="offer-label">Offer</InputLabel>
<Select
labelId="offer-label"
label="Offer"
value={selection.offer}
onChange={(event) => setSelection((prev) => ({ ...prev, offer: event.target.value }))}
>
{(offersQuery.data ?? []).map((offer) => (
<MenuItem key={offer} value={offer}>
{offer}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<FormControl fullWidth disabled={!selection.offer}>
<InputLabel id="sku-label">SKU</InputLabel>
<Select
labelId="sku-label"
label="SKU"
value={selection.sku}
onChange={(event) => setSelection((prev) => ({ ...prev, sku: event.target.value }))}
>
{(skusQuery.data ?? []).map((sku) => (
<MenuItem key={sku} value={sku}>
{sku}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<FormControl fullWidth disabled={!selection.sku}>
<InputLabel id="version-label">Version</InputLabel>
<Select
labelId="version-label"
label="Version"
value={selection.version}
onChange={(event) => setSelection((prev) => ({ ...prev, version: event.target.value }))}
>
{(versionsQuery.data ?? []).map((version) => (
<MenuItem key={version} value={version}>
{version}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 8 }}>
<FormControl fullWidth>
<InputLabel id="template-label">Usage scenario</InputLabel>
<Select
labelId="template-label"
label="Usage scenario"
value={templateFile}
onChange={(event) => setTemplateFile(event.target.value)}
>
{(templatesQuery.data ?? []).map((template) => (
<MenuItem key={template.file} value={template.file}>
{template.label}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Button fullWidth variant="contained" sx={{ height: "100%" }} onClick={onRender} disabled={!canRender}>
Generate usage
</Button>
</Grid>
</Grid>
</CardContent>
</Card>
)}
{renderError ? <Alert severity="error">{renderError}</Alert> : null}
{renderedUsage ? (
<Card>
<CardContent>
<Typography variant="h6">Usage snippet</Typography>
<Box component="pre" sx={{ overflowX: "auto", p: 2, bgcolor: "#0f172a", color: "#e2e8f0", borderRadius: 2 }}>
{renderedUsage}
</Box>
</CardContent>
</Card>
) : null}
{skuExport ? (
<Card>
<CardContent>
<Typography variant="h6">Available SKUs</Typography>
<Box component="pre" sx={{ overflowX: "auto", p: 2, bgcolor: "#111827", color: "#d1fae5", borderRadius: 2 }}>
{skuExport}
</Box>
</CardContent>
</Card>
) : null}
</Stack>
</Container>
</Box>
);
};
export default App;