# PRP-005 — Zones (read-only + Pro teasers)

- **Status:** Done
- **Plan:** Lite
- **Depends on:** PRP-001
- **Estimated effort:** ~0.5 day
- **Pro KB ref:** §12 (Zones & Pricing ⭐)

> **Audience:** junior dev. Zones are seeded per country at registration and listed read-only in Lite.
> This PRP **disables zone mutations** behind a reusable Pro gate (returns a clean `PRO_FEATURE` error)
> and turns the Zones page into an **upsell showcase**. We keep the zone service code intact so Pro can
> switch it on later.

---

## 1. Goal / Why
Zones are the flagship Pro capability. Showing the seeded list (so operators understand their geography)
while gating every edit is a primary conversion surface — the operator sees exactly what they'd unlock.

## 2. Scope
**In:** `GET /zones` (read-only); gate `POST/PUT/DELETE /zones` → `403 PRO_FEATURE`; Zones page as a
showcase (muted prices, disabled controls, explainer, AI-matching teaser).
**Out (Pro, visible only):** add/edit/delete zone, set default, sub-locations/areas, AI city matching,
per-merchant/per-driver pricing, multi-currency pricing.

## 3. Prerequisites
- PRP-001 merged. `env.proUpgradeUrl` available. Zones already list via `GET /api/zones`.

## 4. Step-by-step implementation

### Step 1 — Create a reusable Pro-gate middleware
Create **`server/src/middleware/proGate.js`**:
```js
const { env } = require('../config/env');

/**
 * Blocks a route that is Pro-only in Lite. Returns a machine-readable error the
 * client can detect to show the upgrade CTA. Reuse this for any Lite-locked write.
 *
 * Usage: router.post('/', proGate('zone_pricing'), handler)
 */
function proGate(featureKey) {
  return (req, res, next) => {
    return res.status(403).json({
      success: false,
      code: 'PRO_FEATURE',
      message: 'This is a Shiprex Pro feature.',
      meta: { feature: featureKey, upgradeUrl: env.proUpgradeUrl },
    });
  };
}

module.exports = { proGate };
```

### Step 2 — Gate the zone mutation routes
Open **`server/src/modules/zones/zones.routes.js`**.
1. Import the gate: `const { proGate } = require('../../middleware/proGate');`
2. Add the gate **before** each mutating handler (keep the handlers — Pro will remove the gate later):
   ```js
   router.post('/', proGate('zone_pricing'), asyncHandler(async (req, res) => { /* existing */ }));
   router.put('/:id', proGate('zone_pricing'), asyncHandler(async (req, res) => { /* existing */ }));
   router.delete('/:id', proGate('zone_pricing'), asyncHandler(async (req, res) => { /* existing */ }));
   ```
   `GET /` stays open (read-only list).

> Alternative: you may instead remove the mutation routes entirely in Lite. We **keep + gate** so the
> code path is testable and Pro just drops the gate. Document the choice in the commit.

### Step 3 — Frontend: turn Zones into a read-only showcase
Rewrite **`client/src/pages/Zones.tsx`** so it:
- fetches `GET /zones` and lists name + price (price shown **muted/info**, not editable);
- shows a top banner: *"Editing zone prices, adding zones, sub-locations and AI city matching are Pro
  features."*;
- renders **Add zone**, **Edit price**, **Delete**, **Set default** as **disabled** controls with PRO
  badges linking to `upgradeUrl` (from `getFeatures()`);
- adds an **explainer card**: per-merchant rates, per-driver payout, and an **AI City Matching** blurb
  using the `features.ai_matching.teaser` text from `/features`.

Remove the previous editable price input and the create/delete handlers (they now 403 anyway).

### Step 4 — Update Postman
In the **Zones** folder, add (or mark) the gated requests so the dev can see the 403 contract (§5),
and commit the collection.

### Step 5 — Build & test
```bash
npm run build:client
```
Manually: open Zones → list shows seeded zones, all edit controls disabled; `PUT /zones/:id` via curl
returns `403 PRO_FEATURE`.

---

## 5. API endpoints & Postman documentation

### GET `/api/zones` (auth) — unchanged
- **200** → `{ success, data: [ { id, name, default_price, is_default, active } ] }` for the company.

### POST `/api/zones` · PUT `/api/zones/:id` · DELETE `/api/zones/:id` (auth) — now Pro-gated
- **403** for Lite:
  ```json
  { "success": false, "code": "PRO_FEATURE", "message": "This is a Shiprex Pro feature.",
    "meta": { "feature": "zone_pricing", "upgradeUrl": "https://www.shiprexnow.com" } }
  ```

**Postman setup:**
- Keep **GET /api/zones** as-is.
- Add **PUT /api/zones/:id (Pro-gated → 403)** request: `{{baseUrl}}/api/zones/1`, header
  `Authorization: Bearer {{token}}`, body `{ "default_price": 99 }`. Expected status 403, body has
  `code: "PRO_FEATURE"`. Add a **Tests** assertion:
  ```js
  pm.test('zone edit is Pro-gated', () => pm.expect(pm.response.code).to.eql(403));
  ```

---

## 6. Manual test / acceptance verification
```bash
B=http://localhost:3000/api
T=$(curl -s -X POST $B/auth/login -H "Content-Type: application/json" -d '{"email":"<manager>","password":"<pw>"}' | node -pe "JSON.parse(require('fs').readFileSync(0)).data.token")
curl -s $B/zones -H "Authorization: Bearer $T" | head -c 200; echo          # list OK
curl -s -X PUT $B/zones/1 -H "Authorization: Bearer $T" -H "Content-Type: application/json" -d '{"default_price":99}'  # 403 PRO_FEATURE
```

**Acceptance criteria**
- [x] `GET /zones` lists seeded zones.
- [x] `POST/PUT/DELETE /zones` return `403 { code: "PRO_FEATURE", meta.upgradeUrl }`.
- [x] Order creation ignores zone price (re-confirm from PRP-001).
- [x] Zones page is read-only with disabled Pro controls + AI-matching explainer.

---

## 7. Git commits (follow in order)
**Commit 1 — proGate middleware + zone route gating**
```bash
git add server/src/middleware/proGate.js server/src/modules/zones/zones.routes.js
git commit -m "feat(zones): PRP-005 gate zone mutations behind Pro (PRO_FEATURE)

Adds reusable proGate middleware and applies it to POST/PUT/DELETE /zones so
Lite returns 403 { code: PRO_FEATURE, upgradeUrl }. GET stays open. Service
code kept intact for Pro reuse.

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

**Commit 2 — zones showcase UI**
```bash
git add client/src/pages/Zones.tsx
git commit -m "feat(web): PRP-005 read-only zones showcase with Pro teasers

Lists seeded zones (prices muted), disables add/edit/delete/set-default with
PRO badges, and adds per-merchant/per-driver + AI city matching explainer.

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

**Commit 3 — Postman + PRP status**
```bash
git add PRPs/postman/shiprex-lite.postman_collection.json PRPs/PRP-005-zones.md PRPs/README.md
git commit -m "docs(prp): PRP-005 postman gated request + mark done"
```

---

## 8. Done checklist
- [x] `proGate` created and applied to zone mutations.
- [x] Zones page read-only showcase done.
- [x] Postman updated with the 403 request + assertion.
- [x] `npm run build:client` passes; manual tests pass.
- [ ] Commits made; PRP marked Done.
