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

266 lines
11 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 — 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 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](./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:**
```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 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 |
---
## Related Documents
- [frontend-architecture-audit.md](./frontend-architecture-audit.md)
- [module-boundaries.md](./module-boundaries.md)
- [refactor-risk-analysis.md](./refactor-risk-analysis.md)