Some checks failed
Deploy MyApp on Same Server / build-and-deploy (push) Has been cancelled
- 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
283 lines
12 KiB
Markdown
283 lines
12 KiB
Markdown
# 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`](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`](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:
|
|
```typescript
|
|
type TopHeaderProps = {
|
|
deviceName: string
|
|
pageTitle: string
|
|
pageIcon: LucideIcon
|
|
pageInfo?: string // e.g., "۱۴۰۴/۰۲/۲۱" for date
|
|
iconGradient?: string
|
|
}
|
|
```
|
|
|
|
### 3. [`src/components/layout/BottomNav.tsx`](src/components/layout/BottomNav.tsx) — NEW
|
|
Bottom navigation bar with 4 tabs:
|
|
- Uses `lucide-react` icons
|
|
- Highlights active tab based on current route
|
|
- Fixed at bottom, safe-area aware for notched devices
|
|
- Each tab navigates via `next/navigation` `useRouter`
|
|
|
|
### 4. [`src/components/layout/DeviceProvider.tsx`](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`](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`](src/app/weather/page.tsx) — NEW
|
|
Dedicated weather page (extracted from the weather tab inside daily-report):
|
|
- Shows forecast weather data
|
|
- Reuses existing [`WeatherTab`](src/components/daily-report/WeatherTab.tsx) or 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
|
|
|
|
```typescript
|
|
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`](src/components/layout/AppShell.tsx) | Main layout shell with header + bottom nav |
|
|
| [`src/components/layout/TopHeader.tsx`](src/components/layout/TopHeader.tsx) | Global top header component |
|
|
| [`src/components/layout/BottomNav.tsx`](src/components/layout/BottomNav.tsx) | Bottom navigation bar |
|
|
| [`src/components/layout/DeviceProvider.tsx`](src/components/layout/DeviceProvider.tsx) | Device context provider |
|
|
| [`src/components/layout/index.ts`](src/components/layout/index.ts) | Barrel exports |
|
|
| [`src/app/profile/page.tsx`](src/app/profile/page.tsx) | Profile page |
|
|
| [`src/app/weather/page.tsx`](src/app/weather/page.tsx) | Weather page |
|
|
|
|
### Files to Modify
|
|
|
|
| File | Changes |
|
|
|---|---|
|
|
| [`src/app/layout.tsx`](src/app/layout.tsx) | Wrap children with `DeviceProvider` for authenticated routes |
|
|
| [`src/app/daily-report/page.tsx`](src/app/daily-report/page.tsx) | Remove standalone `PageHeader`, use `AppShell` instead. Remove `BackLink`. |
|
|
| [`src/app/alert-settings/page.tsx`](src/app/alert-settings/page.tsx) | Remove standalone `PageHeader`, use `AppShell`. Remove `BackLink`. |
|
|
| [`src/app/calendar/page.tsx`](src/app/calendar/page.tsx) | Remove standalone `PageHeader`, use `AppShell`. Remove `BackLink`. |
|
|
| [`src/app/day-details/page.tsx`](src/app/day-details/page.tsx) | Remove standalone `PageHeader`, use `AppShell`. Remove `BackLink`. |
|
|
| [`src/app/device-settings/page.tsx`](src/app/device-settings/page.tsx) | Remove standalone `PageHeader`, use `AppShell`. Remove `BackLink`. |
|
|
| [`src/app/devices/page.tsx`](src/app/devices/page.tsx) | After device selection, set `DeviceProvider` context before redirecting |
|
|
| [`src/app/globals.css`](src/app/globals.css) | Add styles for bottom nav (safe-area padding, fixed positioning) |
|
|
|
|
---
|
|
|
|
## Implementation Steps (Ordered)
|
|
|
|
### Step 1: Create Layout Components
|
|
- Create [`DeviceProvider`](src/components/layout/DeviceProvider.tsx) context
|
|
- Create [`TopHeader`](src/components/layout/TopHeader.tsx) component
|
|
- Create [`BottomNav`](src/components/layout/BottomNav.tsx) component
|
|
- Create [`AppShell`](src/components/layout/AppShell.tsx) that composes them
|
|
- Create barrel export [`index.ts`](src/components/layout/index.ts)
|
|
|
|
### Step 2: Update Root Layout
|
|
- Modify [`src/app/layout.tsx`](src/app/layout.tsx) to wrap with `DeviceProvider`
|
|
- Add bottom nav CSS to [`src/app/globals.css`](src/app/globals.css)
|
|
|
|
### Step 3: Create New Pages
|
|
- Create [`src/app/profile/page.tsx`](src/app/profile/page.tsx) — user info + logout
|
|
- Create [`src/app/weather/page.tsx`](src/app/weather/page.tsx) — dedicated weather page
|
|
|
|
### Step 4: Refactor Existing Pages to Use AppShell
|
|
- [`src/app/daily-report/page.tsx`](src/app/daily-report/page.tsx) — remove standalone PageHeader, wrap content in AppShell
|
|
- [`src/app/alert-settings/page.tsx`](src/app/alert-settings/page.tsx) — same
|
|
- [`src/app/calendar/page.tsx`](src/app/calendar/page.tsx) — same
|
|
- [`src/app/day-details/page.tsx`](src/app/day-details/page.tsx) — same
|
|
- [`src/app/device-settings/page.tsx`](src/app/device-settings/page.tsx) — same
|
|
|
|
### Step 5: Update Device Selection Flow
|
|
- Modify [`src/app/devices/page.tsx`](src/app/devices/page.tsx) to 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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
1. **DeviceProvider Context**: Rather than passing `deviceId` as 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.
|
|
|
|
2. **AppShell as a wrapper**: Each page inside the shell imports `AppShell` and wraps its content. The shell provides the header and bottom nav. This keeps pages clean and focused on their content.
|
|
|
|
3. **BottomNav uses route matching**: The active tab is determined by matching `usePathname()` against known routes. This is simpler than managing state across pages.
|
|
|
|
4. **Safe-area handling**: The bottom nav uses `env(safe-area-inset-bottom)` CSS to avoid overlapping with system navigation bars on notched devices.
|
|
|
|
5. **Existing PageHeader component**: The existing `PageHeader` component can still be used inside page content if needed (e.g., for sub-headers within a page), but the main header is now provided by `AppShell`.
|