x
This commit is contained in:
1 parent
3965f4f4da
commit
70877882b0
82 files changed
+4353
-186
No files matched your search
@@ -0,0 +1,179 @@
|
||||
# Codebase Analysis: Menu Fetching, Rendering, and Modeling
|
||||
|
||||
This document provides a detailed investigation and mapping of the menu systems across the HomAroma applications.
|
||||
|
||||
---
|
||||
|
||||
## 1. TypeScript Type Definitions for Menus
|
||||
|
||||
TypeScript definitions for menus are spread across the shared types package and individual application stores/components.
|
||||
|
||||
### Shared / Package Definitions
|
||||
**File**: `packages/types/index.ts`
|
||||
These define the database-facing structures and basic relational entities.
|
||||
```typescript
|
||||
export interface MenuItem {
|
||||
id: number
|
||||
label: string
|
||||
url: string
|
||||
order: number
|
||||
translations?: any[]
|
||||
}
|
||||
|
||||
export interface Menu {
|
||||
id: number
|
||||
name: string
|
||||
items: MenuItem[]
|
||||
payload?: any
|
||||
}
|
||||
```
|
||||
|
||||
### SPA Application definitions
|
||||
**File**: `apps/spa/src/store/menuStore.ts`
|
||||
These define the recursive client-side tree structure used by the Mega Menu payload.
|
||||
```typescript
|
||||
export interface MenuLink {
|
||||
label: Record<string, string>;
|
||||
url?: string;
|
||||
links?: MenuLink[];
|
||||
}
|
||||
|
||||
export interface MenuColumn {
|
||||
title: Record<string, string>;
|
||||
links: MenuLink[];
|
||||
}
|
||||
|
||||
export interface MegaMenuPayload {
|
||||
id?: string;
|
||||
nodeId?: string;
|
||||
label: Record<string, string>;
|
||||
url?: string;
|
||||
columns?: MenuColumn[];
|
||||
}
|
||||
```
|
||||
|
||||
### Website Storefront definitions
|
||||
**File**: `apps/website/src/components/navigation/menuTypes.ts`
|
||||
Similar to the SPA definitions but more flexible, supporting both localized translation maps and plain string fallbacks.
|
||||
```typescript
|
||||
export interface MenuLink {
|
||||
label: Record<string, string> | string
|
||||
url?: string
|
||||
links?: MenuLink[]
|
||||
}
|
||||
|
||||
export interface MenuColumn {
|
||||
title: Record<string, string> | string
|
||||
links: MenuLink[]
|
||||
}
|
||||
|
||||
export interface MegaMenuPayload {
|
||||
id?: string
|
||||
nodeId?: string
|
||||
label: Record<string, string> | string
|
||||
url?: string
|
||||
columns?: MenuColumn[]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Elysia Backend Routes
|
||||
|
||||
**File**: `apps/backend/src/routes/menus.ts`
|
||||
The backend exposes routes under the prefix `/menus` to manage menu structure, fetch menus, and update metadata.
|
||||
|
||||
### Route List and Functionality
|
||||
- **`GET /`**: Retrieves all menus, including their menu items sorted by `order` in ascending order and all associated translations.
|
||||
- **`GET /:name`**: Retrieves a single menu by name. If it is not found in the database, it returns a default fallback structure:
|
||||
```json
|
||||
{ "name": "name", "items": [], "payload": null, "isActive": false }
|
||||
```
|
||||
- **`POST /`** *(Admin only)*: Creates a menu with an optional array of flat items.
|
||||
- **`PUT /:name`** *(Admin only)*: The main entry point to upsert/save a menu's tree payload. If the menu does not exist, it is created. It then uses a database transaction (`prisma.$transaction`) to:
|
||||
1. Retrieve all existing `MenuItem` entries.
|
||||
2. Compute item differences and delete any old `MenuItem`s not present in the payload.
|
||||
3. Iterate over the input items. If an item exists, it updates `label`, `url`, and `order`, and upserts its language translations in `MenuItemTranslation`.
|
||||
4. If the item is new, it creates a new `MenuItem` and its nested translations.
|
||||
- **`PATCH /items/:id/translations`**: Directly manages/patches the translations for a single `MenuItem` for a set list of supported languages (`['th', 'en', 'ja', 'zh', 'vi', 'ko']`).
|
||||
|
||||
### Request Validation
|
||||
Elysia validates request payloads using TypeBox schemas. The `PUT /:name` schema is:
|
||||
```typescript
|
||||
{
|
||||
body: t.Object({
|
||||
payload: t.Optional(t.Any()),
|
||||
isActive: t.Optional(t.Boolean()),
|
||||
items: t.Array(t.Object({
|
||||
id: t.Optional(t.Number()),
|
||||
label: t.String(),
|
||||
url: t.String(),
|
||||
order: t.Number(),
|
||||
translations: t.Optional(t.Array(t.Object({
|
||||
langCode: t.String(),
|
||||
label: t.String()
|
||||
})))
|
||||
}))
|
||||
})
|
||||
}
|
||||
```
|
||||
*Note: The `payload` field is validated as `t.Optional(t.Any())`. This means backend validation is schema-less for the JSON tree structure stored in `Menu.payload`.*
|
||||
|
||||
---
|
||||
|
||||
## 3. Frontend Menu Rendering
|
||||
|
||||
The main menu and dropdown panels are fetched and rendered differently in `apps/spa` and `apps/website`.
|
||||
|
||||
### A. SPA Frontend (`apps/spa`)
|
||||
- **Fetching**: Fetched via the Zustand store (`useMenuStore`) inside `apps/spa/src/components/Navbar.tsx` on component mount (`fetchMegaMenu()`).
|
||||
- **Desktop Mega Menu (`apps/spa/src/components/DesktopMegaMenu.tsx`)**:
|
||||
- Maps `items` (i.e. `MegaMenuPayload[]`) horizontally to trigger buttons.
|
||||
- Dropdown panels are positioned absolutely flush below the navbar using a CSS custom property `--navbar-bottom` computed dynamically by the `Navbar`.
|
||||
- **Image Placements (Currently Mocked)**:
|
||||
- *Root Image (Far Left)*: Mocked using `<img src={`https://picsum.photos/seed/root-${uniqueId}/400/400`} />`.
|
||||
- *Column Image (Inside Center Grid)*: Renders a 1:1 ratio square thumbnail above/next to each column title, mocked using `<img src={`https://picsum.photos/seed/col-${uniqueId}-${idx}/200/200`} />`.
|
||||
- *Link Hover Image (Far Right)*: Tracks the hovered link inside `onMouseEnter` of each link list item. The hovered image URL is stored in local state `hoveredImage` and rendered inside the right column. It is mocked using `https://picsum.photos/seed/link-${uniqueId}-${idx}-${lIdx}/400/400`.
|
||||
- **Mobile Drawer (`apps/spa/src/components/MobileDrawer.tsx`)**:
|
||||
- Uses stack-based navigation to transition between parent menus and child links.
|
||||
- Does **not** render any images.
|
||||
|
||||
### B. Website Storefront (`apps/website`)
|
||||
- **Fetching**: Fetched during Server-Side Rendering (SSR) in `apps/website/src/app/[lang]/layout.tsx` (`getMegaMenu(lang)`) and passed down to client-side components to eliminate layout shifts or content flashes.
|
||||
- **Desktop Mega Menu (`apps/website/src/components/navigation/DesktopMegaMenu.tsx`)**:
|
||||
- Renders a horizontal trigger list and dropdown panels similarly to the SPA.
|
||||
- **Current Gap**: The 3-image layout (Root, Column, Hover) is **not yet implemented**. It only renders the columns and links list structure.
|
||||
- **Mobile Drawer (`apps/website/src/components/navigation/MobileDrawer.tsx`)**:
|
||||
- Stack-based hierarchical navigation drawer.
|
||||
- Hides the complex image elements.
|
||||
|
||||
---
|
||||
|
||||
## 4. Admin Menu Manager Page
|
||||
|
||||
**File**: `apps/admin/src/pages/MenuManagerPage.tsx`
|
||||
|
||||
The `MenuManagerPage` is where administrators configure the flat footer links and the hierarchical mega menu payload.
|
||||
|
||||
### Construction and Editing
|
||||
- **Hierarchy Representation**: The page uses a recursive sub-component `TreeNodeEditor` to build and edit the menu structure:
|
||||
- **Root Nodes (MegaMenuPayload)**:
|
||||
- Editable fields: translations for `label`, `url`.
|
||||
- Contains child array: `columns` (representing `MenuColumn`s).
|
||||
- **Column Nodes (MenuColumn)**:
|
||||
- Editable fields: translations for `title`.
|
||||
- Contains child array: `links` (representing `MenuLink`s).
|
||||
- **Link Nodes (MenuLink)**:
|
||||
- Editable fields: translations for `label`, `url`.
|
||||
- Allows further nested links.
|
||||
- **Save Operations**:
|
||||
- Triggered by submitting the form.
|
||||
- Makes a `PUT` request to `${API_BASE}/menus/${selectedMenuName}`.
|
||||
- For the `main` menu, the payload consists of:
|
||||
```json
|
||||
{
|
||||
"items": [],
|
||||
"payload": [...] // Complete JSON tree structures built via TreeNodeEditor
|
||||
}
|
||||
```
|
||||
- **Current Gap**: There are no input controls in `TreeNodeEditor` to set `rootImage`, `columnImage`, or `hoverImage` values.
|
||||
Reference in new issue
Block a user