update documentation
This commit is contained in:
+108
-149
@@ -1,182 +1,141 @@
|
||||
# Staff Management — Integration Guide
|
||||
# Team Management Integration — Current Implementation
|
||||
|
||||
## 1. Register the routes in your Express app
|
||||
This document describes the current team-management integration in the live codebase.
|
||||
|
||||
```ts
|
||||
// apps/api/src/index.ts (or server.ts)
|
||||
Source of truth:
|
||||
|
||||
import express from 'express'
|
||||
import teamRouter from './routes/team'
|
||||
import webhookRouter from './routes/webhooks'
|
||||
- `apps/api/src/modules/team/team.routes.ts`
|
||||
- `apps/api/src/services/teamService.ts`
|
||||
- `apps/dashboard/src/hooks/useTeam.ts`
|
||||
- `apps/dashboard/src/app/(dashboard)/team/page.tsx`
|
||||
- `apps/dashboard/src/app/reset-password/*`
|
||||
|
||||
const app = express()
|
||||
## Current Model
|
||||
|
||||
// Webhook MUST use raw body — register BEFORE express.json()
|
||||
app.use(
|
||||
'/webhooks',
|
||||
express.raw({ type: 'application/json' }),
|
||||
webhookRouter
|
||||
)
|
||||
Team management is local to RentalDriveGo. It no longer depends on Clerk.
|
||||
|
||||
// All other routes use JSON
|
||||
app.use(express.json())
|
||||
The live flow is:
|
||||
|
||||
// Team routes — mounted under /api/v1/team (matches api-routes.md)
|
||||
app.use('/api/v1/team', teamRouter)
|
||||
1. an owner invites a staff member from the dashboard
|
||||
2. the API creates a pending `Employee`
|
||||
3. the API generates a password-reset token
|
||||
4. an email is sent with a reset-password link
|
||||
5. the invited employee sets a password through the dashboard reset-password page
|
||||
6. the employee can then sign in through the standard employee login flow
|
||||
|
||||
There is no webhook step in the active implementation.
|
||||
|
||||
## Backend Integration
|
||||
|
||||
The team router is already mounted by the main API app in `apps/api/src/app.ts` under:
|
||||
|
||||
- `/api/v1/team`
|
||||
|
||||
The public webhook placeholder under `/api/v1/webhooks/clerk` is intentionally disabled and should not be used for team onboarding.
|
||||
|
||||
### Team endpoints
|
||||
|
||||
- `GET /api/v1/team`
|
||||
- `GET /api/v1/team/stats`
|
||||
- `POST /api/v1/team/invite`
|
||||
- `PATCH /api/v1/team/:id/role`
|
||||
- `POST /api/v1/team/:id/deactivate`
|
||||
- `POST /api/v1/team/:id/reactivate`
|
||||
- `DELETE /api/v1/team/:id`
|
||||
|
||||
### Auth and authorization
|
||||
|
||||
These routes are protected by:
|
||||
|
||||
- employee JWT auth
|
||||
- tenant resolution
|
||||
- subscription guard
|
||||
- role checks inside the router and service
|
||||
|
||||
Important rules in the current implementation:
|
||||
|
||||
- only the `OWNER` can invite, change roles, deactivate/reactivate, or remove team members
|
||||
- invited team members cannot be created with the `OWNER` role
|
||||
- the account owner cannot be deactivated or removed through team endpoints
|
||||
|
||||
## Invite Email Flow
|
||||
|
||||
Invitations are generated in `apps/api/src/services/teamService.ts`.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- a pending employee row is created
|
||||
- `passwordResetToken` and `passwordResetExpiresAt` are populated
|
||||
- the invite email links directly to the dashboard reset-password page
|
||||
|
||||
Expected URL shape:
|
||||
|
||||
```text
|
||||
{DASHBOARD_URL}/reset-password?token=...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Install the Clerk webhook (Clerk Dashboard → Webhooks)
|
||||
|
||||
1. Go to **clerk.com** → your app → **Webhooks** → **Add endpoint**
|
||||
2. URL: `https://api.RentalDriveGo.com/webhooks/clerk`
|
||||
3. Events to subscribe: `user.created`
|
||||
4. Copy the **Signing Secret** and add to `.env`:
|
||||
Relevant environment variables:
|
||||
|
||||
```env
|
||||
CLERK_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxx
|
||||
DASHBOARD_URL=https://your-host/dashboard
|
||||
NEXT_PUBLIC_DASHBOARD_URL=https://your-host/dashboard
|
||||
NEXT_PUBLIC_API_URL=https://your-host/api/v1
|
||||
```
|
||||
|
||||
5. Install `svix` for signature verification:
|
||||
## Dashboard Integration
|
||||
|
||||
```bash
|
||||
npm install svix
|
||||
```
|
||||
The team UI lives here:
|
||||
|
||||
---
|
||||
- `apps/dashboard/src/app/(dashboard)/team/page.tsx`
|
||||
- `apps/dashboard/src/hooks/useTeam.ts`
|
||||
- `apps/dashboard/src/components/team/InviteModal.tsx`
|
||||
- `apps/dashboard/src/components/team/EditMemberModal.tsx`
|
||||
- `apps/dashboard/src/components/team/PermissionsMatrix.tsx`
|
||||
|
||||
## 3. Environment variables needed
|
||||
The dashboard consumes the team API directly through `apiFetch(...)`.
|
||||
|
||||
```env
|
||||
# Clerk
|
||||
CLERK_SECRET_KEY=sk_live_...
|
||||
CLERK_WEBHOOK_SECRET=whsec_...
|
||||
### Dashboard page behavior
|
||||
|
||||
# Frontend
|
||||
NEXT_PUBLIC_API_URL=https://api.RentalDriveGo.com/api/v1
|
||||
- loads members from `/team`
|
||||
- loads summary counts from `/team/stats`
|
||||
- opens invite/edit flows through local modals
|
||||
- updates local state after invite, role change, deactivate/reactivate, and removal
|
||||
|
||||
# Dashboard URL (used in invite redirect)
|
||||
DASHBOARD_URL=https://app.RentalDriveGo.com
|
||||
```
|
||||
## Password Setup / Invite Acceptance
|
||||
|
||||
---
|
||||
The active invite-acceptance mechanism is the dashboard reset-password flow, not a Clerk callback flow.
|
||||
|
||||
## 4. Add the page to the Next.js dashboard app
|
||||
Relevant pages:
|
||||
|
||||
```
|
||||
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/
|
||||
```
|
||||
- `apps/dashboard/src/app/reset-password/page.tsx`
|
||||
- `apps/dashboard/src/app/reset-password/ResetPasswordPageClient.tsx`
|
||||
|
||||
Add the route to your dashboard sidebar navigation:
|
||||
This means:
|
||||
|
||||
```tsx
|
||||
// components/Sidebar.tsx — add to nav items
|
||||
{ href: '/dashboard/team', label: 'Team', icon: UsersIcon }
|
||||
```
|
||||
- there is no `__clerk_ticket`
|
||||
- there is no Clerk redirect callback
|
||||
- there is no employee activation webhook
|
||||
|
||||
---
|
||||
Invite completion is simply password setup using the token created by the API.
|
||||
|
||||
## 5. requireRole middleware placement
|
||||
## Request Typing
|
||||
|
||||
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')`):
|
||||
The request object is extended in the API so team routes can rely on:
|
||||
|
||||
```ts
|
||||
import { requireRole } from './middleware/requireRole'
|
||||
- `req.employee`
|
||||
- `req.company`
|
||||
- `req.companyId`
|
||||
|
||||
// Example: license approval endpoint (OWNER or MANAGER only)
|
||||
router.post(
|
||||
'/customers/:id/approve-license',
|
||||
requireRole('MANAGER'),
|
||||
approveLicenseHandler
|
||||
)
|
||||
```
|
||||
Those values are attached by the company auth and tenant middleware before team handlers run.
|
||||
|
||||
---
|
||||
## What Was Removed
|
||||
|
||||
## 6. Invite acceptance flow (frontend)
|
||||
These older integration assumptions are no longer valid:
|
||||
|
||||
When a user clicks the magic link in their invitation email, Clerk redirects them to:
|
||||
- Clerk webhooks
|
||||
- Clerk invitation acceptance
|
||||
- Clerk `user.created` activation flow
|
||||
- `/webhooks/clerk` as a required part of team onboarding
|
||||
- `@clerk/nextjs` integration in the dashboard team flow
|
||||
|
||||
```
|
||||
https://app.RentalDriveGo.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 |
|
||||
If this functionality returns in the future, it should be documented as a new integration rather than assumed from this file.
|
||||
|
||||
Reference in New Issue
Block a user