Merge branch 'feat/translator-friendly-concept-card-F2' into refactor/pages-v3-19-translator-friendly-redesign

This commit is contained in:
diegosouzapw
2026-05-27 19:56:38 -03:00
3 changed files with 495 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
"use client";
import { useCallback, type ReactNode } from "react";
import { useTranslations } from "next-intl";
import Tooltip from "@/shared/components/Tooltip";
interface FlowNodeProps {
icon: string;
color: "primary" | "orange" | "blue" | "emerald" | "amber" | "purple" | "cyan" | "pink";
title: string;
example: string;
tooltipContent?: string;
}
const COLOR_MAP: Record<
FlowNodeProps["color"],
{ border: string; bg: string; text: string }
> = {
primary: { border: "border-primary/30", bg: "bg-primary/5", text: "text-primary" },
orange: { border: "border-orange-500/30", bg: "bg-orange-500/5", text: "text-orange-500" },
blue: { border: "border-blue-500/30", bg: "bg-blue-500/5", text: "text-blue-500" },
emerald: {
border: "border-emerald-500/30",
bg: "bg-emerald-500/5",
text: "text-emerald-500",
},
amber: { border: "border-amber-500/30", bg: "bg-amber-500/5", text: "text-amber-500" },
purple: {
border: "border-purple-500/30",
bg: "bg-purple-500/5",
text: "text-purple-500",
},
cyan: { border: "border-cyan-500/30", bg: "bg-cyan-500/5", text: "text-cyan-500" },
pink: { border: "border-pink-500/30", bg: "bg-pink-500/5", text: "text-pink-500" },
};
function FlowNode({ icon, color, title, example, tooltipContent }: FlowNodeProps) {
const c = COLOR_MAP[color];
const node: ReactNode = (
<div
className={`flex flex-col items-center gap-1 rounded-lg border ${c.border} ${c.bg} px-3 py-2 text-center min-w-0`}
>
<span className={`material-symbols-outlined text-[20px] ${c.text}`} aria-hidden="true">
{icon}
</span>
<p className="text-[11px] font-semibold text-text-main leading-tight">{title}</p>
<p className="text-[10px] text-text-muted leading-tight">{example}</p>
</div>
);
return tooltipContent ? (
<Tooltip content={tooltipContent} position="top" multiline>
{node}
</Tooltip>
) : (
node
);
}
function FlowArrow({ label }: { label?: string }) {
return (
<div className="flex flex-col items-center justify-center text-text-muted">
<span
className="material-symbols-outlined text-[20px] rotate-90 sm:rotate-0"
aria-hidden="true"
>
arrow_forward
</span>
{label && (
<span className="text-[9px] uppercase tracking-wide mt-0.5">{label}</span>
)}
</div>
);
}
export default function TranslateFlowDiagram() {
const t = useTranslations("translator");
const tr = useCallback(
(key: string, fallback: string) => {
try {
const translated = t(key);
return translated === key || translated === `translator.${key}` ? fallback : translated;
} catch {
return fallback;
}
},
[t],
);
return (
<div className="grid grid-cols-1 sm:grid-cols-[1fr_auto_1fr_auto_1fr] gap-2 sm:gap-3 sm:items-stretch">
<FlowNode
icon="smart_toy"
color="primary"
title={tr("conceptDiagramAppLabel", "Sua app")}
example={tr("conceptDiagramExampleApp", "ex: SDK Anthropic")}
/>
<FlowArrow label={tr("conceptDiagramArrow1", "fala")} />
<FlowNode
icon="psychology"
color="orange"
title={tr("conceptDiagramSourceLabel", "Formato origem")}
example={tr("conceptDiagramExampleSource", "claude")}
tooltipContent={tr(
"conceptDiagramSourceTooltip",
"Formato do protocolo de API que sua app fala (ex: Anthropic Messages, OpenAI Chat Completions, Gemini).",
)}
/>
<FlowArrow label={tr("conceptDiagramArrow2", "Translator")} />
<FlowNode
icon="auto_awesome"
color="blue"
title={tr("conceptDiagramTargetLabel", "Provider destino")}
example={tr("conceptDiagramExampleTarget", "Gemini")}
tooltipContent={tr(
"conceptDiagramTargetTooltip",
"Provider conectado em OmniRoute que vai responder de verdade (ex: Google Gemini, Anthropic, etc).",
)}
/>
</div>
);
}

View File

@@ -0,0 +1,79 @@
"use client";
import { useCallback, useState } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/shared/components";
import TranslateFlowDiagram from "./TranslateFlowDiagram";
export default function TranslatorConceptCard() {
const t = useTranslations("translator");
const [open, setOpen] = useState(false);
const tr = useCallback(
(key: string, fallback: string) => {
try {
const translated = t(key);
return translated === key || translated === `translator.${key}` ? fallback : translated;
} catch {
return fallback;
}
},
[t],
);
return (
<Card className="border-primary/10 bg-primary/5">
<div className="p-4 space-y-3">
<div className="flex items-start gap-3">
<span
className="material-symbols-outlined text-primary text-[22px] mt-0.5 shrink-0"
aria-hidden="true"
>
info
</span>
<div className="flex-1 min-w-0">
<h2 className="text-sm font-semibold text-text-main mb-1">
{tr(
"conceptHeadline",
'Sua app fala o "idioma" de uma API. O Translator converte para usar outro provider.',
)}
</h2>
<p className="text-xs text-text-muted">
{tr(
"friendlySubtitle",
"Use sua app existente com qualquer provider — sem reescrever código.",
)}
</p>
</div>
</div>
<TranslateFlowDiagram />
<button
type="button"
onClick={() => setOpen((v) => !v)}
aria-expanded={open}
aria-controls="translator-concept-how-it-works"
className="flex items-center gap-2 text-xs font-medium text-primary hover:text-primary/80 transition-colors w-full justify-start py-1 rounded"
>
<span>{tr("conceptHowItWorksToggle", "Como funciona")}</span>
<span className="material-symbols-outlined text-[16px]" aria-hidden="true">
{open ? "expand_less" : "expand_more"}
</span>
</button>
{open && (
<div
id="translator-concept-how-it-works"
className="text-xs text-text-muted leading-relaxed border-t border-border pt-3"
>
{tr(
"conceptHowItWorksBody",
"Sua app envia um pedido no formato dela. O Translator detecta o formato, converte via OpenAI como hub intermediário (ou direto, quando há tradutor direto disponível), envia ao provider escolhido e devolve a resposta convertida de volta no formato da sua app.",
)}
</div>
)}
</div>
</Card>
);
}