From 6b35a3bcd35b85030ea7720d9abcff70a1cd80b5 Mon Sep 17 00:00:00 2001 From: AldemirLucas Date: Mon, 25 May 2026 23:42:35 -0300 Subject: [PATCH 1/4] feat: add TypewriterConsole component and multiple FAQ entries for chatbot --- next/components/atoms/TypewriterConsole.js | 84 ++ .../chatbot/FAQ/pt/available-data-chatbot.md | 9 + .../chatbot/FAQ/pt/chatbot-no-answer.md | 9 + .../chatbot/FAQ/pt/free-trial-chatbot.md | 9 + .../FAQ/pt/how-download-response-data.md | 9 + .../FAQ/pt/programming-sql-not-required.md | 9 + .../FAQ/pt/sql-bigquery-download-steps.md | 20 + .../FAQ/pt/technical-support-chatbot.md | 9 + .../chatbot/FAQ/pt/use-results-own-reports.md | 9 + .../chatbot/FAQ/pt/what-is-bd-chatbot.md | 9 + .../chatbot/FAQ/pt/why-beta-version.md | 9 + next/pages/chatbot-lp.js | 874 ++++++++++++++++++ next/pages/user/login.js | 1 - next/public/locales/en/chatbot.json | 61 ++ next/public/locales/es/chatbot.json | 61 ++ next/public/locales/pt/chatbot.json | 61 ++ next/styles/typewriterConsole.module.css | 52 ++ 17 files changed, 1294 insertions(+), 1 deletion(-) create mode 100644 next/components/atoms/TypewriterConsole.js create mode 100644 next/content/chatbot/FAQ/pt/available-data-chatbot.md create mode 100644 next/content/chatbot/FAQ/pt/chatbot-no-answer.md create mode 100644 next/content/chatbot/FAQ/pt/free-trial-chatbot.md create mode 100644 next/content/chatbot/FAQ/pt/how-download-response-data.md create mode 100644 next/content/chatbot/FAQ/pt/programming-sql-not-required.md create mode 100644 next/content/chatbot/FAQ/pt/sql-bigquery-download-steps.md create mode 100644 next/content/chatbot/FAQ/pt/technical-support-chatbot.md create mode 100644 next/content/chatbot/FAQ/pt/use-results-own-reports.md create mode 100644 next/content/chatbot/FAQ/pt/what-is-bd-chatbot.md create mode 100644 next/content/chatbot/FAQ/pt/why-beta-version.md create mode 100644 next/pages/chatbot-lp.js create mode 100644 next/public/locales/en/chatbot.json create mode 100644 next/public/locales/es/chatbot.json create mode 100644 next/public/locales/pt/chatbot.json create mode 100644 next/styles/typewriterConsole.module.css diff --git a/next/components/atoms/TypewriterConsole.js b/next/components/atoms/TypewriterConsole.js new file mode 100644 index 00000000..b70acdb6 --- /dev/null +++ b/next/components/atoms/TypewriterConsole.js @@ -0,0 +1,84 @@ +import { useState, useEffect, useRef } from "react"; +import { Flex, Box } from "@chakra-ui/react"; +import ArrowIcon from "../../public/img/icons/arrowIcon"; +import Button from "../atoms/Button"; +import styles from "../../styles/typewriterConsole.module.css"; + +export default function TypewriterConsole({ + messages = [], + typingSpeed = 45, + deletingSpeed = 25, + pauseDuration = 2000, + maxWidth = "600px", + textBtn="", + onClickBtn=() => {}, + isVariantBtn=false, + targetBtn="_self", + propsBtn={}, +}) { + const [displayText, setDisplayText] = useState(""); + const messageIndexRef = useRef(0); + const isDeletingRef = useRef(false); + const charIndexRef = useRef(0); + const timeoutRef = useRef(null); + + useEffect(() => { + if (!messages.length) return; + + const tick = () => { + const currentMessage = messages[messageIndexRef.current]; + const isDeleting = isDeletingRef.current; + + if (!isDeleting) { + if (charIndexRef.current < currentMessage.length) { + charIndexRef.current += 1; + setDisplayText(currentMessage.slice(0, charIndexRef.current)); + timeoutRef.current = setTimeout(tick, typingSpeed); + } else { + timeoutRef.current = setTimeout(() => { + isDeletingRef.current = true; + tick(); + }, pauseDuration); + } + } else if (charIndexRef.current > 0) { + charIndexRef.current -= 1; + setDisplayText(currentMessage.slice(0, charIndexRef.current)); + timeoutRef.current = setTimeout(tick, deletingSpeed); + } else { + isDeletingRef.current = false; + messageIndexRef.current = (messageIndexRef.current + 1) % messages.length; + timeoutRef.current = setTimeout(tick, typingSpeed); + } + }; + + timeoutRef.current = setTimeout(tick, typingSpeed); + + return () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + }; + }, [messages, typingSpeed, deletingSpeed, pauseDuration]); + + return ( + + ); +} diff --git a/next/content/chatbot/FAQ/pt/available-data-chatbot.md b/next/content/chatbot/FAQ/pt/available-data-chatbot.md new file mode 100644 index 00000000..f9cb7ebf --- /dev/null +++ b/next/content/chatbot/FAQ/pt/available-data-chatbot.md @@ -0,0 +1,9 @@ +--- +question: Quais dados estão disponíveis para consulta? +categories: [] +keywords: [] +order: 3 +id: available-data-chatbot +--- + +O chatbot está conectado a mais de 740 tabelas que compõem o datalake público da BD. É uma fonte preciosa e em constante expansão, com dados sobre temas diversos: economia (CNPJ, RAIS, Caged), saúde (PNS, SINAN), educação (SAEB, Censo Educacional), demografia (Censo 2022, PNAD), meio ambiente e muito mais. diff --git a/next/content/chatbot/FAQ/pt/chatbot-no-answer.md b/next/content/chatbot/FAQ/pt/chatbot-no-answer.md new file mode 100644 index 00000000..369bc96b --- /dev/null +++ b/next/content/chatbot/FAQ/pt/chatbot-no-answer.md @@ -0,0 +1,9 @@ +--- +question: O que fazer se o chatbot não encontrar uma resposta? +categories: [] +keywords: [] +order: 9 +id: chatbot-no-answer +--- + +Para perguntas complexas, recomendamos dividir o pedido em perguntas menores e mais simples. Outra dica útil é mencionar o nome específico de uma coluna (como "município") ou pedir explicitamente para o chatbot buscar em um conjunto de dados que você já conheça. diff --git a/next/content/chatbot/FAQ/pt/free-trial-chatbot.md b/next/content/chatbot/FAQ/pt/free-trial-chatbot.md new file mode 100644 index 00000000..ee580ae3 --- /dev/null +++ b/next/content/chatbot/FAQ/pt/free-trial-chatbot.md @@ -0,0 +1,9 @@ +--- +question: Posso testar o chatbot gratuitamente? +categories: [] +keywords: [] +order: 5 +id: free-trial-chatbot +--- + +Sim. Oferecemos um período de teste gratuito de 7 dias para você conhecer a ferramenta. Após esse período, o chatbot faz parte de um plano de assinatura que garante perguntas ilimitadas e nos ajuda a manter nosso datalake público gratuito. diff --git a/next/content/chatbot/FAQ/pt/how-download-response-data.md b/next/content/chatbot/FAQ/pt/how-download-response-data.md new file mode 100644 index 00000000..c4122c7c --- /dev/null +++ b/next/content/chatbot/FAQ/pt/how-download-response-data.md @@ -0,0 +1,9 @@ +--- +question: Como posso baixar os dados da resposta? +categories: [] +keywords: [] +order: 6 +id: how-download-response-data +--- + +Toda análise do chatbot é acompanhada por uma consulta SQL. Você pode copiar esse código e utilizá-lo no BigQuery (ferramenta do Google) para extrair a tabela completa e criar suas próprias visualizações ou relatórios. diff --git a/next/content/chatbot/FAQ/pt/programming-sql-not-required.md b/next/content/chatbot/FAQ/pt/programming-sql-not-required.md new file mode 100644 index 00000000..c35d6e9e --- /dev/null +++ b/next/content/chatbot/FAQ/pt/programming-sql-not-required.md @@ -0,0 +1,9 @@ +--- +question: Preciso saber programar ou conhecer SQL para usar o Chatbot? +categories: [] +keywords: [] +order: 2 +id: programming-sql-not-required +--- + +Não. O Chatbot foi desenhado para que qualquer pessoa explore dados públicos usando linguagem natural. O próprio processo de extrair os dados utilizados em uma resposta não exige conhecimento prévio de SQL: o chatbot gera a consulta e permite que você vá direto aos dados de que precisa. diff --git a/next/content/chatbot/FAQ/pt/sql-bigquery-download-steps.md b/next/content/chatbot/FAQ/pt/sql-bigquery-download-steps.md new file mode 100644 index 00000000..097caf14 --- /dev/null +++ b/next/content/chatbot/FAQ/pt/sql-bigquery-download-steps.md @@ -0,0 +1,20 @@ +--- +question: Como usar a consulta SQL para baixar os dados da resposta? +categories: [] +keywords: [] +order: 8 +id: sql-bigquery-download-steps +--- + +O Chatbot sempre devolve uma consulta SQL junto com a resposta. Você pode copiar essa consulta e usar o [Google BigQuery](https://cloud.google.com/bigquery) — ferramenta de consulta e visualização de dados do Google — para extrair uma tabela com os dados utilizados na análise. + +Para isso, siga estes passos: + +1. Acesse o site do [Google BigQuery](https://cloud.google.com/bigquery). +2. Se for direcionado à página inicial do produto, clique em algo como **Teste no Console** ou **Console** para abrir a interface de consultas. +3. Faça login com uma conta Google e crie um projeto, se ainda não tiver um. +4. Depois de criar o projeto, abra **Consulta SQL** (ou equivalente no console) para acessar o editor. +5. Cole a consulta fornecida pelo chatbot e execute. +6. Após executar, você pode salvar os resultados no formato preferido ou abrir com outras ferramentas do Google para visualização. + +Se tiver problema nesse fluxo, nossa equipe e comunidade podem ajudar no [Discord da Base dos Dados](https://discord.gg/huKWpsVYx4). diff --git a/next/content/chatbot/FAQ/pt/technical-support-chatbot.md b/next/content/chatbot/FAQ/pt/technical-support-chatbot.md new file mode 100644 index 00000000..88c60342 --- /dev/null +++ b/next/content/chatbot/FAQ/pt/technical-support-chatbot.md @@ -0,0 +1,9 @@ +--- +question: Existe algum suporte técnico? +categories: [] +keywords: [] +order: 7 +id: technical-support-chatbot +--- + +Sim. Assinantes contam com suporte prioritário por e-mail e Discord. Você também pode enviar dúvidas e sugestões diretamente para nossa comunidade e equipe técnica. diff --git a/next/content/chatbot/FAQ/pt/use-results-own-reports.md b/next/content/chatbot/FAQ/pt/use-results-own-reports.md new file mode 100644 index 00000000..954a881f --- /dev/null +++ b/next/content/chatbot/FAQ/pt/use-results-own-reports.md @@ -0,0 +1,9 @@ +--- +question: Posso usar os resultados em meus próprios relatórios e gráficos? +categories: [] +keywords: [] +order: 4 +id: use-results-own-reports +--- + +Sim. Além da análise em texto, você recebe o código para baixar os dados via BigQuery. Isso dá flexibilidade para você levar as informações para o Excel, Google Sheets ou ferramentas de visualização e criar seus próprios mapas e dashboards. diff --git a/next/content/chatbot/FAQ/pt/what-is-bd-chatbot.md b/next/content/chatbot/FAQ/pt/what-is-bd-chatbot.md new file mode 100644 index 00000000..6e4ae665 --- /dev/null +++ b/next/content/chatbot/FAQ/pt/what-is-bd-chatbot.md @@ -0,0 +1,9 @@ +--- +question: O que é o Chatbot da BD? +categories: [] +keywords: [] +order: 1 +id: what-is-bd-chatbot +--- + +É uma interface de consulta que utiliza linguagem natural para que qualquer pessoa explore o datalake público da Base dos Dados. Ele utiliza o modelo Gemini, do Google, para transformar suas perguntas em consultas de dados precisas. diff --git a/next/content/chatbot/FAQ/pt/why-beta-version.md b/next/content/chatbot/FAQ/pt/why-beta-version.md new file mode 100644 index 00000000..b43def4f --- /dev/null +++ b/next/content/chatbot/FAQ/pt/why-beta-version.md @@ -0,0 +1,9 @@ +--- +question: Por que a ferramenta está em versão Beta? +categories: [] +keywords: [] +order: 10 +id: why-beta-version +--- + +A versão Beta significa que o chatbot está em constante aprimoramento. Por isso, seu feedback pelos botões de curtir e descurtir (👍/👎) em cada resposta é fundamental para calibrarmos o modelo e melhorarmos a precisão dos resultados. diff --git a/next/pages/chatbot-lp.js b/next/pages/chatbot-lp.js new file mode 100644 index 00000000..b8d28c32 --- /dev/null +++ b/next/pages/chatbot-lp.js @@ -0,0 +1,874 @@ +import { useState, useRef, useEffect } from "react"; +import { useRouter } from "next/router"; +import Link from "next/link"; +import cookies from "js-cookie"; +import { + Box, + Stack, + VStack, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + Text, + Skeleton, +} from "@chakra-ui/react"; +import Head from "next/head"; +import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; +import { useTranslation } from 'next-i18next'; +import { MainPageTemplate } from "../components/templates/main"; +import { withPages } from "../hooks/pages.hook"; +import { isMobileMod } from "../hooks/useCheckMobile.hook"; +import { triggerGAEventWithData } from "../utils"; + +import { getAllFAQs } from "./api/faqs"; + +import Display from "../components/atoms/Text/Display"; +import TitleText from "../components/atoms/Text/TitleText"; +import BodyText from "../components/atoms/Text/BodyText"; +import LabelText from "../components/atoms/Text/LabelText"; +import QuestionsBox from "../components/molecules/QuestionsBox"; +import TypewriterConsole from "../components/atoms/TypewriterConsole"; +import Button from "../components/atoms/Button"; +import Toggle from "../components/atoms/Toggle"; +import CheckIcon from "../public/img/icons/checkIcon"; + +export async function getStaticProps({ locale }) { + const faqs = await getAllFAQs(locale, "chatbot/FAQ"); + const pagesProps = await withPages(); + return { + props: { + ...pagesProps, + ...(await serverSideTranslations(locale, ["common", "menu", "chatbot", "prices"])), + faqs, + }, + revalidate: 30, + }; +} + +function filterChatbotPlans(edges) { + return edges.filter((item) => { + const name = item.node.productName?.toLowerCase() || ""; + const slug = item.node.productSlug?.toLowerCase() || ""; + const isConsumerChatbot = + (name.includes("chatbot") || slug.includes("chatbot")) && + !name.includes("empresas"); + return ( + isConsumerChatbot && + item.node.isActive === true + ); + }); +} + +async function fetchChatbotPlans() { + const result = await fetch("/api/stripe/getPlans", { method: "GET" }).then( + (res) => res.json() + ); + + if (!result.success) return null; + + const chatbotPlans = filterChatbotPlans(result.data); + + function findByInterval(interval, amount) { + return ( + chatbotPlans.find( + (item) => + item.node.interval === interval && + item.node.amount === amount + )?.node ?? null + ); + } + + return { + month: findByInterval("month", 30), + year: findByInterval("year", 326), + }; +} + +function ChatbotPricingCardSkeleton() { + const skeletonProps = { + startColor: "#F0F0F0", + endColor: "#F3F3F3", + borderRadius: "6px", + }; + + return ( + + + + + + + + + + + + + + + + + {[0, 1, 2, 3].map((i) => ( + + + + + ))} + + + + + + + + ); +} + +function ChatbotPricingCard({ highlightedFeature }) { + const { t } = useTranslation(["chatbot", "prices"]); + const router = useRouter(); + const { locale } = router; + + const [toggleAnual, setToggleAnual] = useState(true); + const [plans, setPlans] = useState(null); + const [username, setUsername] = useState(null); + const [hasSubscribed, setHasSubscribed] = useState(true); + const [isBDChatbot, setIsBDChatbot] = useState({ isCurrentPlan: false }); + const [isLoading, setIsLoading] = useState(true); + + const features = t("plans.chatbot.features", { + returnObjects: true, + ns: "prices", + }); + + const selectedPlan = toggleAnual ? plans?.year : plans?.month; + const totalPrice = selectedPlan?.amount ?? (toggleAnual ? 326 : 30); + const displayPrice = toggleAnual + ? Math.ceil(totalPrice / 12) + : totalPrice; + + const planInterval = toggleAnual ? "year" : "month"; + const isCurrentPlan = + isBDChatbot.isCurrentPlan && isBDChatbot.planInterval === planInterval; + + useEffect(() => { + async function loadData() { + setIsLoading(true); + + let user = null; + if (cookies.get("userBD")) { + user = JSON.parse(cookies.get("userBD")); + } + + const promises = [fetchChatbotPlans()]; + + if (user) { + const reg = new RegExp("(?<=:).*"); + const match = reg.exec(user.id); + if (match) { + const [id] = match; + promises.push( + fetch(`/api/user/getAlreadySubscribed?p=${btoa(id)}`) + .then((res) => res.json()) + .then(setHasSubscribed) + .catch(() => setHasSubscribed(false)) + ); + } else { + setHasSubscribed(false); + } + + const internalNodes = + user?.internalSubscription?.edges?.map((e) => e?.node) || []; + const chatbotNode = internalNodes.find((n) => + (n?.stripeSubscription || "").toLowerCase().includes("chatbot") + ); + + setUsername(user?.username); + setIsBDChatbot({ + isCurrentPlan: !!chatbotNode, + planInterval: chatbotNode?.planInterval, + }); + } else { + setHasSubscribed(false); + } + + const [chatbotPlans] = await Promise.all(promises); + if (chatbotPlans) setPlans(chatbotPlans); + setIsLoading(false); + } + + loadData(); + }, []); + + const handleSubscribe = () => { + triggerGAEventWithData("bd_chatbot_card_price", { + plan_interval: planInterval, + is_free_trial: !hasSubscribed, + source: "chatbot_lp", + }); + + if (selectedPlan?._id) { + cookies.set("plan_selected", selectedPlan._id, { expires: 1, path: "/" }); + } + + if (username === null) { + router.push("/user/login"); + return; + } + + router.push(`/user/${username}?plans_and_payment`); + }; + + const buttonLabel = isCurrentPlan + ? t("currentPlan", { ns: "prices" }) + : hasSubscribed + ? t("subscribe", { ns: "prices" }) + : t("startFreeTrial", { ns: "prices" }); + + if (isLoading) { + return ; + } + + return ( + + + setToggleAnual(!toggleAnual)} + /> + + {t("annualDiscount", { ns: "prices" })} + + {t("save20", { ns: "prices" })} + + + + + + {t("pricingCard.title")} + + + + + R$ {displayPrice} + + {t("perMonth", { ns: "prices" })} + + + + {toggleAnual && + t("annualBillingMessage", { + ns: "prices", + price: totalPrice.toLocaleString("pt-BR", { + style: "currency", + currency: "BRL", + minimumFractionDigits: 0, + }), + })} + + + + + {features.map((feature, index) => { + const isHighlighted = highlightedFeature === index; + + return ( + + + + {feature} + + + ); + })} + + + + {isCurrentPlan ? ( + + {buttonLabel} + + ) : ( + + {buttonLabel} + + )} + + + {t("readThe", { ns: "prices" })} + + + {t("termsOfService", { ns: "prices" })} + + + + + + ); +} + +function WhyChatbotSection() { + const { t, ready } = useTranslation("chatbot"); + const [activeIndex, setActiveIndex] = useState(0); + const isMobile = isMobileMod(); + + if (!ready) return null; + + let items = t("why.items", { returnObjects: true }); + if (!Array.isArray(items) && items && typeof items === "object") { + items = Object.values(items); + } + if (!Array.isArray(items)) return null; + + return ( + + + {t("why.title")} + + + + + setActiveIndex(index)} + width="100%" + > + {items.map((elm, index) => ( + + + + {elm.title} + + + + + + + + + {elm.content} + + + + ))} + + + + + + + + + ); +} + +function clearPresentationHashFromUrl() { + window.history.replaceState( + null, + "", + `${window.location.pathname}${window.location.search}` + ); +} + +function scrollToPresentation() { + const el = document.getElementById("presentation"); + if (!el) return; + + el.scrollIntoView({ behavior: "smooth", block: "start" }); + window.history.replaceState(null, "", `#${"presentation"}`); + + window.setTimeout(clearPresentationHashFromUrl, 1500); +} + +function VideoPlayer({ src }) { + const videoRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const video = videoRef.current; + if (!video) return; + + const observer = new IntersectionObserver( + ([entry]) => { + setIsVisible(entry.isIntersecting); + }, + { threshold: 0.1 } + ); + + observer.observe(video); + + return () => { + observer.unobserve(video); + }; + }, []); + + useEffect(() => { + const video = videoRef.current; + if (!video) return; + + if (isVisible) { + video.play().catch((error) => { + console.error("Video play failed:", error); + }); + } else { + video.pause(); + } + }, [isVisible]); + + return ( + + ); +} + +function Hero({ t }) { + const router = useRouter(); + const prompts = t("hero.prompts", { returnObjects: true }); + + return ( + + + + + {t("hero.title.prefix")}{" "} + + {t("hero.title.highlight1")} + {" "} + {t("hero.title.middle")}{" "} + + {t("hero.title.highlight2")} + {" "} + {t("hero.title.suffix")} + + router.push("/chatbot")} + targetBtn="_blank" + /> + + + + ); +} + +function Presentation({ t }) { + const isMobile = isMobileMod(); + + return ( + + + {t("presentation.title")} + + + + {t("presentation.description")} + + + + + + + ); +} + +function FAQ({ t, faqs }) { + const isMobile = isMobileMod(); + + return ( + + + {t('faq.title')} + + + + {faqs && faqs.map((elm, i) => ( + + ))} + + + ); +} + +export default function ChatbotLPPage({ faqs }) { + const { t } = useTranslation(['chatbot', 'prices']); + + useEffect(() => { + if ( + typeof window === "undefined" || + window.location.hash !== `#${"presentation"}` + ) { + return; + } + + const el = document.getElementById("presentation"); + if (el) { + el.scrollIntoView({ behavior: "smooth", block: "start" }); + } + + const timeout = window.setTimeout(clearPresentationHashFromUrl, 1500); + + return () => window.clearTimeout(timeout); + }, []); + + return ( + + + {t('head.pageTitle')} + + + + + + + + ); +} diff --git a/next/pages/user/login.js b/next/pages/user/login.js index 252ca60c..dfd2fd6b 100644 --- a/next/pages/user/login.js +++ b/next/pages/user/login.js @@ -43,7 +43,6 @@ export async function getStaticProps({ locale }) { export default function Login() { const router = useRouter(); const { t } = useTranslation('user'); - const { query } = useRouter() const [formData, setFormData] = useState({ email: "", password: "" }) const [errors, setErrors] = useState({ email: "", password: "", login: ""}) const [showPassword, setShowPassword] = useState(true) diff --git a/next/public/locales/en/chatbot.json b/next/public/locales/en/chatbot.json new file mode 100644 index 00000000..0fd1c36d --- /dev/null +++ b/next/public/locales/en/chatbot.json @@ -0,0 +1,61 @@ +{ + "head": { + "pageTitle": "Chatbot – Data Basis", + "metaTitle": "Chatbot – Data Basis" + }, + "hero": { + "title": { + "prefix": "Talk to", + "highlight1": "the largest collection", + "middle": "of", + "highlight2": "public data", + "suffix": "in Brazil." + }, + "prompts": [ + "What is the gender pay gap in São Paulo?", + "Help me with analysis ideas using SAEB data", + "How many active companies are there in Serra da Saudade (MG)?", + "Who received the most votes for president in the Itaquera neighborhood in 2022?" + ], + "buttonPrompt": { + "text": "Get started" + }, + "buttonScroll": { + "text": "Demo" + } + }, + "presentation": { + "title": "How does our product work?", + "description": "Discover the AI tool that helps you create analyses in seconds and guides you through the entire Data Basis public datalake" + }, + "why": { + "title": "Why will the Data Basis Chatbot revolutionize the way you work with data?", + "items": [ + { + "title": "Access to Brazil's Largest Public Datalake", + "content": "Browse terabytes of data across more than 740 tables on diverse topics (education, economy, health, labor market, demography, environment).", + "highlightFeature": 0 + }, + { + "title": "Advanced AI Engine with Unlimited Questions", + "content": "Powered by Gemini, Google's state-of-the-art AI model, to turn as many questions as you want into precise queries.", + "highlightFeature": 1 + }, + { + "title": "Natural Language Interface", + "content": "Lets anyone explore the Data Basis public datalake through simple questions, with no programming required." + }, + { + "title": "Transparency and Flexibility", + "content": "Including the SQL query lets you validate insights and download data to build your own visualizations and reports in other tools.", + "highlightFeature": 2 + } + ] + }, + "pricingCard": { + "title": "Try it free and create analyses in seconds with the power of AI" + }, + "faq": { + "title": "Frequently Asked Questions – FAQ" + } +} diff --git a/next/public/locales/es/chatbot.json b/next/public/locales/es/chatbot.json new file mode 100644 index 00000000..072a555e --- /dev/null +++ b/next/public/locales/es/chatbot.json @@ -0,0 +1,61 @@ +{ + "head": { + "pageTitle": "Chatbot – Base de los Datos", + "metaTitle": "Chatbot – Base de los Datos" + }, + "hero": { + "title": { + "prefix": "Conversa con el", + "highlight1": "mayor acervo", + "middle": "de", + "highlight2": "datos públicos", + "suffix": "de Brasil." + }, + "prompts": [ + "¿Cuál es la disparidad salarial por género en São Paulo?", + "Ayúdame con ideas de análisis con los datos del SAEB", + "¿Cuántas empresas activas hay en Serra da Saudade (MG)?", + "¿Quién recibió más votos para presidente en el barrio de Itaquera en 2022?" + ], + "buttonPrompt": { + "text": "Empezar ya" + }, + "buttonScroll": { + "text": "Demostración" + } + }, + "presentation": { + "title": "¿Cómo funciona nuestro producto?", + "description": "Conoce la herramienta de IA que te ayudará a crear análisis en segundos y te guiará por todo el datalake público de Base de los Datos" + }, + "why": { + "title": "¿Por qué el Chatbot de Base de los Datos revolucionará tu trabajo con datos?", + "items": [ + { + "title": "Acceso al mayor datalake público de Brasil", + "content": "Navega por terabytes de datos en más de 740 tablas sobre diversos temas (educación, economía, salud, mercado de trabajo, demografía, medio ambiente).", + "highlightFeature": 0 + }, + { + "title": "Motor de IA avanzado con preguntas ilimitadas", + "content": "Utiliza Gemini, el modelo de inteligencia artificial de última generación de Google, para transformar todas las preguntas que quieras en consultas precisas.", + "highlightFeature": 1 + }, + { + "title": "Interfaz de lenguaje natural", + "content": "Permite que cualquier persona explore el datalake público de Base de los Datos mediante preguntas simples, sin lenguaje de programación." + }, + { + "title": "Transparencia y flexibilidad", + "content": "La inclusión de la consulta SQL permite validar los insights y descargar los datos para crear tus propias visualizaciones e informes en otras herramientas.", + "highlightFeature": 2 + } + ] + }, + "pricingCard": { + "title": "Pruébalo gratis y crea análisis en segundos con el poder de la IA" + }, + "faq": { + "title": "Preguntas frecuentes – FAQ" + } +} diff --git a/next/public/locales/pt/chatbot.json b/next/public/locales/pt/chatbot.json new file mode 100644 index 00000000..52589eec --- /dev/null +++ b/next/public/locales/pt/chatbot.json @@ -0,0 +1,61 @@ +{ + "head": { + "pageTitle": "Chatbot LP - Base dos Dados", + "metaTitle": "Chatbot LP - Base dos Dados" + }, + "hero": { + "title": { + "prefix": "Converse com o", + "highlight1": "maior acervo", + "middle": "de", + "highlight2": "dados públicos", + "suffix": "do Brasil." + }, + "prompts": [ + "Qual é a disparidade salarial por gênero em São Paulo?", + "Me ajude com ideias de análises com os dados do SAEB", + "Quantas empresas ativas existem em Serra da Saudade (MG)?", + "Quem teve mais votos para presidente no bairro de Itaquera em 2022?" + ], + "buttonPrompt": { + "text": "Começar já" + }, + "buttonScroll": { + "text": "Demonstração" + } + }, + "presentation": { + "title": "Como funciona nosso produto?", + "description": "Conheça a ferramenta de IA que vai te ajudar a criar análises em segundos e te guiar por todo o datalake público da BD" + }, + "why": { + "title": "Por que o Chatbot da BD vai revolucionar seu trabalho com dados?", + "items": [ + { + "title": "Acesso ao Maior Datalake Público do Brasil", + "content": "Navega por terabytes de dados em mais de 740 tabelas sobre diversos temas (educação, economia, saúde, mercado de trabalho, demografia, meio ambiente).", + "highlightFeature": 0 + }, + { + "title": "Motor de IA Avançado com perguntas ilimitadas", + "content": "Utiliza o Gemini, modelo de inteligência artificial de última geração do Google, para transformar quantas perguntas quiser em consultas precisas.", + "highlightFeature": 1 + }, + { + "title": "Interface de Linguagem Natural", + "content": "Permite que qualquer pessoa explore o datalake público da BD Brasil através de perguntas simples, sem linguagem de programação." + }, + { + "title": "Transparência e Flexibilidade", + "content": "A inclusão da consulta SQL permite que o usuário valide os insights e baixe os dados para criar suas próprias visualizações e relatórios em outras ferramentas.", + "highlightFeature": 2 + } + ] + }, + "pricingCard": { + "title": "Teste gratuitamente e crie análises em segundos com o poder da IA" + }, + "faq": { + "title": "Dúvidas Frequentes - FAQ" + } +} diff --git a/next/styles/typewriterConsole.module.css b/next/styles/typewriterConsole.module.css new file mode 100644 index 00000000..54476ce7 --- /dev/null +++ b/next/styles/typewriterConsole.module.css @@ -0,0 +1,52 @@ +.console { + position: relative; + width: 100%; + height: 180px; + border-radius: 14px; + background-color: #FFFFFF; + padding: 32px 32px 70px; + box-shadow: 0px 1.6px 16px rgba(100, 96, 103, 0.16); +} + +.text { + flex: 1; + min-width: 0; + min-height: 38px; + font-size: 16px; + line-height: 24px; + font-family: Roboto, sans-serif; + font-weight: 400; + color: #252A32; + text-align: left; +} + +.cursor { + display: inline-block; + width: 2px; + height: 1em; + background-color: #252A32; + margin-left: 1px; + vertical-align: text-bottom; + animation: blink 1s step-end infinite; +} + +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0; + } +} + +@media (max-width: 768px) { + .text { + font-size: 14px; + line-height: 20px; + min-height: 26px; + text-align: left; + white-space: normal; + } +} From 94a1ab99547f65d902a6451c0170d0546e5efb2a Mon Sep 17 00:00:00 2001 From: AldemirLucas Date: Wed, 27 May 2026 15:38:15 -0300 Subject: [PATCH 2/4] feat: implement chatbot plan fetching and redirection logic, enhance footer and menu with chatbot links, and update localization for chatbot --- next/components/molecules/Footer.js | 3 + next/components/molecules/Menu.js | 30 ++++++-- .../componentsUserPage/PlansAndPayment.js | 11 +++ next/pages/bdpro.js | 6 +- next/pages/chatbot.js | 3 +- next/public/locales/en/common.json | 3 +- next/public/locales/en/menu.json | 3 +- next/public/locales/es/common.json | 3 +- next/public/locales/es/menu.json | 3 +- next/public/locales/pt/common.json | 3 +- next/public/locales/pt/menu.json | 3 +- next/utils.js | 69 +++++++++++++++++++ 12 files changed, 124 insertions(+), 16 deletions(-) diff --git a/next/components/molecules/Footer.js b/next/components/molecules/Footer.js index fcaae41c..5d331fb1 100644 --- a/next/components/molecules/Footer.js +++ b/next/components/molecules/Footer.js @@ -216,6 +216,9 @@ export default function Footer({ template, ocult = false }) { "/bdpro"}> {t('footer.products.DBPro')} + + {t('footer.products.chatbot')} + {locale === 'pt' && ( {t('footer.products.DBEdu')} diff --git a/next/components/molecules/Menu.js b/next/components/molecules/Menu.js index fab2ea34..ef88ed1f 100644 --- a/next/components/molecules/Menu.js +++ b/next/components/molecules/Menu.js @@ -32,7 +32,7 @@ import { ControlledInputSimple } from "../atoms/ControlledInput"; import Link from "../atoms/Link"; import Button from "../atoms/Button"; import HelpWidget from "../atoms/HelpWidget"; -import { triggerGAEvent, triggerGAEventWithData, hasBDProSubscription, hasChatbotSubscription } from "../../utils"; +import { triggerGAEvent, triggerGAEventWithData, hasBDProSubscription, hasChatbotSubscription, redirectToChatbotCheckout } from "../../utils"; import LabelText from "../atoms/Text/LabelText"; import BodyText from "../atoms/Text/BodyText"; @@ -203,8 +203,8 @@ function MenuDrawer({ userData, isOpen, onClose, links, hasChatbotAccess, isUser fontSize="20px" fontFamily="Roboto" fontWeight="400" - href={hasChatbotAccess ? "/chatbot" : "/prices"} - onClick={() => { + href={hasChatbotAccess ? "/chatbot" : "#"} + onClick={(e) => { onClose(); if (hasChatbotAccess) { trackMenuOpenChatbot({ @@ -214,6 +214,9 @@ function MenuDrawer({ userData, isOpen, onClose, links, hasChatbotAccess, isUser isUserPro, pagePath, }); + } else { + e.preventDefault(); + redirectToChatbotCheckout(router); } }} > @@ -391,8 +394,8 @@ function MenuDrawerUser({ userData, isOpen, onClose, isUserPro, haveInterprisePl gap="6px" color="#71757A" fontWeight="400" - href={hasChatbotAccess ? "/chatbot" : "/prices"} - onClick={() => { + href={hasChatbotAccess ? "/chatbot" : "#"} + onClick={(e) => { onClose() if (hasChatbotAccess) { trackMenuOpenChatbot({ @@ -402,6 +405,9 @@ function MenuDrawerUser({ userData, isOpen, onClose, isUserPro, haveInterprisePl isUserPro, pagePath, }) + } else { + e.preventDefault() + redirectToChatbotCheckout(router) } }} > @@ -991,7 +997,7 @@ function DesktopLinks({ : { type: "button", onClick: () => { - router.push("/prices"); + redirectToChatbotCheckout(router); }, })} minWidth="auto" @@ -1152,6 +1158,10 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } name: [t('exclusive_data')], href: "/bdpro" }, + { + name: [t('chatbot_lp')], + href: "/chatbot-lp" + }, { name: [t('courses')], href: "https://info.basedosdados.org/bd-edu-cursos" @@ -1180,6 +1190,10 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } { name: [t('exclusive_data')], href: "/en/bdpro" + }, + { + name: [t('chatbot_lp')], + href: "/chatbot-lp" } ], [t('resources')]: [ @@ -1202,6 +1216,10 @@ export default function MenuNav({ simpleTemplate = false, userTemplate = false } { name: [t('exclusive_data')], href: "/es/bdpro" + }, + { + name: [t('chatbot_lp')], + href: "/chatbot-lp" } ], [t('resources')]: [ diff --git a/next/components/organisms/componentsUserPage/PlansAndPayment.js b/next/components/organisms/componentsUserPage/PlansAndPayment.js index 7584a085..b8c688ca 100644 --- a/next/components/organisms/componentsUserPage/PlansAndPayment.js +++ b/next/components/organisms/componentsUserPage/PlansAndPayment.js @@ -225,6 +225,17 @@ export default function PlansAndPayment ({ userData }) { } }, [plan, plans, userData, chatbotSubscriptionInfo]) + useEffect(() => { + if (!plans || plan !== "") return + if (query.checkout !== "chatbot") return + if (hasChatbotSubscription(userData)) return + + const planId = plans.bd_chatbot_year?._id + if (planId) { + setPlan(planId) + } + }, [query.checkout, plans, userData, plan]) + useEffect(() => { const planSelected = cookies.get('plan_selected'); if (planSelected && plans) { diff --git a/next/pages/bdpro.js b/next/pages/bdpro.js index fcca41c9..8a51c677 100644 --- a/next/pages/bdpro.js +++ b/next/pages/bdpro.js @@ -481,10 +481,10 @@ function PricingSection({ t }) { textAlign="center" margin="0 auto" > - {t('prices:comparePlans')} + {t("prices:comparePlans")} - - + + ); diff --git a/next/pages/chatbot.js b/next/pages/chatbot.js index 08fe0c6f..7a8723bb 100644 --- a/next/pages/chatbot.js +++ b/next/pages/chatbot.js @@ -15,6 +15,7 @@ import ChatWindow from "../components/organisms/chatbot/ChatWindow"; import Display from "../components/atoms/Text/Display"; import useChatbot from "../hooks/useChatbot"; import { ChatbotProvider } from "../context/ChatbotContext"; +import { redirectToChatbotCheckout } from "../utils"; function getGreetingFirstNameFromCookie() { try { @@ -76,7 +77,7 @@ function ChatbotAccessGate({ children }) { return; } if (!data.has_chatbot_access) { - router.replace("/prices"); + await redirectToChatbotCheckout(router); return; } setCanEnter(true); diff --git a/next/public/locales/en/common.json b/next/public/locales/en/common.json index 72862639..9bdfd4aa 100644 --- a/next/public/locales/en/common.json +++ b/next/public/locales/en/common.json @@ -33,7 +33,8 @@ "publicDatalake": "Public datalake", "dataPackages": "Packages", "DBPro": "DB Pro", - "DBEdu": "DB Edu" + "DBEdu": "DB Edu", + "chatbot": "Chatbot" }, "services": { "title": "SERVICES", diff --git a/next/public/locales/en/menu.json b/next/public/locales/en/menu.json index 5da0430d..c0d1c5b3 100644 --- a/next/public/locales/en/menu.json +++ b/next/public/locales/en/menu.json @@ -34,5 +34,6 @@ "DBPro": "DB Pro", "DBEnterprise": "DB Enterprise", "openChatbot": "Chatbot", - "chatbotNewBadge": "Beta" + "chatbotNewBadge": "Beta", + "chatbot_lp": "Chatbot" } diff --git a/next/public/locales/es/common.json b/next/public/locales/es/common.json index 132608ca..e0a44d16 100644 --- a/next/public/locales/es/common.json +++ b/next/public/locales/es/common.json @@ -33,7 +33,8 @@ "publicDatalake": "Datalake público", "dataPackages": "Paquetes", "DBPro": "BD Pro", - "DBEdu": "BD Edu" + "DBEdu": "BD Edu", + "chatbot": "Chatbot" }, "services": { "title": "SERVICIOS", diff --git a/next/public/locales/es/menu.json b/next/public/locales/es/menu.json index e7e7108f..a3fa7624 100644 --- a/next/public/locales/es/menu.json +++ b/next/public/locales/es/menu.json @@ -34,5 +34,6 @@ "DBPro": "BD Pro", "DBEnterprise": "BD Empresas", "openChatbot": "Chatbot", - "chatbotNewBadge": "Beta" + "chatbotNewBadge": "Beta", + "chatbot_lp": "Chatbot" } diff --git a/next/public/locales/pt/common.json b/next/public/locales/pt/common.json index 5da34631..7b73d28e 100644 --- a/next/public/locales/pt/common.json +++ b/next/public/locales/pt/common.json @@ -33,7 +33,8 @@ "publicDatalake": "Datalake público", "dataPackages": "Pacotes", "DBPro": "BD Pro", - "DBEdu": "BD Edu" + "DBEdu": "BD Edu", + "chatbot": "Chatbot" }, "services": { "title": "SERVIÇOS", diff --git a/next/public/locales/pt/menu.json b/next/public/locales/pt/menu.json index 96759459..bc7c15c3 100644 --- a/next/public/locales/pt/menu.json +++ b/next/public/locales/pt/menu.json @@ -34,5 +34,6 @@ "DBPro": "BD Pro", "DBEnterprise": "BD Empresas", "openChatbot": "Chatbot", - "chatbotNewBadge": "Beta" + "chatbotNewBadge": "Beta", + "chatbot_lp": "Chatbot" } diff --git a/next/utils.js b/next/utils.js index 0a7b9fa1..b712c912 100644 --- a/next/utils.js +++ b/next/utils.js @@ -273,3 +273,72 @@ export function getSubscriptionType(user) { if (user?.isSubscriber) return "unknown" return "none" } + +function filterConsumerChatbotPlans(edges) { + return (edges || []).filter((item) => { + const name = item?.node?.productName?.toLowerCase() || "" + const slug = item?.node?.productSlug?.toLowerCase() || "" + const isConsumerChatbot = + (name.includes("chatbot") || slug.includes("chatbot")) && + !name.includes("empresas") + return isConsumerChatbot && item?.node?.isActive === true + }) +} + +export async function fetchChatbotPlan(interval = "year") { + const amounts = { month: 30, year: 326 } + const amount = amounts[interval] || amounts.year + + try { + const result = await fetch("/api/stripe/getPlans", { method: "GET" }).then((res) => + res.json() + ) + if (!result?.success) return null + + const chatbotPlans = filterConsumerChatbotPlans(result.data) + return ( + chatbotPlans.find( + (item) => item?.node?.interval === interval && item?.node?.amount === amount + )?.node ?? null + ) + } catch { + return null + } +} + +export function getUserFromCookie() { + try { + const raw = cookies.get("userBD") + if (!raw || raw === "undefined") return null + return JSON.parse(raw) + } catch { + return null + } +} + +export async function redirectToChatbotCheckout(router, { interval = "year" } = {}) { + const plan = await fetchChatbotPlan(interval) + + if (plan?._id) { + cookies.set("plan_selected", plan._id, { expires: 1, path: "/" }) + } + + const user = getUserFromCookie() + + if (!user?.username) { + if (typeof window !== "undefined") { + localStorage.setItem("previousPath", window.location.href) + } + return router.push("/user/login") + } + + const query = { plans_and_payment: "" } + if (!plan?._id) { + query.checkout = "chatbot" + } + + return router.push({ + pathname: `/user/${user.username}`, + query, + }) +} From c6ce96693ec6756d7f4cab0196b0e7d8835f86b1 Mon Sep 17 00:00:00 2001 From: AldemirLucas Date: Fri, 29 May 2026 14:09:12 -0300 Subject: [PATCH 3/4] feat: add tracking for chatbot navigation in footer and menu, enhance chatbot access checks, and update localization strings --- next/components/molecules/Footer.js | 14 +++-- next/components/molecules/Menu.js | 49 ++++++++++++++---- next/pages/chatbot-lp.js | 79 +++++++++++++++++++++++++++-- next/public/locales/en/chatbot.json | 3 +- next/public/locales/en/common.json | 2 +- next/public/locales/es/chatbot.json | 3 +- next/public/locales/es/common.json | 2 +- next/public/locales/pt/chatbot.json | 3 +- next/public/locales/pt/common.json | 2 +- next/utils.js | 25 +++++++++ 10 files changed, 159 insertions(+), 23 deletions(-) diff --git a/next/components/molecules/Footer.js b/next/components/molecules/Footer.js index 5d331fb1..1dd35070 100644 --- a/next/components/molecules/Footer.js +++ b/next/components/molecules/Footer.js @@ -8,7 +8,7 @@ import Link from "../atoms/Link"; import { isMobileMod } from "../../hooks/useCheckMobile.hook" import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; -import { triggerGAEvent } from "../../utils"; +import { triggerGAEvent, trackNavigateToChatbotLp } from "../../utils"; import Display from "../atoms/Text/Display"; import LabelText from "../atoms/Text/LabelText"; import BodyText from "../atoms/Text/BodyText"; @@ -84,7 +84,7 @@ function TextFooterSimple({children, ...props}) { export default function Footer({ template, ocult = false }) { const { t } = useTranslation('common'); - const { locale } = useRouter(); + const { locale, pathname: pagePath } = useRouter(); function handlerTriggerEvent(event, value) { triggerGAEvent(event, value) @@ -216,7 +216,15 @@ export default function Footer({ template, ocult = false }) { "/bdpro"}> {t('footer.products.DBPro')} - + trackNavigateToChatbotLp({ + value: "footer", + placement: "footer_products", + pagePath, + isMobile: isMobileMod(), + })} + > {t('footer.products.chatbot')} {locale === 'pt' && ( diff --git a/next/components/molecules/Menu.js b/next/components/molecules/Menu.js index ef88ed1f..230df662 100644 --- a/next/components/molecules/Menu.js +++ b/next/components/molecules/Menu.js @@ -32,7 +32,7 @@ import { ControlledInputSimple } from "../atoms/ControlledInput"; import Link from "../atoms/Link"; import Button from "../atoms/Button"; import HelpWidget from "../atoms/HelpWidget"; -import { triggerGAEvent, triggerGAEventWithData, hasBDProSubscription, hasChatbotSubscription, redirectToChatbotCheckout } from "../../utils"; +import { triggerGAEvent, triggerGAEventWithData, hasBDProSubscription, hasChatbotSubscription, trackNavigateToChatbotLp } from "../../utils"; import LabelText from "../atoms/Text/LabelText"; import BodyText from "../atoms/Text/BodyText"; @@ -68,13 +68,29 @@ function trackMenuOpenChatbot({ }); } -function handleMenuLinkClick(href) { +function navigateToChatbotLp(router, { menuPlacement, pagePath }) { + trackNavigateToChatbotLp({ + value: "menu", + placement: menuPlacement, + pagePath, + }); + router.push("/chatbot-lp"); +} + +function handleMenuLinkClick(href, { pagePath, placement } = {}) { if (href === "/services") { triggerGAEvent("navigating_to_services", "menu"); } if (href === "/search") { triggerGAEvent("navigating_to_data", "menu"); } + if (href === "/chatbot-lp") { + trackNavigateToChatbotLp({ + value: "menu", + placement, + pagePath, + }); + } } function MenuDrawer({ userData, isOpen, onClose, links, hasChatbotAccess, isUserPro }) { @@ -157,7 +173,10 @@ function MenuDrawer({ userData, isOpen, onClose, links, hasChatbotAccess, isUser letterSpacing="0.1px" fontWeight="400" href={c.href} - onClick={() => handleMenuLinkClick(c.href)} + onClick={() => handleMenuLinkClick(c.href, { + pagePath, + placement: "mobile_drawer_solutions", + })} > {c.icon && c.icon} {c.name} @@ -176,7 +195,7 @@ function MenuDrawer({ userData, isOpen, onClose, links, hasChatbotAccess, isUser letterSpacing="0.1px" fontWeight="400" href={elm} - onClick={() => handleMenuLinkClick(elm)} + onClick={() => handleMenuLinkClick(elm, { pagePath, placement: "mobile_drawer" })} > {key} @@ -216,7 +235,10 @@ function MenuDrawer({ userData, isOpen, onClose, links, hasChatbotAccess, isUser }); } else { e.preventDefault(); - redirectToChatbotCheckout(router); + navigateToChatbotLp(router, { + menuPlacement: "mobile_drawer", + pagePath, + }); } }} > @@ -407,7 +429,10 @@ function MenuDrawerUser({ userData, isOpen, onClose, isUserPro, haveInterprisePl }) } else { e.preventDefault() - redirectToChatbotCheckout(router) + navigateToChatbotLp(router, { + menuPlacement: "mobile_drawer_user", + pagePath, + }) } }} > @@ -790,7 +815,10 @@ function DesktopLinks({ href={url} padding="10px 0" gap="16px" - onClick={() => handleMenuLinkClick(url)} + onClick={() => handleMenuLinkClick(url, { + pagePath, + placement: "desktop_solutions_dropdown", + })} onMouseEnter={setFlag.on} onMouseLeave={setFlag.off} > @@ -873,7 +901,7 @@ function DesktopLinks({ fontWeight="400" href={v} target={v.startsWith("https") ? "_blank" : null} - onClick={() => handleMenuLinkClick(v)} + onClick={() => handleMenuLinkClick(v, { pagePath, placement: "desktop" })} > {k} @@ -997,7 +1025,10 @@ function DesktopLinks({ : { type: "button", onClick: () => { - redirectToChatbotCheckout(router); + navigateToChatbotLp(router, { + menuPlacement: "desktop_header_right", + pagePath, + }); }, })} minWidth="auto" diff --git a/next/pages/chatbot-lp.js b/next/pages/chatbot-lp.js index b8d28c32..ecdfd163 100644 --- a/next/pages/chatbot-lp.js +++ b/next/pages/chatbot-lp.js @@ -19,7 +19,7 @@ import { useTranslation } from 'next-i18next'; import { MainPageTemplate } from "../components/templates/main"; import { withPages } from "../hooks/pages.hook"; import { isMobileMod } from "../hooks/useCheckMobile.hook"; -import { triggerGAEventWithData } from "../utils"; +import { triggerGAEventWithData, getUserFromCookie, hasChatbotSubscription } from "../utils"; import { getAllFAQs } from "./api/faqs"; @@ -537,7 +537,12 @@ function WhyChatbotSection() { flexDirection={{ base: "column", lg: "row" }} spacing={0} > - + setActiveIndex(index)} @@ -589,6 +594,8 @@ function WhyChatbotSection() { { + let cancelled = false; + + async function checkAccess() { + const token = cookies.get("token"); + const userRaw = cookies.get("userBD"); + + if (!token || !userRaw || userRaw === "undefined") { + if (!cancelled) setHasChatbotAccess(false); + return; + } + + try { + JSON.parse(userRaw); + } catch { + if (!cancelled) setHasChatbotAccess(false); + return; + } + + try { + const params = new URLSearchParams({ p: btoa(token) }); + const res = await fetch(`/api/user/validateToken?${params}`); + const data = await res.json(); + + if (!cancelled) { + setHasChatbotAccess(Boolean(res.ok && data.success && data.has_chatbot_access)); + } + } catch { + if (!cancelled) { + setHasChatbotAccess(hasChatbotSubscription(getUserFromCookie())); + } + } + } + + checkAccess(); + + return () => { + cancelled = true; + }; + }, []); + + const heroPromptButtonText = hasChatbotAccess + ? t("hero.buttonPrompt.textStart") + : t("hero.buttonPrompt.textSubscribe"); + + const handleHeroPromptClick = () => { + if (hasChatbotAccess) { + router.push("/chatbot"); + return; + } + + scrollToPricing(); + }; return ( router.push("/chatbot")} - targetBtn="_blank" + textBtn={heroPromptButtonText} + onClickBtn={handleHeroPromptClick} + targetBtn={hasChatbotAccess ? "_blank" : "_self"} />