Conversation
- Remove duplicate 401 interceptor in client.ts that raced with AuthContext refresh logic
- Remove duplicate request interceptor from AuthContext (kept in client.ts)
- Add htmlFor/id association to FormField for accessible label clicks
- Convert ConfirmDialog from div overlay to native <dialog> with role="alertdialog"
- Short-circuit DetailDrawer render when closed to prevent child data-fetching
- Replace raw useState/useEffect fetch in AuditLog with TanStack Query hook
- Fix AuditLog Fragment key warning by using <Fragment key={log.id}>
- Replace hardcoded hex colors in Settings.module.css with CSS variables
- Add --accent-teal-dark CSS variable, replace hardcoded #00A67E in Sidebar
- Remove non-functional cursor/hover from tenant switcher
- Add role="presentation" to mobile sidebar backdrop
- Redirect authenticated users away from /login and /register routes
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Reports: replace hardcoded CHART_COLORS with CSS variable reads - Reports: replace inline table styles with CrudPage.module.css classes - Suppliers: extract performance modal inline styles to Suppliers.module.css - CsvImport: replace inline result card styles with CSS classes, add toast for non-CSV files - Login/Register: add password visibility toggle (Eye/EyeOff) - Register: add inline password mismatch validation on blur, success toast - Register: consolidate CSS to shared Login.module.css - Inventory: add sortable column headers (SKU, Name, Qty, Unit Price) - Inventory: replace hand-rolled Prev/Next with shared Pagination component - Inventory: add id/htmlFor to Category and Warehouse select fields - PurchaseOrders: keep pipeline visible when filter active, highlight active stage - Categories & Warehouses: add ExportDropdown with CSV + PDF export - StockMovements: fix button size from sm to md - Sidebar: add ConfirmDialog before logout Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Create shared ErrorState component with retry button - Add ErrorState to all CRUD pages (Categories, Warehouses, Inventory, Suppliers, PurchaseOrders, StockMovements) for API failure recovery - Disable save/delete buttons during mutation with "Saving..." text - Add aria-labels to all icon-only action buttons and checkboxes - ExportDropdown: add keyboard navigation (ArrowUp/Down, Enter, Escape) - ExportDropdown: add ARIA attributes (role="menu", aria-expanded) - Header: only render search input when onSearchClick is provided - Dashboard.module.css: remove unused classes (contentGrid, contentGridWithFeed, tableWrap, hideMobile, heatmapSection) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Preview DeployStatus: ✅ Deployed successfully Deployment History
Build Logs (last 50 lines)``` dist/assets/suppliers-CRoaLqw8.js 0.46 kB │ gzip: 0.23 kB dist/assets/FormField-Czi0nmxX.js 0.53 kB │ gzip: 0.34 kB dist/assets/shared.module-BcxFr9pL.js 0.57 kB │ gzip: 0.28 kB dist/assets/usePagination-B9fiFiWr.js 0.68 kB │ gzip: 0.43 kB dist/assets/exportCsv-BU8gXNVC.js 0.74 kB │ gzip: 0.50 kB dist/assets/DetailDrawer-Ct8bM7UZ.js 1.08 kB │ gzip: 0.57 kB dist/assets/Pagination-zNaoGULI.js 1.28 kB │ gzip: 0.70 kB dist/assets/useBulkSelect-DCzvbVYa.js 1.43 kB │ gzip: 0.74 kB dist/assets/CrudPage.module-DckKj5DD.js 1.79 kB │ gzip: 0.75 kB dist/assets/useTableSort-LoniQF40.js 1.95 kB │ gzip: 0.94 kB dist/assets/CsvImport-BJ_ySymu.js 3.40 kB │ gzip: 1.33 kB dist/assets/AuditLog-BxFaE0Nx.js 5.32 kB │ gzip: 2.06 kB dist/assets/Reports-BbJ3kGWM.js 5.44 kB │ gzip: 2.01 kB dist/assets/Categories-DzVOyvWb.js 6.04 kB │ gzip: 2.26 kB dist/assets/Warehouses-Bz2AuWq8.js 7.04 kB │ gzip: 2.54 kB dist/assets/StockMovements-DSV5x0b-.js 7.04 kB │ gzip: 2.67 kB dist/assets/Suppliers-BEl4blXs.js 9.23 kB │ gzip: 3.13 kB dist/assets/Settings-Cl8ucCdw.js 10.93 kB │ gzip: 3.39 kB dist/assets/Analytics-DsCqdOYe.js 11.06 kB │ gzip: 3.86 kB dist/assets/PurchaseOrders-Lb5F2B4k.js 13.81 kB │ gzip: 4.55 kB dist/assets/Inventory-CeyPjMYz.js 16.28 kB │ gzip: 4.73 kB dist/assets/purify.es-CovBOfck.js 22.58 kB │ gzip: 8.67 kB dist/assets/vendor-react-B9CkSc1y.js 48.73 kB │ gzip: 17.17 kB dist/assets/index.es-Bqw8QK-r.js 158.75 kB │ gzip: 53.02 kB dist/assets/html2canvas.esm-DXEQVQnt.js 201.04 kB │ gzip: 47.43 kB dist/assets/vendor-recharts-C9M_SBP_.js 411.05 kB │ gzip: 120.07 kB dist/assets/exportPdf--w_gQJe0.js 419.18 kB │ gzip: 136.62 kB dist/assets/index-C1J_kQfD.js 473.72 kB │ gzip: 146.32 kB ✓ built in 6.88s Build Completed in .vercel/output [23s] Retrieving project… Deploying abdallah-safis-projects-bae5f9c3/logistics-dashboard Uploading [--------------------] (0.0B/1.2MB) Uploading [=====---------------] (301.5KB/1.2MB) Uploading [==========----------] (607.7KB/1.2MB) Uploading [===============-----] (895.7KB/1.2MB) Uploading [====================] (1.2MB/1.2MB) Inspect: https://vercel.com/abdallah-safis-projects-bae5f9c3/logistics-dashboard/CWnigXaMsGRNKNeK18yMSKhrqQyk [3s] Preview: https://logistics-dashboard-73k9qhdht-abdallah-safis-projects-bae5f9c3.vercel.app [3s] https://logistics-dashboard-73k9qhdht-abdallah-safis-projects-bae5f9c3.vercel.appBuilding... Building... Building: Running build in Portland, USA (West) – pdx1 Building: Build machine configuration: 2 cores, 8 GB Building: Retrieving list of deployment files... Building: Previous build caches not available. Building: Downloading 49 deployment files... Preview: https://logistics-dashboard-73k9qhdht-abdallah-safis-projects-bae5f9c3.vercel.app [7s] Completing... To deploy to production (logistics-dashboard-pi.vercel.app), run `vercel --prod` |
Code Review: PR #27 — UI/UX Polish (42 issues across 3 waves)Executive Summary
Top Risks1. ConfirmDialog: early
|
| cancelRef.current?.focus(); | ||
| }, [open]); | ||
|
|
||
| if (!open) return null; |
There was a problem hiding this comment.
High: The early return null executes before the useEffect (lines 30-35) can call dialog.close(). When open goes true→false, the <dialog> is unmounted from DOM while still in the browser's top-layer/modal state. This can cause showModal() to throw on subsequent opens.
Fix: always render the <dialog> element, conditionally render only the card contents:
return (
<dialog ref={dialogRef} className={styles.overlay} ...>
{open && <div className={styles.card}>...</div>}
</dialog>
);|
|
||
| try { | ||
| await register(email, password, fullName || undefined); | ||
| addToast('Account created successfully!', 'success'); |
There was a problem hiding this comment.
Medium: addToast() fires but navigate() immediately unmounts this page and its <ToastContainer>, so the user never sees the success toast. Consider a global toast provider, removing the toast, or delaying navigation.
| const CHART_COLORS = ['#00D4AA', '#58A6FF', '#F59E0B', '#3FB950', '#F85149', '#A371F7', '#D2A8FF', '#8B949E']; | ||
| function getChartColors(): string[] { | ||
| const root = getComputedStyle(document.documentElement); | ||
| return Array.from({ length: 8 }, (_, i) => root.getPropertyValue(`--chart-color-${i + 1}`).trim() || '#8B949E'); |
There was a problem hiding this comment.
Medium: getChartColors() calls getComputedStyle() and reads 8 CSS properties on every render. Wrap in useMemo:
const chartColors = useMemo(() => getChartColors(), []);|
|
||
| export default function FormField({ label, error, ...props }: FormFieldProps) { | ||
| export default function FormField({ label, error, id, ...props }: FormFieldProps) { | ||
| const fieldId = id || label.toLowerCase().replace(/\s+/g, '-'); |
There was a problem hiding this comment.
Low: This ID generation collides if two FormFields with the same label exist simultaneously (e.g., modal form + page form both with "Name"). Use useId() as fallback:
const autoId = useId();
const fieldId = id || autoId;| @@ -53,6 +55,7 @@ interface SidebarProps { | |||
| export default function Sidebar({ lowStockCount, apiLatency, apiConnected = true, mobileOpen, onClose }: SidebarProps) { | |||
There was a problem hiding this comment.
Low: The <ConfirmDialog> block is duplicated in both mobile (line 152) and desktop (line 169) return paths. Extract it outside the conditional to avoid the duplication.
Code Review: fix: UI/UX polish — 42 issues across 3 wavesReviewer verdict: REQUEST_CHANGES — 2 bugs to fix before merge, several medium items to address. Executive Summary
Top Risks1. ConfirmDialog:
|
There was a problem hiding this comment.
REQUEST_CHANGES — 2 blocking issues, 6 medium items.
Blocking:
-
ConfirmDialog race condition —
if (!open) return nullon line 42 conflicts withshowModal()effect. The dialog may render without modal behavior (no backdrop, no focus trap, no top-layer). Remove the early return and always render the<dialog>element. -
401 interceptor gap — removing the 401 handler from
client.tscreates a window where early API requests (before AuthProvider mounts) silently swallow 401 errors. Keep a baseline handler at module scope.
Medium (should fix):
3. Hardcoded rgba() in CrudPage.module.css and ConfirmDialog.module.css — add CSS variables
4. Sidebar has duplicated ConfirmDialog JSX — render once outside the conditional
5. AuditLog params object needs useMemo to prevent cache misses
6. ExportDropdown: focus return on Escape, Tab handling, aria-controls
7. useAuditLogQueries should use AuditLogParams type, not Record<string, string | number>
8. Pagination.tsx imports CrudPage.module.css — extract its own CSS Module
See full review comment for details, quick wins, and non-blocking suggestions.
Summary
Comprehensive UI/UX polish addressing 42 actionable issues found during a code-level audit of the React dashboard. Changes organized into 3 waves:
Wave 1: Critical Bugs + Rule Violations
<dialog>withrole="alertdialog"and focus traphtmlFor/idlabel association to FormField component--accent-teal-darkCSS variable, remove non-functional tenant switcher hoverWave 2: UX Improvements + Consistency
Wave 3: Accessibility + Error States + Polish
<ErrorState>component with AlertTriangle icon and retry buttonaria-labelto all icon-only action buttons and bulk select checkboxesonSearchClickis providedTest plan
npm run buildpasses with no errorsnpm test— all 69 tests pass<dialog>, Tab trapped, Escape closes🤖 Generated with Claude Code