---
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"
---
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.
@/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md
@/home/adelorenzo/.claude/get-shit-done/templates/summary.md
@.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
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: ...
```
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)
Task 1: Slack OAuth callback route, onboarding wizard with 3-step stepper, and channel connection forms
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
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
cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20
- 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
Task 2: BYO API key management settings page
packages/portal/app/(dashboard)/settings/api-keys/page.tsx,
packages/portal/lib/queries.ts
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.
cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20
- 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
Task 3: Verify onboarding wizard and BYO key management
n/a
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
Human visual inspection of onboarding flow and settings page
Operator confirms onboarding wizard works through all 3 steps and BYO key page renders correctly
- 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)
- 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