new product admin page to flag bad products

This commit is contained in:
2025-12-30 09:04:56 -05:00
parent cc386d14f4
commit ee91fa1353
4 changed files with 703 additions and 446 deletions
+46 -29
View File
@@ -92,10 +92,10 @@ const CATEGORY_GROUPS: {
categoryIds: [
"lower-receiver",
"complete-lower",
"lower-parts-kit", // ✅ was lower-parts
"lower-parts-kit",
"trigger",
"grip",
"safety-selector", // ✅ was safety
"safety-selector",
"buffer",
"stock",
],
@@ -192,6 +192,21 @@ export default function GunbuilderPage() {
return {};
});
type PageResponse<T> = {
content: T[];
totalElements?: number;
totalPages?: number;
number?: number;
size?: number;
};
function unwrapPage<T>(json: any): T[] {
if (!json) return [];
if (Array.isArray(json)) return json;
if (Array.isArray(json.content)) return json.content;
return [];
}
// -----------------------------
// Toast notifications (save success / errors)
// -----------------------------
@@ -374,10 +389,11 @@ export default function GunbuilderPage() {
setLoading(true);
setError(null);
const size = 2000; // temporary "big bucket" until we optimize
const scopedUrl = `${API_BASE_URL}/api/v1/products?platform=${encodeURIComponent(
platform
)}`;
const universalUrl = `${API_BASE_URL}/api/v1/products`;
)}&page=0&size=${size}`;
const universalUrl = `${API_BASE_URL}/api/v1/products?page=0&size=${size}`;
const [scopedRes, universalRes] = await Promise.all([
fetch(scopedUrl, { signal: controller.signal }),
@@ -391,13 +407,15 @@ export default function GunbuilderPage() {
throw new Error(`Failed to load products (${status})`);
}
const scopedData: GunbuilderProductFromApi[] = await scopedRes.json();
const scopedJson = await scopedRes.json();
const scopedData: GunbuilderProductFromApi[] =
unwrapPage<GunbuilderProductFromApi>(scopedJson);
let universalData: GunbuilderProductFromApi[] = [];
if (universalRes && universalRes.ok) {
universalData = await universalRes.json();
const universalJson = await universalRes.json();
universalData = unwrapPage<GunbuilderProductFromApi>(universalJson);
}
const normalize = (data: GunbuilderProductFromApi[]): Part[] =>
data
.map((p): Part | null => {
@@ -649,31 +667,30 @@ export default function GunbuilderPage() {
}
};
// -----------------------------
// Selection handler (with hint warnings)
// -----------------------------
const handleSelectPart = useCallback(
(
categoryId: BuilderSlotKey,
partId: string,
_opts?: { confirm?: boolean }
) => {
const warn = (msg: string) => {
setShareStatus(msg);
window.setTimeout(() => setShareStatus(null), 4000);
};
// Selection handler (with hint warnings)
// -----------------------------
const handleSelectPart = useCallback(
(
categoryId: BuilderSlotKey,
partId: string,
_opts?: { confirm?: boolean }
) => {
const warn = (msg: string) => {
setShareStatus(msg);
window.setTimeout(() => setShareStatus(null), 4000);
};
const hints = getSelectionHints(categoryId, build);
if (hints.length > 0) warn(hints[0]);
const hints = getSelectionHints(categoryId, build);
if (hints.length > 0) warn(hints[0]);
setBuild((prev) => ({
...prev,
[categoryId]: partId,
}));
},
[build]
);
setBuild((prev) => ({
...prev,
[categoryId]: partId,
}));
},
[build]
);
// -----------------------------
// Handle URL query parameters:
// - ?select=categoryId:partId