183 lines
4.7 KiB
Markdown
183 lines
4.7 KiB
Markdown
# 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 (
|
||
<div className="flex items-center justify-center min-h-screen">
|
||
<p className="text-sm text-zinc-500">Setting up your account…</p>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 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 |
|