mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 16:18:57 +00:00
feat(web): domain-layer conventions doc + react-hook-form deps (Phase 4 prep)
This commit is contained in:
parent
8dc8f7814b
commit
924bfdd8f6
3 changed files with 79 additions and 0 deletions
30
web/package-lock.json
generated
30
web/package-lock.json
generated
|
|
@ -13,6 +13,7 @@
|
||||||
"@fontsource/dm-sans": "^5.2.8",
|
"@fontsource/dm-sans": "^5.2.8",
|
||||||
"@fontsource/jetbrains-mono": "^5.2.8",
|
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||||
"@fontsource/montserrat": "^5.2.8",
|
"@fontsource/montserrat": "^5.2.8",
|
||||||
|
"@hookform/resolvers": "^5.4.0",
|
||||||
"@mui/icons-material": "^9.2.0",
|
"@mui/icons-material": "^9.2.0",
|
||||||
"@mui/material": "^9.1.1",
|
"@mui/material": "^9.1.1",
|
||||||
"@proposal-system/api-contracts": "file:../shared/api-contracts",
|
"@proposal-system/api-contracts": "file:../shared/api-contracts",
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
"lucide-react": "^1.24.0",
|
"lucide-react": "^1.24.0",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
|
"react-hook-form": "^7.81.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.18.1",
|
"react-router-dom": "^7.18.1",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
|
|
@ -625,6 +627,18 @@
|
||||||
"url": "https://github.com/sponsors/ayuhito"
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hookform/resolvers": {
|
||||||
|
"version": "5.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.4.0.tgz",
|
||||||
|
"integrity": "sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/utils": "^0.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-hook-form": "^7.55.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
|
|
@ -3309,6 +3323,22 @@
|
||||||
"react": "^19.2.7"
|
"react": "^19.2.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hook-form": {
|
||||||
|
"version": "7.81.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.81.0.tgz",
|
||||||
|
"integrity": "sha512-ocbmr2p5KBMoAfj4WCUvped33lVi1Kd5DuDUvQDnB6VEAacOjPI/jMbtDdbhco4y9ct4xUuCmMY0b/C9L0QHjw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/react-hook-form"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "19.2.7",
|
"version": "19.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"@fontsource/dm-sans": "^5.2.8",
|
"@fontsource/dm-sans": "^5.2.8",
|
||||||
"@fontsource/jetbrains-mono": "^5.2.8",
|
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||||
"@fontsource/montserrat": "^5.2.8",
|
"@fontsource/montserrat": "^5.2.8",
|
||||||
|
"@hookform/resolvers": "^5.4.0",
|
||||||
"@mui/icons-material": "^9.2.0",
|
"@mui/icons-material": "^9.2.0",
|
||||||
"@mui/material": "^9.1.1",
|
"@mui/material": "^9.1.1",
|
||||||
"@proposal-system/api-contracts": "file:../shared/api-contracts",
|
"@proposal-system/api-contracts": "file:../shared/api-contracts",
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
"lucide-react": "^1.24.0",
|
"lucide-react": "^1.24.0",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
|
"react-hook-form": "^7.81.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.18.1",
|
"react-router-dom": "^7.18.1",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
|
|
|
||||||
47
web/src/domain/README.md
Normal file
47
web/src/domain/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# 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`.
|
||||||
Loading…
Add table
Reference in a new issue