# TorbatYar Frontend — Migration Plan **Date:** 2026-07-26 **Status:** Planning document — no code changes applied **Prerequisite:** Read [frontend-architecture-audit.md](./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 2–3) **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](./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 4–6) **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 7–10) **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 11–16) **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:** ```tsx // 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 17–20) **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 2–3) Phase 2 ░░░░░░░░████████ Portal infra (Weeks 4–6) Phase 3 ░░░░░░░░░░░░░░████████████ API clients (Weeks 7–10) Phase 4 ░░░░░░░░░░░░░░░░░░░░████████████████ Accounting routes (Weeks 11–16) Phase 5 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░████████ Hardening (Weeks 17–20) 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 | --- ## Related Documents - [frontend-architecture-audit.md](./frontend-architecture-audit.md) - [module-boundaries.md](./module-boundaries.md) - [refactor-risk-analysis.md](./refactor-risk-analysis.md)