Threema: customer-friendly texts + QR setup component
All checks were successful
Build and Push / build (push) Successful in 1m27s

This commit is contained in:
2026-05-17 16:50:23 +02:00
parent 6f42b56ad5
commit 395d2f43cc
7 changed files with 197 additions and 115 deletions

176
README.md
View File

@@ -1,100 +1,116 @@
# PieCed Portal
# Threema UX rework + QR code
Customer self-service portal for the PieCed IT multi-tenant OpenClaw platform.
## Files
## Stack
```
src/lib/threema-gateway-config.ts # NEW — single source of truth for gateway ID + QR path
src/components/channel-users/threema-setup.tsx # NEW — QR + 3-step instructions component
src/components/channel-users/channel-users.tsx # MODIFIED — renders <ThreemaSetup /> for the threema channel
deploy/patch-i18n-threema.mjs # REPLACES earlier version — customer-friendly texts in 4 langs
public/threema/qr_code_AIAGENT.png # NEW — the QR you uploaded
```
| Layer | Choice |
|-------|--------|
| Framework | Next.js 15 LTS (App Router, standalone output, Turbopack) |
| Auth | NextAuth v5 + ZITADEL OIDC (CODE flow) |
| Tenant mgmt | Direct K8s API → `PiecedTenant` CRs (Option A) |
| Usage data | LiteLLM `/team/info` + `/global/spend/logs` |
| i18n | next-intl 4.x (en/de) |
| Styling | Tailwind CSS 4 |
| Deployment | Container in `pieced-system`, exposed at `app.pieced.ch` |
## What changed (UX, customer-facing)
## Setup
1. **Package description / instructions / disclaimer.** No mention of
"Gateway account", "Gateway credentials", or anything about asterisks.
The disclaimer now explicitly says messages are end-to-end encrypted
only up to PieCed's messaging service (where they get decrypted to
route to the assistant) — accurate, not overclaiming. Crucially it
also says **Threema charges per message** so customers know that
sending/receiving on this channel has a cost separate from their
PieCed subscription.
### 1. ZITADEL Application
2. **Threema ID help text.** Now tells the customer to add **their
own** ID, with explicit instructions for finding it in the Threema
app (Settings → My Threema ID). Drops the asterisk / Gateway-prefix
explanation entirely.
In ZITADEL console (`auth.pieced.ch`), project "OpenClaw Platform":
3. **QR code component (NEW).** Shown above the help text in the
channel-users panel when the threema channel is enabled. Three-step
flow: open Threema, scan, add your own ID below.
1. Create Application → **PieCed Portal** → Web → Authentication Method: **CODE**
2. Redirect URI: `https://app.pieced.ch/api/auth/callback/zitadel`
3. Post-logout URI: `https://app.pieced.ch/login`
4. Note Client ID and Client Secret
4. **All four languages** (en/de/fr/it) updated consistently.
### 2. OpenBao Secrets
## What changed (technical)
- Gateway constants centralised in `src/lib/threema-gateway-config.ts`.
Today hardcoded to `*AIAGENT` + `/threema/qr_code_AIAGENT.png`. When
you need multiple gateway accounts, edit that one file (and the
inline TODO block tells the next person how).
- The component uses Next.js `<Image>` — automatic optimisation,
lazy-loaded by default (no `priority`).
- The PNG goes in `public/threema/`, served as a static asset under
`/threema/qr_code_AIAGENT.png` — no API route, no auth wrapper, the
QR encodes a Threema invitation anyone can scan anyway.
## Apply
```bash
bao kv put pieced/portal/oidc \
client_id="<from step 1>" \
client_secret="<from step 1>" \
nextauth_secret="$(openssl rand -base64 32)"
cd /path/to/pieced-portal
# Drop in new files (overwrites prior channel-users.tsx and i18n script)
cp -r <unzipped>/* .
# Quick TS check (the new component uses next/image — should be fine)
npx tsc --noEmit
# Patch the message files
node deploy/patch-i18n-threema.mjs
# Should print 4 lines, one per language
# Commit + push
git add -A
git status # eyeball the changes
git commit -m "Threema: customer-friendly texts + QR setup component"
git push
```
### 3. Build & Push
## Verify after redeploy
1. Open the portal as a customer admin, pick the test tenant.
2. Disable + re-enable Threema in the package list (or just look at
the channel-users panel — the QR shows there regardless).
3. Authorized Users → threema section should show:
- QR code on the left
- "AIAGENT" label under the QR (no asterisk)
- 3-step instruction list
- Help text below: "Enter your own Threema ID..."
- Existing user pills + Add input
Scan the QR with your phone — it should prompt to add `*AIAGENT` as a
contact in Threema with trust level "verified by the operator" (the
QR encodes the contact's public key).
## Billing log query (re-run after sending a few messages)
```bash
docker build -t registry.c5ai.ch/pieced/pieced-portal:0.1.0 .
docker push registry.c5ai.ch/pieced/pieced-portal:0.1.0
POD=$(kubectl -n threema-gateway get pods -l 'cnpg.io/cluster=pieced-threema-gateway-db,role=primary' -o jsonpath='{.items[0].metadata.name}')
DBPASS=$(kubectl -n threema-gateway get secret pieced-threema-gateway-db-app -o jsonpath='{.data.password}' | base64 -d)
# Per-tenant in/out counts
kubectl -n threema-gateway exec $POD -- env PGPASSWORD="$DBPASS" \
psql -U relay -d relay -c \
"SELECT tenant_name, direction, count(*), max(created_at) FROM messages GROUP BY tenant_name, direction ORDER BY tenant_name, direction;"
```
Update image tag in `pieced-gitops/apps/portal/deployment.yaml`, push, ArgoCD syncs.
That's your billing source — count(*) × Threema's per-message rate ×
direction-specific multiplier = customer charge.
### 4. DNS
## Future: dynamic gateway accounts
Ensure `app.pieced.ch` A record → MetalLB ingress IP (or ExternalDNS handles it).
Today's hardcoded `*AIAGENT` works for one shared gateway. When you
move to per-tenant gateway accounts (Threema's bigger plans, or to
isolate billing per tenant):
## Local Development
1. Update `src/lib/threema-gateway-config.ts`:
- Make the constants a lookup keyed by tenant
- Or fetch from `/api/tenants/<name>/threema` (new admin route)
2. Update `threema-setup.tsx` to accept gateway info as props
3. Update the parent `channel-users.tsx` to pass tenant-specific values
4. Replace the static PNG with a server route that generates per-tenant
QR codes from each gateway's `id` + `publicKey`
```bash
cp .env.example .env.local
# Fill in values — K8s client uses ~/.kube/config locally
npm install
npm run dev
```
## Project Structure
```
src/
├── app/
│ ├── api/
│ │ ├── auth/[...nextauth]/route.ts # NextAuth handler
│ │ ├── tenants/route.ts # Tenant CRUD (K8s API)
│ │ └── usage/route.ts # Usage stub
│ ├── [locale]/
│ │ ├── layout.tsx # Locale layout + NavShell
│ │ ├── page.tsx # Redirect → /dashboard
│ │ ├── login/page.tsx # ZITADEL sign-in
│ │ ├── dashboard/page.tsx # Customer dashboard
│ │ └── admin/page.tsx # Platform admin tenant list
│ ├── layout.tsx # Root layout
│ └── globals.css # Tailwind 4 theme
├── components/
│ ├── layout/nav-shell.tsx # Header + navigation
│ └── ui/ # Reusable UI components
├── i18n/
│ ├── routing.ts # next-intl 4.x routing config
│ ├── navigation.ts # Localized Link, redirect, etc.
│ └── request.ts # Server-side i18n config
├── lib/
│ ├── auth.ts # NextAuth v5 + ZITADEL config
│ ├── k8s.ts # K8s client for PiecedTenant CRs
│ ├── litellm.ts # LiteLLM API client
│ └── session.ts # Session helpers
├── messages/
│ ├── en.json
│ └── de.json
└── types/index.ts # Shared TypeScript types
```
## Session Roadmap
- **6.1** ← This session: scaffold, auth, basic pages
- **6.2**: Instance management, package config, usage display
- **6.3**: Onboarding flow (create ZITADEL org → PiecedTenant CR)
- **6.4**: Workspace editor (SOUL.md, AGENTS.md, TOOLS.md)
- **6.5**: Admin panel (tenant lifecycle, billing overview)
The single config file means this refactor is contained — every
consumer reads from one place, so once that one place returns the
right values, everything else falls in line.