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

16 KiB
Raw Blame History

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: ThemeProviderColorModeProviderAppProviders
  • 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.

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-tablezero 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: rechartszero imports in source
  • Analytics routes exist; chart rendering not implemented

7. Route Implementation Patterns

Thin re-export (Beauty/Healthcare — ~175 routes)

// 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:409loading 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 adoptioncomponents/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

Document Focus
frontend-migration-plan.md Phased improvement roadmap
module-boundaries.md Import rules and ownership
shared-components-report.md DS inventory and duplicates
dependency-report.md npm, API clients, dead code
import-report.md Alias usage and violations
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