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>
390 lines
16 KiB
Markdown
390 lines
16 KiB
Markdown
# 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 200–660 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 2–3 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` | ~6–7 | 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 3–25 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,300–1,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 |
|