Surface where the logged-in user sits in the hierarchy via a ScopeCard
in the sidebar, below the brand mark.
API:
- Enrich chapter login/me responses with tenantName + organization
(LoginResponse.admin, AdminProfile types updated)
- Add GET /my/scope returning member's chapter + organization
Web:
- ScopeCard component (Organisation row + Chapter row, themed icons)
- Chapter portal: scope from auth context, blue accent
- Member portal: scope fetched server-side in layout, emerald accent
- PortalShell gains a `scope` slot rendered under the brand
Org and admin portals intentionally omit it — org IS the scope, admin is
platform-wide.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix blank /org/login (and /admin/login): the guarded portal layout was
wrapping its own login route, rendering null when unauthenticated. Move
each portal's authed pages into a (authed)/(chapter) route group so login
pages live outside the guard.
Structure:
- app/(chapter)/* — chapter admin pages + guarded layout (was root-level)
- app/org/(authed)/* — org pages + guarded layout; /org/login now free
- app/admin/(authed)/* — admin pages + guarded layout; /admin/login free
- Root layout slimmed to providers only (no shared sidebar)
- Convert moved files' relative imports to @/app alias
Design system:
- PortalShell: shared sidebar shell (brand mark, themed active nav with
accent bar, avatar dropdown user menu, loading skeletons)
- portal-theme.ts: per-portal theme tokens (violet/slate/blue/emerald)
- PortalLoginShell redesigned: two-column with gradient branding panel,
dotted pattern, value-prop highlights, built-in back link
- New shadcn components: Avatar, DropdownMenu, Skeleton
- Move shared DraftCard to _components/draft-card.tsx (used by 2 portals)
- Portal selector: remove Super Admin card, icon tiles, hover lift
- All 4 login pages use react-hook-form + Zod + themed shell
- Member nav restored to full 11-section list with icons
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Click "Assign" on any bot row to expand an inline form with a chapter
dropdown. Selecting a chapter and confirming calls POST /api/admin/bots/:id/assign
with the tenantId. The chapter list is loaded alongside bots on mount.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the home page with a portal selector showing three access paths:
- Organisation Portal → /org/login
- Chapter Portal → /login
- Member Portal → /member-login
Remove /signup and its BFF route — tenants are now only created by org
owners through the org portal or assigned by super admins. Self-serve
community creation is no longer supported.
Update sidebar to bypass auth guard for / (exact match) without
accidentally matching all paths via startsWith.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add POST /org/tenants/:id/admins so org admins can provision the first
login for a chapter's admin team. New chapter admins can then sign in at
/login with email + password.
- OrgService.createTenantAdmin — validates chapter belongs to org, hashes
password, creates Admin record
- OrgController POST tenants/:id/admins endpoint
- BFF route /api/org/tenants/[id]/admins
- Add Admin form on /org/tenants/[id] page (email, password, role select)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/org/* is the org-admin portal — it has its own auth context and should
not redirect to /login. Add ORG_PATHS bypass and render null sidebar
on those routes (same pattern as /my/*).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/org/login was throwing "useOrgAdmin must be used within <OrgAdminProvider>"
because the provider was never mounted in the app shell.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add NestJS endpoints the admin panel org UI was calling (all returning 404):
- GET/POST /admin/orgs — list and create organizations
- GET /admin/orgs/:id — org detail with tenants and orgAdmins
- POST /admin/orgs/:id/admins — create OrgAdmin with hashed password
- PATCH /admin/tenants/:id/org — assign/unassign tenant from org
Also ship the member login page and sidebar fix from the previous session:
- Move /my/login to /member-login to break infinite redirect loop
- Add /member-login to PUBLIC_PATHS in sidebar so it is not intercepted
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Returning members can now sign in with just their phone number.
The bot DMs them a 6-digit OTP; on verify a 30-day session cookie is set.
First-time users are directed to their invite link from the login page.
- Make OtpChallenge.groupId optional (migration) for re-login challenges
- Add memberLogin / memberVerify service methods
- Add POST /public/auth/member-login and /member-verify controller endpoints
- Add /api/my/login BFF route (sets tower_member_token cookie)
- Add /my/login page (phone → OTP two-step form)
- /my/* now redirects to /my/login instead of /onboard on no session
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the AI-driven content pipeline so portal content is never manually
created — the system proposes it, admins review and approve.
Worker:
- `tower.event.extract` queue + Event Extractor processor: LLM extracts
event title/date/time/location from messages with #event hashtag,
date/time patterns, RSVP phrases, or location terms; seva action items
within event messages become separate SEVA_TASK drafts
- `tower.faq.candidate` queue + FAQ Curator processor: LLM extracts Q&A
pairs from messages with question patterns + meaningful answer length
- `classify.processor.ts`: `isEventCandidate()` and `isFaqCandidate()`
run after rule matching on all non-spam, non-DNC messages; candidates
are enqueued to the extraction lanes in parallel with normal routing
- `match-rules.ts`: add P1 to TenantRuleRow action union so P1 rules
are fully typed end-to-end
- `NormalizedMessage`: add `quotedPlatformMsgId` field
- `IngestJobData.effectiveAction`: add `'P1'` to the union
Schema:
- `ContentDraft` model with `DraftType` (EVENT|SEVA_TASK|FAQ_ITEM) and
`DraftStatus` (PENDING_REVIEW|APPROVED|REJECTED); carries `proposedJson`
from the LLM, `confidence` score, and `publishedId` after approval
- Migration: `20260617270000_add_content_drafts`
API:
- `DraftsModule`: `GET /admin/drafts`, `POST /admin/drafts/:id/approve`,
`POST /admin/drafts/:id/reject`; approve creates the actual
Event/SevaOpportunity/KnowledgeItem and marks the draft APPROVED;
reject marks it REJECTED — both audit-logged
Web:
- `/admin/drafts` and `/drafts` — tabbed view by type (All/Events/Seva/FAQ)
- `DraftCard` — shows source message excerpt, AI proposed content, and
approve/reject buttons; approve/reject immediately refresh the list
- "AI Drafts" added to both super-admin and tenant-admin sidebars
- "AI Content Drafts" button added to admin dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Circle + CircleMembership models + migration (CircleRole: MEMBER/LEAD, public/invite-only)
- CirclesModule: admin CRUD + member list; unique circle name per tenant
- Member endpoints in MyController: GET /my/circles, POST /my/circles/:id/join|leave
- listForMember returns public circles + private ones the member belongs to, with isMember/myRole/memberCount
- join() enforces invite-only; leave() removes membership
- /my/circles page: "My circles" + "Discover" split, join/leave button, LEAD + Private badges
- Member + admin BFF routes; Circles nav item
- Register CirclesModule; add CIRCLE_CREATED/DELETED audit actions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the Groups & Routes admin page with a client-side RouteManager
component (add/delete sync routes via fetch), server-side groups page that
pre-fetches groups/routes from the API, and Next.js Route Handler proxies
for /api/routes (GET, POST) and /api/routes/[id] (DELETE). Adds a custom
jest environment that polyfills Node 18+ native fetch into the jsdom sandbox
so tests can use jest.spyOn(global, 'fetch').
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>