"use client"; import { useMemo } from "react"; import type { AiStatusBoardCopy } from "./copy"; import AddLinkOutlinedIcon from "@mui/icons-material/AddLinkOutlined"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import LinkOffOutlinedIcon from "@mui/icons-material/LinkOffOutlined"; import MenuBookOutlinedIcon from "@mui/icons-material/MenuBookOutlined"; import { createConfirmButton } from "@12-apps/ui/feedback/ConfirmAction"; import { Button } from "@12-apps/ui/form/Button"; import { Box } from "@12-apps/ui/mui/Box"; import { Stack } from "@12-apps/ui/mui/Stack"; import { Text } from "@12-apps/ui/typography/Text"; import type { AiHostGuide } from "../guide"; import { HostBrandAvatar } from "./ai-icons"; /** * Revoke a host's access. Returning a promise holds the confirmation popup in * its pending state until the write settles, and a rejection keeps it open * carrying the reason — so the owner sees the disconnect finish or fail. */ export type DisconnectHandler = (hostId: string) => void | Promise; /** One assistant's connection state. */ export interface HostStatus { host: AiHostGuide; connected: boolean; /** Freeform activity line for a connected host (e.g. "ativo há 3 min"). */ detail?: string; } /** * The right-hand side of a CONNECTED box: the green pill, then the two things * the pill alone left the owner unable to do. * * "{copy.instructions}" exists because the setup steps were reachable ONLY from a RED * card's "Conectar" — so on an all-green board there was no way to re-read how * any of them was connected, which is exactly when someone re-doing the setup on * a second machine needs them. It re-enters the same flow that button does. * * "Desconectar" is only drawn when the app supplies a revoke path: a board with * nowhere to send the revoke must not offer a button that does nothing. */ function ConnectedControls({ host, onConnect, onDisconnect, copy, }: { host: AiHostGuide; onConnect: (hostId: string) => void; onDisconnect?: DisconnectHandler; copy: AiStatusBoardCopy; }): React.JSX.Element { // Built from the copy rather than at module scope: `createConfirmButton` // takes the words, and they only arrive as a prop. Memoised so the wrapped // component keeps its identity across renders and React does not remount it. const DisconnectButton = useMemo( () => createConfirmButton(copy.confirmAction, copy.disconnectError), [copy.confirmAction, copy.disconnectError], ); return ( {copy.connected} {onDisconnect ? ( onDisconnect(host.id)} confirm={{ title: copy.disconnectTitle, entityName: host.label, description: copy.disconnectBody, confirmText: copy.disconnectConfirm, dataTestId: `ai-status-disconnect-confirm-${host.id}`, }} data-testid={`ai-status-disconnect-${host.id}`} > {copy.disconnect} ) : null} ); } /** A single green (connected) / red (to connect) status box for one assistant. */ function StatusBox({ status, onConnect, onDisconnect, copy, }: { status: HostStatus; onConnect: (hostId: string) => void; onDisconnect?: DisconnectHandler; copy: AiStatusBoardCopy; }): React.JSX.Element { const { host, connected, detail } = status; return ( {host.label} {connected ? : null} {connected ? (detail ?? copy.connected) : copy.notConnected} {connected ? ( ) : ( )} ); } /** * The completed-state status board for the AI integration: every assistant as a * box — green when connected, red when still to connect (with a "Conectar" * button that re-enters the guided flow for that host). Each connection is * attributed to its provider (derived from the OAuth client + confirmed by * `announceAiConnection`), so several assistants can show connected at once. * * A connected box carries two more controls, because the pill it used to show * alone was a dead end in both directions: "{copy.instructions}" re-enters the host's * setup steps (previously reachable only from a RED card, so an all-green board * hid them entirely), and "Desconectar" — when the app passes `onDisconnect` — * revokes access, which nothing in the UI could do at all. */ export function AiStatusBoard({ statuses, onConnect, onDisconnect, copy, }: { statuses: readonly HostStatus[]; onConnect: (hostId: string) => void; /** Revoke this host's access. Omit to render a read-only board. */ onDisconnect?: DisconnectHandler; copy: AiStatusBoardCopy; }): React.JSX.Element { return ( {copy.boardTitle} {copy.boardCaption} {statuses.map((status) => ( ))} ); }