Seven reported items, of which the first four had two root causes. The active tab was invisible in dark mode because colorPrimary was hardcoded to #1a1a1a in both themes. The accent now inverts with the theme, and colorTextLightSolid inverts with it, or a near-white accent would get antd's default white label and disappear. The Category tab, Tag tab, and item-form category selector ignored the theme entirely. antd declares main: lib/index.js and module: es/index.js, so importing from 'antd' resolves to the ES build while 'antd/lib/...' loads the CommonJS one — two copies, two React contexts, and no ConfigProvider for anything deep-imported. Switching those files to antd/es/* keeps the deep-import convention and shares the instance. This was introduced by my own use of the lib path; es is correct under Vite. Two storefront components had the same latent bug. "Colour" is now "Color". The Customers tab shows how many items each customer is holding, as a link opening the item list with a Release button. Release mirrors the customer's own cart removal — drop the cart row, return the item to available, guarded on 'reserved' so it can never resurrect a sold item — and deliberately sends no email about an action the customer did not take. The count is a subquery rather than another join, which would have multiplied rows and inflated order_count and total_spent_cents. The Inventory tab filters by category, tags, price, and status, reusing the storefront's parser and query builder so the two cannot drift. Reserved is one option in a Status filter rather than a standalone toggle. Also fixes two defects the screenshots exposed: the reserved-count link bubbled to the row handler and opened the customer drawer behind the dialog, and .admin-category-node had no CSS at all, so the tree node name, item count, and actions ran together as one string. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
92 lines
3.4 KiB
TypeScript
Executable File
92 lines
3.4 KiB
TypeScript
Executable File
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';
|
|
import 'antd/dist/reset.css';
|
|
import App from './App';
|
|
import Admin from './admin/Admin';
|
|
import Login from './customer/Login';
|
|
import Register from './customer/Register';
|
|
import Account from './customer/Account';
|
|
import PrivacyPolicy from './customer/PrivacyPolicy';
|
|
import VerifyEmail from './customer/VerifyEmail';
|
|
import Cart from './cart/Cart';
|
|
import { CustomerAuthProvider } from './customer/CustomerAuthContext';
|
|
import { CartProvider } from './cart/CartContext';
|
|
import { ThemeModeProvider, useThemeMode } from './theme/ThemeContext';
|
|
import './styles.css';
|
|
|
|
// The brand accent is monochrome, so it inverts between themes rather than
|
|
// switching to a different hue.
|
|
const LIGHT_ACCENT = '#1a1a1a';
|
|
const DARK_ACCENT = '#f0f0f0';
|
|
|
|
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: {
|
|
// The accent inverts with the theme rather than staying near-black.
|
|
// Held constant, it rendered the active tab label in #1a1a1a on a
|
|
// dark background — invisible.
|
|
colorPrimary: mode === 'dark' ? DARK_ACCENT : LIGHT_ACCENT,
|
|
// Text drawn *on* the accent (primary buttons, selected rows) has to
|
|
// invert with it too, or a near-white accent gets antd's default
|
|
// white label and disappears.
|
|
colorTextLightSolid: mode === 'dark' ? LIGHT_ACCENT : '#ffffff',
|
|
motion: !prefersReducedMotion
|
|
}
|
|
}}
|
|
>
|
|
<BrowserRouter>
|
|
<Routes>
|
|
<Route path="/" element={<App />} />
|
|
<Route path="/admin" element={<Admin />} />
|
|
<Route path="/login" element={<Login />} />
|
|
<Route path="/register" element={<Register />} />
|
|
<Route path="/account" element={<Account />} />
|
|
<Route path="/cart" element={<Cart />} />
|
|
<Route path="/privacy" element={<PrivacyPolicy />} />
|
|
<Route path="/verify-email" element={<VerifyEmail />} />
|
|
</Routes>
|
|
</BrowserRouter>
|
|
</ConfigProvider>
|
|
);
|
|
}
|
|
|
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
<React.StrictMode>
|
|
<ThemeModeProvider>
|
|
<CustomerAuthProvider>
|
|
<CartProvider>
|
|
<Root />
|
|
</CartProvider>
|
|
</CustomerAuthProvider>
|
|
</ThemeModeProvider>
|
|
</React.StrictMode>
|
|
);
|