Threema QR: on-demand modal + auto-open on first add
All checks were successful
Build and Push / build (push) Successful in 1m29s
All checks were successful
Build and Push / build (push) Successful in 1m29s
This commit is contained in:
140
README.md
140
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
|
||||||
|
`<img>` (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
|
## Files
|
||||||
|
|
||||||
```
|
```
|
||||||
src/lib/threema-gateway-config.ts # NEW — single source of truth for gateway ID + QR path
|
src/lib/threema-gateway-config.ts # unchanged from before — central gateway constants
|
||||||
src/components/channel-users/threema-setup.tsx # NEW — QR + 3-step instructions component
|
src/components/channel-users/threema-qr-modal.tsx # NEW — the modal
|
||||||
src/components/channel-users/channel-users.tsx # MODIFIED — renders <ThreemaSetup /> for the threema channel
|
src/components/channel-users/channel-users.tsx # MODIFIED — Show QR button + focus auto-open + modal mount
|
||||||
deploy/patch-i18n-threema.mjs # REPLACES earlier version — customer-friendly texts in 4 langs
|
deploy/patch-i18n-threema.mjs # adds threemaSetup.showQr label in 4 langs
|
||||||
public/threema/qr_code_AIAGENT.png # NEW — the QR you uploaded
|
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 `<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
|
## Apply
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /path/to/pieced-portal
|
cd /path/to/pieced-portal
|
||||||
|
|
||||||
# Drop in new files (overwrites prior channel-users.tsx and i18n script)
|
# Remove the old inline component if you applied the previous archive
|
||||||
cp -r <unzipped>/* .
|
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
|
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 add -A
|
||||||
git status # eyeball the changes
|
git commit -m "Threema QR: on-demand modal + auto-open on first add"
|
||||||
git commit -m "Threema: customer-friendly texts + QR setup component"
|
|
||||||
git push
|
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
|
threema [Show QR] 0 users
|
||||||
the channel-users panel — the QR shows there regardless).
|
────────────────────
|
||||||
3. Authorized Users → threema section should show:
|
<help text: 'Enter your own Threema ID...'>
|
||||||
- QR code on the left
|
────────────────────
|
||||||
- "AIAGENT" label under the QR (no asterisk)
|
[ input: A8K2P3X7 ] [ Add ]
|
||||||
- 3-step instruction list
|
^^^ focusing this opens the QR modal the first time
|
||||||
- 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;"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
That's your billing source — count(*) × Threema's per-message rate ×
|
Clicking "Show QR" or focusing the input → modal with QR + steps.
|
||||||
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/<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`
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const i18n = {
|
|||||||
step2: "Tap the scan icon and scan this QR code to add the assistant as a contact.",
|
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.",
|
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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
de: {
|
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.",
|
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.",
|
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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fr: {
|
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.",
|
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.",
|
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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
it: {
|
it: {
|
||||||
@@ -94,6 +97,7 @@ const i18n = {
|
|||||||
step2: "Tocca l'icona di scansione e scansiona questo QR code per aggiungere l'assistente ai contatti.",
|
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.",
|
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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback } from "react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { ThreemaQrModal } from "./threema-qr-modal";
|
||||||
|
|
||||||
/** Maps channel IDs to the instructions for finding the user ID. */
|
/** Maps channel IDs to the instructions for finding the user ID. */
|
||||||
const CHANNEL_ID_HELP: Record<string, string> = {
|
const CHANNEL_ID_HELP: Record<string, string> = {
|
||||||
@@ -51,6 +52,14 @@ export function ChannelUsers({
|
|||||||
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
||||||
const [channelUsers, setChannelUsers] =
|
const [channelUsers, setChannelUsers] =
|
||||||
useState<Record<string, string[]>>(initialChannelUsers);
|
useState<Record<string, string[]>>(initialChannelUsers);
|
||||||
|
/** Which channel's QR helper modal is open, if any. */
|
||||||
|
const [showQrFor, setShowQrFor] = useState<string | null>(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<Set<string>>(() => new Set());
|
||||||
|
|
||||||
const updateChannelUsers = useCallback(
|
const updateChannelUsers = useCallback(
|
||||||
async (updated: Record<string, string[]>) => {
|
async (updated: Record<string, string[]>) => {
|
||||||
@@ -216,9 +225,19 @@ export function ChannelUsers({
|
|||||||
className="bg-surface-2 border border-border rounded-lg p-4"
|
className="bg-surface-2 border border-border rounded-lg p-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h4 className="text-sm font-medium text-text-primary capitalize">
|
<div className="flex items-center gap-2">
|
||||||
{channel}
|
<h4 className="text-sm font-medium text-text-primary capitalize">
|
||||||
</h4>
|
{channel}
|
||||||
|
</h4>
|
||||||
|
{channel === "threema" && (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowQrFor("threema")}
|
||||||
|
className="text-xs font-medium text-accent hover:text-accent-dim cursor-pointer underline underline-offset-2"
|
||||||
|
>
|
||||||
|
{t("threemaSetup.showQr")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<span className="text-xs text-text-muted tabular-nums">
|
<span className="text-xs text-text-muted tabular-nums">
|
||||||
{users.length} {t("users")}
|
{users.length} {t("users")}
|
||||||
</span>
|
</span>
|
||||||
@@ -266,6 +285,17 @@ export function ChannelUsers({
|
|||||||
[channel]: e.target.value,
|
[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) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") handleAdd(channel);
|
if (e.key === "Enter") handleAdd(channel);
|
||||||
}}
|
}}
|
||||||
@@ -284,6 +314,11 @@ export function ChannelUsers({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
<ThreemaQrModal
|
||||||
|
open={showQrFor === "threema"}
|
||||||
|
onClose={() => setShowQrFor(null)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
82
src/components/channel-users/threema-qr-modal.tsx
Normal file
82
src/components/channel-users/threema-qr-modal.tsx
Normal file
@@ -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 <img> 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 (
|
||||||
|
<div
|
||||||
|
onClick={onClose}
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={(e) => 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"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<h3 className="text-base font-semibold text-text-primary">
|
||||||
|
{t("title")}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="text-text-muted hover:text-text-primary text-xl leading-none"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div className="bg-white p-3 rounded-md">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={THREEMA_GATEWAY.qrCodePath}
|
||||||
|
alt={t("qrAlt", { gateway: THREEMA_GATEWAY.displayName })}
|
||||||
|
width={220}
|
||||||
|
height={220}
|
||||||
|
style={{ display: "block" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-center text-xs text-text-muted font-mono">
|
||||||
|
{THREEMA_GATEWAY.displayName}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ol className="list-decimal list-inside text-xs text-text-secondary space-y-1.5">
|
||||||
|
<li>{t("step1")}</li>
|
||||||
|
<li>{t("step2")}</li>
|
||||||
|
<li>{t("step3")}</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
src/lib/threema-gateway-config.ts
Normal file
33
src/lib/threema-gateway-config.ts
Normal file
@@ -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;
|
||||||
@@ -402,7 +402,8 @@
|
|||||||
"step1": "Öffnen Sie Threema auf Ihrem Telefon.",
|
"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.",
|
"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.",
|
"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": {
|
"team": {
|
||||||
|
|||||||
@@ -402,7 +402,8 @@
|
|||||||
"step1": "Open Threema on your phone.",
|
"step1": "Open Threema on your phone.",
|
||||||
"step2": "Tap the scan icon and scan this QR code to add the assistant as a contact.",
|
"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.",
|
"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": {
|
"team": {
|
||||||
|
|||||||
@@ -402,7 +402,8 @@
|
|||||||
"step1": "Ouvrez Threema sur votre téléphone.",
|
"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.",
|
"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.",
|
"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": {
|
"team": {
|
||||||
|
|||||||
@@ -402,7 +402,8 @@
|
|||||||
"step1": "Apri Threema sul tuo telefono.",
|
"step1": "Apri Threema sul tuo telefono.",
|
||||||
"step2": "Tocca l'icona di scansione e scansiona questo QR code per aggiungere l'assistente ai contatti.",
|
"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.",
|
"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": {
|
"team": {
|
||||||
|
|||||||
Reference in New Issue
Block a user