feat: full UI redesign with design system, Nielsen heuristics compliance

- Install lucide-react, framer-motion, react-hot-toast, clsx, tailwind-merge
- Custom Tailwind config: semantic color tokens, Inter font, shadow scale,
  border radius scale, custom animations (fadeIn, slideUp, shimmer)
- Shared component library: Button, Badge, Card, Input, Select, Textarea,
  EmptyState, Skeleton, LoadingSpinner
- Global CSS with @layer components (.btn, .card, .input, .badge, .skeleton)
- Toast notification system via react-hot-toast + showToast utility
- ErrorBoundary wrapper for graceful error recovery
- Redesigned navigation: sticky, active state indicators, Lucide icons
- Dashboard: hero header, today highlighting, scrollable week grid,
  redesigned meal cards, empty states, skeleton loading
- Meal Detail: hero image with gradient overlay, metadata row with icons,
  Lucide star rating, edit-existing-feedback flow
- Pantry: inline add form, search/filter, visual quantity badges,
  expiry warnings, confirmation dialogs
- Shopping List: gradient summary cards, aisle grouping with badges,
  sale strikethrough pricing, empty state
- Login: centered card with icon, Input component, Button component
- All old gray/blue utility classes migrated to new surface/primary tokens
- TypeScript clean, production build passes
This commit is contained in:
2026-05-14 10:26:53 -07:00
parent f7ed10651b
commit 6a0c9d0c4e
23 changed files with 1772 additions and 528 deletions
+58 -45
View File
@@ -1,5 +1,6 @@
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
import { BrowserRouter, Routes, Route, Link, useLocation } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ErrorBoundary } from './components/ErrorBoundary'
import Dashboard from './pages/Dashboard'
import MealDetail from './pages/MealDetail'
import Pantry from './pages/Pantry'
@@ -9,52 +10,64 @@ import { mealPlannerApi } from './api'
const queryClient = new QueryClient()
function App() {
function Navigation() {
const location = useLocation()
const isActive = (path: string) => location.pathname === path
const linkClass = (path: string) =>
`inline-flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors ${
isActive(path)
? 'text-primary-700 bg-primary-50'
: 'text-surface-600 hover:bg-surface-100'
}`
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex space-x-8">
<Link to="/" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900">
MealPlanner
</Link>
<Link to="/pantry" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-500 hover:text-gray-900">
Pantry
</Link>
<Link to="/shopping-list" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-500 hover:text-gray-900">
Shopping List
</Link>
</div>
<div className="flex items-center">
<button
onClick={async () => {
await mealPlannerApi.auth.logout().catch(() => {})
window.location.href = '/login'
}}
className="text-sm text-gray-500 hover:text-gray-900"
>
Sign out
</button>
</div>
</div>
</div>
</nav>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/meals/:id" element={<MealDetail />} />
<Route path="/pantry" element={<Pantry />} />
<Route path="/shopping-list" element={<ShoppingList />} />
<Route path="/login" element={<Login />} />
</Routes>
</main>
<nav className="bg-white border-b border-surface-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-14">
<div className="flex items-center space-x-1">
<Link to="/" className={linkClass('/')}>MealPlanner</Link>
<Link to="/pantry" className={linkClass('/pantry')}>Pantry</Link>
<Link to="/shopping-list" className={linkClass('/shopping-list')}>Shopping List</Link>
</div>
<div className="flex items-center">
<button
onClick={async () => {
await mealPlannerApi.auth.logout().catch(() => {})
window.location.href = '/login'
}}
className="text-sm font-medium text-surface-500 hover:text-surface-900 px-3 py-2 rounded-lg hover:bg-surface-100 transition-colors"
>
Sign out
</button>
</div>
</div>
</BrowserRouter>
</QueryClientProvider>
</div>
</nav>
)
}
export default App
function App() {
return (
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<div className="min-h-screen bg-surface-50">
<Navigation />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/meals/:id" element={<MealDetail />} />
<Route path="/pantry" element={<Pantry />} />
<Route path="/shopping-list" element={<ShoppingList />} />
<Route path="/login" element={<Login />} />
</Routes>
</main>
</div>
</BrowserRouter>
</QueryClientProvider>
</ErrorBoundary>
)
}
export default App