From 11f23eb643154e38398e97ccff8ceff25cf33bd1 Mon Sep 17 00:00:00 2001 From: Nico Date: Tue, 27 Jan 2026 00:03:58 -0800 Subject: [PATCH] styling fix and readme files reorg --- .github/copilot-instructions.md | 173 +++++++++-- backend/controllers/stores.controller.js | 11 +- backend/models/household.model.js | 12 +- docs/README.md | 74 +++++ .../{ => architecture}/component-structure.md | 0 .../multi-household-architecture-plan.md | 0 .../ACCOUNT_MANAGEMENT_IMPLEMENTATION.md | 0 .../HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md | 0 .../archive/IMPLEMENTATION_STATUS.md | 0 docs/archive/TEST_SUITE_README.md | 43 +++ docs/{ => archive}/code-cleanup-guide.md | 0 docs/{ => archive}/settings-dark-mode.md | 0 .../classification-implementation.md | 0 .../image-storage-implementation.md | 0 docs/guides/MOBILE_RESPONSIVE_AUDIT.md | 283 ++++++++++++++++++ docs/{ => guides}/api-documentation.md | 0 docs/{ => guides}/frontend-readme.md | 0 docs/{ => guides}/setup-checklist.md | 0 docs/migration/MIGRATION_GUIDE.md | 243 +++++++++++++++ .../migration/POST_MIGRATION_UPDATES.md | 0 frontend/src/api/stores.js | 2 +- frontend/src/components/layout/Navbar.jsx | 81 ++++- .../src/components/manage/ManageStores.jsx | 9 +- frontend/src/styles/AddImageModal.css | 35 +++ frontend/src/styles/ImageModal.css | 17 ++ frontend/src/styles/ImageUploadModal.css | 64 ++++ .../src/styles/ItemClassificationModal.css | 49 +++ frontend/src/styles/SimilarItemModal.css | 20 ++ .../components/AddItemWithDetailsModal.css | 59 +++- .../components/ConfirmAddExistingModal.css | 27 ++ .../src/styles/components/EditItemModal.css | 80 +++++ .../styles/components/HouseholdSwitcher.css | 12 +- frontend/src/styles/components/Navbar.css | 236 +++++++++++++-- frontend/src/styles/pages/Settings.css | 7 + 34 files changed, 1442 insertions(+), 95 deletions(-) create mode 100644 docs/README.md rename docs/{ => architecture}/component-structure.md (100%) rename docs/{ => architecture}/multi-household-architecture-plan.md (100%) rename ACCOUNT_MANAGEMENT_IMPLEMENTATION.md => docs/archive/ACCOUNT_MANAGEMENT_IMPLEMENTATION.md (100%) rename HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md => docs/archive/HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md (100%) rename IMPLEMENTATION_STATUS.md => docs/archive/IMPLEMENTATION_STATUS.md (100%) create mode 100644 docs/archive/TEST_SUITE_README.md rename docs/{ => archive}/code-cleanup-guide.md (100%) rename docs/{ => archive}/settings-dark-mode.md (100%) rename docs/{ => features}/classification-implementation.md (100%) rename docs/{ => features}/image-storage-implementation.md (100%) create mode 100644 docs/guides/MOBILE_RESPONSIVE_AUDIT.md rename docs/{ => guides}/api-documentation.md (100%) rename docs/{ => guides}/frontend-readme.md (100%) rename docs/{ => guides}/setup-checklist.md (100%) create mode 100644 docs/migration/MIGRATION_GUIDE.md rename POST_MIGRATION_UPDATES.md => docs/migration/POST_MIGRATION_UPDATES.md (100%) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 996b319..86b8366 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -7,40 +7,142 @@ This is a full-stack grocery list management app with **role-based access contro - **Frontend**: React 19 + TypeScript + Vite (port 3000/5173) - **Deployment**: Docker Compose with separate dev/prod configurations +## Mobile-First Design Principles + +**CRITICAL**: All UI components MUST be designed for both mobile and desktop from the start. + +**Responsive Design Requirements**: +- Use relative units (`rem`, `em`, `%`, `vh/vw`) over fixed pixels where possible +- Implement mobile breakpoints: `480px`, `768px`, `1024px` +- Test layouts at: 320px (small phone), 375px (phone), 768px (tablet), 1024px+ (desktop) +- Avoid horizontal scrolling on mobile devices +- Touch targets minimum 44x44px for mobile usability +- Use `max-width` with `margin: 0 auto` for content containers +- Stack elements vertically on mobile, use flexbox/grid for larger screens +- Hide/collapse navigation into hamburger menus on mobile +- Ensure modals/dropdowns work well on small screens + +**Common Patterns**: +```css +/* Mobile-first approach */ +.container { + padding: 1rem; + max-width: 100%; +} + +@media (min-width: 768px) { + .container { + padding: 2rem; + max-width: 800px; + margin: 0 auto; + } +} +``` + ### Key Design Patterns -**Three-tier RBAC system** (`viewer`, `editor`, `admin`): -- `viewer`: Read-only access to grocery lists -- `editor`: Can add items and mark as bought -- `admin`: Full user management via admin panel -- Roles defined in [backend/models/user.model.js](backend/models/user.model.js) and mirrored in [frontend/src/constants/roles.js](frontend/src/constants/roles.js) +**Dual RBAC System** - Two separate role hierarchies: + +**1. System Roles** (users.role column): +- `system_admin`: Access to Admin Panel for system-wide management (stores, users) +- `user`: Regular system user (default for new registrations) +- Defined in [backend/models/user.model.js](backend/models/user.model.js) +- Used for Admin Panel access control + +**2. Household Roles** (household_members.role column): +- `admin`: Can manage household members, change roles, delete household +- `user`: Can add/edit items, mark as bought (standard member permissions) +- Defined per household membership +- Used for household-level permissions (item management, member management) + +**Important**: Always distinguish between system role and household role: +- **System role**: From `AuthContext` or `req.user.role` - controls Admin Panel access +- **Household role**: From `activeHousehold.role` or `household_members.role` - controls household operations **Middleware chain pattern** for protected routes: ```javascript -router.post("/add", auth, requireRole(ROLES.EDITOR, ROLES.ADMIN), controller.addItem); +// System-level protection +router.get("/stores", auth, requireRole("system_admin"), controller.getAllStores); + +// Household-level checks done in controller +router.post("/lists/:householdId/items", auth, controller.addItem); ``` - `auth` middleware extracts JWT from `Authorization: Bearer ` header -- `requireRole` checks if user's role matches allowed roles -- See [backend/routes/list.routes.js](backend/routes/list.routes.js) for examples +- `requireRole` checks system role only +- Household role checks happen in controllers using `household.model.js` methods **Frontend route protection**: - ``: Requires authentication, redirects to `/login` if no token -- ``: Requires specific role(s), redirects to `/` if unauthorized +- ``: Requires system_admin role for Admin Panel +- Household permissions: Check `activeHousehold.role` in components (not route-level) - Example in [frontend/src/App.jsx](frontend/src/App.jsx) +**Multi-Household Architecture**: +- Users can belong to multiple households +- Each household has its own grocery lists, stores, and item classifications +- `HouseholdContext` manages active household selection +- All list operations are scoped to the active household + ## Database Schema **PostgreSQL server runs externally** - not in Docker Compose. Connection configured in [backend/.env](backend/.env) via standard environment variables. -**Tables** (inferred from models, no formal migrations): -- **users**: `id`, `username`, `password` (bcrypt hashed), `name`, `role` -- **grocery_list**: `id`, `item_name`, `quantity`, `bought`, `added_by` -- **grocery_history**: Junction table tracking which users added which items +**Core Tables**: + +**users** - System users +- `id` (PK), `username`, `password` (bcrypt), `name`, `display_name` +- `role`: `system_admin` | `user` (default: `viewer` - legacy) +- System-level authentication and authorization + +**households** - Household entities +- `id` (PK), `name`, `invite_code`, `created_by`, `created_at` +- Each household is independent with own lists and members + +**household_members** - Junction table (users ↔ households) +- `id` (PK), `household_id` (FK), `user_id` (FK), `role`, `joined_at` +- `role`: `admin` | `user` (household-level permissions) +- One user can belong to multiple households with different roles + +**items** - Master item catalog +- `id` (PK), `name`, `default_image`, `default_image_mime_type`, `usage_count` +- Shared across all households, case-insensitive unique names + +**stores** - Store definitions (system-wide) +- `id` (PK), `name`, `default_zones` (JSONB array) +- Managed by system_admin in Admin Panel + +**household_stores** - Stores available to each household +- `id` (PK), `household_id` (FK), `store_id` (FK), `is_default` +- Links households to stores they use + +**household_lists** - Grocery list items per household +- `id` (PK), `household_id` (FK), `store_id` (FK), `item_id` (FK) +- `quantity`, `bought`, `custom_image`, `custom_image_mime_type` +- `added_by`, `modified_on` +- Scoped to household + store combination + +**household_list_history** - Tracks quantity contributions +- `id` (PK), `household_list_id` (FK), `quantity`, `added_by`, `added_on` +- Multi-contributor tracking (who added how much) + +**household_item_classifications** - Item classifications per household/store +- `id` (PK), `household_id`, `store_id`, `item_id` +- `item_type`, `item_group`, `zone`, `confidence`, `source` +- Household-specific overrides of global classifications + +**item_classification** - Global item classifications +- `id` (PK), `item_type`, `item_group`, `zone`, `confidence`, `source` +- System-wide defaults for item categorization + +**Legacy Tables** (deprecated, may still exist): +- `grocery_list`, `grocery_history` - Old single-household implementation **Important patterns**: -- No migration system - schema changes are manual SQL +- No formal migration system - schema changes are manual SQL - Items use case-insensitive matching (`ILIKE`) to prevent duplicates -- JOINs with `ARRAY_AGG` for multi-contributor queries (see [backend/models/list.model.js](backend/models/list.model.js)) +- JOINs with `ARRAY_AGG` for multi-contributor queries (see [backend/models/list.model.v2.js](backend/models/list.model.v2.js)) +- All list operations require `household_id` parameter for scoping +- Image storage: `bytea` columns for images with separate MIME type columns ## Development Workflow @@ -137,11 +239,16 @@ See [.gitea/workflows/deploy.yml](.gitea/workflows/deploy.yml) for full workflow ## Authentication Flow -1. User logs in → backend returns `{token, role, username}` ([backend/controllers/auth.controller.js](backend/controllers/auth.controller.js)) +1. User logs in → backend returns `{token, userId, role, username}` ([backend/controllers/auth.controller.js](backend/controllers/auth.controller.js)) + - `role` is the **system role** (`system_admin` or `user`) 2. Frontend stores in `localStorage` and `AuthContext` ([frontend/src/context/AuthContext.jsx](frontend/src/context/AuthContext.jsx)) -3. Axios interceptor auto-attaches `Authorization: Bearer ` header ([frontend/src/api/axios.js](frontend/src/api/axios.js)) -4. Backend validates JWT on protected routes ([backend/middleware/auth.js](backend/middleware/auth.js)) -5. On 401 "Invalid or expired token" response, frontend clears storage and redirects to login +3. `HouseholdContext` loads user's households and sets active household + - Active household includes `household.role` (the **household role**) +4. Axios interceptor auto-attaches `Authorization: Bearer ` header ([frontend/src/api/axios.js](frontend/src/api/axios.js)) +5. Backend validates JWT on protected routes ([backend/middleware/auth.js](backend/middleware/auth.js)) + - Sets `req.user = { id, role, username }` with **system role** +6. Controllers check household membership/role using [backend/models/household.model.js](backend/models/household.model.js) +7. On 401 "Invalid or expired token" response, frontend clears storage and redirects to login ## Critical Conventions @@ -167,16 +274,36 @@ See [.gitea/workflows/deploy.yml](.gitea/workflows/deploy.yml) for full workflow ## Common Tasks **Add a new protected route**: -1. Backend: Add route with `auth` + `requireRole(...)` middleware -2. Frontend: Add route in [frontend/src/App.jsx](frontend/src/App.jsx) wrapped in `` and/or `` +1. Backend: Add route with `auth` middleware (+ `requireRole(...)` if system role check needed) +2. Frontend: Add route in [frontend/src/App.jsx](frontend/src/App.jsx) wrapped in `` (and `` for Admin Panel) **Access user info in backend controller**: ```javascript -const { id, role } = req.user; // Set by auth middleware +const { id, role } = req.user; // Set by auth middleware (system role) +const userId = req.user.id; +``` + +**Check household permissions in backend controller**: +```javascript +const householdRole = await household.getUserRole(householdId, userId); +if (!householdRole) return res.status(403).json({ message: "Not a member of this household" }); +if (householdRole !== 'admin') return res.status(403).json({ message: "Household admin required" }); +``` + +**Check household permissions in frontend**: +```javascript +const { activeHousehold } = useContext(HouseholdContext); +const householdRole = activeHousehold?.role; // 'admin' or 'user' + +// Allow all members except viewers (no viewer role in households) +const canManageItems = householdRole && householdRole !== 'viewer'; // Usually just check if role exists + +// Admin-only actions +const canManageMembers = householdRole === 'admin'; ``` **Query grocery items with contributors**: -Use the JOIN pattern in [backend/models/list.model.js](backend/models/list.model.js) - aggregates user names via `grocery_history` table. +Use the JOIN pattern in [backend/models/list.model.v2.js](backend/models/list.model.v2.js) - aggregates user names via `household_list_history` table. ## Testing diff --git a/backend/controllers/stores.controller.js b/backend/controllers/stores.controller.js index 8fe09f5..51a48f8 100644 --- a/backend/controllers/stores.controller.js +++ b/backend/controllers/stores.controller.js @@ -26,21 +26,20 @@ exports.getHouseholdStores = async (req, res) => { exports.addStoreToHousehold = async (req, res) => { try { const { storeId, isDefault } = req.body; - + // console.log("Adding store to household:", { householdId: req.params.householdId, storeId, isDefault }); if (!storeId) { return res.status(400).json({ error: "Store ID is required" }); } - // Check if store exists const store = await storeModel.getStoreById(storeId); - if (!store) { - return res.status(404).json({ error: "Store not found" }); - } + if (!store) return res.status(404).json({ error: "Store not found" }); + const foundStores = await storeModel.getHouseholdStores(req.params.householdId); + // if (foundStores.length == 0) isDefault = 'true'; await storeModel.addStoreToHousehold( req.params.householdId, storeId, - isDefault || false + foundStores.length == 0 ? true : isDefault || false ); res.status(201).json({ diff --git a/backend/models/household.model.js b/backend/models/household.model.js index db269e9..b26fe0b 100644 --- a/backend/models/household.model.js +++ b/backend/models/household.model.js @@ -94,7 +94,6 @@ exports.refreshInviteCode = async (householdId) => { // Join household via invite code exports.joinHousehold = async (inviteCode, userId) => { - // Find household by invite code const householdResult = await pool.query( `SELECT id, name FROM households WHERE invite_code = $1 @@ -102,22 +101,19 @@ exports.joinHousehold = async (inviteCode, userId) => { [inviteCode] ); - if (householdResult.rows.length === 0) { - return null; // Invalid or expired code - } + + if (householdResult.rows.length === 0) return null; const household = householdResult.rows[0]; - // Check if already member const existingMember = await pool.query( `SELECT id FROM household_members WHERE household_id = $1 AND user_id = $2`, [household.id, userId] ); - if (existingMember.rows.length > 0) { - return { ...household, alreadyMember: true }; - } + if (existingMember.rows.length > 0) return { ...household, alreadyMember: true }; + // Add as user role await pool.query( diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..0d9285e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,74 @@ +# Documentation Index + +This directory contains all project documentation organized by category. + +## 📁 Directory Structure + +### `/architecture` - System Design & Structure +- **[component-structure.md](architecture/component-structure.md)** - Frontend component organization and patterns +- **[multi-household-architecture-plan.md](architecture/multi-household-architecture-plan.md)** - Multi-household system architecture design + +### `/features` - Feature Implementation Details +- **[classification-implementation.md](features/classification-implementation.md)** - Item classification system (zones, types, groups) +- **[image-storage-implementation.md](features/image-storage-implementation.md)** - Image storage and handling (bytea, MIME types) + +### `/guides` - How-To & Reference Guides +- **[api-documentation.md](guides/api-documentation.md)** - REST API endpoints and usage +- **[frontend-readme.md](guides/frontend-readme.md)** - Frontend development guide +- **[MOBILE_RESPONSIVE_AUDIT.md](guides/MOBILE_RESPONSIVE_AUDIT.md)** - Mobile-first design guidelines and audit checklist +- **[setup-checklist.md](guides/setup-checklist.md)** - Development environment setup steps + +### `/migration` - Database Migrations & Updates +- **[MIGRATION_GUIDE.md](migration/MIGRATION_GUIDE.md)** - Multi-household migration instructions (also in `backend/migrations/`) +- **[POST_MIGRATION_UPDATES.md](migration/POST_MIGRATION_UPDATES.md)** - Required updates after migration + +### `/archive` - Completed Implementation Records +Historical documentation of completed features. Useful for reference but not actively maintained. + +- **[ACCOUNT_MANAGEMENT_IMPLEMENTATION.md](archive/ACCOUNT_MANAGEMENT_IMPLEMENTATION.md)** - Phase 4: Display name and password change +- **[code-cleanup-guide.md](archive/code-cleanup-guide.md)** - Code cleanup checklist (completed) +- **[HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md](archive/HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md)** - Household management UI implementation +- **[IMPLEMENTATION_STATUS.md](archive/IMPLEMENTATION_STATUS.md)** - Multi-household migration sprint status +- **[settings-dark-mode.md](archive/settings-dark-mode.md)** - Dark mode implementation notes +- **[TEST_SUITE_README.md](archive/TEST_SUITE_README.md)** - Testing infrastructure documentation + +--- + +## 📄 Root-Level Documentation + +These files remain at the project root for easy access: + +- **[../README.md](../README.md)** - Project overview and quick start +- **[../.github/copilot-instructions.md](../.github/copilot-instructions.md)** - AI assistant instructions (architecture, RBAC, conventions) + +--- + +## 🔍 Quick Reference + +**Setting up the project?** → Start with [setup-checklist.md](guides/setup-checklist.md) + +**Understanding the API?** → See [api-documentation.md](guides/api-documentation.md) + +**Working on mobile UI?** → Check [MOBILE_RESPONSIVE_AUDIT.md](guides/MOBILE_RESPONSIVE_AUDIT.md) + +**Need architecture context?** → Read [../.github/copilot-instructions.md](../.github/copilot-instructions.md) + +**Running migrations?** → Follow [MIGRATION_GUIDE.md](migration/MIGRATION_GUIDE.md) + +--- + +## 📝 Contributing to Documentation + +When adding new documentation: + +1. **Guides** (`/guides`) - General how-to, setup, reference +2. **Features** (`/features`) - Specific feature implementation details +3. **Architecture** (`/architecture`) - System design, patterns, structure +4. **Migration** (`/migration`) - Database migrations and upgrade guides +5. **Archive** (`/archive`) - Completed implementation records (for reference only) + +Keep documentation: +- ✅ Up-to-date with code changes +- ✅ Concise and scannable +- ✅ Linked to relevant files (use relative paths) +- ✅ Organized by category diff --git a/docs/component-structure.md b/docs/architecture/component-structure.md similarity index 100% rename from docs/component-structure.md rename to docs/architecture/component-structure.md diff --git a/docs/multi-household-architecture-plan.md b/docs/architecture/multi-household-architecture-plan.md similarity index 100% rename from docs/multi-household-architecture-plan.md rename to docs/architecture/multi-household-architecture-plan.md diff --git a/ACCOUNT_MANAGEMENT_IMPLEMENTATION.md b/docs/archive/ACCOUNT_MANAGEMENT_IMPLEMENTATION.md similarity index 100% rename from ACCOUNT_MANAGEMENT_IMPLEMENTATION.md rename to docs/archive/ACCOUNT_MANAGEMENT_IMPLEMENTATION.md diff --git a/HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md b/docs/archive/HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md similarity index 100% rename from HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md rename to docs/archive/HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md diff --git a/IMPLEMENTATION_STATUS.md b/docs/archive/IMPLEMENTATION_STATUS.md similarity index 100% rename from IMPLEMENTATION_STATUS.md rename to docs/archive/IMPLEMENTATION_STATUS.md diff --git a/docs/archive/TEST_SUITE_README.md b/docs/archive/TEST_SUITE_README.md new file mode 100644 index 0000000..ab6627a --- /dev/null +++ b/docs/archive/TEST_SUITE_README.md @@ -0,0 +1,43 @@ +# API Test Suite + +The test suite has been reorganized into separate files for better maintainability: + +## New Modular Structure (✅ Complete) +- **api-tests.html** - Main HTML file +- **test-config.js** - Global state management +- **test-definitions.js** - All 62 test cases across 8 categories +- **test-runner.js** - Test execution logic +- **test-ui.js** - UI manipulation functions +- **test-styles.css** - All CSS styles + +## How to Use +1. Start the dev server: `docker-compose -f docker-compose.dev.yml up` +2. Navigate to: `http://localhost:5000/test/api-tests.html` +3. Configure credentials (default: admin/admin123) +4. Click "▶ Run All Tests" + +## Features +- ✅ 62 comprehensive tests +- ✅ Collapsible test cards (collapsed by default) +- ✅ Expected field validation with visual indicators +- ✅ Color-coded HTTP status badges +- ✅ Auto-expansion on test run +- ✅ Expand/Collapse all buttons +- ✅ Real-time pass/fail/error states +- ✅ Summary dashboard + +## File Structure +``` +backend/public/ +├── api-tests.html # Main entry point (use this) +├── test-config.js # State management (19 lines) +├── test-definitions.js # Test cases (450+ lines) +├── test-runner.js # Test execution (160+ lines) +├── test-ui.js # UI functions (90+ lines) +└── test-styles.css # All styles (310+ lines) +``` + +## Old File +- **api-test.html** - Original monolithic version (kept for reference) + +Total: ~1030 lines split into 6 clean, modular files diff --git a/docs/code-cleanup-guide.md b/docs/archive/code-cleanup-guide.md similarity index 100% rename from docs/code-cleanup-guide.md rename to docs/archive/code-cleanup-guide.md diff --git a/docs/settings-dark-mode.md b/docs/archive/settings-dark-mode.md similarity index 100% rename from docs/settings-dark-mode.md rename to docs/archive/settings-dark-mode.md diff --git a/docs/classification-implementation.md b/docs/features/classification-implementation.md similarity index 100% rename from docs/classification-implementation.md rename to docs/features/classification-implementation.md diff --git a/docs/image-storage-implementation.md b/docs/features/image-storage-implementation.md similarity index 100% rename from docs/image-storage-implementation.md rename to docs/features/image-storage-implementation.md diff --git a/docs/guides/MOBILE_RESPONSIVE_AUDIT.md b/docs/guides/MOBILE_RESPONSIVE_AUDIT.md new file mode 100644 index 0000000..7fafcc2 --- /dev/null +++ b/docs/guides/MOBILE_RESPONSIVE_AUDIT.md @@ -0,0 +1,283 @@ +# Mobile Responsive Design Audit & Recommendations + +## ✅ Already Mobile-Friendly + +### Components +1. **Navbar** - Just updated with hamburger menu, dropdowns, sticky positioning +2. **AdminPanel** - Has responsive breakpoints (768px, 480px) +3. **Manage page** - Has responsive breakpoints (768px, 480px) +4. **ManageHousehold** - Has 768px breakpoint +5. **Settings** - Has 768px breakpoint +6. **StoreManagement** - Has 768px breakpoint +7. **GroceryList** - Has 480px breakpoint + +## ✅ Recently Completed (2026-01-26) + +### **All Modals** - Mobile optimization COMPLETE ✓ +**Files updated with responsive styles:** +- ✅ `frontend/src/styles/AddImageModal.css` - Added 768px & 480px breakpoints +- ✅ `frontend/src/styles/ImageUploadModal.css` - Added 768px & 480px breakpoints +- ✅ `frontend/src/styles/ItemClassificationModal.css` - Added 768px & 480px breakpoints +- ✅ `frontend/src/styles/SimilarItemModal.css` - Added 768px & 480px breakpoints +- ✅ `frontend/src/styles/components/EditItemModal.css` - Added 768px & 480px breakpoints +- ✅ `frontend/src/styles/components/ConfirmAddExistingModal.css` - Added 768px & 480px breakpoints +- ✅ `frontend/src/styles/ImageModal.css` - Enhanced with 480px breakpoint +- ✅ `frontend/src/styles/components/AddItemWithDetailsModal.css` - Enhanced with 768px breakpoint +- ✅ `frontend/src/styles/ConfirmBuyModal.css` - Already excellent (480px & 360px breakpoints) + +**Mobile improvements implemented:** +- Modal width: 95% at 768px, 100% at 480px +- All buttons: Full-width stacking on mobile with 44px minimum height +- Input fields: 16px font-size to prevent iOS zoom +- Image previews: Responsive sizing (180-200px on mobile) +- Touch targets: 44x44px minimum for all interactive elements +- Overflow: Auto scrolling for tall modals (max-height: 90vh) +- Spacing: Reduced padding on small screens + +## ⚠️ Needs Improvement + +### High Priority + +#### 1. **HouseholdSwitcher** - Dropdown might overflow on mobile +**File:** `frontend/src/styles/components/HouseholdSwitcher.css` + +**Current:** No mobile breakpoints +**Needs:** +```css +@media (max-width: 480px) { + .household-switcher-dropdown { + max-width: 90vw; + right: auto; + left: 50%; + transform: translateX(-50%); + } +} +``` + +#### 2. **StoreTabs** - Horizontal scrolling tabs on mobile +**File:** `frontend/src/styles/components/StoreTabs.css` + +**Needs:** +```css +@media (max-width: 768px) { + .store-tabs { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .store-tab { + min-width: 100px; + font-size: 0.9rem; + padding: 0.6rem 1rem; + } +} +``` + +#### 3. **Login/Register Pages** - Need better mobile padding +**Files:** +- `frontend/src/styles/pages/Login.css` +- `frontend/src/styles/pages/Register.css` + +**Needs:** +```css +@media (max-width: 480px) { + .card { + padding: 1.5rem 1rem; + margin: 0.5rem; + } + + .form-input { + font-size: 16px; /* Prevents iOS zoom on focus */ + } +} +``` + +### Medium Priority + +#### 4. **GroceryList Item Cards** - Could be more touch-friendly +**File:** `frontend/src/styles/pages/GroceryList.css` + +**Current:** Has 480px breakpoint +**Enhancement needed:** +- Increase touch target sizes for mobile +- Better spacing between items on small screens +- Optimize image display on mobile + +#### 5. **AddItemForm** - Input width and spacing +**File:** `frontend/src/styles/components/AddItemForm.css` + +**Has 480px breakpoint** but verify: +- Input font-size is 16px+ (prevents iOS zoom) +- Buttons are full-width on mobile +- Adequate spacing between form elements + +#### 6. **CreateJoinHousehold Modal** +**File:** `frontend/src/styles/components/manage/CreateJoinHousehold.css` + +**Has 600px breakpoint** - Review for: +- Full-screen on very small devices +- Button sizing and spacing +- Tab navigation usability + +### Low Priority + +#### 7. **SuggestionList** - Touch interactions +**File:** `frontend/src/styles/components/SuggestionList.css` + +**Needs:** Mobile-specific styles for: +- Larger tap targets +- Better scrolling behavior +- Touch feedback + +#### 8. **ClassificationSection** - Zone selection on mobile +**File:** `frontend/src/styles/components/ClassificationSection.css` + +**Needs:** +- Ensure zone buttons are touch-friendly +- Stack vertically if needed on small screens + +#### 9. **ImageUploadSection** +**File:** `frontend/src/styles/components/ImageUploadSection.css` + +**Needs:** +- Camera access optimization for mobile +- Preview image sizing +- Upload button sizing + +## 🎯 General Recommendations + +### 1. **Global Styles** +Update `frontend/src/index.css`: +```css +/* Prevent zoom on input focus (iOS) */ +input, select, textarea { + font-size: 16px; +} + +/* Better touch scrolling */ +* { + -webkit-overflow-scrolling: touch; +} + +/* Ensure body doesn't overflow horizontally */ +body { + overflow-x: hidden; +} +``` + +### 2. **Container Max-Widths** +Standardize across the app: +- Small components: `max-width: 600px` +- Medium pages: `max-width: 800px` +- Wide layouts: `max-width: 1200px` +- Always pair with `margin: 0 auto` and `padding: 1rem` + +### 3. **Button Sizing** +Mobile-friendly buttons: +```css +.btn-primary, .btn-secondary { + min-height: 44px; /* Apple's recommended minimum */ + padding: 0.75rem 1.5rem; +} + +@media (max-width: 768px) { + .btn-primary, .btn-secondary { + width: 100%; + margin-bottom: 0.5rem; + } +} +``` + +### 4. **Form Layouts** +Stack form fields on mobile: +```css +.form-row { + display: flex; + gap: 1rem; +} + +@media (max-width: 768px) { + .form-row { + flex-direction: column; + } +} +``` + +### 5. **Image Handling** +Responsive images: +```css +img { + max-width: 100%; + height: auto; +} +``` + +### 6. **Typography** +Adjust for mobile readability: +```css +@media (max-width: 768px) { + h1 { font-size: 1.75rem; } + h2 { font-size: 1.5rem; } + h3 { font-size: 1.25rem; } + body { font-size: 16px; } /* Prevents iOS zoom */ +} +``` + +## 📱 Testing Checklist + +Test on these viewports: +- [ ] 320px (iPhone SE) +- [ ] 375px (iPhone 12/13 Pro) +- [ ] 390px (iPhone 14 Pro) +- [ ] 414px (iPhone Pro Max) +- [ ] 768px (iPad Portrait) +- [ ] 1024px (iPad Landscape) +- [ ] 1280px+ (Desktop) + +Test these interactions: +- [ ] Navigation menu (hamburger) +- [ ] Dropdowns (household, user menu) +- [ ] All modals +- [ ] Form inputs (no zoom on focus) +- [ ] Touch gestures (swipe, long-press) +- [ ] Scrolling (no horizontal overflow) +- [ ] Image upload/viewing +- [ ] Tab navigation + +## 🔄 Future Considerations + +1. **Progressive Web App (PWA)** + - Add manifest.json + - Service worker for offline support + - Install prompt + +2. **Touch Gestures** + - Swipe to delete items + - Pull to refresh lists + - Long-press for context menu + +3. **Keyboard Handling** + - iOS keyboard overlap handling + - Android keyboard behavior + - Input focus management + +4. **Performance** + - Lazy load images + - Virtual scrolling for long lists + - Code splitting by route + +## 📝 How to Maintain Mobile-First Design + +I've updated `.github/copilot-instructions.md` with mobile-first design principles. This will be included in all future conversations automatically. + +**To ensure I remember in new conversations:** +1. ✅ Mobile-first guidelines are now in copilot-instructions.md (automatically loaded) +2. Start conversations with: "Remember to keep mobile/desktop responsiveness in mind" +3. Review this audit document before making UI changes +4. Run mobile testing after any CSS/layout changes + +**Quick reminder phrases:** +- "Make this mobile-friendly" +- "Add responsive breakpoints" +- "Test on mobile viewports" +- "Ensure touch-friendly targets" diff --git a/docs/api-documentation.md b/docs/guides/api-documentation.md similarity index 100% rename from docs/api-documentation.md rename to docs/guides/api-documentation.md diff --git a/docs/frontend-readme.md b/docs/guides/frontend-readme.md similarity index 100% rename from docs/frontend-readme.md rename to docs/guides/frontend-readme.md diff --git a/docs/setup-checklist.md b/docs/guides/setup-checklist.md similarity index 100% rename from docs/setup-checklist.md rename to docs/guides/setup-checklist.md diff --git a/docs/migration/MIGRATION_GUIDE.md b/docs/migration/MIGRATION_GUIDE.md new file mode 100644 index 0000000..65604ca --- /dev/null +++ b/docs/migration/MIGRATION_GUIDE.md @@ -0,0 +1,243 @@ +# Multi-Household Architecture Migration Guide + +## Pre-Migration Checklist + +- [ ] **Backup Database** + ```bash + pg_dump -U your_user -d grocery_list > backup_$(date +%Y%m%d_%H%M%S).sql + ``` + +- [ ] **Test on Staging First** + - Copy production database to staging environment + - Run migration on staging + - Verify all data migrated correctly + - Test application functionality + +- [ ] **Review Migration Script** + - Read through `multi_household_architecture.sql` + - Understand each step + - Note verification queries + +- [ ] **Announce Maintenance Window** + - Notify users of downtime + - Schedule during low-usage period + - Estimate 15-30 minutes for migration + +## Running the Migration + +### 1. Connect to Database + +```bash +psql -U your_user -d grocery_list +``` + +### 2. Run Migration + +```sql +\i backend/migrations/multi_household_architecture.sql +``` + +The script will: +1. ✅ Create 8 new tables +2. ✅ Create default "Main Household" +3. ✅ Create default "Costco" store +4. ✅ Migrate all users to household members +5. ✅ Extract items to master catalog +6. ✅ Migrate grocery_list → household_lists +7. ✅ Migrate classifications +8. ✅ Migrate history records +9. ✅ Update user system roles + +### 3. Verify Migration + +Run these queries inside psql: + +```sql +-- Check household created +SELECT * FROM households; + +-- Check all users migrated +SELECT u.username, u.role as system_role, hm.role as household_role +FROM users u +JOIN household_members hm ON u.id = hm.user_id +ORDER BY u.id; + +-- Check item counts match +SELECT + (SELECT COUNT(DISTINCT item_name) FROM grocery_list) as old_unique_items, + (SELECT COUNT(*) FROM items) as new_items; + +-- Check list counts +SELECT + (SELECT COUNT(*) FROM grocery_list) as old_lists, + (SELECT COUNT(*) FROM household_lists) as new_lists; + +-- Check classification counts +SELECT + (SELECT COUNT(*) FROM item_classification) as old_classifications, + (SELECT COUNT(*) FROM household_item_classifications) as new_classifications; + +-- Check history counts +SELECT + (SELECT COUNT(*) FROM grocery_history) as old_history, + (SELECT COUNT(*) FROM household_list_history) as new_history; + +-- Verify no data loss - check if all old items have corresponding new records +SELECT gl.item_name +FROM grocery_list gl +LEFT JOIN items i ON LOWER(i.name) = LOWER(TRIM(gl.item_name)) +LEFT JOIN household_lists hl ON hl.item_id = i.id +WHERE hl.id IS NULL; +-- Should return 0 rows + +-- Check invite code +SELECT name, invite_code FROM households; +``` + +### 4. Test Application + +- [ ] Users can log in +- [ ] Can view "Main Household" list +- [ ] Can add items +- [ ] Can mark items as bought +- [ ] History shows correctly +- [ ] Classifications preserved +- [ ] Images display correctly + +## Post-Migration Cleanup + +**Only after verifying everything works correctly:** + +```sql +-- Drop old tables (CAREFUL - THIS IS IRREVERSIBLE) +DROP TABLE IF EXISTS grocery_history CASCADE; +DROP TABLE IF EXISTS item_classification CASCADE; +DROP TABLE IF EXISTS grocery_list CASCADE; +``` + +## Rollback Plan + +### If Migration Fails + +```sql +-- Inside psql during migration +ROLLBACK; + +-- Then restore from backup +\q +psql -U your_user -d grocery_list < backup_YYYYMMDD_HHMMSS.sql +``` + +### If Issues Found After Migration + +```bash +# Drop the database and restore +dropdb grocery_list +createdb grocery_list +psql -U your_user -d grocery_list < backup_YYYYMMDD_HHMMSS.sql +``` + +## Common Issues & Solutions + +### Issue: Duplicate items in items table +**Cause**: Case-insensitive matching not working +**Solution**: Check item names for leading/trailing spaces + +### Issue: Foreign key constraint errors +**Cause**: User or item references not found +**Solution**: Verify all users and items exist before migrating lists + +### Issue: History not showing +**Cause**: household_list_id references incorrect +**Solution**: Check JOIN conditions in history migration + +### Issue: Images not displaying +**Cause**: BYTEA encoding issues +**Solution**: Verify image_mime_type correctly migrated + +## Migration Timeline + +- **T-0**: Begin maintenance window +- **T+2min**: Backup complete +- **T+3min**: Start migration script +- **T+8min**: Migration complete (for ~1000 items) +- **T+10min**: Run verification queries +- **T+15min**: Test application functionality +- **T+20min**: If successful, announce completion +- **T+30min**: End maintenance window + +## Data Integrity Checks + +```sql +-- Ensure all users belong to at least one household +SELECT u.id, u.username +FROM users u +LEFT JOIN household_members hm ON u.id = hm.user_id +WHERE hm.id IS NULL; +-- Should return 0 rows + +-- Ensure all household lists have valid items +SELECT hl.id +FROM household_lists hl +LEFT JOIN items i ON hl.item_id = i.id +WHERE i.id IS NULL; +-- Should return 0 rows + +-- Ensure all history has valid list references +SELECT hlh.id +FROM household_list_history hlh +LEFT JOIN household_lists hl ON hlh.household_list_id = hl.id +WHERE hl.id IS NULL; +-- Should return 0 rows + +-- Check for orphaned classifications +SELECT hic.id +FROM household_item_classifications hic +LEFT JOIN household_lists hl ON hic.item_id = hl.item_id + AND hic.household_id = hl.household_id + AND hic.store_id = hl.store_id +WHERE hl.id IS NULL; +-- Should return 0 rows (or classifications for removed items, which is ok) +``` + +## Success Criteria + +✅ All tables created successfully +✅ All users migrated to "Main Household" +✅ Item count matches (unique items from old → new) +✅ List count matches (all grocery_list items → household_lists) +✅ Classification count matches +✅ History count matches +✅ No NULL foreign keys +✅ Application loads without errors +✅ Users can perform all CRUD operations +✅ Images display correctly +✅ Bought items still marked as bought +✅ Recently bought still shows correctly + +## Next Steps After Migration + +1. ✅ Update backend models (Sprint 2) +2. ✅ Update API routes +3. ✅ Update controllers +4. ✅ Test all endpoints +5. ✅ Update frontend contexts +6. ✅ Update UI components +7. ✅ Enable multi-household features + +## Support & Troubleshooting + +If issues arise: +1. Check PostgreSQL logs: `/var/log/postgresql/` +2. Check application logs +3. Restore from backup if needed +4. Review migration script for errors + +## Monitoring Post-Migration + +For the first 24 hours after migration: +- Monitor error logs +- Watch for performance issues +- Verify user activity normal +- Check for any data inconsistencies +- Be ready to rollback if critical issues found diff --git a/POST_MIGRATION_UPDATES.md b/docs/migration/POST_MIGRATION_UPDATES.md similarity index 100% rename from POST_MIGRATION_UPDATES.md rename to docs/migration/POST_MIGRATION_UPDATES.md diff --git a/frontend/src/api/stores.js b/frontend/src/api/stores.js index 057384f..2b92f18 100644 --- a/frontend/src/api/stores.js +++ b/frontend/src/api/stores.js @@ -15,7 +15,7 @@ export const getHouseholdStores = (householdId) => * Add a store to a household */ export const addStoreToHousehold = (householdId, storeId, isDefault = false) => - api.post(`/stores/household/${householdId}`, { store_id: storeId, is_default: isDefault }); + api.post(`/stores/household/${householdId}`, { storeId: storeId, isDefault: isDefault }); /** * Remove a store from a household diff --git a/frontend/src/components/layout/Navbar.jsx b/frontend/src/components/layout/Navbar.jsx index 166c812..1a7c82f 100644 --- a/frontend/src/components/layout/Navbar.jsx +++ b/frontend/src/components/layout/Navbar.jsx @@ -1,35 +1,84 @@ import "../../styles/components/Navbar.css"; -import { useContext } from "react"; +import { useContext, useState } from "react"; import { Link } from "react-router-dom"; import { AuthContext } from "../../context/AuthContext"; import HouseholdSwitcher from "../household/HouseholdSwitcher"; export default function Navbar() { const { role, logout, username } = useContext(AuthContext); + const [showNavMenu, setShowNavMenu] = useState(false); + const [showUserMenu, setShowUserMenu] = useState(false); + + const closeMenus = () => { + setShowNavMenu(false); + setShowUserMenu(false); + }; return ( ); } \ No newline at end of file diff --git a/frontend/src/components/manage/ManageStores.jsx b/frontend/src/components/manage/ManageStores.jsx index 9d2b38b..4325a1f 100644 --- a/frontend/src/components/manage/ManageStores.jsx +++ b/frontend/src/components/manage/ManageStores.jsx @@ -11,7 +11,7 @@ import "../../styles/components/manage/ManageStores.css"; export default function ManageStores() { const { activeHousehold } = useContext(HouseholdContext); - const { stores: householdStores, loadStores } = useContext(StoreContext); + const { stores: householdStores, refreshStores } = useContext(StoreContext); const [allStores, setAllStores] = useState([]); const [loading, setLoading] = useState(true); const [showAddStore, setShowAddStore] = useState(false); @@ -36,8 +36,9 @@ export default function ManageStores() { const handleAddStore = async (storeId) => { try { + console.log("Adding store with ID:", storeId); await addStoreToHousehold(activeHousehold.id, storeId, false); - await loadStores(); + await refreshStores(); setShowAddStore(false); } catch (error) { console.error("Failed to add store:", error); @@ -50,7 +51,7 @@ export default function ManageStores() { try { await removeStoreFromHousehold(activeHousehold.id, storeId); - await loadStores(); + await refreshStores(); } catch (error) { console.error("Failed to remove store:", error); alert("Failed to remove store"); @@ -60,7 +61,7 @@ export default function ManageStores() { const handleSetDefault = async (storeId) => { try { await setDefaultStore(activeHousehold.id, storeId); - await loadStores(); + await refreshStores(); } catch (error) { console.error("Failed to set default store:", error); alert("Failed to set default store"); diff --git a/frontend/src/styles/AddImageModal.css b/frontend/src/styles/AddImageModal.css index a0608d4..3f64c08 100644 --- a/frontend/src/styles/AddImageModal.css +++ b/frontend/src/styles/AddImageModal.css @@ -81,3 +81,38 @@ .add-image-remove:hover { background: rgba(255, 0, 0, 1); } + +/* Mobile Responsive Styles */ +@media (max-width: 768px) { + .add-image-preview-container { + margin: 1rem 0; + } + + .add-image-preview { + width: 200px; + height: 200px; + } + + .add-image-option-btn { + padding: 1rem; + font-size: 1rem; + min-height: 44px; + } +} + +@media (max-width: 480px) { + .add-image-preview { + width: 180px; + height: 180px; + } + + .add-image-option-btn { + font-size: 0.95rem; + } + + .add-image-remove { + width: 36px; + height: 36px; + font-size: 1.3em; + } +} diff --git a/frontend/src/styles/ImageModal.css b/frontend/src/styles/ImageModal.css index fe4fbe7..1547ba2 100644 --- a/frontend/src/styles/ImageModal.css +++ b/frontend/src/styles/ImageModal.css @@ -64,3 +64,20 @@ } } +@media (max-width: 480px) { + .image-modal-overlay { + padding: 0.5rem; + } + + .image-modal-content { + padding: 0.5rem; + border-radius: 8px; + max-width: 95vw; + } + + .image-modal-img { + max-height: 50vh; + border-radius: 4px; + } +} + diff --git a/frontend/src/styles/ImageUploadModal.css b/frontend/src/styles/ImageUploadModal.css index c528556..76c6407 100644 --- a/frontend/src/styles/ImageUploadModal.css +++ b/frontend/src/styles/ImageUploadModal.css @@ -201,3 +201,67 @@ opacity: 1; } } + +/* Mobile Responsive Styles */ +@media (max-width: 768px) { + .image-upload-modal { + width: 95%; + padding: 1.5rem; + max-height: 90vh; + overflow-y: auto; + } + + .image-upload-modal h2 { + font-size: 1.3em; + } + + .image-upload-option-btn { + padding: 1rem; + font-size: 1rem; + min-height: 44px; + } + + .modal-image-preview { + width: 180px; + height: 180px; + } + + .image-upload-actions { + flex-direction: column; + gap: 0.75rem; + } + + .image-upload-cancel, + .image-upload-skip, + .image-upload-confirm { + width: 100%; + min-height: 44px; + } +} + +@media (max-width: 480px) { + .image-upload-modal { + width: 100%; + padding: 1rem; + border-radius: 8px; + } + + .image-upload-modal h2 { + font-size: 1.15em; + } + + .image-upload-subtitle { + font-size: 0.9em; + } + + .modal-image-preview { + width: 160px; + height: 160px; + } + + .modal-remove-image { + width: 36px; + height: 36px; + font-size: 1.3em; + } +} diff --git a/frontend/src/styles/ItemClassificationModal.css b/frontend/src/styles/ItemClassificationModal.css index 0232ada..e7f4f85 100644 --- a/frontend/src/styles/ItemClassificationModal.css +++ b/frontend/src/styles/ItemClassificationModal.css @@ -100,3 +100,52 @@ .classification-modal-btn-confirm:hover { background: #0056b3; } + +/* Mobile Responsive Styles */ +@media (max-width: 768px) { + .classification-modal-content { + width: 95%; + padding: 1.25rem; + max-height: 85vh; + overflow-y: auto; + } + + .classification-modal-title { + font-size: 1.3em; + } + + .classification-modal-subtitle { + font-size: 0.9em; + } + + .classification-modal-select { + padding: 0.7em; + font-size: 16px; /* Prevents iOS zoom */ + } + + .classification-modal-actions { + flex-direction: column; + gap: 0.6rem; + } + + .classification-modal-btn { + width: 100%; + min-height: 44px; + } +} + +@media (max-width: 480px) { + .classification-modal-content { + width: 100%; + padding: 1rem; + border-radius: 8px; + } + + .classification-modal-title { + font-size: 1.2em; + } + + .classification-modal-field label { + font-size: 0.9em; + } +} diff --git a/frontend/src/styles/SimilarItemModal.css b/frontend/src/styles/SimilarItemModal.css index 32e8279..7c6660a 100644 --- a/frontend/src/styles/SimilarItemModal.css +++ b/frontend/src/styles/SimilarItemModal.css @@ -22,3 +22,23 @@ width: 100%; } +/* Mobile Responsive Styles */ +@media (max-width: 768px) { + .similar-item-suggested, + .similar-item-original { + font-size: 1em; + } + + .similar-modal-actions .btn { + min-height: 44px; + font-size: 1rem; + } +} + +@media (max-width: 480px) { + .similar-item-suggested, + .similar-item-original { + font-size: 0.95em; + } +} + diff --git a/frontend/src/styles/components/AddItemWithDetailsModal.css b/frontend/src/styles/components/AddItemWithDetailsModal.css index da8e9eb..071f1be 100644 --- a/frontend/src/styles/components/AddItemWithDetailsModal.css +++ b/frontend/src/styles/components/AddItemWithDetailsModal.css @@ -195,13 +195,61 @@ } /* Mobile responsiveness */ -@media (max-width: 480px) { +@media (max-width: 768px) { + .add-item-details-overlay { + padding: var(--spacing-sm); + } + .add-item-details-modal { - padding: 1.2em; + width: 95%; + max-width: 95%; + padding: var(--spacing-md); } .add-item-details-title { - font-size: 1.2em; + font-size: 1.3em; + } + + .add-item-details-select { + padding: 0.7em; + font-size: 16px; /* Prevents iOS zoom */ + } + + .add-item-details-image-options { + gap: 0.6em; + } + + .add-item-details-image-btn { + min-height: 44px; + } + + .add-item-details-actions { + flex-direction: column; + gap: 0.5rem; + } + + .add-item-details-btn { + width: 100%; + min-height: 44px; + } +} + +@media (max-width: 480px) { + .add-item-details-modal { + padding: 1rem; + border-radius: 8px; + } + + .add-item-details-title { + font-size: 1.15em; + } + + .add-item-details-subtitle { + font-size: 0.85em; + } + + .add-item-details-section-title { + font-size: 1em; } .add-item-details-image-options { @@ -210,9 +258,10 @@ .add-item-details-image-btn { min-width: 100%; + font-size: 0.9em; } - .add-item-details-actions { - flex-direction: column; + .add-item-details-field label { + font-size: 0.85em; } } diff --git a/frontend/src/styles/components/ConfirmAddExistingModal.css b/frontend/src/styles/components/ConfirmAddExistingModal.css index 96033f9..d916597 100644 --- a/frontend/src/styles/components/ConfirmAddExistingModal.css +++ b/frontend/src/styles/components/ConfirmAddExistingModal.css @@ -39,3 +39,30 @@ font-size: var(--font-size-lg); } +/* Mobile Responsive Styles */ +@media (max-width: 768px) { + .confirm-add-existing-qty-info { + padding: var(--spacing-sm); + } + + .qty-row { + font-size: 0.95em; + } + + .qty-value { + font-size: 1em; + } + + .qty-total .qty-label, + .qty-total .qty-value { + font-size: 1em; + } +} + +@media (max-width: 480px) { + .qty-row { + font-size: 0.9em; + padding: var(--spacing-xxs) 0; + } +} + diff --git a/frontend/src/styles/components/EditItemModal.css b/frontend/src/styles/components/EditItemModal.css index 9613520..1e3973c 100644 --- a/frontend/src/styles/components/EditItemModal.css +++ b/frontend/src/styles/components/EditItemModal.css @@ -187,3 +187,83 @@ opacity: 0.6; cursor: not-allowed; } + +/* Mobile Responsive Styles */ +@media (max-width: 768px) { + .edit-modal-overlay { + padding: var(--spacing-sm); + } + + .edit-modal-content { + width: 95%; + max-width: 95%; + padding: var(--spacing-md); + max-height: 90vh; + } + + .edit-modal-title { + font-size: 1.3em; + } + + .edit-modal-input, + .edit-modal-select { + font-size: 16px; /* Prevents iOS zoom */ + } + + .edit-modal-quantity-input { + width: 70px; + font-size: 16px; /* Prevents iOS zoom */ + } + + .quantity-btn { + width: 44px; + height: 44px; + font-size: 1.4em; + } + + .edit-modal-inline-field { + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-xs); + } + + .edit-modal-inline-field label { + min-width: auto; + } + + .edit-modal-inline-field .edit-modal-select { + width: 100%; + } + + .edit-modal-actions { + flex-direction: column; + gap: var(--spacing-xs); + } + + .edit-modal-btn { + width: 100%; + min-height: 44px; + } +} + +@media (max-width: 480px) { + .edit-modal-content { + width: 100%; + padding: 1rem; + border-radius: 8px; + } + + .edit-modal-title { + font-size: 1.2em; + } + + .edit-modal-quantity-control { + gap: var(--spacing-xs); + } + + .quantity-btn { + width: 40px; + height: 40px; + font-size: 1.2em; + } +} diff --git a/frontend/src/styles/components/HouseholdSwitcher.css b/frontend/src/styles/components/HouseholdSwitcher.css index d05a5b6..b13ce602 100644 --- a/frontend/src/styles/components/HouseholdSwitcher.css +++ b/frontend/src/styles/components/HouseholdSwitcher.css @@ -6,6 +6,7 @@ .household-switcher-toggle { display: flex; align-items: center; + justify-content: space-between; gap: 0.5rem; padding: 0.5rem 1rem; background: var(--card-bg); @@ -15,6 +16,7 @@ font-size: 1rem; cursor: pointer; transition: all 0.2s ease; + width: 100%; } .household-switcher-toggle:hover { @@ -29,11 +31,18 @@ .household-name { font-weight: 500; + flex: 1; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .dropdown-icon { font-size: 0.75rem; transition: transform 0.2s ease; + flex-shrink: 0; + margin-left: auto; } .dropdown-icon.open { @@ -53,7 +62,8 @@ position: absolute; top: calc(100% + 0.5rem); left: 0; - min-width: 220px; + right: 0; + width: 100%; background: var(--card-bg); border: 2px solid var(--border); border-radius: 8px; diff --git a/frontend/src/styles/components/Navbar.css b/frontend/src/styles/components/Navbar.css index 30d23a2..082db82 100644 --- a/frontend/src/styles/components/Navbar.css +++ b/frontend/src/styles/components/Navbar.css @@ -1,58 +1,232 @@ +/* Navbar - Sticky at top */ .navbar { + position: sticky; + top: 0; + z-index: 1000; background: #343a40; color: white; - padding: 0.6em 1em; + padding: 0.75rem 1rem; display: flex; justify-content: space-between; align-items: center; - border-radius: 4px; - margin-bottom: 1em; + gap: 1rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } -.navbar-links a { +/* Navbar Sections */ +.navbar-section { + display: flex; + align-items: center; +} + +.navbar-left { + flex: 0 0 auto; +} + +.navbar-center { + flex: 1 1 auto; + display: flex; + justify-content: center; + max-width: 80%; + margin: 0 auto; +} + +.navbar-right { + flex: 0 0 auto; + position: relative; +} + +/* Hamburger Menu Button */ +.navbar-menu-btn { + background: transparent; + border: none; + cursor: pointer; + padding: 0.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.hamburger-icon { + display: flex; + flex-direction: column; + gap: 4px; + width: 24px; +} + +.hamburger-icon span { + display: block; + width: 100%; + height: 3px; + background: white; + border-radius: 2px; + transition: all 0.3s; +} + +.navbar-menu-btn:hover .hamburger-icon span { + background: #ddd; +} + +/* User Button */ +.navbar-user-btn { + background: #495057; color: white; - margin-right: 1em; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.95rem; + font-weight: 500; + white-space: nowrap; + transition: background 0.2s; +} + +.navbar-user-btn:hover { + background: #5a6268; +} + +/* Dropdown Overlay */ +.menu-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 999; + background: transparent; +} + +/* Dropdown Base Styles */ +.navbar-dropdown { + position: absolute; + top: calc(100% + 0.5rem); + background: #fff; + border: 1px solid #dee2e6; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + z-index: 1001; + min-width: 180px; + overflow: hidden; +} + +/* Navigation Dropdown */ +.nav-dropdown { + left: 0; + display: flex; + flex-direction: column; +} + +.nav-dropdown a { + color: #343a40; text-decoration: none; - font-size: 1.1em; + padding: 0.75rem 1.25rem; + font-size: 1rem; + transition: background 0.2s; + border-bottom: 1px solid #f0f0f0; } -.navbar-links a:hover { - text-decoration: underline; +.nav-dropdown a:last-child { + border-bottom: none; } -.navbar-logout { +.nav-dropdown a:hover { + background: #f8f9fa; + color: var(--color-primary); +} + +/* User Dropdown */ +.user-dropdown { + right: 0; + min-width: 200px; +} + +.user-dropdown-info { + padding: 1rem 1.25rem; + border-bottom: 1px solid #dee2e6; + background: #f8f9fa; + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.user-dropdown-username { + font-size: 1rem; + font-weight: 600; + color: #343a40; +} + +.user-dropdown-role { + font-size: 0.85rem; + color: #6c757d; + text-transform: capitalize; +} + +.user-dropdown-logout { + width: 100%; background: #dc3545; color: white; border: none; - padding: 0.4em 0.8em; - border-radius: 4px; + padding: 0.75rem 1.25rem; cursor: pointer; - width: 100px; + font-size: 1rem; + font-weight: 500; + transition: background 0.2s; } -.navbar-idcard { - display: flex; - align-items: center; - align-content: center; - margin-right: 1em; - padding: 0.3em 0.6em; - background: #495057; - border-radius: 4px; - color: white; +.user-dropdown-logout:hover { + background: #c82333; } -.navbar-idinfo { - display: flex; - flex-direction: column; - line-height: 1.1; +/* Household Switcher - Centered with max width */ +.navbar-center > * { + width: 100%; + max-width: 24ch; /* 24 characters max width */ } -.navbar-username { - font-size: 0.95em; - font-weight: bold; +/* Mobile Responsive */ +@media (max-width: 768px) { + .navbar { + padding: 0.5rem 0.75rem; + gap: 0.5rem; + } + + .navbar-center { + max-width: 60%; + } + + .navbar-user-btn { + padding: 0.4rem 0.75rem; + font-size: 0.9rem; + } + + .nav-dropdown { + min-width: 160px; + } + + .user-dropdown { + min-width: 180px; + } } -.navbar-role { - font-size: 0.75em; - opacity: 0.8; +@media (max-width: 480px) { + .navbar { + padding: 0.5rem; + } + + .navbar-center { + max-width: 50%; + } + + .navbar-user-btn { + padding: 0.4rem 0.6rem; + font-size: 0.85rem; + } + + .hamburger-icon { + width: 20px; + } + + .hamburger-icon span { + height: 2.5px; + } } diff --git a/frontend/src/styles/pages/Settings.css b/frontend/src/styles/pages/Settings.css index 6acd6a2..56205c4 100644 --- a/frontend/src/styles/pages/Settings.css +++ b/frontend/src/styles/pages/Settings.css @@ -12,6 +12,13 @@ gap: var(--spacing-sm); margin-bottom: var(--spacing-xl); border-bottom: 2px solid var(--color-border-light); + touch-action: pan-x; /* Lock Y-axis, allow only horizontal scrolling */ + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ +} + +.settings-tabs::-webkit-scrollbar { + display: none; /* Chrome/Safari/Opera */ } .settings-tab {