fix(admin): confirm writes succeeded and allow inline category creation (#23)
Reported from testing: the item form said "Item added" for a save that never happened. saveItem and the other admin calls returned res.json() without checking res.ok, so a 4xx/5xx resolved normally and every caller reported success for a write the server had rejected. That is worse than failing outright, because nothing prompts the user to look for the missing row. All admin calls now throw on a non-OK response, and the handlers report the error, keep the form open so entered values survive, and only claim success once the server has accepted the write. Mark sold/available previously did nothing visible on failure at all. Categories can now be created from the item form, as tags already could. Previously a category that did not exist yet meant abandoning a half-filled form for the Categories tab. New categories are created at the top level; nesting stays in the Categories tab. The control lives in its own component: inline, every keystroke re-rendered the whole Inventory component and rebuilt the category tree, which visibly jittered the open popup. It sits above the tree rather than below it, where a long list both hid it and made its position depend on the list's measured height. The tree no longer expands everything on open, which does not scale past a screenful; it has search instead. The app now honours prefers-reduced-motion by disabling antd transitions, and the e2e suite runs with that preference set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+24
-2
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { ConfigProvider, theme as antdTheme } from 'antd';
|
||||
@@ -16,13 +16,35 @@ import { CartProvider } from './cart/CartContext';
|
||||
import { ThemeModeProvider, useThemeMode } from './theme/ThemeContext';
|
||||
import './styles.css';
|
||||
|
||||
const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)';
|
||||
|
||||
// Respects the OS-level "reduce motion" accessibility setting by turning off
|
||||
// antd's transitions. Beyond the accessibility win, animated popups are a
|
||||
// standing source of flake in end-to-end tests, which drive the app with this
|
||||
// preference enabled.
|
||||
function usePrefersReducedMotion(): boolean {
|
||||
const [prefers, setPrefers] = useState(
|
||||
() => typeof window !== 'undefined' && window.matchMedia(REDUCED_MOTION_QUERY).matches
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const query = window.matchMedia(REDUCED_MOTION_QUERY);
|
||||
const update = () => setPrefers(query.matches);
|
||||
query.addEventListener('change', update);
|
||||
return () => query.removeEventListener('change', update);
|
||||
}, []);
|
||||
|
||||
return prefers;
|
||||
}
|
||||
|
||||
function Root() {
|
||||
const { mode } = useThemeMode();
|
||||
const prefersReducedMotion = usePrefersReducedMotion();
|
||||
return (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
algorithm: mode === 'dark' ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
||||
token: { colorPrimary: '#1a1a1a' }
|
||||
token: { colorPrimary: '#1a1a1a', motion: !prefersReducedMotion }
|
||||
}}
|
||||
>
|
||||
<BrowserRouter>
|
||||
|
||||
Reference in New Issue
Block a user