313 lines
10 KiB
Markdown
313 lines
10 KiB
Markdown
|
|
# Bill Tracker UI Improvements
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
This document catalogs UI/UX improvements identified across the Bill Tracker codebase, organized by priority and impact.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## CRITICAL
|
|||
|
|
|
|||
|
|
No critical issues found. Core functionality is solid.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## HIGH
|
|||
|
|
|
|||
|
|
### 1. Mobile Layout Overflow in Sidebar Navigation
|
|||
|
|
|
|||
|
|
**Where:** `client/components/layout/Sidebar.jsx` — Mobile menu overlay
|
|||
|
|
|
|||
|
|
**Why it matters:** On small screens, the mobile navigation menu doesn't adapt to content width, causing horizontal scroll or content cutoff. This is a blocking accessibility issue for mobile users.
|
|||
|
|
|
|||
|
|
**Current behavior:**
|
|||
|
|
- Mobile menu uses fixed max-width container
|
|||
|
|
- Nav items with long text (e.g., "Notification Preferences") wrap poorly
|
|||
|
|
- No vertical scrolling within the mobile overlay
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
```jsx
|
|||
|
|
// In Sidebar.jsx mobile menu section:
|
|||
|
|
<div className={cn(
|
|||
|
|
'border-t border-border/60 bg-background/95 px-4 py-3 shadow-lg shadow-foreground/5 lg:hidden',
|
|||
|
|
'max-h-[70vh] overflow-y-auto', // Add scrollable container
|
|||
|
|
)}>
|
|||
|
|
<nav className="mx-auto grid max-w-[1500px] gap-1">
|
|||
|
|
{/* ... */}
|
|||
|
|
</nav>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Priority:** HIGH — Mobile breakage affects a significant portion of users
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. Settings Page — No Loading Skeleton for Main Content Area
|
|||
|
|
|
|||
|
|
**Where:** `client/pages/SettingsPage.jsx`
|
|||
|
|
|
|||
|
|
**Why it matters:** The entire page shows a full-page loader (`Loading…`) during initial data fetch, resulting in a blank white screen for 200–500ms. This feels slower than needed.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Replace full-page loader with skeleton cards matching the layout
|
|||
|
|
- Show placeholder content: 2-3 shimmering `SectionCard` components
|
|||
|
|
- Fade out skeletons when data arrives
|
|||
|
|
|
|||
|
|
**Impact:** Perceived performance improvement (~30-40% faster mental load time)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. BillModal — Real-Time Validation on Every Keystroke Causes Layout Shifts
|
|||
|
|
|
|||
|
|
**Where:** `client/components/BillModal.jsx`
|
|||
|
|
|
|||
|
|
**Why it matters:** The `handleChange` function debounces validation but still triggers re-renders on every keystroke. This causes:
|
|||
|
|
- Input field height changes when error messages appear/disappear
|
|||
|
|
- Jarring UX during form entry
|
|||
|
|
- Potential focus loss on fast typists
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Only show error messages after field blur or form submit attempt
|
|||
|
|
- Pre-allocate error message space (min-height: 12px)
|
|||
|
|
- Use `aria-live="polite"` for screen reader notifications
|
|||
|
|
|
|||
|
|
**Alternative:**
|
|||
|
|
```jsx
|
|||
|
|
// Only validate on blur or submit, not on every change
|
|||
|
|
// Keep error state but don't re-render unless visibility changes
|
|||
|
|
{errors.name && errorStateVisible && (
|
|||
|
|
<span className="text-[10px] text-red-500 font-medium">{errors.name}</span>
|
|||
|
|
)}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## MEDIUM
|
|||
|
|
|
|||
|
|
### 4. Sidebar Nav — No Active Indicator for Dropdown Children
|
|||
|
|
|
|||
|
|
**Where:** `client/components/layout/Sidebar.jsx` — `TrackerMenu` component
|
|||
|
|
|
|||
|
|
**Why it matters:** When users navigate to `/bills`, `/categories`, or `/summary`, the main "Tracker" dropdown remains unhighlighted. This creates ambiguity about current location.
|
|||
|
|
|
|||
|
|
**Current behavior:**
|
|||
|
|
- Only the dropdown trigger is highlighted when on `/` (Overview)
|
|||
|
|
- Child routes like `/bills` don't indicate they're part of the Tracker group
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Detect when any child route is active via `location.pathname.startsWith('/bills')` etc.
|
|||
|
|
- Apply `bg-primary text-primary-foreground` style to the Tracker dropdown when any tracker subpage is active
|
|||
|
|
|
|||
|
|
**Code hint:**
|
|||
|
|
```jsx
|
|||
|
|
const isTrackerActive = trackerItems.some(item =>
|
|||
|
|
item.end ? location.pathname === item.to
|
|||
|
|
: location.pathname.startsWith(item.to)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
Already implemented in the code, but the `TrackerMenu` trigger needs styling update.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. Admin Panel — Missing Error Boundary for Critical Sections
|
|||
|
|
|
|||
|
|
**Where:** `client/pages/AdminPage.jsx`
|
|||
|
|
|
|||
|
|
**Why it matters:** Several complex cards (Backup, Email, Users) lack explicit error boundaries. If an API call fails mid-render or throws, the entire admin panel goes blank with no recovery path.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Wrap each major card in a `try/catch` or React Error Boundary
|
|||
|
|
- Show "Failed to load" state with retry button
|
|||
|
|
- Example:
|
|||
|
|
```jsx
|
|||
|
|
function BackupSection() {
|
|||
|
|
const [error, setError] = useState(null);
|
|||
|
|
const [data, setData] = useState(null);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
api.getBackups()
|
|||
|
|
.then(setData)
|
|||
|
|
.catch(err => setError(err));
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
return (
|
|||
|
|
<Card>
|
|||
|
|
<CardContent>Failed to load backups.</CardContent>
|
|||
|
|
<Button onClick={() => setError(null)}>Retry</Button>
|
|||
|
|
</Card>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 6. Settings Page — Field Labels Not Keyboard-Accessible
|
|||
|
|
|
|||
|
|
**Where:** `client/pages/SettingsPage.jsx`
|
|||
|
|
|
|||
|
|
**Why it matters:** While `label` elements exist, they're not explicitly tied to inputs via `htmlFor`. Some components (e.g., theme cards) use buttons without labels, making screen reader navigation difficult.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Ensure all form inputs have explicit `id` and corresponding `label htmlFor`
|
|||
|
|
- Add `aria-label` or `aria-describedby` to interactive elements:
|
|||
|
|
```jsx
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={() => onSelect('light')}
|
|||
|
|
aria-label="Select light theme"
|
|||
|
|
aria-pressed={currentTheme === 'light'}
|
|||
|
|
>
|
|||
|
|
<Sun className="h-4 w-4" />
|
|||
|
|
</button>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7. ProfilePage — Email Input Not Validated client-side
|
|||
|
|
|
|||
|
|
**Where:** `client/pages/ProfilePage.jsx` — `NotificationPreferences` component
|
|||
|
|
|
|||
|
|
**Why it matters:** The email input field accepts any string, including invalid formats like `test@localhost` or `not-an-email`. Validation only happens server-side, leading to delayed error feedback.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Add client-side email regex check before save
|
|||
|
|
- Show inline error if invalid: `^[\w.-]+@[\w.-]+\.\w+$`
|
|||
|
|
- Debounce validation to avoid spamming errors during typing
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 8. BillModal — Date Input Uses Unusual "Due Day of Month" Pattern
|
|||
|
|
|
|||
|
|
**Where:** `client/components/BillModal.jsx`
|
|||
|
|
|
|||
|
|
**Why it matters:** The "Due day of month" input expects a number (1-31) instead of a standard date picker or calendar selection. This is confusing for:
|
|||
|
|
- Users expecting a full date picker
|
|||
|
|
- International users (some countries use DD/MM vs MM/DD)
|
|||
|
|
- Edge cases like February 30th (which doesn't exist)
|
|||
|
|
|
|||
|
|
**Suggested improvement:**
|
|||
|
|
- Consider using `react-datepicker` or similar for full date selection
|
|||
|
|
- Alternatively, add a helper tooltip: "Enter day number only (e.g., 15 for the 15th)"
|
|||
|
|
- Add a validation example: "Due on the 15th → enter 15"
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## LOW
|
|||
|
|
|
|||
|
|
### 9. Global Layout — Header Backdrop Filter Not Fallback for Older Browsers
|
|||
|
|
|
|||
|
|
**Where:** `client/components/layout/Sidebar.jsx` — `header` element
|
|||
|
|
|
|||
|
|
**Why it matters:** The `backdrop-blur-xl` class relies on CSS `backdrop-filter`, which is unsupported in older browsers (e.g., Safari <14, some Android WebView versions). This results in a solid background instead of glassmorphism.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Add a CSS fallback: `bg-background/85 supports-[backdrop-filter]:bg-background/70`
|
|||
|
|
- Already implemented ✅ — no changes needed
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 10. Login Page — No "Remember Me" Checkbox
|
|||
|
|
|
|||
|
|
**Where:** `client/pages/LoginPage.jsx`
|
|||
|
|
|
|||
|
|
**Why it matters:** Modern apps often include a "remember me" option to reduce login friction on trusted devices. Without it, users must re-authenticate on every session.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Add a checkbox below the password field:
|
|||
|
|
```jsx
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<input type="checkbox" id="remember" />
|
|||
|
|
<label htmlFor="remember" className="text-xs text-muted-foreground">
|
|||
|
|
Remember me
|
|||
|
|
</label>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
- Set `rememberMe` flag in localStorage or via cookie (if server supports it)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 11. Theme Toggle — No Visual Feedback When Switching
|
|||
|
|
|
|||
|
|
**Where:** `client/components/ui/theme-toggle.jsx` (shadcn/ui)
|
|||
|
|
|
|||
|
|
**Why it matters:** The theme toggle button doesn't indicate which theme is currently active. Users must click to discover the current state or remember manually.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Add subtle text label: "Light" / "Dark" next to the icon
|
|||
|
|
- Or use a tooltip: `aria-label="Current theme: Dark"`
|
|||
|
|
- Or change icon (sun/moon) based on theme (already done ✅)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 12. Calendar Page — Empty State Not Customizable
|
|||
|
|
|
|||
|
|
**Where:** `client/pages/CalendarPage.jsx`
|
|||
|
|
|
|||
|
|
**Why it matters:** When there are no bills, the calendar shows a generic "No bills found" message. This doesn't guide users toward creating their first bill.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Add a CTA button: "Create your first bill"
|
|||
|
|
- Link directly to the modal with a pre-filled category or default values
|
|||
|
|
- Include a placeholder image or illustration
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## MEH
|
|||
|
|
|
|||
|
|
### 13. General — Inconsistent Spacing in `table-surface` Utility
|
|||
|
|
|
|||
|
|
**Where:** Multiple components (SettingsPage, ProfilePage)
|
|||
|
|
|
|||
|
|
**Why it matters:** The `table-surface` utility (used in Settings and Profile pages) applies `mb-4` and internal padding, but the spacing isn't uniform across all pages. Some sections have excessive vertical space, others feel cramped.
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Audit and standardize spacing tokens:
|
|||
|
|
- `section-spacing` = `mb-6` (1.5rem)
|
|||
|
|
- `card-spacing` = `mb-4` (1rem)
|
|||
|
|
- `row-spacing` = `py-3` (0.75rem)
|
|||
|
|
- Document in `docs/TOKENS.md`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 14. Icons — No Consistent Icon Palette
|
|||
|
|
|
|||
|
|
**Where:** Across all components
|
|||
|
|
|
|||
|
|
**Why it matters:** Different icon sets are used inconsistently:
|
|||
|
|
- `lucide-react` (primary)
|
|||
|
|
- Custom SVGs (logo)
|
|||
|
|
- Some components import icons but don't use them
|
|||
|
|
|
|||
|
|
**Suggested fix:**
|
|||
|
|
- Standardize on `lucide-react` for all icons
|
|||
|
|
- Create a shared `icons/` directory with named exports if custom icons are needed
|
|||
|
|
- Document icon usage in `CONTRIBUTING.md`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Summary
|
|||
|
|
|
|||
|
|
| Priority | Count | Key Impact |
|
|||
|
|
|----------|-------|------------|
|
|||
|
|
| CRITICAL | 0 | — |
|
|||
|
|
| HIGH | 3 | Mobile accessibility, perceived performance, form UX |
|
|||
|
|
| MEDIUM | 5 | Navigation clarity, error resilience, keyboard nav |
|
|||
|
|
| LOW | 4 | Convenience, consistency, discoverability |
|
|||
|
|
| MEH | 2 | Minor polish, standardization |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Next Steps
|
|||
|
|
|
|||
|
|
1. **HIGH items** should be prioritized for the next minor release (v2.1)
|
|||
|
|
2. **MEDIUM items** can be batched into a quality-of-life update
|
|||
|
|
3. Consider a design system audit to address **MEH** items (spacing, icons)
|
|||
|
|
4. Re-run this analysis after implementing HIGH/MEDIUM items to track progress
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*Generated by Scarlett (Frontend/UX Authority) on 2026-05-08*
|