TorbatYar/frontend/docs/frontend-architecture-audit.md
Mortezakoohjani 6f4a484051 Migrate Beauty, Healthcare, and Accounting frontend to modular src/modules architecture.
Move business logic out of App Router into module packages, add boundary validation scripts, and keep all routes as thin re-exports without changing URLs or API behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 22:28:27 +03:30

390 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TorbatYar Frontend — Enterprise Architecture Audit
**Audit date:** 2026-07-26
**Scope:** `frontend/` source (excluding `.next`, `node_modules`)
**Method:** Read-only static analysis — no source files modified
**Stack:** Next.js 15.5.18 · React 19 · TypeScript 5.4 · Tailwind CSS 3.4
---
## Executive Summary
The TorbatYar SuperApp frontend is a **single Next.js monolith** hosting the platform shell plus three mature business modules (Accounting, Beauty Business, Healthcare). The codebase contains **~481 TypeScript/TSX files** and **350 route pages**, organized around a consistent multi-tenant, portal-based architecture.
### Overall readiness for long-term scaling: **Moderate — structurally sound, operationally strained**
| Dimension | Rating | Notes |
|-----------|--------|-------|
| Module isolation | ✅ Strong | No cross-domain imports between accounting/beauty/healthcare |
| Routing architecture | ✅ Good | App Router with portal layouts and BFF proxies |
| Design system | ⚠️ Mixed | `components/ds/` is mature; parallel `components/ui/` and per-module DS exist |
| Route vs component separation | ⚠️ Inconsistent | Beauty/healthcare use thin routes; accounting embeds logic in routes |
| Build health | ❌ Blocked | Production build fails TypeScript check (see Build Performance) |
| Dependency hygiene | ⚠️ Weak | Unused npm packages; 3 monolithic API clients (1,800+ lines each) |
| Observability / resilience | ⚠️ Missing | No `middleware.ts`, no route-level `error.tsx` / `loading.tsx` |
---
## 1. Repository Topology
```
frontend/
├── app/ # Next.js App Router (350 pages, 20 layouts, 3 API routes)
├── components/ # Shared + domain UI
│ ├── ds/ # Primary design system (11 files)
│ ├── ui/ # Legacy/marketing UI (6 files)
│ ├── providers/ # Query + color mode
│ ├── accounting/ # Accounting shell + screen aggregators
│ ├── beauty/ # Portal shells, design-system/, pages/
│ ├── healthcare/ # Portal shells, ui/, pages/
│ ├── admin/, auth/, settings/
│ └── *.tsx # Root-level guards, header, theme
├── hooks/ # 10 shared hooks
├── lib/ # 20 files — API clients, nav, auth, theme
├── styles/globals.css # CSS variables, RTL, module accents
├── public/ # Fonts, theme.config.json
├── scripts/
├── tailwind.config.ts
├── tsconfig.json # paths: @/* → ./*
└── next.config.mjs # env defaults, healthcare redirects
```
**Path alias:** Single alias `@/*` maps to project root. No scoped aliases (`@accounting/*`, `@shared/*`).
---
## 2. App Router Structure
### 2.1 Top-level segments
| Segment | ~Pages | Layout depth | Auth pattern |
|---------|--------|--------------|--------------|
| `/` (platform) | 6 | Root only | Mixed |
| `/accounting` | ~130 | Root → Accounting | `AuthGuard` + tenant gate in layout |
| `/admin` | 9 | Root → Admin | `AdminAuthGuard` (bypass on `/admin/login`) |
| `/beauty` | ~100 | Root → pass-through → portal layouts | `createPortalLayout()` per portal |
| `/healthcare` | ~100 | Root → pass-through → portal layouts | `createPortalLayout()` per portal |
| `/auth`, `/login`, `/register`, `/onboarding`, `/dashboard` | 6 | Root only | Inline guards |
### 2.2 Route groups
Only one route group exists: `app/beauty/(owner)/`. Parentheses are invisible in URLs — `/beauty/organizations` resolves through `(owner)/organizations/page.tsx`.
**Architecture smell:** Beauty owner functionality is split across two URL trees:
- `app/beauty/(owner)/*` — CRUD pages with inline form logic (~20 routes)
- `app/beauty/owner/*` — analytics, calendar, integrations via thin re-exports (~6 routes)
Both use `createPortalLayout("owner")` but live in separate folder hierarchies.
### 2.3 Dynamic routes (13)
| Pattern | Module |
|---------|--------|
| `/accounting/vouchers/[id]` | Accounting |
| `/admin/tenants/[id]` | Platform admin |
| `/beauty/mobile/[portal]` | Beauty |
| `/beauty/site/{center,salon,service,staff,tracking}/[id]` | Beauty public |
| `/healthcare/mobile/[portal]` | Healthcare |
| `/healthcare/site/{clinic,doctor,hospital,tracking}/[id]` | Healthcare public |
### 2.4 Missing App Router conventions
| File | Status | Impact |
|------|--------|--------|
| `middleware.ts` | Absent | No edge auth, tenant routing, or geo redirects |
| `loading.tsx` | Absent | No streaming skeletons at segment boundaries |
| `error.tsx` | Absent | Uncaught errors bubble to default Next.js error UI |
| `not-found.tsx` | Absent | Custom 404 not defined |
| `template.tsx` | Absent | No per-navigation remount behavior |
---
## 3. Layouts (20 total)
### Root layout (`app/layout.tsx`)
- RTL Persian (`lang="fa" dir="rtl"`)
- Provider stack: `ThemeProvider``ColorModeProvider``AppProviders`
- Global `SiteHeader` wraps all routes including module portals and public sites
**Concern:** Public marketing sites (`/beauty/site`, `/healthcare/site`) inherit the platform `SiteHeader`, which may not match intended public-site UX.
### Module layout patterns
**Pattern A — Dedicated module shell (Accounting, Admin):**
```
app/layout.tsx → accounting/layout.tsx → AccountingShell + prefetch
app/layout.tsx → admin/layout.tsx → AdminShell
```
**Pattern B — Pass-through + portal factory (Beauty, Healthcare):**
```
app/layout.tsx → beauty/layout.tsx (pass-through)
→ beauty/{portal}/layout.tsx → createPortalLayout(id)
```
**Pattern C — Public site layout:**
```
app/layout.tsx → beauty/site/layout.tsx → PublicBeautyLayout
```
Portal factory (`createPortalLayout`) encapsulates:
1. `AuthGuard`
2. Tenant/onboarding gate via `useMe()`
3. Domain portal shell with nav from `lib/*-portals.ts`
---
## 4. API Routes (BFF Layer)
Three same-origin catch-all proxies under `app/api/`:
| Route | Upstream env | Default |
|-------|--------------|---------|
| `/api/accounting/[...path]` | `ACCOUNTING_SERVICE_URL` | `:8002` |
| `/api/beauty-business/[...path]` | `BEAUTY_BUSINESS_SERVICE_URL` | `:8011` |
| `/api/healthcare/[...path]` | `HEALTHCARE_SERVICE_URL` | `:8010` |
**Methods:** GET, POST, PATCH, PUT, DELETE
**Forwarded headers:** `authorization`, `content-type`, `x-tenant-id`, `accept` (+ `x-patient-id` for healthcare)
**Gaps:**
- No BFF for platform Core API (uses direct `NEXT_PUBLIC_BACKEND_URL`)
- No BFF for Identity/Keycloak
- No `/api/delivery/*` despite backend delivery service existing
- No rate limiting, request logging, or response caching at BFF layer
---
## 5. Business Modules
### 5.1 Accounting (`/accounting`)
- **Architecture:** Monolithic screen components + inline route pages
- **Components:** `AccountingShell`, `BusinessDocsPage`, `WorkflowScreens`, `DomainScreens`, `OperationalDocumentPage`
- **API:** `lib/accounting-api.ts` (1,856 lines), consumed via `/api/accounting/*`
- **Forms:** Heavy RHF + Zod (~20 files); `useFieldArray` in voucher flows
- **Route pattern:** Most logic lives directly in `app/accounting/**/page.tsx` (many 200660 line files)
### 5.2 Beauty Business (`/beauty`)
- **Architecture:** Portal-based with page bundles
- **Portals:** owner, admin, customer, reception, staff, site (public), hub, auth, mobile
- **Route pattern:** ~75 routes are 23 line re-exports to `components/beauty/pages/{portal}.tsx`
- **Owner CRUD:** ~20 routes under `(owner)/` contain inline page logic (forms, mutations)
- **API:** `lib/beauty-business-api.ts` (1,446 lines)
### 5.3 Healthcare (`/healthcare`)
- **Architecture:** Mirrors beauty portal pattern
- **Portals:** patient, doctor, reception, clinic, hospital, pharmacy, admin, site, hub, auth, mobile
- **Route pattern:** ~90 routes are thin re-exports to `components/healthcare/pages/`
- **Legacy redirects:** 13 stub pages + 13 `next.config.mjs` redirects for URL compatibility
- **Delivery:** Only accessible via `/healthcare/pharmacy-portal/delivery` — no standalone delivery module
### 5.4 Platform Admin (`/admin`)
- Tenant/user/plan/feature management
- Uses `lib/api.ts` (Core backend) directly, not BFF
---
## 6. Shared Infrastructure
### 6.1 Design system
Two parallel UI layers — see [shared-components-report.md](./shared-components-report.md).
Primary: `components/ds/` — CVA-based Button, FormField, DataTable, DatePicker (Jalali), Dialog, etc.
### 6.2 Theme
- White-label via CSS variables in `styles/globals.css`
- Runtime theme from `public/theme.config.json` or tenant API (`lib/theme.ts`, `useTheme`)
- Module accents: `--beauty-accent`, `--health-accent`
- Tailwind maps semantic tokens to CSS vars (`tailwind.config.ts`)
### 6.3 State management
| Layer | Technology | Scope |
|-------|------------|-------|
| Server/async state | TanStack Query v5 | Global (`AppProviders`) |
| Auth/session | Custom hooks + localStorage tokens | `useMe`, `useAuth`, `lib/auth.ts` |
| UI state | `useState` | Local per component |
| Global UI | React Context | Color mode only (`ColorModeProvider`) |
No Redux, Zustand, Jotai, or URL state library.
**Query defaults:** staleTime 60s, gcTime 10min, retry 1, no refetch on focus/mount.
### 6.4 Hooks (`hooks/` — 10 files)
| Hook | Usage count | Role |
|------|-------------|------|
| `useTenantId` | ~67 | Tenant scoping for API calls |
| `useMe` | ~7 | Session/user/workspace |
| `useBeautyLookups` / `useHealthcareLookups` | ~67 | Cached dropdown data |
| `useHealthcarePatientContext` / `useHealthcareDoctorContext` | 1 each | Portal identity resolution |
### 6.5 Forms
- **Stack:** react-hook-form + @hookform/resolvers + zod
- **Accounting:** Mature — Controller for MoneyInput/DatePicker, useFieldArray for vouchers
- **Beauty owner:** CRUD dialogs with zodResolver
- **Healthcare:** Display-heavy; minimal form usage
- **No FormProvider** pattern; `FormField` wrapper from DS used instead
### 6.6 Tables
- **Installed:** `@tanstack/react-table`**zero imports in source**
- **Actual:** Custom `DataTable` in `components/ds/Page.tsx` — simple column/row renderer
- **Beauty wrapper:** `BeautyTablePage` adds client-side search
### 6.7 Charts
- **Installed:** `recharts`**zero imports in source**
- Analytics routes exist; chart rendering not implemented
---
## 7. Route Implementation Patterns
### Thin re-export (Beauty/Healthcare — ~175 routes)
```tsx
// app/beauty/customer/wallet/page.tsx
export { CustomerWallet as default } from "@/components/beauty/pages/customer";
```
**Benefits:** Clean URL-to-component mapping, page bundles co-locate related screens
**Risks:** Large page bundle files (753 lines in `beauty/pages/public.tsx`)
### Inline page logic (Accounting — ~130 routes)
Most accounting routes are `"use client"` components with queries, forms, and mutations inline. Some delegate to shared screen components (`WorkflowScreens`, `DomainScreens`) via 325 line wrappers.
### Hybrid (Beauty owner CRUD — ~20 routes)
`(owner)/*` pages contain full CRUD implementation inline rather than delegating to page bundles like other beauty portals.
---
## 8. Architecture Violations & Smells
### Critical
| Issue | Location | Severity |
|-------|----------|----------|
| Production build fails TypeScript | `app/accounting/purchase/returns/page.tsx:409``loading` prop on DS `Button` | 🔴 Blocker |
| Business logic in routes (accounting) | 644-line `chart-of-accounts/page.tsx`, 663-line `reports/page.tsx` | 🔴 Maintainability |
| Split owner portal (beauty) | `(owner)/` vs `owner/` folders | 🟡 Confusion |
### Moderate
| Issue | Details |
|-------|---------|
| Dual Button/Badge systems | `components/ui/` vs `components/ds/` |
| Monolithic API clients | 3 files × 1,3001,850 lines |
| Unused npm dependencies | `@tanstack/react-table`, `recharts` |
| Global SiteHeader on public sites | May leak platform chrome into marketing pages |
| No middleware | Auth/tenant checks duplicated in layouts |
| Dead code | 5 files with zero imports (see dependency-report.md) |
### Low
| Issue | Details |
|-------|---------|
| Inconsistent module DS folder naming | beauty: `design-system/` vs healthcare: `ui/` |
| Deprecated `BeautyBusinessShell` | Still in codebase |
| Healthcare legacy redirect stubs | 13 client-side redirect pages duplicate config redirects |
---
## 9. Build Performance
**Build command:** `npm run build` (Next.js 15.5.18)
| Metric | Value |
|--------|-------|
| Webpack compile | ~55 seconds |
| Type check | **Failed** |
| Lint | Not reached (blocked by type error) |
**Type error:**
```
app/accounting/purchase/returns/page.tsx:409
Property 'loading' does not exist on ButtonProps (components/ds/Button.tsx)
```
**Implications for scaling:**
- 350 static/SSR pages in single build graph — compile time will grow linearly with new modules
- No module federation or package splitting
- All business modules share one `node_modules` and one build pipeline
- Incremental compilation enabled (`tsconfig incremental: true`) but full builds remain heavy
**Recommendations (documented in migration plan):**
- Fix type error immediately
- Consider route-group-level code splitting audit
- Evaluate `@next/bundle-analyzer` before adding 4th business module
---
## 10. Scaling Readiness Assessment
### What works well
1. **Clean domain boundaries** — accounting, beauty, healthcare do not import each other's components or lib
2. **Consistent BFF pattern** — browser never calls microservice origins directly for module APIs
3. **Portal factory pattern** — new role-based portals can reuse `createPortalLayout`
4. **Shared DS adoption**`components/ds/` used across all modules (~90+ import sites)
5. **TanStack Query conventions** — predictable cache keys per domain
6. **No circular dependencies** — import graph analysis found zero cycles
### What blocks enterprise scale
1. **Accounting route bloat** — business logic not extracted from App Router pages
2. **API client monoliths** — unmaintainable at 2,000+ methods per service
3. **Missing edge/middleware layer** — auth and tenant resolution duplicated
4. **No error/loading boundaries** — poor resilience UX at scale
5. **Build not green** — CI cannot enforce quality gate
6. **Beauty/healthcare duplication** — ~90% identical portal infrastructure copied twice
### Verdict
The architecture is **ready for adding routes and portals within existing modules** but **not ready for adding many new business modules** without first establishing:
- Enforced module boundary lint rules
- Shared portal infrastructure abstraction
- Route-thin / component-thick convention across all modules
- Green build pipeline
---
## 11. Related Documents
| Document | Focus |
|----------|-------|
| [frontend-migration-plan.md](./frontend-migration-plan.md) | Phased improvement roadmap |
| [module-boundaries.md](./module-boundaries.md) | Import rules and ownership |
| [shared-components-report.md](./shared-components-report.md) | DS inventory and duplicates |
| [dependency-report.md](./dependency-report.md) | npm, API clients, dead code |
| [import-report.md](./import-report.md) | Alias usage and violations |
| [refactor-risk-analysis.md](./refactor-risk-analysis.md) | Risk matrix for future changes |
---
## Appendix: File Counts
| Category | Count |
|----------|------:|
| Total `.ts`/`.tsx` source files | ~481 |
| `page.tsx` routes | 350 |
| `layout.tsx` | 20 |
| API route handlers | 3 |
| `components/ds/` files | 11 |
| `hooks/` files | 10 |
| `lib/` files | 20 |
| `@/` import statements | 756 |
| Relative import statements | 55 |
| Files > 300 lines | 20+ |
| Circular dependencies | 0 |