# Staff Management — Integration Guide ## 1. Register the routes in your Express app ```ts // apps/api/src/index.ts (or server.ts) import express from 'express' import teamRouter from './routes/team' import webhookRouter from './routes/webhooks' const app = express() // Webhook MUST use raw body — register BEFORE express.json() app.use( '/webhooks', express.raw({ type: 'application/json' }), webhookRouter ) // All other routes use JSON app.use(express.json()) // Team routes — mounted under /api/v1/team (matches api-routes.md) app.use('/api/v1/team', teamRouter) ``` --- ## 2. Install the Clerk webhook (Clerk Dashboard → Webhooks) 1. Go to **clerk.com** → your app → **Webhooks** → **Add endpoint** 2. URL: `https://api.rentflow.com/webhooks/clerk` 3. Events to subscribe: `user.created` 4. Copy the **Signing Secret** and add to `.env`: ```env CLERK_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxx ``` 5. Install `svix` for signature verification: ```bash npm install svix ``` --- ## 3. Environment variables needed ```env # Clerk CLERK_SECRET_KEY=sk_live_... CLERK_WEBHOOK_SECRET=whsec_... # Frontend NEXT_PUBLIC_API_URL=https://api.rentflow.com/api/v1 # Dashboard URL (used in invite redirect) DASHBOARD_URL=https://app.rentflow.com ``` --- ## 4. Add the page to the Next.js dashboard app ``` apps/dashboard/ ├── app/ │ └── dashboard/ │ └── team/ │ └── page.tsx ← copy frontend/pages/team.tsx here ├── components/ │ └── team/ │ ├── InviteModal.tsx ← copy from frontend/components/team/ │ ├── EditMemberModal.tsx │ └── PermissionsMatrix.tsx └── hooks/ └── useTeam.ts ← copy from frontend/hooks/ ``` Add the route to your dashboard sidebar navigation: ```tsx // components/Sidebar.tsx — add to nav items { href: '/dashboard/team', label: 'Team', icon: UsersIcon } ``` --- ## 5. requireRole middleware placement The `requireRole` middleware is already applied inside the team router. Only expose it if you need it elsewhere (e.g., `PATCH /pricing-rules` → `requireRole('MANAGER')`): ```ts import { requireRole } from './middleware/requireRole' // Example: license approval endpoint (OWNER or MANAGER only) router.post( '/customers/:id/approve-license', requireRole('MANAGER'), approveLicenseHandler ) ``` --- ## 6. Invite acceptance flow (frontend) When a user clicks the magic link in their invitation email, Clerk redirects them to: ``` https://app.rentflow.com/onboarding/accept-invite?__clerk_ticket=... ``` Create this page in your Next.js app: ```tsx // app/onboarding/accept-invite/page.tsx 'use client' import { useEffect } from 'react' import { useClerk } from '@clerk/nextjs' import { useRouter } from 'next/navigation' export default function AcceptInvite() { const { handleRedirectCallback } = useClerk() const router = useRouter() useEffect(() => { handleRedirectCallback({}).then(() => { // Clerk webhook fires user.created → activates Employee record in DB // Redirect to dashboard after a short delay setTimeout(() => router.replace('/dashboard'), 1000) }) }, []) return (

Setting up your account…

) } ``` --- ## 7. Prisma type extensions (optional, for `req.employee`) To get TypeScript to recognize `req.companyId` and `req.employee` on Express requests, add a type declaration file: ```ts // types/express.d.ts import { Employee, Company } from '@prisma/client' declare global { namespace Express { interface Request { companyId: string company: Company employee: Employee } } } ``` --- ## 8. Summary of files delivered | File | Purpose | |------|---------| | `backend/services/teamService.ts` | All business logic — invite, update role, deactivate, remove, Clerk webhook handler | | `backend/routes/team.ts` | Express router — all `/team` endpoints | | `backend/routes/webhooks.ts` | Clerk webhook listener — activates employee on invite acceptance | | `backend/middleware/requireRole.ts` | Role-rank guard middleware — reusable across all routers | | `frontend/hooks/useTeam.ts` | React data hook — fetch, invite, updateRole, deactivate, reactivate, remove | | `frontend/pages/team.tsx` | Full `/dashboard/team` Next.js page | | `frontend/components/team/InviteModal.tsx` | Invite new member modal with role picker | | `frontend/components/team/EditMemberModal.tsx` | Edit role, deactivate, reactivate, remove modal with confirmation step | | `frontend/components/team/PermissionsMatrix.tsx` | Static role × feature permissions table |