- Replace custom page layout with AppShell component for consistent UI - Remove unused PageHeader and UpdateAlertConditionDto imports - Update service worker cache names and version.json to new timestamp
12 KiB
Plan: Bottom Navigation Bar & Global Header Restructure
Overview
Restructure the GreenHomeUI app to have an Android-style layout with:
- A persistent Bottom Navigation Bar (4 tabs: Profile, Alerts, Report, Weather)
- A global Top Header showing device title, page title with icon, and page info
- Applied to all authenticated pages (after device selection)
Current Architecture
App Flow:
/ (login) → /verify-code → /devices → /daily-report?deviceId=X&date=Y
├── /alert-settings?deviceId=X
├── /calendar?deviceId=X
├── /day-details?deviceId=X&year=Y&month=Z
└── /device-settings?deviceId=X
Each page currently has its own PageHeader and manages its own layout independently. There is no shared shell/layout for authenticated pages.
Target Architecture
Authenticated Shell (Layout)
┌──────────────────────────────────┐
│ Top Header │
│ [Device Name] [Page Icon+Title]│
│ [Page-specific info] │
├──────────────────────────────────┤
│ │
│ Page Content │
│ (children) │
│ │
├──────────────────────────────────┤
│ [Profile] [Alerts] [Report] [W] │
│ Bottom Navigation Bar │
└──────────────────────────────────┘
Route Mapping
| Bottom Nav Tab | Route | Page Component | Icon |
|---|---|---|---|
| Profile | /profile?deviceId=X |
New src/app/profile/page.tsx |
User |
| Alerts | /alert-settings?deviceId=X |
Existing (refactored) | Bell |
| Report | /daily-report?deviceId=X&date=Y |
Existing (refactored) | BarChart3 |
| Weather | /weather?deviceId=X |
New src/app/weather/page.tsx |
CloudSun |
Pages OUTSIDE the shell (no bottom nav/header):
/(login page)/login/verify-code/devices(device selection - this is the gateway before entering the shell)
Pages INSIDE the shell (with bottom nav + header):
/daily-report(Report tab)/alert-settings(Alerts tab)/profile(Profile tab - NEW)/weather(Weather tab - NEW)/calendar(calendar navigation)/day-details(day detail view)/device-settings(device configuration)
Component Architecture
1. src/components/layout/AppShell.tsx — NEW
The main layout wrapper for authenticated pages. Provides:
- Top header bar
- Bottom navigation bar
- Main content area (children)
2. src/components/layout/TopHeader.tsx — NEW
Global top header component showing:
- Left side: Device name (from context/query param)
- Center/Right: Page title with icon
- Optional: Page-specific info (e.g., selected date for daily-report)
Props:
type TopHeaderProps = {
deviceName: string
pageTitle: string
pageIcon: LucideIcon
pageInfo?: string // e.g., "۱۴۰۴/۰۲/۲۱" for date
iconGradient?: string
}
3. src/components/layout/BottomNav.tsx — NEW
Bottom navigation bar with 4 tabs:
- Uses
lucide-reacticons - Highlights active tab based on current route
- Fixed at bottom, safe-area aware for notched devices
- Each tab navigates via
next/navigationuseRouter
4. src/components/layout/DeviceProvider.tsx — NEW
React Context provider that stores the currently selected deviceId and deviceName so all pages within the shell can access them without passing through query params everywhere.
5. src/app/profile/page.tsx — NEW
User profile page showing:
- User name, family, mobile
- Logout button
- Link to device settings
6. src/app/weather/page.tsx — NEW
Dedicated weather page (extracted from the weather tab inside daily-report):
- Shows forecast weather data
- Reuses existing
WeatherTabor creates a standalone version
Data Flow
User logs in → /devices (selects device)
→ Stores deviceId + deviceName in DeviceProvider context
→ Redirects to /daily-report?deviceId=X&date=today
→ AppShell renders with TopHeader + BottomNav + page content
→ BottomNav tab changes update the route
→ DeviceProvider persists across tab switches
DeviceProvider Context
type DeviceContextType = {
deviceId: number
deviceName: string
setDevice: (id: number, name: string) => void
}
The provider wraps all authenticated pages. When a user selects a device on /devices, it sets the context. The AppShell reads from this context to display the device name in the header.
File Changes Summary
New Files to Create
| File | Purpose |
|---|---|
src/components/layout/AppShell.tsx |
Main layout shell with header + bottom nav |
src/components/layout/TopHeader.tsx |
Global top header component |
src/components/layout/BottomNav.tsx |
Bottom navigation bar |
src/components/layout/DeviceProvider.tsx |
Device context provider |
src/components/layout/index.ts |
Barrel exports |
src/app/profile/page.tsx |
Profile page |
src/app/weather/page.tsx |
Weather page |
Files to Modify
| File | Changes |
|---|---|
src/app/layout.tsx |
Wrap children with DeviceProvider for authenticated routes |
src/app/daily-report/page.tsx |
Remove standalone PageHeader, use AppShell instead. Remove BackLink. |
src/app/alert-settings/page.tsx |
Remove standalone PageHeader, use AppShell. Remove BackLink. |
src/app/calendar/page.tsx |
Remove standalone PageHeader, use AppShell. Remove BackLink. |
src/app/day-details/page.tsx |
Remove standalone PageHeader, use AppShell. Remove BackLink. |
src/app/device-settings/page.tsx |
Remove standalone PageHeader, use AppShell. Remove BackLink. |
src/app/devices/page.tsx |
After device selection, set DeviceProvider context before redirecting |
src/app/globals.css |
Add styles for bottom nav (safe-area padding, fixed positioning) |
Implementation Steps (Ordered)
Step 1: Create Layout Components
- Create
DeviceProvidercontext - Create
TopHeadercomponent - Create
BottomNavcomponent - Create
AppShellthat composes them - Create barrel export
index.ts
Step 2: Update Root Layout
- Modify
src/app/layout.tsxto wrap withDeviceProvider - Add bottom nav CSS to
src/app/globals.css
Step 3: Create New Pages
- Create
src/app/profile/page.tsx— user info + logout - Create
src/app/weather/page.tsx— dedicated weather page
Step 4: Refactor Existing Pages to Use AppShell
src/app/daily-report/page.tsx— remove standalone PageHeader, wrap content in AppShellsrc/app/alert-settings/page.tsx— samesrc/app/calendar/page.tsx— samesrc/app/day-details/page.tsx— samesrc/app/device-settings/page.tsx— same
Step 5: Update Device Selection Flow
- Modify
src/app/devices/page.tsxto set device context on selection - Ensure single-device auto-redirect also sets context
Step 6: Polish & Testing
- Ensure bottom nav highlights correct tab based on route
- Ensure safe-area padding for notched phones
- Test navigation between all tabs
- Verify header shows correct device name and page info
Mermaid Diagram: Component Tree
flowchart TD
RootLayout[RootLayout] --> DeviceProvider[DeviceProvider]
DeviceProvider --> AuthPages[Auth Pages Group]
subgraph AuthPages[Authenticated Pages]
AppShell[AppShell] --> TopHeader[TopHeader]
AppShell --> PageContent[Page Content]
AppShell --> BottomNav[BottomNav]
PageContent --> DailyReport[/daily-report]
PageContent --> AlertSettings[/alert-settings]
PageContent --> Profile[/profile]
PageContent --> Weather[/weather]
PageContent --> Calendar[/calendar]
PageContent --> DayDetails[/day-details]
PageContent --> DeviceSettings[/device-settings]
end
NonAuth[Non-Auth Pages] -.-> Login[/login]
NonAuth -.-> VerifyCode[/verify-code]
NonAuth -.-> Devices[/devices]
Devices -.->|select device| AppShell
Mermaid Diagram: Navigation Flow
flowchart LR
Login[/login] --> Verify[/verify-code]
Verify --> Devices[/devices]
Devices -->|select device + set context| DailyReport[/daily-report<br/>Tab: Report]
DailyReport -->|BottomNav: Alerts| AlertSettings[/alert-settings<br/>Tab: Alerts]
DailyReport -->|BottomNav: Profile| Profile[/profile<br/>Tab: Profile]
DailyReport -->|BottomNav: Weather| Weather[/weather<br/>Tab: Weather]
AlertSettings -->|BottomNav: Report| DailyReport
AlertSettings -->|BottomNav: Profile| Profile
AlertSettings -->|BottomNav: Weather| Weather
Profile -->|BottomNav: Report| DailyReport
Profile -->|BottomNav: Alerts| AlertSettings
Profile -->|BottomNav: Weather| Weather
Weather -->|BottomNav: Report| DailyReport
Weather -->|BottomNav: Alerts| AlertSettings
Weather -->|BottomNav: Profile| Profile
DailyReport -->|internal link| Calendar[/calendar]
Calendar -->|select day| DayDetails[/day-details]
DayDetails -->|select date| DailyReport
DailyReport -->|settings icon| DeviceSettings[/device-settings]
Profile -->|settings link| DeviceSettings
Key Design Decisions
-
DeviceProvider Context: Rather than passing
deviceIdas a query param on every navigation, we use React Context. This simplifies all page components and makes the header always aware of the current device. -
AppShell as a wrapper: Each page inside the shell imports
AppShelland wraps its content. The shell provides the header and bottom nav. This keeps pages clean and focused on their content. -
BottomNav uses route matching: The active tab is determined by matching
usePathname()against known routes. This is simpler than managing state across pages. -
Safe-area handling: The bottom nav uses
env(safe-area-inset-bottom)CSS to avoid overlapping with system navigation bars on notched devices. -
Existing PageHeader component: The existing
PageHeadercomponent can still be used inside page content if needed (e.g., for sub-headers within a page), but the main header is now provided byAppShell.