# PRP-002 — Auth & Onboarding

- **Status:** Done
- **Plan:** Lite
- **Depends on:** PRP-001
- **Estimated effort:** ~0.5 day
- **Pro KB ref:** §1.2 (actors), §6 (RBAC)

> **Audience:** a junior developer who has never seen this repo. Follow the steps top-to-bottom.
> Most of auth is already implemented in the scaffold; this PRP adds the **registration event seam**
> (so PRP-009 can send the welcome email) and the **locked "Team & Roles" Pro UI**, and verifies the
> one-user-per-company invariant.

---

## 1. Goal / Why
A shipping company must be able to sign up and start working in under a minute, with a single
operation-manager login. We also need a clean **event hook** that fires when a company registers, so
later PRPs (Brevo email, analytics) can react without touching the auth code again.

## 2. Scope
**In:**
- Confirm/keep: self-registration → instant active; login; `GET /me`.
- **NEW:** an in-process event emitter; emit `user.registered` after a successful registration.
- **NEW (frontend):** a "Team & Roles" card in a Settings area showing the single user, with
  **Add user** / **Add admin** rendered **disabled with a PRO badge**.
**Out (Pro):** invite teammates, multiple admins, roles UI, email verification, password reset.

## 3. Prerequisites
- PRP-001 merged. Dev DB migrated + seeded. You can run `npm run dev:server` and `npm run dev:client`.
- Create a working branch is **not** required (team works on `main` for now), but pull latest first:
  ```bash
  git checkout main
  git pull            # if a remote exists; otherwise skip
  ```

## 4. Step-by-step implementation

### Step 1 — Add a tiny in-process event bus
We need a seam other modules can subscribe to. Create the file:

**`server/src/lib/events.js`**
```js
const { EventEmitter } = require('events');

// App-wide event bus. Keep payloads plain objects. Listeners must be fail-soft
// (wrap their own work in try/catch) so one bad listener never breaks a request.
const bus = new EventEmitter();
bus.setMaxListeners(50);

// Named events (use these constants, do not pass raw strings around).
const EVENTS = Object.freeze({
  USER_REGISTERED: 'user.registered',     // { user, company } — fired after self-registration
  COMPANY_CREATED: 'company.created',     // { company, manager, tempPassword } — root-created (PRP-003)
  LEAD_CAPTURED: 'lead.captured',         // { lead } (PRP-009)
});

module.exports = { bus, EVENTS };
```

> Why an emitter and not direct calls? It lets PRP-009 add the welcome email with **zero changes** to
> auth code — it just subscribes to `user.registered`.

### Step 2 — Emit `user.registered` from the auth service
Open **`server/src/modules/auth/auth.service.js`**.

1. At the top, add the import (next to the other requires):
   ```js
   const { bus, EVENTS } = require('../../lib/events');
   ```
2. In `registerCompany(...)`, after the transaction returns `result` and **before** `return { token … }`,
   emit the event (fail-soft — never let an emit break registration):
   ```js
   try {
     bus.emit(EVENTS.USER_REGISTERED, { user: result.user, company: result.company });
   } catch (e) {
     console.error('[events] user.registered emit failed:', e.message);
   }
   ```
   The final block becomes:
   ```js
   try {
     bus.emit(EVENTS.USER_REGISTERED, { user: result.user, company: result.company });
   } catch (e) {
     console.error('[events] user.registered emit failed:', e.message);
   }

   return {
     token: tokenFor(result.user),
     user: publicUser(result.user),
     company: result.company,
   };
   ```

> There is no listener yet — that is fine. PRP-009 adds one. For now, registration behaves exactly as
> before; you have just opened the seam.

### Step 3 — Confirm the one-user invariant (no code change expected)
Verify there is **no** endpoint that adds a second user to a company:
```bash
# from repo root
grep -rn "users').insert" server/src
```
You should see inserts only in: `auth.service.js` (registration) and `db/seeds/00_root_admin.js`
(root). If you find any other user-insert route, that violates the Lite invariant — flag it, do not add
one. (Root-created companies are handled in PRP-003, still one manager.)

### Step 4 — Frontend: a Settings page with the locked "Team & Roles" card
We need a reusable "locked Pro" affordance. PRP-010 will generalize it; for now create a minimal
inline version so this PRP is self-contained, then PRP-010 refactors it.

1. Create **`client/src/pages/Settings.tsx`**:
   ```tsx
   import { useEffect, useState } from 'react';
   import { useAuth } from '../auth';
   import { getFeatures, UPGRADE_URL_FALLBACK } from '../lib/features';

   export default function Settings() {
     const { user, company } = useAuth();
     const [upgradeUrl, setUpgradeUrl] = useState(UPGRADE_URL_FALLBACK);
     useEffect(() => { getFeatures().then((f) => setUpgradeUrl(f.upgradeUrl)).catch(() => {}); }, []);

     return (
       <>
         <div className="topbar"><h1>Settings</h1></div>

         <div className="card">
           <h3>Company</h3>
           <p className="muted">{company?.name} · {company?.country} · {company?.currency} · plan: {company?.plan}</p>
         </div>

         <div className="card">
           <h3>Team & Roles</h3>
           <table>
             <thead><tr><th>Name</th><th>Email</th><th>Role</th></tr></thead>
             <tbody>
               <tr><td>{user?.name}</td><td>{user?.email}</td><td><span className="badge">manager</span></td></tr>
             </tbody>
           </table>
           <div style={{ display: 'flex', gap: 10, marginTop: 12 }}>
             <button disabled title="Pro feature">Add user <span className="lock">PRO</span></button>
             <button disabled title="Pro feature">Add admin <span className="lock">PRO</span></button>
           </div>
           <p className="muted" style={{ marginTop: 10 }}>
             Multiple users, admins and role-based permissions are available in{' '}
             <a href={upgradeUrl} target="_blank" rel="noreferrer">Shiprex Pro</a>.
           </p>
         </div>
       </>
     );
   }
   ```
2. Register the route in **`client/src/App.tsx`**:
   - import it: `import Settings from './pages/Settings';`
   - add inside `<Routes>` (manager area):
     `<Route path="/settings" element={<Protected><Settings /></Protected>} />`
   - add a sidebar link in the `Shell` manager block (under Zones):
     `<NavLink to="/settings" className="nav-link">Settings</NavLink>`

### Step 5 — Run & sanity check
```bash
npm run dev:server      # terminal 1
npm run dev:client      # terminal 2
```
- Register a new company at `http://localhost:5173/register` → lands on dashboard.
- Open **Settings** → see the single manager row and the disabled **Add user / Add admin** buttons.
- Server log shows no errors on register.

---

## 5. API endpoints & Postman documentation
**No new endpoints** in this PRP (register/login/me already exist). No Postman changes required.

For reference, the existing auth contract the dev will rely on:

### POST `/api/auth/register` (public)
- **Body**
  ```json
  { "company_name": "Acme Couriers", "contact_name": "Sam Ops", "email": "sam@acme.test",
    "phone": "+201000000000", "country": "EG", "currency": "USD", "password": "password123" }
  ```
- **201**
  ```json
  { "success": true, "data": { "token": "<jwt>", "user": { "id": 2, "role": "manager", "company_id": 1 }, "company": { "id": 1, "plan": "lite", "status": "active" } } }
  ```
- **409** duplicate email · **422** validation (e.g. password < 8).

### POST `/api/auth/login` (public)
- **Body** `{ "email": "...", "password": "..." }` → **200** `{ token, user }`.
- **401** bad credentials · **403** company suspended.

### GET `/api/auth/me` (auth)
- Header `Authorization: Bearer <token>` → **200** `{ user, company }`.

> These are already in the Postman collection (Auth folder). Nothing to add here.

---

## 6. Frontend summary
- New `Settings.tsx` page (Company info + locked Team & Roles).
- Route + sidebar link added.

---

## 7. Manual test / acceptance verification
Run these (server on :3000):
```bash
# 1. Register → expect 201 + token
curl -s -X POST http://localhost:3000/api/auth/register -H "Content-Type: application/json" \
  -d '{"company_name":"T Co","contact_name":"T","email":"t1@test.io","phone":"+201","country":"EG","password":"password123"}'

# 2. Duplicate email → expect 409
curl -s -X POST http://localhost:3000/api/auth/register -H "Content-Type: application/json" \
  -d '{"company_name":"T Co","contact_name":"T","email":"t1@test.io","phone":"+201","country":"EG","password":"password123"}'

# 3. Weak password → expect 422
curl -s -X POST http://localhost:3000/api/auth/register -H "Content-Type: application/json" \
  -d '{"company_name":"T2","contact_name":"T","email":"t2@test.io","phone":"+201","country":"EG","password":"123"}'
```
Then clean up the test rows:
```bash
"/c/xampp/mysql/bin/mysql.exe" -uroot -h127.0.0.1 shiprexlite -e "DELETE FROM companies WHERE email LIKE 't%@test.io';"
```

**Acceptance criteria**
- [x] New company registers and is immediately active + logged in.
- [x] Duplicate email → 409; weak password → 422.
- [x] No endpoint exists to add a second user to a company (Step 3 grep).
- [x] `user.registered` is emitted on register (temporary listener or log to confirm; removed after).
- [x] Settings page shows one user and disabled Add user/Add admin with PRO badges.

---

## 8. Git commits (follow in order)
Commit in two logical chunks so history is reviewable.

**Commit 1 — backend event seam**
```bash
git add server/src/lib/events.js server/src/modules/auth/auth.service.js
git commit -m "feat(auth): PRP-002 add event bus and emit user.registered

Adds server/src/lib/events.js (EventEmitter + EVENTS constants) and emits
user.registered after successful company registration (fail-soft). Opens the
seam for PRP-009 welcome email; no behavior change yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
```

**Commit 2 — frontend Settings + locked team UI**
```bash
git add client/src/pages/Settings.tsx client/src/App.tsx
git commit -m "feat(web): PRP-002 Settings page with locked Team & Roles (Pro)

Adds /settings route + sidebar link. Shows the single manager and renders
Add user / Add admin disabled with PRO badges linking to the upgrade URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
```

**After acceptance:** set this PRP's Status to **Done**, check the boxes, and:
```bash
git add PRPs/PRP-002-auth-onboarding.md PRPs/README.md
git commit -m "docs(prp): mark PRP-002 done"
```

---

## 9. Done checklist
- [x] `events.js` created; `user.registered` emitted.
- [x] One-user invariant confirmed (grep).
- [x] Settings page + route + link added; locked buttons show PRO.
- [x] `npm run build:client` passes; manual tests pass; test rows cleaned.
- [ ] Two commits made; PRP marked Done.
