TorbatYar/frontend/docs/frontend-migration-plan.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

11 KiB
Raw Permalink Blame History

TorbatYar Frontend — Migration Plan

Date: 2026-07-26
Status: Planning document — no code changes applied
Prerequisite: Read frontend-architecture-audit.md

This plan prioritizes stabilization before restructuring. Each phase is independently shippable.


Guiding Principles

  1. Do not refactor working modules mid-feature — changes are incremental and gated
  2. Enforce boundaries with tooling, not convention alone
  3. Accounting follows beauty/healthcare route patterns over time, not immediately
  4. Shared abstractions emerge from deduplication, not upfront framework design
  5. Keep the monolith until build times or team size force extraction

Phase 0 — Stabilize (Week 1)

Goal: Green build, remove dead weight, zero functional changes.

Task Files Effort Risk
Fix DS Button type error OR remove loading prop usage app/accounting/purchase/returns/page.tsx, possibly components/ds/Button.tsx S Low
Remove unused files (5 confirmed dead) lib/api-client.ts, lib/optional-sso.ts, lib/beauty-business-nav.ts, components/HealthStatus.tsx, components/beauty/pages/mobile.tsx S Low
Remove or use unused npm deps @tanstack/react-table, recharts from package.json S Low
Add npm run typecheck script package.json S None
Verify npm run build passes in CI CI config S None

Exit criteria: npm run build succeeds; no files with zero imports (except intentional barrels).


Phase 1 — Boundary Enforcement (Weeks 23)

Goal: Prevent architectural regression as new modules are added.

Task Description Effort
Add ESLint no-restricted-imports rules Block @/components/accounting/* from beauty/healthcare and vice versa M
Add ESLint rule: DS must not import domain code Already clean — add rule to keep it that way S
Document allowed import matrix See module-boundaries.md S
Add path aliases (optional) @/ds/*, @/platform/* — reduce @/ ambiguity M
Consolidate beauty owner routes Merge beauty/(owner)/* CRUD into components/beauty/pages/owner.tsx pattern OR merge owner/ into (owner)/ L

Exit criteria: Lint fails on cross-domain imports; beauty owner has single URL tree.


Phase 2 — Shared Portal Infrastructure (Weeks 46)

Goal: Extract beauty/healthcare duplication into reusable platform layer.

2.1 Create components/portals/ (shared)

Extract from duplicated files:

Shared abstraction Source files
createPortalLayout() beauty/createPortalLayout.tsx, healthcare/createPortalLayout.tsx
PortalShell base BeautyPortalShell, HealthcarePortalShell, AccountingShell (~90% overlap)
pages/shared.tsx helpers beauty/pages/shared.tsx, healthcare/pages/shared.tsx
PublicSiteLayout PublicBeautyLayout, PublicHealthcareLayout
Accent-token cards/chips BeautyCards, HealthcareCards, BeautyStatusChip, MedicalStatusChip

Parameterize by:

  • Portal ID type (generic)
  • Nav config (lib/*-portals.ts stays domain-specific)
  • CSS accent variable (--beauty-accent vs --health-accent)

2.2 Normalize module DS folder naming

Pick one convention:

  • Option A (recommended): components/{module}/design-system/ for both beauty and healthcare
  • Option B: Move module-specific tokens to components/portals/tokens.ts with domain presets

Exit criteria: Single createPortalLayout factory; portal shells share one base component; healthcare ui/ renamed to design-system/.


Phase 3 — API Client Decomposition (Weeks 710)

Goal: Split monolithic API clients into maintainable modules.

Current state

File Lines Methods (approx)
lib/accounting-api.ts 1,856 200+
lib/beauty-business-api.ts 1,446 150+
lib/healthcare-api.ts 1,322 120+

Target structure

lib/
├── api/
│   ├── core.ts              # Shared fetch wrapper, error class, token refresh
│   ├── accounting/
│   │   ├── index.ts         # Re-export barrel
│   │   ├── vouchers.ts
│   │   ├── treasury.ts
│   │   ├── inventory.ts
│   │   └── ...
│   ├── beauty/
│   │   ├── index.ts
│   │   ├── appointments.ts
│   │   ├── catalog.ts
│   │   └── ...
│   └── healthcare/
│       ├── index.ts
│       └── ...
├── accounting-api.ts        # Deprecated barrel → re-exports from api/accounting/

Migration strategy:

  1. Extract shared createApiClient(baseUrl, serviceName) helper
  2. Move one domain subdomain at a time (e.g. accounting/vouchers)
  3. Keep deprecated barrel exports until all imports updated
  4. Run grep after each subdomain to verify zero old imports

Exit criteria: No API file exceeds 400 lines; shared fetch logic in one place.


Phase 4 — Accounting Route Extraction (Weeks 1116)

Goal: Align accounting with beauty/healthcare thin-route pattern.

Priority extractions (largest route files)

Route file Lines Target component
app/accounting/chart-of-accounts/page.tsx 644 components/accounting/ChartOfAccountsPage.tsx
app/accounting/reports/page.tsx 663 components/accounting/ReportsPage.tsx
app/accounting/fiscal/page.tsx 579 components/accounting/FiscalPage.tsx
app/accounting/vouchers/[id]/page.tsx 550 components/accounting/VoucherDetailPage.tsx
app/accounting/treasury/page.tsx 490 components/accounting/TreasuryPage.tsx
app/accounting/payroll/page.tsx 457 components/accounting/PayrollPage.tsx

Screen aggregator split

Current file Lines Action
BusinessDocsPage.tsx 1,328 Split by document type
WorkflowScreens.tsx 1,132 Split by workflow domain
OperationalDocumentPage.tsx 996 Keep; already reusable
DomainScreens.tsx 652 Split by domain section

Pattern:

// app/accounting/reports/page.tsx (after)
export { ReportsPage as default } from "@/components/accounting/ReportsPage";

Exit criteria: No accounting page.tsx exceeds 50 lines except dynamic route params; no component exceeds 500 lines.


Phase 5 — Platform Hardening (Weeks 1720)

Goal: Production-grade resilience and auth.

Task Description
Add middleware.ts Edge auth check, tenant header injection, public route allowlist
Add segment error.tsx Per-module error boundaries (accounting/, beauty/, healthcare/)
Add segment loading.tsx Skeleton states using DS LoadingState / Skeleton
Conditional SiteHeader Hide platform header on /beauty/site/*, /healthcare/site/*
BFF for Core API /api/core/[...path] proxy for admin/dashboard
Bundle analysis @next/bundle-analyzer baseline before module #4

Phase 6 — New Module Template (Ongoing)

When adding module #4 (e.g. Delivery, CRM, Hospitality):

Required scaffold

app/{module}/
├── layout.tsx                    # pass-through or module shell
├── hub/page.tsx                  # portal picker
├── {portal}/layout.tsx           # createPortalLayout("{portal}")
├── {portal}/.../page.tsx         # thin re-exports
└── site/layout.tsx               # public site (if applicable)

components/{module}/
├── design-system/
│   ├── tokens.ts
│   └── index.ts
├── pages/
│   ├── shared.tsx
│   └── {portal}.tsx
├── {Module}PortalShell.tsx       # extends shared PortalShell
└── createPortalLayout.tsx        # OR import from components/portals/

lib/
├── {module}-api.ts               # OR lib/api/{module}/
└── {module}-portals.ts

app/api/{module}/[...path]/route.ts

Checklist before merge

  • Zero imports from other business modules
  • All pages use @/components/ds (not components/ui)
  • BFF proxy route exists
  • Nav config in lib/{module}-portals.ts
  • Query keys prefixed ["{module}", tenantId, ...]
  • ESLint boundary rules pass

Phase Summary Timeline

Phase 0  ████                          Stabilize (Week 1)
Phase 1  ░░░░████                      Boundaries (Weeks 23)
Phase 2  ░░░░░░░░████████              Portal infra (Weeks 46)
Phase 3  ░░░░░░░░░░░░░░████████████    API clients (Weeks 710)
Phase 4  ░░░░░░░░░░░░░░░░░░░░████████████████  Accounting routes (Weeks 1116)
Phase 5  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░████████  Hardening (Weeks 1720)
Phase 6  ────────────────────────────────────►  Module template (ongoing)

What NOT to do

Anti-pattern Reason
Split into npm packages prematurely Team size doesn't justify overhead yet
Introduce Redux/Zustand TanStack Query + local state is sufficient
Rewrite accounting in beauty page-bundle style in one PR Too risky; extract file-by-file
Move public sites to separate Next.js app Shared auth/tenant infra makes monolith correct for now
Adopt TanStack Table without requirement Custom DataTable works; add features when needed

Success Metrics

Metric Current Phase 0 Phase 4 Phase 5
Build passes
Max page.tsx lines (accounting) 663 663 ≤50 ≤50
Max component file lines 1,856 1,856 ≤500 ≤500
Cross-domain imports 0 0 0 0 (enforced)
Dead files 5 0 0 0
Unused npm deps 2 0 0 0
Portal factory implementations 2 2 1 1
Middleware coverage 0% 0% 0% 100% public/private