React frontend for Brew Inventory, a full-stack inventory management app for a specialty tea and coffee store.
Live: brew-inventory.pages.dev
Note: The backend is hosted on Render's free tier, which spins down after inactivity. The first request after idle may take 30–50 seconds while the server wakes up. Subsequent requests are fast.
brew-inventory-server — Express/PostgreSQL API
- Browse inventory by category or view all products at once
- Filter products by category, availability and product attributes
- Search products by name
- Add, edit, and delete products with category-specific attribute fields
- Availability status derived from stock quantity (in stock / low stock / out of stock)
- Fully responsive: mobile bottom navigation, desktop top navigation
- Accessible markup throughout
To edit or delete products, enter the admin password when prompted:
Password: brew123
- React with Vite
- React Router for client-side routing
- Vitest and React Testing Library for unit and integration testing
- use-debounce for debounced search input
- PropTypes for runtime prop validation
- ESLint for linting
| Route | Component | Description |
|---|---|---|
/ |
Categories |
Grid of all categories with product counts |
/products |
Products |
Full product list with search and filter |
/categories/:id |
Products |
Products filtered by category |
/products/:id |
Product |
Single product detail with attributes |
/products/new |
AddProduct |
Add a new product |
/products/:id/edit |
EditProduct |
Edit an existing product |
└── 📁brew-inventory-client
└── 📁public
├── apple-touch-icon.png
├── favicon-96x96.png
├── favicon.ico
├── favicon.svg
├── icons.svg
├── robots.txt
├── site.webmanifest
├── web-app-manifest-192x192.png
├── web-app-manifest-512x512.png
└── 📁screenshots
├── categories-desktop.png
├── categories-mobile.png
├── product-detail-desktop.png
├── product-detail-mobile.png
├── products-desktop.png
├── products-mobile.png
└── 📁src
└── 📁assets
├── logo.png
└── 📁components
└── 📁BottomNav
├── BottomNav.jsx
├── BottomNav.module.css
├── BottomNav.test.jsx
└── 📁BottomNavLink
├── BottomNavLink.jsx
├── BottomNavLink.module.css
├── BottomNavLink.test.jsx
└── 📁Breadcrumb
├── Breadcrumb.jsx
├── Breadcrumb.module.css
├── Breadcrumb.test.jsx
└── 📁FieldError
├── FieldError.jsx
├── FieldError.module.css
├── FieldError.test.jsx
└── 📁FormError
├── FormError.jsx
├── FormError.module.css
├── FormError.test.jsx
└── 📁Header
├── Header.jsx
├── Header.module.css
├── Header.test.jsx
└── 📁NavLink
├── NavLink.jsx
├── NavLink.module.css
├── NavLink.test.jsx
└── 📁ProductForm
├── ProductForm.jsx
├── ProductForm.module.css
├── ProductForm.test.jsx
└── 📁hooks
├── useAvailability.jsx
├── useAvailability.test.js
├── useIsWide.js
├── useIsWide.test.js
├── useSections.js
├── useSections.test.js
└── 📁pages
└── 📁addProduct
└── 📁components
└── 📁AddProduct
├── AddProduct.jsx
├── AddProduct.test.jsx
└── 📁categories
└── 📁components
└── 📁Categories
├── Categories.jsx
├── Categories.module.css
├── Categories.test.jsx
└── 📁Category
├── Category.jsx
├── Category.module.css
├── Category.test.jsx
└── 📁SkeletonCategories
├── SkeletonCategories.jsx
├── SkeletonCategories.module.css
└── 📁editProduct
└── 📁components
└── 📁EditProduct
├── EditProduct.jsx
├── EditProduct.test.jsx
└── 📁SkeletonEditProduct
├── SkeletonEditProduct.jsx
├── SkeletonEditProduct.module.css
└── 📁error
└── 📁components
└── 📁ErrorState
├── ErrorState.jsx
├── ErrorState.module.css
├── ErrorState.test.jsx
└── 📁productDetail
└── 📁components
└── 📁DeleteModal
├── DeleteModal.jsx
├── DeleteModal.module.css
├── DeleteModal.test.jsx
└── 📁ProductDetail
├── ProductDetail.jsx
├── ProductDetail.module.css
├── ProductDetail.test.jsx
└── 📁SkeletonProductDetail
├── SkeletonProductDetail.jsx
├── SkeletonProductDetail.module.css
└── 📁products
└── 📁components
└── 📁FilterBottomSheet
├── FilterBottomSheet.jsx
├── FilterBottomSheet.module.css
├── FilterBottomSheet.test.jsx
└── 📁FilterDropdown
├── FilterDropdown.jsx
├── FilterDropdown.module.css
├── FilterDropdown.test.jsx
└── 📁FilterEmptyState
├── FilterEmptyState.jsx
├── FilterEmptyState.module.css
└── 📁FilterSection
├── FilterSection.jsx
├── FilterSection.module.css
├── FilterSection.test.jsx
└── 📁Product
├── Product.jsx
├── Product.module.css
├── Product.test.jsx
└── 📁ProductEmptyState
├── ProductEmptyState.jsx
├── ProductEmptyState.module.css
├── ProductEmptyState.test.jsx
└── 📁ProductRow
├── ProductRow.jsx
├── ProductRow.module.css
├── ProductRow.test.jsx
└── 📁Products
├── Products.jsx
├── Products.module.css
├── Products.test.jsx
└── 📁ProductsTable
├── ProductsTable.jsx
├── ProductsTable.module.css
├── ProductsTable.test.jsx
└── 📁SearchBar
├── SearchBar.jsx
├── SearchBar.module.css
├── SearchBar.test.jsx
└── 📁SheetChip
├── SheetChip.jsx
├── SheetChip.module.css
├── SheetChip.test.jsx
└── 📁SkeletonProducts
├── SkeletonProducts.jsx
├── SkeletonProducts.module.css
└── 📁router
├── routes.jsx
└── 📁styles
├── App.css
├── normalize.css
└── 📁tests
└── 📁integration
├── addProductForm.test.jsx
├── filterProducts.test.jsx
├── searchProducts.test.jsx
└── 📁utils
├── filterUtils.js
├── filterUtils.test.js
├── utils.js
├── utils.test.js
├── App.jsx
├── constants.js
├── contexts.js
├── main.jsx
├── .gitignore
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── README.md
├── test-setup.js
└── vite.config.js
- Node.js
- The brew-inventory-server running locally
- Clone the repository
git clone https://github.com/Maddily/brew-inventory-client.git
cd brew-inventory-client- Install dependencies
npm install- Start the development server
npm run devThe app will be running at http://localhost:5173.
Make sure the API server is running at
http://localhost:3000before using the app.
Hosted on Cloudflare Pages. VITE_API_URL is set as a build-time environment variable pointing to the deployed brew-inventory-server API on Render.
Unit tests:
npm testIntegration tests:
npm run test:integrationUnit tests cover components, hooks, and utility functions in isolation using Vitest and React Testing Library, with network requests mocked. Integration tests exercise real component trees together — filling out the actual product form, selecting real filter chips, and typing in the real search bar — to verify the seams between components work end to end, not just each piece in isolation.





