proposal-system/web/src/domain
2026-07-14 01:18:30 -04:00
..
__tests__ feat(api): Phase 6 — optimistic concurrency (SHOC contract) + atomic audit staging (#226) 2026-07-14 01:18:30 -04:00
admin feat(api): Phase 6 — optimistic concurrency (SHOC contract) + atomic audit staging (#226) 2026-07-14 01:18:30 -04:00
customers ci(web): Phase 5 — Prettier check + Playwright smoke, org frontend workflow (#225) 2026-07-14 01:02:20 +00:00
lineItems feat(api): Phase 6 — optimistic concurrency (SHOC contract) + atomic audit staging (#226) 2026-07-14 01:18:30 -04:00
pricingLibrary ci(web): Phase 5 — Prettier check + Playwright smoke, org frontend workflow (#225) 2026-07-14 01:02:20 +00:00
proposals ci(web): Phase 5 — Prettier check + Playwright smoke, org frontend workflow (#225) 2026-07-14 01:02:20 +00:00
shared refactor(web): domain-layer restructure (SHOC layering) + react-hook-form (#223) 2026-07-14 00:11:11 +00:00
sites refactor(web): domain-layer restructure (SHOC layering) + react-hook-form (#223) 2026-07-14 00:11:11 +00:00
README.md refactor(web): domain-layer restructure (SHOC layering) + react-hook-form (#223) 2026-07-14 00:11:11 +00:00

Frontend domain layer — conventions

SHOC-alignment Phase 4 (mirrors shoc-frontend-new dev's src/domain/<entity>/ layering). Every agent/contributor working in this tree builds to THIS spec.

Structure

src/domain/<entity>/
├── api.ts        # HTTP calls only: axios via lib/api/client, path constants, no hooks
├── types.ts      # re-exports from @proposal-system/api-contracts + view-only types
├── schemas.ts    # re-exports from @proposal-system/api-contracts/schemas + form schemas
└── use-cases.ts  # TanStack Query hooks + this domain's query keys (the ONLY public surface)

Domains: proposals, lineItems, customers, pricingLibrary, admin, sites. (auth arrives with the separate auth-storage PR — do NOT create it here.)

Rules

  1. Pages import ONLY from domain/<x>/use-cases (and types) — never from lib/api/*, never apiClient directly, never useQuery/useMutation inline in a page. Pages are thin callers.
  2. Query keys live in the domain: each use-cases.ts exports export const <entity>Keys = { all: ['<entity>'] as const, detail: (id: string) => ['<entity>', id] as const, ... } (TanStack hierarchical-key convention). src/constants/queryKeys.ts is deleted at integration — do not add imports of it.
  3. Invalidation uses the domain key objects across domains where needed (e.g. approving a proposal invalidates proposalsKeys.all).
  4. Forms: react-hook-form + zodResolver (from @hookform/resolvers/zod). Form schemas live in the domain's schemas.ts, derived from the shared contract schemas (@proposal-system/api-contracts/schemas) via .pick/.extend/.omit — never hand-written duplicates. Form field state maps to the request type at submit (a toCreateRequest(formValues) mapper in schemas.ts when non-trivial).
  5. lib/api/client.ts stays — the single axios instance (interceptors, 401 handling). Domain api.ts files import it. The old lib/api/<domain>.ts modules are deleted once no page imports them (integration step) — lib/api/auth.ts stays until the auth PR.
  6. Mutations: toast on error stays in the hook (matching current UX), success invalidation in the hook; page-specific side effects (navigate, dialog close) via the mutation's callbacks at the call site.
  7. No new state managers, no context — server state = TanStack Query, existing Redux auth/ui slices untouched (auth refactor is a separate PR).
  8. Styling: tokens only (var(--...), theme) — no hardcoded hexes.
  9. Verify before returning/committing: npx tsc --noEmit, npm test -- --run, npm run build — all green, no skipped tests, no @ts-ignore.