Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 73f1af185f |
@@ -225,6 +225,7 @@ export default async function TenantDetailPage({
|
||||
unreachable). */}
|
||||
<section className="mb-8 animate-in animate-in-delay-1">
|
||||
<ConnectPanel
|
||||
tenantName={name}
|
||||
enabledChannels={enabledChannels}
|
||||
phase={tenant.status?.phase ?? "Pending"}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { THREEMA_GATEWAY } from "@/lib/threema-gateway-config";
|
||||
|
||||
@@ -16,11 +17,16 @@ import { THREEMA_GATEWAY } from "@/lib/threema-gateway-config";
|
||||
* NO channel is enabled it says so explicitly (a running assistant with
|
||||
* no channel is unreachable).
|
||||
*
|
||||
* Once a customer has connected they don't need the steps every visit,
|
||||
* so the panel is dismissible: clicking "I've connected" collapses it
|
||||
* to a slim row and remembers that per-tenant (localStorage). The slim
|
||||
* row keeps a "Show connection details" toggle so it's never lost.
|
||||
* The no-channel warning is NOT dismissible — it's an actionable alert,
|
||||
* not reference material.
|
||||
*
|
||||
* It is intentionally complementary to ChannelUsers below it:
|
||||
* - ConnectPanel → "how do *I* reach the assistant"
|
||||
* - ChannelUsers → "*who* is allowed to reach it"
|
||||
* The Threema/Telegram/Discord steps reference the authorised-users
|
||||
* list rather than duplicating it.
|
||||
*/
|
||||
|
||||
// Render order is fixed (not the order packages happen to appear in
|
||||
@@ -40,10 +46,15 @@ const CHANNEL_STEPS_KEY: Record<string, string> = {
|
||||
discord: "discordSteps",
|
||||
};
|
||||
|
||||
const dismissKey = (tenantName: string) =>
|
||||
`pieced:connect-hidden:${tenantName}`;
|
||||
|
||||
export function ConnectPanel({
|
||||
tenantName,
|
||||
enabledChannels,
|
||||
phase,
|
||||
}: {
|
||||
tenantName: string;
|
||||
enabledChannels: string[];
|
||||
/** Tenant phase — connection details only "work" once it's Ready. */
|
||||
phase: string;
|
||||
@@ -53,7 +64,41 @@ export function ConnectPanel({
|
||||
const channels = CHANNEL_ORDER.filter((c) => enabledChannels.includes(c));
|
||||
const ready = phase === "Ready" || phase === "Running" || phase === "Active";
|
||||
|
||||
// No channel at all → the assistant is unreachable. Make it loud.
|
||||
// Dismissed state is read from localStorage after mount to avoid a
|
||||
// hydration mismatch (server has no localStorage). `hydrated` gates
|
||||
// the collapsed view so the first paint matches the server output.
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
setCollapsed(localStorage.getItem(dismissKey(tenantName)) === "1");
|
||||
} catch {
|
||||
/* private mode / storage disabled — just stay expanded */
|
||||
}
|
||||
setHydrated(true);
|
||||
}, [tenantName]);
|
||||
|
||||
const dismiss = () => {
|
||||
setCollapsed(true);
|
||||
try {
|
||||
localStorage.setItem(dismissKey(tenantName), "1");
|
||||
} catch {
|
||||
/* no-op */
|
||||
}
|
||||
};
|
||||
|
||||
const reopen = () => {
|
||||
setCollapsed(false);
|
||||
try {
|
||||
localStorage.removeItem(dismissKey(tenantName));
|
||||
} catch {
|
||||
/* no-op */
|
||||
}
|
||||
};
|
||||
|
||||
// No channel at all → the assistant is unreachable. Make it loud and
|
||||
// keep it non-dismissible (it's an alert, not reference material).
|
||||
if (channels.length === 0) {
|
||||
return (
|
||||
<div className="rounded-xl border border-amber-500/30 bg-amber-500/10 p-5">
|
||||
@@ -85,11 +130,51 @@ export function ConnectPanel({
|
||||
);
|
||||
}
|
||||
|
||||
// Collapsed: a slim, unobtrusive row with a toggle to bring the full
|
||||
// panel back. Only shown once hydrated so SSR/CSR agree.
|
||||
if (hydrated && collapsed) {
|
||||
return (
|
||||
<div className="flex items-center justify-between rounded-lg border border-border bg-surface-1 px-4 py-2">
|
||||
<span className="text-xs text-text-muted">{t("title")}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={reopen}
|
||||
className="text-xs font-medium text-accent hover:text-accent-dim transition-colors cursor-pointer"
|
||||
>
|
||||
{t("show")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-accent/30 bg-accent/5 p-5">
|
||||
<h2 className="font-display text-base font-semibold text-text-primary mb-1">
|
||||
{t("title")}
|
||||
</h2>
|
||||
<div className="flex items-start justify-between gap-3 mb-1">
|
||||
<h2 className="font-display text-base font-semibold text-text-primary">
|
||||
{t("title")}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={dismiss}
|
||||
className="shrink-0 inline-flex items-center gap-1 text-xs font-medium text-text-muted hover:text-text-secondary transition-colors cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.5 12.75l6 6 9-13.5"
|
||||
/>
|
||||
</svg>
|
||||
{t("dismiss")}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-text-secondary mb-4 leading-relaxed">
|
||||
{t("description")}
|
||||
</p>
|
||||
|
||||
@@ -1005,6 +1005,8 @@
|
||||
"threemaBotIdLabel": "Threema-ID",
|
||||
"threemaSteps": "1. Öffnen Sie Threema und scannen Sie diesen QR-Code (oder fügen Sie die obige ID als Kontakt hinzu).\n2. Senden Sie eine Nachricht, um den Chat zu starten.\nStellen Sie sicher, dass Ihre eigene Threema-ID in der Liste der autorisierten Benutzer unten steht – nur gelistete IDs erhalten eine Antwort.",
|
||||
"telegramSteps": "Öffnen Sie den verbundenen Telegram-Bot und senden Sie ihm eine Nachricht, um den Chat zu starten. Nur die Benutzer-IDs in der Liste der autorisierten Benutzer unten erhalten eine Antwort.",
|
||||
"discordSteps": "Schreiben Sie dem verbundenen Discord-Bot oder erwähnen Sie ihn in einem Kanal, dem er beigetreten ist. Nur die Benutzer-IDs in der Liste der autorisierten Benutzer unten erhalten eine Antwort."
|
||||
"discordSteps": "Schreiben Sie dem verbundenen Discord-Bot oder erwähnen Sie ihn in einem Kanal, dem er beigetreten ist. Nur die Benutzer-IDs in der Liste der autorisierten Benutzer unten erhalten eine Antwort.",
|
||||
"dismiss": "Verbunden",
|
||||
"show": "Verbindungsdetails anzeigen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,6 +1005,8 @@
|
||||
"threemaBotIdLabel": "Threema ID",
|
||||
"threemaSteps": "1. Open Threema and scan this QR code (or add the ID above as a contact).\n2. Send it a message to start chatting.\nMake sure your own Threema ID is on the authorised users list below — only listed IDs get a reply.",
|
||||
"telegramSteps": "Open the Telegram bot you connected and send it a message to start chatting. Only the user IDs on the authorised users list below get a reply.",
|
||||
"discordSteps": "Message the Discord bot you connected, or mention it in a channel it has joined. Only the user IDs on the authorised users list below get a reply."
|
||||
"discordSteps": "Message the Discord bot you connected, or mention it in a channel it has joined. Only the user IDs on the authorised users list below get a reply.",
|
||||
"dismiss": "I've connected",
|
||||
"show": "Show connection details"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,6 +1005,8 @@
|
||||
"threemaBotIdLabel": "Identifiant Threema",
|
||||
"threemaSteps": "1. Ouvrez Threema et scannez ce QR code (ou ajoutez l'identifiant ci-dessus comme contact).\n2. Envoyez-lui un message pour commencer à discuter.\nAssurez-vous que votre propre identifiant Threema figure dans la liste des utilisateurs autorisés ci-dessous — seuls les identifiants listés reçoivent une réponse.",
|
||||
"telegramSteps": "Ouvrez le bot Telegram que vous avez connecté et envoyez-lui un message pour commencer à discuter. Seuls les identifiants utilisateur de la liste des utilisateurs autorisés ci-dessous reçoivent une réponse.",
|
||||
"discordSteps": "Écrivez au bot Discord que vous avez connecté, ou mentionnez-le dans un salon qu'il a rejoint. Seuls les identifiants utilisateur de la liste des utilisateurs autorisés ci-dessous reçoivent une réponse."
|
||||
"discordSteps": "Écrivez au bot Discord que vous avez connecté, ou mentionnez-le dans un salon qu'il a rejoint. Seuls les identifiants utilisateur de la liste des utilisateurs autorisés ci-dessous reçoivent une réponse.",
|
||||
"dismiss": "Je suis connecté",
|
||||
"show": "Afficher les détails de connexion"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,6 +1005,8 @@
|
||||
"threemaBotIdLabel": "ID Threema",
|
||||
"threemaSteps": "1. Apri Threema e scansiona questo codice QR (oppure aggiungi l'ID sopra come contatto).\n2. Inviagli un messaggio per iniziare a chattare.\nAssicurati che il tuo ID Threema sia presente nell'elenco degli utenti autorizzati qui sotto: solo gli ID elencati ricevono una risposta.",
|
||||
"telegramSteps": "Apri il bot Telegram che hai collegato e inviagli un messaggio per iniziare a chattare. Solo gli ID utente nell'elenco degli utenti autorizzati qui sotto ricevono una risposta.",
|
||||
"discordSteps": "Scrivi al bot Discord che hai collegato, oppure menzionalo in un canale a cui si è unito. Solo gli ID utente nell'elenco degli utenti autorizzati qui sotto ricevono una risposta."
|
||||
"discordSteps": "Scrivi al bot Discord che hai collegato, oppure menzionalo in un canale a cui si è unito. Solo gli ID utente nell'elenco degli utenti autorizzati qui sotto ricevono una risposta.",
|
||||
"dismiss": "Mi sono collegato",
|
||||
"show": "Mostra dettagli di connessione"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user