feat(residential-toggle): port-level module gate for Residential
Adds a `residential_module_enabled` port setting (default ON) that hides/disables the entire Residential surface when an admin turns it off, mirroring the Tenancies / Invoices / Expenses module-toggle pattern. Disabling is a soft hide — residential clients/interests are preserved and reappear on re-enable. Surfaces gated: - Route guard: new residential/layout.tsx renders ModuleDisabledPage (covers all 5 residential pages) - Sidebar "Residential" section + mobile more-sheet tile (SSR-resolved residentialModuleByPort threaded layout → app-shell → sidebar) - Global search: residential client/interest buckets early-return at the shared chokepoint so disabled-port records don't dead-end - Public intake: /api/public/residential-inquiries 404s when off - Admin Switch in settings-manager (writes via settings PUT) Service TDD'd (residential-module.test.ts, 6 tests) plus a disabled-port rejection test on the public endpoint. tsc + lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -602,6 +602,41 @@ The catch: most ports will have ~3 templates total (EOI, Reservation Agreement,
|
||||
- **Fix proposal:** replace the type+picker pair with a single unified search field (same idiom as the global Command-search). Typing surfaces matching clients/companies/yachts/interests/tenancies inline, each row carrying its type label as a badge. Recent interactions surface first when the input is empty. The chosen entity sets both `subjectType` and `subjectId` in one click.
|
||||
- **Bundle with:** the larger wizard refactor (above) — if `/documents/new` becomes a `<GenerateDocumentDialog>`, this is the natural place to ship the unified subject picker as one consistent pattern.
|
||||
|
||||
### Admin toggle to disable Residential entirely (module gate)
|
||||
|
||||
- **`SHIPPED locally (not yet committed) — 2026-05-31`** — net-new wiring; mirrors the Tenancies / Invoices / Expenses module-toggle pattern.
|
||||
- **Fix applied (2026-05-31):** full module gate shipped end-to-end, defaulting ON.
|
||||
- New `src/lib/services/residential-module.service.ts` (`isResidentialModuleEnabled` / `enableResidentialModule` / `disableResidentialModule` / `assertResidentialModuleEnabled`) — TDD'd via `tests/integration/residential-module.test.ts` (6 tests, RED→GREEN).
|
||||
- Registry key `residential_module_enabled` (`section: 'operations.residential'`, `defaultValue: true`) in `src/lib/settings/registry.ts`.
|
||||
- Route guard `src/app/(dashboard)/[portSlug]/residential/layout.tsx` renders `<ModuleDisabledPage>` when off — covers all 5 residential pages.
|
||||
- Sidebar: `requiresResidentialModule` section flag + `residentialModuleByPort` map resolved SSR in `src/app/(dashboard)/layout.tsx`, threaded through `app-shell.tsx` → `sidebar.tsx`; mobile `more-sheet.tsx` Residential tile gated via new `residentialModuleEnabled` prop.
|
||||
- Global search: module gate added at the shared chokepoint (`searchResidentialClients` / `searchResidentialInterests` early-return `[]` when off) so disabled-port records don't dead-end on the guard page — covers both the all-buckets fan-out and the single-bucket `type=` path.
|
||||
- Public intake: `src/app/api/public/residential-inquiries/route.ts` now `assertResidentialModuleEnabled` after port resolution → 404 when off (regression test added to `tests/integration/public-residential-inquiry.test.ts`).
|
||||
- Admin Switch: `residential_module_enabled` added to `settings-manager.tsx` KNOWN_SETTINGS (writes via `PUT /api/v1/admin/settings/[key]`).
|
||||
- **Verification:** tsc clean; lint clean (0 errors); residential-module + public-residential-inquiry + search unit suites green (10 + 22 tests).
|
||||
- **Deliberately NOT gated:** the `admin/residential-stages` page stays reachable when the module is off — an admin may legitimately configure residential stages before enabling. Reconsider if the user wants it hidden too.
|
||||
- **Deferred (separate cleanup):** the consolidated `admin/operations` page hosting all four module toggles (+ retiring the orphaned `tenancies-module/*` endpoints) — see open question 3 below.
|
||||
- **User ask (verbatim, 2026-05-31):** "is it possible to make the residential interests sections/functions in the platform to be toggleable in the admin space?"
|
||||
- **Answer:** yes. The platform already has the exact pattern for Tenancies / Invoices / Expenses; residential can copy it. Caveat: residential is currently gated by **permissions** (`residential_clients` / `residential_interests` access verbs + the `residentialAccess` role flag at _src/lib/db/schema/users.ts:455_, auto-granting perms at _src/lib/api/helpers.ts:209-213_), **not** a module toggle, and has **no layout gate at all** today. So this is genuinely new wiring, not a flag flip.
|
||||
- **Fix proposal (copy the Tenancies template — the most complete of the three):**
|
||||
1. **Registry entry** — add `residential_module_enabled` to _src/lib/settings/registry.ts_ (mirror the `tenancies_module_enabled` entry at lines 614-623): `section: 'operations.residential'`, `type: 'boolean'`, `scope: 'port'`, `defaultValue: true` (residential is in active use; default ON so existing ports aren't surprised — unlike tenancies/invoices which default OFF).
|
||||
2. **Module service** — new _src/lib/services/residential-module.service.ts_ mirroring _tenancies-module.service.ts_: `isResidentialModuleEnabled(portId)` / `enableResidentialModule` / `disableResidentialModule` / `assertResidentialModuleEnabled` (throws `NotFoundError` when off; used by API handlers). Lazy "any residential_clients row exists" auto-enable is optional.
|
||||
3. **Route gate** — new _src/app/(dashboard)/[portSlug]/residential/layout.tsx_ rendering `<ModuleDisabledPage moduleName="Residential" …>` (copy _expenses/layout.tsx:26-43_). One layout covers all 5 residential pages (clients list/detail, interests list/detail, index redirect). The `admin/residential-stages` page should also be gated.
|
||||
4. **Sidebar** — add a `requiresResidentialModule` flag to the Residential nav section in _src/components/layout/sidebar.tsx:119-134_ (alongside the existing `residentialRequired`); resolve a `residentialModuleByPort` map in _src/app/(dashboard)/layout.tsx:82-109_ (mirror the tenancies/expenses maps) and thread it through _src/components/layout/app-shell.tsx:28-34,97-98,150-151_; add the filter at the existing nav filter (sidebar.tsx ~390/419). **Also gate the mobile entry** _src/components/layout/mobile/more-sheet.tsx:58_ (currently ungated).
|
||||
5. **Search** — gate the two residential buckets in _src/lib/services/search.service.ts_ (`searchResidentialClients` line 497, `searchResidentialInterests` line 725; permission checks at 1949-1956 / 2163-2169 / 2199-2205) behind the module flag too, plus recently-viewed hydration in _src/lib/services/dashboard.service.ts:484-506_.
|
||||
6. **Public inquiry endpoint** — _src/app/api/public/residential-inquiries/route.ts_ should `assertResidentialModuleEnabled` (or 404) when off, so a disabled port stops accepting residential inquiries from the website. Currently only rate-limit + validation gate it.
|
||||
7. **Admin UI** — realistic path is the generic settings manager: add a `residential_module_enabled` Switch entry to _src/components/admin/settings/settings-manager.tsx_ (mirror the `tenancies_module_enabled` entry at lines 51-57), writing via `PUT /api/v1/admin/settings/[key]`. **Note:** the dedicated `/api/v1/admin/tenancies-module/enable|disable` endpoints are orphaned (nothing in the UI calls them) and the Invoices toggle has a registry entry + gate but no UI — so the settings-manager Switch is the path that actually works. Optionally build the long-promised `admin/operations` page to host all four module toggles in one place (closes the orphaned-endpoint gap for tenancies too).
|
||||
- **Surfaces to gate (user-facing, ~a dozen):** 5 dashboard pages (1 new layout), 1 admin stages page, sidebar section, mobile more-sheet entry, 2 search buckets + recently-viewed, public inquiry endpoint. **Backend stays preserved (~28 files):** 4 DB tables + relations (_src/lib/db/schema/residential.ts_), ~12 service fns (_residential.service.ts_, _residential-stages.service.ts_), ~14 v1 API routes (_src/app/api/v1/residential/\*_), 11 components (_src/components/residential/\*_), 2 email templates (_residential-inquiry.tsx_), validators, seeds, constants — disabled but invisible, exactly like the Tenancies/Expenses "soft hide, data preserved" model.
|
||||
- **Effort:** ~4-6h (half a day). Bulk is the sidebar/app-shell map plumbing + the new layout + search gating; the registry/service/Switch are ~1h.
|
||||
- **Alternatives considered + rejected:**
|
||||
- Reuse the existing permission gate (just strip `residentialAccess` from all roles) — rejected: that's per-user, not a clean port-level "this port doesn't do residential" switch, and leaves the public inquiry endpoint live + the nav logic fragile.
|
||||
- Hard-delete residential tables for ports that don't use it — rejected: violates the established non-destructive module-toggle convention (data preserved, re-enable any time).
|
||||
- **Open questions for the user:**
|
||||
1. **Default state** — ON for existing ports (residential is live; least surprising) or OFF (treat residential as opt-in like tenancies/invoices)? Default proposal: ON.
|
||||
2. **Scope** — just hide the UI surfaces, or also hard-reject the public residential-inquiry endpoint when off? Default proposal: both (a disabled port shouldn't silently accept inquiries it can't see).
|
||||
3. Build the proper `admin/operations` page to host all four module toggles (and retire the orphaned tenancies endpoints), or just add the residential Switch to the existing settings manager? Default proposal: settings-manager Switch now; Operations page as a separate cleanup.
|
||||
- **Cross-refs:** sibling of the "Admin toggle to disable Tenancies entirely" finding (Bucket 1, `PARTIALLY SHIPPED`) and the invoices module-toggle work in `docs/launch-readiness.md` Initiative 1c. All four toggles share the same incomplete admin-UI story — worth adding the Operations page once and wiring all of them through it.
|
||||
|
||||
---
|
||||
|
||||
## Bucket 4 — Bugs (severity-tagged)
|
||||
|
||||
Reference in New Issue
Block a user