@12-apps/mcp 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import type { McpAiCopy } from "./copy";
3
4
  import CheckCircleIcon from "@mui/icons-material/CheckCircle";
4
5
  import OpenInNewIcon from "@mui/icons-material/OpenInNew";
5
6
  import { useEffect, useState } from "react";
@@ -32,21 +33,21 @@ const POLL_INTERVAL_MS = 3000;
32
33
  function StepNav({
33
34
  nav,
34
35
  onNext,
35
- nextLabel = "Próximo",
36
+ nextLabel,
36
37
  nextDisabled = false,
37
38
  nextTestId,
39
+ copy,
38
40
  }: {
39
41
  nav: GuidedNav;
40
42
  onNext: () => void;
41
43
  nextLabel?: string;
42
44
  nextDisabled?: boolean;
43
45
  nextTestId?: string;
46
+ copy: McpAiCopy;
44
47
  }): React.JSX.Element {
45
48
  return (
46
49
  <Stack direction="row" spacing={1} alignItems="center" justifyContent="space-between">
47
- <Button variant="ghost" onClick={() => nav.back()} data-testid="ai-step-back">
48
- Voltar
49
- </Button>
50
+ <Button variant="ghost" onClick={() => nav.back()} data-testid="ai-step-back">{copy.flow.back}</Button>
50
51
  <Button onClick={onNext} disabled={nextDisabled} data-testid={nextTestId}>
51
52
  {nextLabel}
52
53
  </Button>
@@ -55,39 +56,53 @@ function StepNav({
55
56
  }
56
57
 
57
58
  /** Step 2 — copy the store URL; copying is the action that advances the wizard. */
58
- export function CopyUrlStep({ nav, endpointUrl }: { nav: GuidedNav; endpointUrl: string }): React.JSX.Element {
59
+ export function CopyUrlStep({
60
+ nav,
61
+ endpointUrl,
62
+ copy,
63
+ }: {
64
+ nav: GuidedNav;
65
+ endpointUrl: string;
66
+ copy: McpAiCopy;
67
+ }): React.JSX.Element {
59
68
  return (
60
69
  <Stack spacing={3} data-testid="ai-copy-step">
61
70
  <Box>
62
71
  <Text variant="heading" size="sm" as="h2">
63
- Copie a URL da sua loja
72
+ {copy.flow.copyUrlTitle}
64
73
  </Text>
65
74
  <Text variant="caption" as="p" color="secondary">
66
- É o único dado que você cola no assistente — ao copiar, seguimos para o próximo passo.
75
+ {copy.flow.urlCaption}
67
76
  </Text>
68
77
  </Box>
69
- <EndpointCopyBlock endpointUrl={endpointUrl} copied={false} onCopy={() => nav.next()} />
78
+ <EndpointCopyBlock copy={copy.connectGuide} flowCopy={copy.flow} endpointUrl={endpointUrl} copied={false} onCopy={() => nav.next()} />
70
79
  <Box>
71
- <Button variant="ghost" onClick={() => nav.back()} data-testid="ai-step-back">
72
- Voltar
73
- </Button>
80
+ <Button variant="ghost" onClick={() => nav.back()} data-testid="ai-step-back">{copy.flow.back}</Button>
74
81
  </Box>
75
82
  </Stack>
76
83
  );
77
84
  }
78
85
 
79
86
  /** Step 3 — open the host's connectors and add + configure the custom connector. */
80
- export function ConfigureStep({ nav, host }: { nav: GuidedNav; host: AiHostGuide }): React.JSX.Element {
87
+ export function ConfigureStep({
88
+ nav,
89
+ host,
90
+ copy,
91
+ }: {
92
+ nav: GuidedNav;
93
+ host: AiHostGuide;
94
+ copy: McpAiCopy;
95
+ }): React.JSX.Element {
81
96
  // Hosts with no direct link (e.g. Claude Desktop) have nothing to open, so
82
97
  // "Próximo" is available immediately; otherwise it unlocks on the open click.
83
98
  const [opened, setOpened] = useState(!host.link);
84
99
  return (
85
100
  <Stack spacing={2.5} data-testid="ai-configure-step">
86
- <HostConnectHeader host={host} />
101
+ <HostConnectHeader copy={copy.connectGuide} host={host} />
87
102
  <HostOpenButton host={host} onOpen={() => setOpened(true)} />
88
103
  <HostStepList steps={host.steps.slice(0, CONFIGURE_STEP_COUNT)} start={1} />
89
- <HostDocsLink host={host} />
90
- <StepNav nav={nav} onNext={() => nav.next()} nextDisabled={!opened} nextTestId="ai-configure-next" />
104
+ <HostDocsLink copy={copy.connectGuide} host={host} />
105
+ <StepNav copy={copy} nav={nav} onNext={() => nav.next()} nextDisabled={!opened} nextTestId="ai-configure-next" />
91
106
  </Stack>
92
107
  );
93
108
  }
@@ -101,15 +116,17 @@ export function ConfigureStageStep({
101
116
  nav,
102
117
  host,
103
118
  stage,
119
+ copy,
104
120
  }: {
105
121
  nav: GuidedNav;
106
122
  host: AiHostGuide;
107
123
  stage: AiHostConfigureStage;
124
+ copy: McpAiCopy;
108
125
  }): React.JSX.Element {
109
126
  const [opened, setOpened] = useState(!stage.link);
110
127
  return (
111
128
  <Stack spacing={2.5} data-testid={`ai-stage-${stage.id}`}>
112
- <HostConnectHeader host={host} />
129
+ <HostConnectHeader copy={copy.connectGuide} host={host} />
113
130
  {stage.link && (
114
131
  <Box>
115
132
  <Button
@@ -125,8 +142,8 @@ export function ConfigureStageStep({
125
142
  </Box>
126
143
  )}
127
144
  <HostStepList steps={stage.steps} start={1} />
128
- <HostDocsLink host={host} />
129
- <StepNav
145
+ <HostDocsLink copy={copy.connectGuide} host={host} />
146
+ <StepNav copy={copy}
130
147
  nav={nav}
131
148
  onNext={() => nav.next()}
132
149
  nextDisabled={!opened}
@@ -141,22 +158,24 @@ export function ConnectStep({
141
158
  nav,
142
159
  host,
143
160
  connectPrompt,
161
+ copy,
144
162
  }: {
145
163
  nav: GuidedNav;
146
164
  host: AiHostGuide;
147
165
  connectPrompt: string;
166
+ copy: McpAiCopy;
148
167
  }): React.JSX.Element {
149
168
  return (
150
169
  <Stack spacing={2.5} data-testid="ai-connect-step">
151
- <HostConnectHeader host={host} />
170
+ <HostConnectHeader copy={copy.connectGuide} host={host} />
152
171
  <HostStepList steps={host.steps.slice(CONFIGURE_STEP_COUNT)} start={CONFIGURE_STEP_COUNT + 1} />
153
- <PromptCopyBlock
154
- title="Cole esta mensagem no assistente"
155
- caption="Assim ele se conecta, se identifica (Claude, ChatGPT…) e registramos a conexão."
172
+ <PromptCopyBlock flowCopy={copy.flow}
173
+ title={copy.flow.pasteTitle}
174
+ caption={copy.flow.promptCaption}
156
175
  message={connectPrompt}
157
176
  />
158
- <HostDocsLink host={host} />
159
- <StepNav nav={nav} onNext={() => nav.next()} nextLabel="Continuar" nextTestId="ai-connect-done" />
177
+ <HostDocsLink copy={copy.connectGuide} host={host} />
178
+ <StepNav copy={copy} nav={nav} onNext={() => nav.next()} nextLabel={copy.flow.advance} nextTestId="ai-connect-done" />
160
179
  </Stack>
161
180
  );
162
181
  }
@@ -170,18 +189,19 @@ export function InstallStep({
170
189
  nav,
171
190
  host,
172
191
  connectPrompt,
192
+ copy,
173
193
  }: {
174
194
  nav: GuidedNav;
175
195
  host: AiHostGuide;
176
196
  connectPrompt: string;
197
+ copy: McpAiCopy;
177
198
  }): React.JSX.Element {
178
199
  const [opened, setOpened] = useState(false);
179
200
  return (
180
201
  <Stack spacing={2.5} data-testid="ai-install-step">
181
- <HostConnectHeader host={host} />
202
+ <HostConnectHeader copy={copy.connectGuide} host={host} />
182
203
  <Text variant="body" as="p" color="secondary">
183
- Abra o plugin da sua loja, clique em Instalar e autorize o acesso — sem copiar URL nem gerar
184
- credenciais.
204
+ {copy.flow.installBody}
185
205
  </Text>
186
206
  {host.pluginUrl && (
187
207
  <Box>
@@ -192,21 +212,21 @@ export function InstallStep({
192
212
  }}
193
213
  data-testid="ai-install-open"
194
214
  >
195
- Instalar o plugin da loja
215
+ {copy.flow.installAction}
196
216
  <OpenInNewIcon sx={{ fontSize: 16, ml: 0.5 }} />
197
217
  </Button>
198
218
  </Box>
199
219
  )}
200
- <PromptCopyBlock
201
- title="Peça ao assistente para conectar"
202
- caption="Cole no assistente para ele se conectar, se identificar e confirmar o acesso."
220
+ <PromptCopyBlock flowCopy={copy.flow}
221
+ title={copy.flow.askTitle}
222
+ caption={copy.flow.pasteInstallCaption}
203
223
  message={connectPrompt}
204
224
  />
205
- <HostDocsLink host={host} />
206
- <StepNav
225
+ <HostDocsLink copy={copy.connectGuide} host={host} />
226
+ <StepNav copy={copy}
207
227
  nav={nav}
208
228
  onNext={() => nav.next()}
209
- nextLabel="Continuar"
229
+ nextLabel={copy.flow.advance}
210
230
  nextDisabled={!opened}
211
231
  nextTestId="ai-install-done"
212
232
  />
@@ -225,11 +245,13 @@ export function ConfirmStep({
225
245
  connections,
226
246
  hosts,
227
247
  onRetest,
248
+ copy,
228
249
  }: {
229
250
  nav: GuidedNav;
230
251
  connections: readonly AiConnection[];
231
252
  hosts: readonly AiHostGuide[];
232
253
  onRetest: () => void;
254
+ copy: McpAiCopy;
233
255
  }): React.JSX.Element {
234
256
  const selectedHostId = nav.data.selectedHost as string | undefined;
235
257
  const connection = connectionForHost(connections, selectedHostId);
@@ -250,15 +272,15 @@ export function ConfirmStep({
250
272
  <CheckCircleIcon sx={{ color: "success.main" }} />
251
273
  <Box>
252
274
  <Text variant="body" weight="bold" as="p">
253
- {label} está conectado à sua loja.
275
+ {copy.flow.connectedTo(label)}
254
276
  </Text>
255
277
  <Text variant="caption" as="p" color="secondary">
256
- {activeAgo(connection.lastActiveAt)}
278
+ {activeAgo(connection.lastActiveAt, copy.summary)}
257
279
  </Text>
258
280
  </Box>
259
281
  </Stack>
260
282
  <Box>
261
- <Button onClick={() => nav.complete({ connectedHost: selectedHostId })}>Concluir</Button>
283
+ <Button onClick={() => nav.complete({ connectedHost: selectedHostId })}>{copy.flow.finish}</Button>
262
284
  </Box>
263
285
  </Stack>
264
286
  );
@@ -270,20 +292,18 @@ export function ConfirmStep({
270
292
  <Progress variant="circular" circularSize={22} thickness={4} dataTestId="ai-confirm-spinner" />
271
293
  <Box>
272
294
  <Text variant="body" weight="bold" as="p">
273
- Esperando conexão
295
+ {copy.flow.waitingTitle}
274
296
  </Text>
275
297
  <Text variant="caption" as="p" color="secondary">
276
- Assim que você autorizar o acesso no {label}, ela aparece aqui automaticamente.
298
+ {copy.flow.waitingBody(label)}
277
299
  </Text>
278
300
  </Box>
279
301
  </Stack>
280
302
  <Stack direction="row" spacing={1}>
281
303
  <Button variant="ghost" size="sm" onClick={onRetest} data-testid="ai-confirm-retest">
282
- Testar agora
283
- </Button>
284
- <Button variant="ghost" size="sm" onClick={() => nav.back()}>
285
- Voltar
304
+ {copy.flow.testNow}
286
305
  </Button>
306
+ <Button variant="ghost" size="sm" onClick={() => nav.back()}>{copy.flow.back}</Button>
287
307
  </Stack>
288
308
  </Stack>
289
309
  );
@@ -10,19 +10,42 @@ import { Box } from "@12-apps/ui/mui/Box";
10
10
  import { Stack } from "@12-apps/ui/mui/Stack";
11
11
  import { Text } from "@12-apps/ui/typography/Text";
12
12
 
13
- import { AI_CAPABILITIES, AI_PERMISSION_MODEL, type AiCapability } from "../guide";
13
+ import type { AiCapability } from "../guide";
14
+ import type { AiCapabilitiesCopy, AiLandingCopy } from "./copy";
14
15
  import { AiCapabilities } from "./ai-capabilities";
15
16
  import { FeatureBadge, type FeatureBadgeItem } from "./feature-badge";
16
17
 
17
- const FEATURES: FeatureBadgeItem[] = [
18
- { icon: <LockOpenOutlinedIcon />, label: "Usa o seu próprio login", caption: "Sem chaves ou credenciais extras" },
19
- { icon: <BoltOutlinedIcon />, label: "Sem instalar nada", caption: "Conecta em poucos minutos" },
20
- { icon: <VerifiedUserOutlinedIcon />, label: "Só o que você pode", caption: "As suas permissões, nada além" },
21
- { icon: <LanguageOutlinedIcon />, label: "Navegador ou app", caption: "Claude, ChatGPT, Codex" },
22
- ];
18
+ /**
19
+ * The ICONS for the landing's reassurance strip, keyed by the copy's own ids.
20
+ *
21
+ * The package keeps the icons and the host keeps the words: an icon is not
22
+ * copy, and a pack that had to ship React elements could not be a plain data
23
+ * file. An id the map does not know renders without an icon rather than
24
+ * throwing — a host adding a fifth point should get its words on screen.
25
+ */
26
+ const TRUST_ICONS: Readonly<Record<string, React.JSX.Element>> = {
27
+ login: <LockOpenOutlinedIcon />,
28
+ install: <BoltOutlinedIcon />,
29
+ permissions: <VerifiedUserOutlinedIcon />,
30
+ surface: <LanguageOutlinedIcon />,
31
+ };
32
+
33
+ function trustBadges(trust: AiLandingCopy["trust"]): FeatureBadgeItem[] {
34
+ return trust.map((point) => ({
35
+ icon: TRUST_ICONS[point.id],
36
+ label: point.label,
37
+ caption: point.caption,
38
+ }));
39
+ }
23
40
 
24
41
  /** The single-column marketing hero: eyebrow + headline + description + CTA. */
25
- function Hero({ onStart }: { onStart: () => void }): React.JSX.Element {
42
+ function Hero({
43
+ onStart,
44
+ copy,
45
+ }: {
46
+ onStart: () => void;
47
+ copy: AiLandingCopy;
48
+ }): React.JSX.Element {
26
49
  return (
27
50
  <Stack spacing={2.5} sx={{ maxWidth: 680 }}>
28
51
  <Box
@@ -34,19 +57,17 @@ function Hero({ onStart }: { onStart: () => void }): React.JSX.Element {
34
57
  textTransform: "uppercase",
35
58
  }}
36
59
  >
37
- Integração com IA
60
+ {copy.eyebrow}
38
61
  </Box>
39
62
  <Text variant="heading" size="xl" weight="bold" as="h1">
40
63
  Conecte{" "}
41
64
  <Box component="span" sx={{ color: "primary.main" }}>
42
65
  assistentes de IA
43
66
  </Box>{" "}
44
- à sua loja
67
+ {copy.titleTail}
45
68
  </Text>
46
69
  <Text variant="body" color="secondary" as="p">
47
- Deixe o Claude, o ChatGPT e outros assistentes responderem sobre o seu cardápio, estoque e
48
- pedidos — e executarem ações por você, direto na conversa. Com segurança e sem instalar
49
- nada.
70
+ {copy.lede}
50
71
  </Text>
51
72
  <Box sx={{ pt: 1 }}>
52
73
  <Button onClick={onStart} data-testid="ai-landing-start">
@@ -61,20 +82,25 @@ function Hero({ onStart }: { onStart: () => void }): React.JSX.Element {
61
82
  * Homepage-style marketing landing for the AI integration (shown before the
62
83
  * owner starts): a hero, the permission reassurance, a trust/feature strip, and
63
84
  * the capability highlights. `onStart` begins the guided flow. `permissionModel`
64
- * and `capabilities` default to the shared copy; apps can override.
85
+ * and `capabilities` are REQUIRED host copy — the package ships no default
86
+ * sentence (FUT-760).
65
87
  */
66
88
  export function AiLanding({
67
89
  onStart,
68
- permissionModel = AI_PERMISSION_MODEL,
69
- capabilities = AI_CAPABILITIES,
90
+ permissionModel,
91
+ capabilities,
92
+ copy,
93
+ capabilitiesCopy,
70
94
  }: {
71
95
  onStart: () => void;
72
- permissionModel?: string;
73
- capabilities?: readonly AiCapability[];
96
+ permissionModel: string;
97
+ capabilities: readonly AiCapability[];
98
+ copy: AiLandingCopy;
99
+ capabilitiesCopy: AiCapabilitiesCopy;
74
100
  }): React.JSX.Element {
75
101
  return (
76
102
  <Stack spacing={6} data-testid="ai-landing">
77
- <Hero onStart={onStart} />
103
+ <Hero onStart={onStart} copy={copy} />
78
104
 
79
105
  <Stack
80
106
  direction="row"
@@ -111,14 +137,14 @@ export function AiLanding({
111
137
  gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr", md: "repeat(4, 1fr)" },
112
138
  }}
113
139
  >
114
- {FEATURES.map((f) => (
140
+ {trustBadges(copy.trust).map((f) => (
115
141
  <FeatureBadge key={f.label} icon={f.icon} label={f.label} caption={f.caption} />
116
142
  ))}
117
143
  </Box>
118
144
  </Box>
119
145
 
120
146
  <Box>
121
- <AiCapabilities capabilities={capabilities} />
147
+ <AiCapabilities capabilities={capabilities} copy={capabilitiesCopy} />
122
148
  </Box>
123
149
  </Stack>
124
150
  );
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import type { McpAiCopy } from "./copy";
3
4
  import {
4
5
  GuidedSection,
5
6
  OnboardingProvider,
@@ -9,9 +10,6 @@ import {
9
10
  } from "@12-apps/onboarding";
10
11
 
11
12
  import {
12
- AI_CAPABILITIES,
13
- aiHostGuides,
14
- AI_PERMISSION_MODEL,
15
13
  type AiCapability,
16
14
  type AiHostGuide,
17
15
  } from "../guide";
@@ -44,20 +42,20 @@ export interface AiIntegrationOnboardingProps {
44
42
  /** Show the dev-only "reset onboarding" button. @default false */
45
43
  devReset?: boolean;
46
44
  /**
47
- * The platform operating this MCP server, as its OAuth consent button names
48
- * it. REQUIRED, and it is the reason `hosts` can have a default at all: one
49
- * ChatGPT step tells the owner which "Sign in with …" button to click, and
50
- * that button carries whoever runs the server. It used to be a hard-coded
51
- * name of a single STORE on one deployment, not even the product — so every
52
- * other adopter pointed its owners at a button that does not exist.
45
+ * Assistants offered in the flow, with their step-by-step instructions.
46
+ *
47
+ * REQUIRED since FUT-760. These used to default to this package's own pt-BR
48
+ * walkthrough, so a host that said nothing shipped one product's Portuguese
49
+ * and had no field to decline it. `PT_BR_AI_HOST_GUIDES(platformName)` is that
50
+ * exact content, now chosen by name.
53
51
  */
54
- platformName: string;
55
- /** Assistants offered in the flow. @default aiHostGuides(platformName) */
56
- hosts?: readonly AiHostGuide[];
57
- /** Capability cards on the landing. @default the shared AI_CAPABILITIES */
58
- capabilities?: readonly AiCapability[];
59
- /** Permission reassurance copy on the landing. @default AI_PERMISSION_MODEL */
60
- permissionModel?: string;
52
+ hosts: readonly AiHostGuide[];
53
+ /** Capability cards on the landing. REQUIRED see `hosts`. */
54
+ capabilities: readonly AiCapability[];
55
+ /** Permission reassurance copy on the landing. REQUIRED see `hosts`. */
56
+ permissionModel: string;
57
+ /** Every sentence the screens render. REQUIRED see `hosts`. */
58
+ copy: McpAiCopy;
61
59
  /**
62
60
  * Message the owner pastes into the assistant on the Conectar step.
63
61
  *
@@ -97,6 +95,7 @@ interface FlowProps {
97
95
  onRetest: () => void;
98
96
  onDisconnect: DisconnectHandler | undefined;
99
97
  devReset: boolean;
98
+ copy: McpAiCopy;
100
99
  }
101
100
 
102
101
  /**
@@ -116,6 +115,7 @@ function AiOnboardingFlow(props: FlowProps): React.JSX.Element {
116
115
  onRetest,
117
116
  onDisconnect,
118
117
  devReset,
118
+ copy,
119
119
  } = props;
120
120
  const { state } = useOnboarding();
121
121
  const selectedHost =
@@ -128,6 +128,7 @@ function AiOnboardingFlow(props: FlowProps): React.JSX.Element {
128
128
  connectPrompt,
129
129
  connections,
130
130
  onRetest,
131
+ copy,
131
132
  });
132
133
  const connectedHostId = (state.data.connectedHost ??
133
134
  state.data.selectedHost) as string | undefined;
@@ -135,24 +136,27 @@ function AiOnboardingFlow(props: FlowProps): React.JSX.Element {
135
136
  return (
136
137
  <GuidedSection
137
138
  steps={steps}
138
- title="Conecte assistentes de IA à sua loja"
139
- startLabel="Ver como conectar"
139
+ title={copy.onboarding.title}
140
+ startLabel={copy.landing.start}
140
141
  renderLanding={(start) => (
141
142
  <AiLanding
142
143
  onStart={start}
143
144
  permissionModel={permissionModel}
144
145
  capabilities={capabilities}
146
+ copy={copy.landing}
147
+ capabilitiesCopy={copy.capabilities}
145
148
  />
146
149
  )}
147
- configuredTitle={connectedTitle(connections, hosts, connectedHostId)}
148
- configuredSummary={connectedSummary(connections)}
149
- editLabel="Conectar IA"
150
+ configuredTitle={connectedTitle(connections, hosts, connectedHostId, copy.summary)}
151
+ configuredSummary={connectedSummary(connections, copy.summary)}
152
+ editLabel={copy.onboarding.editLabel}
150
153
  completedContent={(nav) => (
151
154
  <StatusBoard
152
155
  nav={nav}
153
156
  connections={connections}
154
157
  hosts={hosts}
155
158
  onDisconnect={onDisconnect}
159
+ copy={copy}
156
160
  />
157
161
  )}
158
162
  devReset={devReset}
@@ -179,15 +183,15 @@ export function AiIntegrationOnboarding({
179
183
  connections,
180
184
  featureKey = DEFAULT_FEATURE_KEY,
181
185
  devReset = false,
182
- platformName,
183
- hosts = aiHostGuides(platformName),
184
- capabilities = AI_CAPABILITIES,
185
- permissionModel = AI_PERMISSION_MODEL,
186
+ hosts,
187
+ capabilities,
188
+ permissionModel,
186
189
  connectPrompt,
187
190
  onRetest = () => {
188
191
  if (typeof window !== "undefined") window.location.reload();
189
192
  },
190
193
  onDisconnect,
194
+ copy,
191
195
  }: AiIntegrationOnboardingProps): React.JSX.Element {
192
196
  return (
193
197
  <OnboardingProvider
@@ -205,6 +209,7 @@ export function AiIntegrationOnboarding({
205
209
  onRetest={onRetest}
206
210
  onDisconnect={onDisconnect}
207
211
  devReset={devReset}
212
+ copy={copy}
208
213
  />
209
214
  </OnboardingProvider>
210
215
  );
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import type { AiStatusBoardCopy } from "./copy";
3
4
  import AddLinkOutlinedIcon from "@mui/icons-material/AddLinkOutlined";
4
5
  import CheckCircleIcon from "@mui/icons-material/CheckCircle";
5
6
  import LinkOffOutlinedIcon from "@mui/icons-material/LinkOffOutlined";
@@ -33,7 +34,7 @@ export interface HostStatus {
33
34
  * The right-hand side of a CONNECTED box: the green pill, then the two things
34
35
  * the pill alone left the owner unable to do.
35
36
  *
36
- * "Instruções" exists because the setup steps were reachable ONLY from a RED
37
+ * "{copy.instructions}" exists because the setup steps were reachable ONLY from a RED
37
38
  * card's "Conectar" — so on an all-green board there was no way to re-read how
38
39
  * any of them was connected, which is exactly when someone re-doing the setup on
39
40
  * a second machine needs them. It re-enters the same flow that button does.
@@ -45,10 +46,12 @@ function ConnectedControls({
45
46
  host,
46
47
  onConnect,
47
48
  onDisconnect,
49
+ copy,
48
50
  }: {
49
51
  host: AiHostGuide;
50
52
  onConnect: (hostId: string) => void;
51
53
  onDisconnect?: DisconnectHandler;
54
+ copy: AiStatusBoardCopy;
52
55
  }): React.JSX.Element {
53
56
  return (
54
57
  <Stack
@@ -80,7 +83,7 @@ function ConnectedControls({
80
83
  data-testid={`ai-status-instructions-${host.id}`}
81
84
  >
82
85
  <MenuBookOutlinedIcon sx={{ fontSize: 16, mr: 0.5 }} />
83
- Instruções
86
+ {copy.instructions}
84
87
  </Button>
85
88
  {onDisconnect ? (
86
89
  <ConfirmButton
@@ -89,11 +92,11 @@ function ConnectedControls({
89
92
  size="sm"
90
93
  onClick={() => onDisconnect(host.id)}
91
94
  confirm={{
92
- title: "Desconectar o assistente?",
95
+ title: copy.disconnectTitle,
93
96
  entityName: host.label,
94
97
  description:
95
- "Ele perde o acesso à sua loja na hora. Para voltar a usar, será preciso conectar de novo.",
96
- confirmText: "Desconectar",
98
+ copy.disconnectBody,
99
+ confirmText: copy.disconnectConfirm,
97
100
  dataTestId: `ai-status-disconnect-confirm-${host.id}`,
98
101
  }}
99
102
  data-testid={`ai-status-disconnect-${host.id}`}
@@ -111,10 +114,12 @@ function StatusBox({
111
114
  status,
112
115
  onConnect,
113
116
  onDisconnect,
117
+ copy,
114
118
  }: {
115
119
  status: HostStatus;
116
120
  onConnect: (hostId: string) => void;
117
121
  onDisconnect?: DisconnectHandler;
122
+ copy: AiStatusBoardCopy;
118
123
  }): React.JSX.Element {
119
124
  const { host, connected, detail } = status;
120
125
  return (
@@ -141,11 +146,11 @@ function StatusBox({
141
146
  {connected ? <CheckCircleIcon sx={{ fontSize: 18, color: "success.main" }} /> : null}
142
147
  </Stack>
143
148
  <Text variant="caption" as="p" color="secondary">
144
- {connected ? (detail ?? "Conectado") : "Ainda não conectado"}
149
+ {connected ? (detail ?? copy.connected) : copy.notConnected}
145
150
  </Text>
146
151
  </Box>
147
152
  {connected ? (
148
- <ConnectedControls host={host} onConnect={onConnect} onDisconnect={onDisconnect} />
153
+ <ConnectedControls copy={copy} host={host} onConnect={onConnect} onDisconnect={onDisconnect} />
149
154
  ) : (
150
155
  <Button
151
156
  variant="outline"
@@ -169,7 +174,7 @@ function StatusBox({
169
174
  * `announceAiConnection`), so several assistants can show connected at once.
170
175
  *
171
176
  * A connected box carries two more controls, because the pill it used to show
172
- * alone was a dead end in both directions: "Instruções" re-enters the host's
177
+ * alone was a dead end in both directions: "{copy.instructions}" re-enters the host's
173
178
  * setup steps (previously reachable only from a RED card, so an all-green board
174
179
  * hid them entirely), and "Desconectar" — when the app passes `onDisconnect` —
175
180
  * revokes access, which nothing in the UI could do at all.
@@ -178,20 +183,22 @@ export function AiStatusBoard({
178
183
  statuses,
179
184
  onConnect,
180
185
  onDisconnect,
186
+ copy,
181
187
  }: {
182
188
  statuses: readonly HostStatus[];
183
189
  onConnect: (hostId: string) => void;
184
190
  /** Revoke this host's access. Omit to render a read-only board. */
185
191
  onDisconnect?: DisconnectHandler;
192
+ copy: AiStatusBoardCopy;
186
193
  }): React.JSX.Element {
187
194
  return (
188
195
  <Stack spacing={2} data-testid="ai-status-board">
189
196
  <Box>
190
197
  <Text variant="heading" size="sm" as="h2">
191
- Assistentes conectados
198
+ {copy.boardTitle}
192
199
  </Text>
193
200
  <Text variant="caption" as="p" color="secondary">
194
- Em verde os que já operam a sua loja; em vermelho os que faltam conectar.
201
+ {copy.boardCaption}
195
202
  </Text>
196
203
  </Box>
197
204
  <Box
@@ -202,7 +209,7 @@ export function AiStatusBoard({
202
209
  }}
203
210
  >
204
211
  {statuses.map((status) => (
205
- <StatusBox
212
+ <StatusBox copy={copy}
206
213
  key={status.host.id}
207
214
  status={status}
208
215
  onConnect={onConnect}