diff --git a/README.md b/README.md index afc7ca2..ed5fdad 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,66 @@ -# Threema UX rework + QR code +# Threema UX v2 — QR available on demand + +Replaces the previous attempt that had the QR rendering inline above +the channel help text. That placement didn't work for you in practice +because it wasn't visible when you actually wanted it. + +## What this does instead + +1. **"Show QR" link** next to the channel title in the threema card — + visible at all times, clickable any time you want the QR. + +2. **Auto-opens the modal** the first time you focus the add-ID input + on the page (so a new user adding their first ID sees it without + needing to click "Show QR" themselves). Doesn't re-pop after + dismissal — the link covers re-opens. + +3. **Modal** with QR + 3-step instructions + "AIAGENT" label. Plain + `` (no next/image), closes on ESC / overlay click / × button. + +The earlier `threema-setup.tsx` component file is removed — replaced by +`threema-qr-modal.tsx`. ## Files ``` -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 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 +src/lib/threema-gateway-config.ts # unchanged from before — central gateway constants +src/components/channel-users/threema-qr-modal.tsx # NEW — the modal +src/components/channel-users/channel-users.tsx # MODIFIED — Show QR button + focus auto-open + modal mount +deploy/patch-i18n-threema.mjs # adds threemaSetup.showQr label in 4 langs +public/threema/qr_code_AIAGENT.png # unchanged ``` -## What changed (UX, customer-facing) - -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. - -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. - -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. - -4. **All four languages** (en/de/fr/it) updated consistently. - -## 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 `` — 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 cd /path/to/pieced-portal -# Drop in new files (overwrites prior channel-users.tsx and i18n script) -cp -r /* . +# Remove the old inline component if you applied the previous archive +rm -f src/components/channel-users/threema-setup.tsx -# Quick TS check (the new component uses next/image — should be fine) +# Drop new + modified files +unzip -o /path/to/threema-ux-v2.zip + +# Update messages +node deploy/patch-i18n-threema.mjs + +# TS check 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 commit -m "Threema QR: on-demand modal + auto-open on first add" git push ``` -## Verify after redeploy +After redeploy, the threema card under Authorized Users shows: -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 -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;" +``` +threema [Show QR] 0 users + ──────────────────── + + ──────────────────── + [ input: A8K2P3X7 ] [ Add ] + ^^^ focusing this opens the QR modal the first time ``` -That's your billing source — count(*) × Threema's per-message rate × -direction-specific multiplier = customer charge. - -## Future: dynamic gateway accounts - -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): - -1. Update `src/lib/threema-gateway-config.ts`: - - Make the constants a lookup keyed by tenant - - Or fetch from `/api/tenants//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` - -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. +Clicking "Show QR" or focusing the input → modal with QR + steps. diff --git a/deploy/patch-i18n-threema.mjs b/deploy/patch-i18n-threema.mjs index 87a7dcc..decdcc5 100644 --- a/deploy/patch-i18n-threema.mjs +++ b/deploy/patch-i18n-threema.mjs @@ -37,6 +37,7 @@ const i18n = { step2: "Tap the scan icon and scan this QR code to add the assistant as a contact.", step3: "Then add your own Threema ID below.", qrAlt: "QR code to add {gateway} as a Threema contact", + showQr: "Show QR", }, }, de: { @@ -56,6 +57,7 @@ const i18n = { step2: "Tippen Sie auf das Scan-Symbol und scannen Sie diesen QR-Code, um den Assistenten als Kontakt hinzuzufügen.", step3: "Fügen Sie anschliessend unten Ihre eigene Threema-ID hinzu.", qrAlt: "QR-Code, um {gateway} als Threema-Kontakt hinzuzufügen", + showQr: "QR anzeigen", }, }, fr: { @@ -75,6 +77,7 @@ const i18n = { step2: "Appuyez sur l'icône de scan et scannez ce QR code pour ajouter l'assistant comme contact.", step3: "Puis ajoutez votre propre identifiant Threema ci-dessous.", qrAlt: "QR code pour ajouter {gateway} comme contact Threema", + showQr: "Afficher le QR", }, }, it: { @@ -94,6 +97,7 @@ const i18n = { step2: "Tocca l'icona di scansione e scansiona questo QR code per aggiungere l'assistente ai contatti.", step3: "Quindi aggiungi il tuo ID Threema qui sotto.", qrAlt: "QR code per aggiungere {gateway} come contatto Threema", + showQr: "Mostra QR", }, }, }; diff --git a/src/components/channel-users/channel-users.tsx b/src/components/channel-users/channel-users.tsx index 06b4c0a..02a2edf 100644 --- a/src/components/channel-users/channel-users.tsx +++ b/src/components/channel-users/channel-users.tsx @@ -3,6 +3,7 @@ import { useState, useCallback } from "react"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; +import { ThreemaQrModal } from "./threema-qr-modal"; /** Maps channel IDs to the instructions for finding the user ID. */ const CHANNEL_ID_HELP: Record = { @@ -51,6 +52,14 @@ export function ChannelUsers({ const [inputValues, setInputValues] = useState>({}); const [channelUsers, setChannelUsers] = useState>(initialChannelUsers); + /** Which channel's QR helper modal is open, if any. */ + const [showQrFor, setShowQrFor] = useState(null); + /** + * Tracks channels for which we've already auto-opened the helper + * modal on this page load. Prevents the modal from re-popping every + * time the user refocuses the input after dismissing it. + */ + const [autoOpened, setAutoOpened] = useState>(() => new Set()); const updateChannelUsers = useCallback( async (updated: Record) => { @@ -216,9 +225,19 @@ export function ChannelUsers({ className="bg-surface-2 border border-border rounded-lg p-4" >
-

- {channel} -

+
+

+ {channel} +

+ {channel === "threema" && ( + + )} +
{users.length} {t("users")} @@ -266,6 +285,17 @@ export function ChannelUsers({ [channel]: e.target.value, })) } + onFocus={() => { + // For threema specifically, open the QR helper the + // first time the user clicks into the input on this + // page load. We don't repeat after dismiss — the + // "Show QR" button next to the channel name covers + // re-opens on demand. + if (channel === "threema" && !autoOpened.has("threema")) { + setShowQrFor("threema"); + setAutoOpened((prev) => new Set(prev).add("threema")); + } + }} onKeyDown={(e) => { if (e.key === "Enter") handleAdd(channel); }} @@ -284,6 +314,11 @@ export function ChannelUsers({
); })} + + setShowQrFor(null)} + /> ); } \ No newline at end of file diff --git a/src/components/channel-users/threema-qr-modal.tsx b/src/components/channel-users/threema-qr-modal.tsx new file mode 100644 index 0000000..e0d0072 --- /dev/null +++ b/src/components/channel-users/threema-qr-modal.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { useEffect } from "react"; +import { THREEMA_GATEWAY } from "@/lib/threema-gateway-config"; + +interface ThreemaQrModalProps { + open: boolean; + onClose: () => void; +} + +/** + * On-demand modal showing the QR for adding the assistant on Threema. + * Triggered by the "Show QR" button in the threema channel card and + * closes on overlay click, ESC, or the close button. + * + * Uses a plain not next/image — image optimization adds nothing + * for a 57KB static PNG and removes a potential source of rendering + * bugs in the Next.js standalone build. + */ +export function ThreemaQrModal({ open, onClose }: ThreemaQrModalProps) { + const t = useTranslations("channelUsers.threemaSetup"); + + useEffect(() => { + if (!open) return; + function onKey(e: KeyboardEvent) { + if (e.key === "Escape") onClose(); + } + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [open, onClose]); + + if (!open) return null; + + return ( +
+
e.stopPropagation()} + className="w-full max-w-md bg-surface-1 border border-border rounded-2xl p-6 shadow-2xl shadow-black/40 space-y-4" + > +
+

+ {t("title")} +

+ +
+ +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {t("qrAlt", +
+
+ +

+ {THREEMA_GATEWAY.displayName} +

+ +
    +
  1. {t("step1")}
  2. +
  3. {t("step2")}
  4. +
  5. {t("step3")}
  6. +
+
+
+ ); +} diff --git a/src/lib/threema-gateway-config.ts b/src/lib/threema-gateway-config.ts new file mode 100644 index 0000000..414d17f --- /dev/null +++ b/src/lib/threema-gateway-config.ts @@ -0,0 +1,33 @@ +/** + * Threema central gateway info shown to customers. + * + * Today PieCed runs exactly one Threema Gateway account (*AIAGENT) and + * every tenant talks to that. The constants below are hardcoded for + * that account. The values are intentionally kept here (and not split + * across i18n / env / runtime config) so when we move to multiple + * gateway accounts there's a single file to refactor. + * + * To go dynamic (future): + * 1. Replace `THREEMA_GATEWAY` constant with a runtime lookup — + * either per-tenant from the relay's admin API, or from an + * env var that lists the active account. + * 2. Move the QR PNG into a server-rendered route that takes a + * gateway ID query param. + * 3. Update consumers (today only ThreemaSetup) to accept the + * gateway info as a prop and pass it from a server component. + * + * In display contexts we strip the leading asterisk from the Threema + * ID — customers don't understand the `*X` prefix convention used for + * Gateway accounts, and the QR code carries the real value anyway. We + * keep the asterisk only for places where the technical value matters + * (server-side message routing, debug logs). + */ + +export const THREEMA_GATEWAY = { + /** Technical Threema Gateway ID, with leading asterisk. */ + id: "*AIAGENT", + /** Display name shown to customers (no asterisk). */ + displayName: "AIAGENT", + /** Public path to the QR code PNG served from `public/`. */ + qrCodePath: "/threema/qr_code_AIAGENT.png", +} as const; diff --git a/src/messages/de.json b/src/messages/de.json index a11a453..1b6988d 100644 --- a/src/messages/de.json +++ b/src/messages/de.json @@ -402,7 +402,8 @@ "step1": "Öffnen Sie Threema auf Ihrem Telefon.", "step2": "Tippen Sie auf das Scan-Symbol und scannen Sie diesen QR-Code, um den Assistenten als Kontakt hinzuzufügen.", "step3": "Fügen Sie anschliessend unten Ihre eigene Threema-ID hinzu.", - "qrAlt": "QR-Code, um {gateway} als Threema-Kontakt hinzuzufügen" + "qrAlt": "QR-Code, um {gateway} als Threema-Kontakt hinzuzufügen", + "showQr": "QR anzeigen" } }, "team": { diff --git a/src/messages/en.json b/src/messages/en.json index fecc918..63cd2a5 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -402,7 +402,8 @@ "step1": "Open Threema on your phone.", "step2": "Tap the scan icon and scan this QR code to add the assistant as a contact.", "step3": "Then add your own Threema ID below.", - "qrAlt": "QR code to add {gateway} as a Threema contact" + "qrAlt": "QR code to add {gateway} as a Threema contact", + "showQr": "Show QR" } }, "team": { diff --git a/src/messages/fr.json b/src/messages/fr.json index ca37cc0..490e77b 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -402,7 +402,8 @@ "step1": "Ouvrez Threema sur votre téléphone.", "step2": "Appuyez sur l'icône de scan et scannez ce QR code pour ajouter l'assistant comme contact.", "step3": "Puis ajoutez votre propre identifiant Threema ci-dessous.", - "qrAlt": "QR code pour ajouter {gateway} comme contact Threema" + "qrAlt": "QR code pour ajouter {gateway} comme contact Threema", + "showQr": "Afficher le QR" } }, "team": { diff --git a/src/messages/it.json b/src/messages/it.json index bbd71f8..5050e06 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -402,7 +402,8 @@ "step1": "Apri Threema sul tuo telefono.", "step2": "Tocca l'icona di scansione e scansiona questo QR code per aggiungere l'assistente ai contatti.", "step3": "Quindi aggiungi il tuo ID Threema qui sotto.", - "qrAlt": "QR code per aggiungere {gateway} come contatto Threema" + "qrAlt": "QR code per aggiungere {gateway} come contatto Threema", + "showQr": "Mostra QR" } }, "team": {