---
phase: 03-operator-experience
plan: 02
type: execute
wave: 2
depends_on: ["03-01"]
files_modified:
  - packages/portal/app/api/slack/callback/route.ts
  - packages/portal/app/(dashboard)/onboarding/page.tsx
  - packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx
  - packages/portal/app/(dashboard)/onboarding/steps/configure-agent.tsx
  - packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx
  - packages/portal/app/(dashboard)/settings/api-keys/page.tsx
  - packages/portal/lib/queries.ts
  - packages/portal/components/onboarding-stepper.tsx
autonomous: false
requirements:
  - PRTA-03
  - PRTA-04
  - LLM-03

must_haves:
  truths:
    - "Operator can click Add to Slack and complete OAuth flow to connect their workspace"
    - "Operator can paste WhatsApp credentials and have them validated and stored"
    - "After connecting a channel, operator can send a test message that verifies end-to-end connectivity"
    - "Agent goes live automatically after test message succeeds"
    - "Operator completes the full onboarding sequence (connect -> configure -> test) in a guided wizard"
    - "Operator can add, view, and delete BYO LLM API keys from a settings page"
  artifacts:
    - path: "packages/portal/app/api/slack/callback/route.ts"
      provides: "Next.js Route Handler for Slack OAuth redirect"
    - path: "packages/portal/app/(dashboard)/onboarding/page.tsx"
      provides: "Onboarding wizard with 3-step stepper"
    - path: "packages/portal/app/(dashboard)/settings/api-keys/page.tsx"
      provides: "BYO API key management page"
  key_links:
    - from: "packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx"
      to: "/api/portal/channels/slack/install"
      via: "fetch to get OAuth URL, then window.location redirect"
      pattern: "channels/slack/install"
    - from: "packages/portal/app/api/slack/callback/route.ts"
      to: "/api/portal/channels/slack/callback"
      via: "proxy the OAuth callback to FastAPI backend"
      pattern: "channels/slack/callback"
    - from: "packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx"
      to: "/api/portal/channels/{tenant_id}/test"
      via: "POST to send test message"
      pattern: "channels.*test"
    - from: "packages/portal/app/(dashboard)/settings/api-keys/page.tsx"
      to: "/api/portal/tenants/{tenant_id}/llm-keys"
      via: "GET/POST/DELETE for BYO key CRUD (endpoints created in Plan 01 Task 3)"
      pattern: "tenants.*llm-keys"
---

<objective>
Channel connection wizard (Slack OAuth + WhatsApp manual setup), onboarding flow with 3-step stepper, and BYO API key management page.

Purpose: Operators can connect their messaging channels and onboard their AI employee through a guided wizard, plus manage their own LLM API keys -- all from the portal UI.
Output: Onboarding wizard (connect channel -> configure agent -> test message), Slack OAuth callback handler, WhatsApp manual connect form, BYO key settings page.
</objective>

<execution_context>
@/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md
@/home/adelorenzo/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/03-operator-experience/03-CONTEXT.md
@.planning/phases/03-operator-experience/03-RESEARCH.md
@.planning/phases/03-operator-experience/03-01-SUMMARY.md

<interfaces>
<!-- From Plan 01 backend APIs -->

From packages/shared/shared/api/channels.py (created in Plan 01):
```python
# GET /api/portal/channels/slack/install?tenant_id={id} -> { "authorize_url": "https://slack.com/oauth/v2/authorize?..." }
# GET /api/portal/channels/slack/callback?code={code}&state={state} -> { "success": true, "workspace_name": "..." }
# POST /api/portal/channels/whatsapp/connect -> { "success": true } (body: { tenant_id, phone_number_id, waba_id, system_user_token })
# POST /api/portal/channels/{tenant_id}/test -> { "success": true, "message": "Test message sent" } (body: { channel_type })
```

From packages/shared/shared/api/llm_keys.py (created in Plan 01 Task 3):
```python
# GET /api/portal/tenants/{tenant_id}/llm-keys -> [{ id, provider, label, key_hint, created_at }]
# POST /api/portal/tenants/{tenant_id}/llm-keys -> { id, provider, label, key_hint, created_at } (body: { provider, label, api_key })
# DELETE /api/portal/tenants/{tenant_id}/llm-keys/{key_id} -> 204
```

From packages/shared/shared/api/billing.py (created in Plan 01):
```python
# POST /api/portal/billing/checkout -> { "checkout_url": "https://checkout.stripe.com/..." }
# POST /api/portal/billing/portal -> { "portal_url": "https://billing.stripe.com/..." }
```

From packages/shared/shared/crypto.py (created in Plan 01):
```python
class KeyEncryptionService:
    def encrypt(self, plaintext: str) -> str: ...
    def decrypt(self, ciphertext: str) -> str: ...
    def rotate(self, ciphertext: str) -> str: ...
```

<!-- Existing portal patterns -->

From packages/portal/lib/api.ts:
```typescript
// API client for FastAPI backend — use this for all portal API calls
```

From packages/portal/lib/queries.ts:
```typescript
// TanStack Query hooks — add new hooks for channels, billing, usage here
```

Established patterns:
- shadcn/ui components
- react-hook-form + zod v4 + standardSchemaResolver
- TanStack Query for data fetching
- App Router with (dashboard) route group
- proxy.ts for auth protection (Next.js 16 pattern)
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Slack OAuth callback route, onboarding wizard with 3-step stepper, and channel connection forms</name>
  <files>
    packages/portal/app/api/slack/callback/route.ts,
    packages/portal/app/(dashboard)/onboarding/page.tsx,
    packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx,
    packages/portal/app/(dashboard)/onboarding/steps/configure-agent.tsx,
    packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx,
    packages/portal/components/onboarding-stepper.tsx,
    packages/portal/lib/queries.ts
  </files>
  <action>
    1. Create `packages/portal/app/api/slack/callback/route.ts`:
       - Next.js Route Handler (GET) that receives the OAuth redirect from Slack
       - Extract `code` and `state` from searchParams
       - Forward to FastAPI backend: GET /api/portal/channels/slack/callback?code={code}&state={state}
       - On success: redirect to /onboarding?step=2&channel=slack&connected=true
       - On error: redirect to /onboarding?step=1&error=slack_auth_failed

    2. Create `packages/portal/components/onboarding-stepper.tsx`:
       - 3-step stepper component using shadcn/ui: "Connect Channel" -> "Configure Agent" -> "Test Message"
       - Visual progress indicator (numbered steps with active/complete/pending states)
       - Step state managed via URL searchParams (step=1|2|3) for shareable/refreshable URLs
       - Each step renders the corresponding step component

    3. Create `packages/portal/app/(dashboard)/onboarding/page.tsx`:
       - Reads `step` from searchParams (default 1)
       - Reads `tenant_id` from session or query param
       - Renders OnboardingStepper with the appropriate step component
       - Guards: if no tenant selected, redirect to tenant selection

    4. Create `packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx`:
       - Two cards: "Add to Slack" and "Connect WhatsApp"
       - **Slack card:** Button that fetches /api/portal/channels/slack/install?tenant_id={id}, then does window.location.href = authorize_url (external redirect to Slack)
       - **WhatsApp card:** Expandable form with 3 fields (Phone Number ID, WhatsApp Business Account ID, System User Token) + step-by-step instructions text
         - On submit: POST /api/portal/channels/whatsapp/connect
         - Validation with zod + react-hook-form
       - On successful connection (either channel): advance to step 2 automatically
       - If returning from Slack OAuth (connected=true in searchParams): show success toast and advance

    5. Create `packages/portal/app/(dashboard)/onboarding/steps/configure-agent.tsx`:
       - If tenant already has an agent: show existing agent summary with "Edit" link to Agent Designer
       - If no agent: redirect to Agent Designer page with return URL back to onboarding?step=3
       - Minimal step — the Agent Designer (from Phase 1) handles all agent configuration
       - "Next" button enabled only when at least one active agent exists for the tenant

    6. Create `packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx`:
       - Shows connected channel(s) with a "Send Test Message" button per channel
       - On click: POST /api/portal/channels/{tenant_id}/test with {channel_type}
       - Shows loading state while test runs, then success/failure result
       - On success: show "Your AI employee is live!" celebration message
       - Agent goes live automatically (is_active already true by default) — NO separate "Go Live" button per user decision
       - Per user decision: test message step is REQUIRED, not skippable

    7. Add TanStack Query hooks to `packages/portal/lib/queries.ts`:
       - useSlackInstallUrl(tenantId) — GET /channels/slack/install
       - useConnectWhatsApp() — mutation POST /channels/whatsapp/connect
       - useSendTestMessage() — mutation POST /channels/{tenantId}/test
       - useChannelConnections(tenantId) — GET to list existing connections
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20</automated>
  </verify>
  <done>
    - Slack OAuth callback route handler proxies to FastAPI and redirects appropriately
    - Onboarding page renders 3-step stepper with progress indicator
    - Connect Channel step shows Slack OAuth button and WhatsApp manual form
    - Configure Agent step links to existing Agent Designer
    - Test Message step sends test and shows result
    - Agent goes live automatically after successful test (no Go Live button)
    - All TanStack Query hooks defined
    - Portal builds without errors
  </done>
</task>

<task type="auto">
  <name>Task 2: BYO API key management settings page</name>
  <files>
    packages/portal/app/(dashboard)/settings/api-keys/page.tsx,
    packages/portal/lib/queries.ts
  </files>
  <action>
    1. Create `packages/portal/app/(dashboard)/settings/api-keys/page.tsx`:
       - Tenant-level settings page (per user decision — simpler than per-agent for v1)
       - List existing BYO keys: show provider name, label, key_hint (last 4 chars), created date (NOT the key itself — never display decrypted keys)
       - "Add API Key" button opens a form:
         - Provider: select dropdown (OpenAI, Anthropic, Custom)
         - Label: text input (human-readable name, e.g., "Production OpenAI key")
         - API Key: password input (masked by default)
         - Submit: POST to /api/portal/tenants/{tenant_id}/llm-keys (backend endpoint created in Plan 01 Task 3)
       - Delete button per key with confirmation dialog
       - Use shadcn/ui Card, Table, Dialog, Button, Input, Select components
       - react-hook-form + zod for validation (provider required, label 3-100 chars, key not empty)

    2. Add TanStack Query hooks to `packages/portal/lib/queries.ts`:
       - useLlmKeys(tenantId) — GET /api/portal/tenants/{tenant_id}/llm-keys
       - useAddLlmKey() — mutation POST /api/portal/tenants/{tenant_id}/llm-keys
       - useDeleteLlmKey() — mutation DELETE /api/portal/tenants/{tenant_id}/llm-keys/{keyId}

    3. Add navigation link to settings/api-keys in the dashboard layout sidebar (if sidebar exists) or in the tenant detail page.
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20</automated>
  </verify>
  <done>
    - BYO API key settings page renders with list of existing keys (redacted — shows key_hint only)
    - Add key form validates and submits to backend
    - Delete key with confirmation dialog works
    - Portal builds without errors
  </done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 3: Verify onboarding wizard and BYO key management</name>
  <files>n/a</files>
  <action>
    Human verifies the onboarding wizard and BYO key management UI:
    1. Start the portal: `cd packages/portal && npm run dev`
    2. Navigate to /onboarding — verify 3-step stepper displays
    3. Step 1: Verify "Add to Slack" button and WhatsApp form are present
    4. Step 2: Verify it links to Agent Designer or shows existing agent
    5. Step 3: Verify "Send Test Message" button is present
    6. Navigate to /settings/api-keys — verify key list and add form render
    7. Try adding a BYO key with the form — verify it submits without JS errors
    8. Confirm there is NO separate "Go Live" button — agent goes live after test
  </action>
  <verify>Human visual inspection of onboarding flow and settings page</verify>
  <done>Operator confirms onboarding wizard works through all 3 steps and BYO key page renders correctly</done>
</task>

</tasks>

<verification>
- Portal builds successfully: `cd packages/portal && npx next build`
- Onboarding wizard navigable through all 3 steps
- BYO API key page renders and accepts input
- No separate "Go Live" button exists (per user decision)
- Test message step is required (not skippable)
</verification>

<success_criteria>
- Operator can initiate Slack OAuth from the portal
- Operator can paste WhatsApp credentials via guided form
- Onboarding wizard completes in 3 steps: connect -> configure -> test
- Agent goes live automatically after successful test message
- Operator can manage BYO API keys from settings page (backed by Plan 01 Task 3 endpoints)
- Portal builds without errors
</success_criteria>

<output>
After completion, create `.planning/phases/03-operator-experience/03-02-SUMMARY.md`
</output>
