From f0b86641b6bf1e3f341b1d7a9b6e17b60eeb5712 Mon Sep 17 00:00:00 2001 From: developer1054 Date: Fri, 14 Nov 2025 17:17:30 -0300 Subject: [PATCH] integracao-core --- .env.example | 20 +- .env.production | 19 ++ package-lock.json | 9 + package.json | 1 + src/App.tsx | 24 +- src/GlobalFunctions.ts | 286 ++++++++++++++++++++ src/components/FullScreenLoader.tsx | 35 +++ src/components/agents/CreateAgentDialog.tsx | 2 +- src/components/layout/AppLayout.tsx | 17 +- src/components/layout/AppSidebar.tsx | 6 +- src/lib/api/agents.ts | 3 +- src/lib/api/chat.ts | 3 +- src/pages/AgentDetails.tsx | 2 +- src/pages/Agents.tsx | 2 +- src/pages/NotFound.tsx | 11 +- src/pages/Redirect.tsx | 50 ++++ src/pages/RedirectService.ts | 32 +++ src/vite-env.d.ts | 12 + 18 files changed, 517 insertions(+), 17 deletions(-) create mode 100644 .env.production create mode 100644 src/GlobalFunctions.ts create mode 100644 src/components/FullScreenLoader.tsx create mode 100644 src/pages/Redirect.tsx create mode 100644 src/pages/RedirectService.ts diff --git a/.env.example b/.env.example index 84d27d8..ef0e91d 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,19 @@ -VITE_ESTABELECIMENTO_ID=27 -VITE_USER_EMAIL=your-email@example.com +# API Configuration +# Base URL da API do HGTX VITE_API_BASE_URL=https://prod-hgtx-intelligence-n8n.hgtx.com.br +VITE_BASE_URL_HGTX_CORE_API=https://core-api.hgtx.com.br/api/ +VITE_BASE_URL_HGTX_CORE=https://core.hgtx.com.br/ + +# API Key para autenticação +# IMPORTANTE: Mantenha esta chave segura e não compartilhe publicamente +VITE_API_KEY=zp01m-pmn5q-0l2eg-aj5vy + +# User Configuration +# Configure com os dados do usuário/estabelecimento +VITE_USER_EMAIL=suporte@hightechx.com +VITE_ESTABELECIMENTO_ID=27 + +# Instruções: +# 1. Copie este arquivo para .env +# 2. Substitua os valores acima com as configurações reais +# 3. NUNCA commite o arquivo .env no git diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..3ceb625 --- /dev/null +++ b/.env.production @@ -0,0 +1,19 @@ +# API Configuration +# Base URL da API do HGTX +VITE_API_BASE_URL=https://prod-hgtx-intelligence-n8n.hgtx.com.br +VITE_BASE_URL_HGTX_CORE_API=https://core-api.hgtx.com.br/api/ +VITE_BASE_URL_HGTX_CORE=https://core.hgtx.com.br/ + +# API Key para autenticação +# IMPORTANTE: Mantenha esta chave segura e não compartilhe publicamente +VITE_API_KEY=zp01m-pmn5q-0l2eg-aj5vy + +# User Configuration +# Configure com os dados do usuário/estabelecimento +VITE_USER_EMAIL=suporte@hightechx.com +VITE_ESTABELECIMENTO_ID=1 + +# Instruções: +# 1. Copie este arquivo para .env +# 2. Substitua os valores acima com as configurações reais +# 3. NUNCA commite o arquivo .env no git diff --git a/package-lock.json b/package-lock.json index e6188e7..31424b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "date-fns": "^3.6.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", + "jwt-decode": "^4.0.0", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", "react": "^18.3.1", @@ -4828,6 +4829,14 @@ "dev": true, "license": "MIT" }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", diff --git a/package.json b/package.json index 8d78e9b..a2a0a11 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "date-fns": "^3.6.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", + "jwt-decode": "^4.0.0", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", "react": "^18.3.1", diff --git a/src/App.tsx b/src/App.tsx index 830f3a5..35605b9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,8 @@ import AgentDetails from "./pages/AgentDetails"; import TestPrompt from "./pages/TestPrompt"; import History from "./pages/History"; import NotFound from "./pages/NotFound"; +import Redirect from "./pages/Redirect"; +import React from "react"; const queryClient = new QueryClient(); @@ -21,14 +23,26 @@ const App = () => ( - }> - } /> - } /> - } /> - } /> + } + /> + {/* Layout principal para /prompt-labs */} + }> + {/* Página inicial: /prompt-labs */} + } /> + + {/* Rotas internas */} + } /> + } /> + } /> + + {/* Catch-all exclusivo para o contexto de /prompt-labs */} + } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> + } /> diff --git a/src/GlobalFunctions.ts b/src/GlobalFunctions.ts new file mode 100644 index 0000000..71834f3 --- /dev/null +++ b/src/GlobalFunctions.ts @@ -0,0 +1,286 @@ +import { jwtDecode } from 'jwt-decode'; +import { RedirectService } from './pages/RedirectService'; + +export class GlobalFunctions { + + public static getTranferAreaObj(): TransferAreaResponse { + let objRetorno: TransferAreaResponse = { + aplicativoID: 0, + aplicativoNome: "", + areaTransferenciaID: 0, + dataRegistro: "", + estabelecimentoID: 0, + usuarioID: 0, + usuarioEmail: "", + usuarioNome: "", + aplicativoShareCode: "", + nivelAcesso: 0 + }; + try { + const objTransferStr = sessionStorage.getItem("TRANSFER_AREA"); + if ( + objTransferStr != null && + objTransferStr != undefined && + objTransferStr.trim() != "" + ) { + const objTransfer = JSON.parse(objTransferStr); + objRetorno = objTransfer; + } + } catch (error) {} + + return objRetorno; + } + + public static getRedirectComponent(): string { + let objRetorno = ""; + try { + const objTransfer = GlobalFunctions.getTranferAreaObj(); + if (objTransfer != null) { + // let objRedir = objTransfer.find(x => x.name == 'REDIRECT'); + // objRetorno = objRedir?.value ?? ''; + objRetorno = objTransfer.urlDestino ?? ""; + if (objRetorno.startsWith("/")) objRetorno = objRetorno.substring(1); + } else { + objRetorno = ""; + } + } catch (error) { + //console.log(error); + objRetorno = ""; + } + + return objRetorno; + } + + public static getTransferProperty(propertyName: TransferAreaProperties): any { + let objRetorno: any = ""; + try { + const objTransfer = this.getTranferAreaObj(); + if (objTransfer != null) { + //let obj: any = objTransfer[propertyName]; + const element = objTransfer[propertyName]; + objRetorno = element; + } else { + objRetorno = null; + } + } catch (error) { + objRetorno = null; + } + + return objRetorno; + } + + public static isUsuarioLogado(): boolean { + let jsonUsuario = sessionStorage.getItem('usuarioLogado'); + if (jsonUsuario) { + let tokenDecode: any = GlobalFunctions.getUsuarioLogado(); + let d: Date = new Date(); + let dateAtual: number = Date.UTC(d.getFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes() + 5, d.getUTCSeconds()) / 1000; + if (dateAtual > tokenDecode.exp) return false; + return true; + } + return false; + } + + public static tokenExpired(tokenDecode: any): boolean { + let d: Date = new Date(); + let dateAtual: number = Date.UTC(d.getFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes() + 5, d.getUTCSeconds()) / 1000; + if (dateAtual > tokenDecode.exp) return true; + return false; + } + + public static handleNavigateBack(): void { + try { + // Verifica se existe histórico de navegação + if (window.history && window.history.length > 1) { + window.history.back(); + return; + } + + // Tenta fechar a janela + if (!window.closed) { + try { + const windowRef = window.open('', '_self'); + if (windowRef) { + windowRef.close(); + + // Pequeno timeout para verificar se a janela foi fechada + setTimeout(() => { + // Se ainda estiver aberta, redireciona + if (!windowRef.closed) { + window.location.href = "https://core.hgtx.com.br/aplicativos/"; + } + }, 100); + return; + } + } catch (closeError) { + console.warn("Não foi possível fechar a janela:", closeError); + } + } + + // Se não conseguiu fechar, redireciona + window.location.href = "https://core.hgtx.com.br/aplicativos/"; + } catch (error) { + // Em caso de qualquer erro, garante o redirecionamento + console.error("Erro ao tentar navegar:", error); + window.location.href = "https://core.hgtx.com.br/aplicativos/"; + } + } + + public static getUsuarioLogado(): UsuarioLogadoData { + const jsonUsuario = sessionStorage.getItem("usuarioLogado") ?? ""; + try { + const objJwt = JSON.parse(jsonUsuario); + //return jwt_decode(objJwt.token); + return jwtDecode(objJwt.token); + } catch (error) {} + + return { + EID: "0", + email: "", + exp: 0, + iat: 0, + jti: "", + name: "", + nbf: 0, + UID: "0", + }; + } + + public static getUsuarioLogadoToken(): string { + let retorno = ""; + const jsonUsuario = sessionStorage.getItem("usuarioLogado") ?? ""; + try { + const objJwt = JSON.parse(jsonUsuario); + retorno = objJwt.token; + } catch (error) {} + + return retorno; + } + + public static getUsuarioLogadoRefreshToken(): string { + let retorno = ""; + const jsonUsuario = sessionStorage.getItem("usuarioLogado") ?? ""; + try { + const objJwt = JSON.parse(jsonUsuario); + retorno = objJwt.refreshToken; + } catch (error) {} + + return retorno; + } + + public static getPerfilUsuario(): PerfilUsuario | null { + let retorno: PerfilUsuario | null = null; + const jsonUsuario = sessionStorage.getItem("perfilUsuario") ?? ""; + try { + const objJwt = JSON.parse(jsonUsuario); + retorno = objJwt; + } catch (error) { + retorno = null; + } + + return retorno; + } + + public static getHighUserProfile(): number { + let retorno = 6; + const objPerfil = GlobalFunctions.getPerfilUsuario(); + + if (objPerfil) { + const tiposPerfil: number[] = objPerfil.results.map((v) => v.tipoPerfil); + retorno = Math.min(...tiposPerfil); + } + + return retorno; + } + + public static async getToken(): Promise { + let token: string = GlobalFunctions.getUsuarioLogadoToken(); + + const tokenDecode: any = GlobalFunctions.getUsuarioLogado(); + + let currentDate = new Date(); + + // JWT exp is in seconds + if ((tokenDecode.exp * 1000) < currentDate.getTime()) { + //console.log("Token expired."); + let gsikey: number = parseInt(sessionStorage.getItem('gsikey') ?? '0'); + RedirectService.loadTransferArea(gsikey).then((response) => { + if(response.data.errorCode == 0){ + sessionStorage.setItem('TRANSFER_AREA',JSON.stringify(response.data.areTrs)); + sessionStorage.setItem('usuarioLogado',JSON.stringify({ + token: response.data.jwt.token, + refreshToken: response.data.jwt.refreshToken + })); + + token = response.data.jwt.token; + } + // else if(error !== 1401){ + // navigate('/',{replace: true}); + // } + }); + } + // else { + // //console.log("Valid token"); + // result = true; + // } + + return token; + } +} + +export interface UsuarioLogadoData { + UID: string; + EID: string; + name: string; + email: string; + jti: string; + nbf: number; + exp: number; + iat: number; +} + +export interface TransferAreaResponse { + estabelecimentoID: number; + nivelAcesso: number; + aplicativoID: number; + usuarioID: number; + areaTransferenciaID: number; + dataRegistro: string; + urlBase?: string; + urlDestino?: string; + aplicativoNome: string; + usuarioNome: string; + usuarioEmail: string; + aplicativoShareCode: string; +} + +export enum TransferAreaProperties { + EstabelecimentoCodigo = "estabelecimentoID", + UsuarioCodigo = "usuarioID", + UsuarioNome = "usuarioNome", + UsuarioEmail = "usuarioEmail", + APP_COD = "aplicativoID", + APP_TITULO = "aplicativoNome", + areaTransferenciaID = "areaTransferenciaID", + urlBase = "urlBase", + urlDestino = "urlDestino", + aplicativoShareCode = "aplicativoShareCode", + nivelAcesso = "nivelAcesso", +} + +type PerfilUsuario = { + errorCode: number; + errorMessage: string; + results: PerfilUsuarioResult[]; +}; + +export type PerfilUsuarioResult = { + tipoPerfil: number; + itens: PerfilUsuarioItem[]; +}; + +type PerfilUsuarioItem = { + id: number; + descricao: string; + identificador: string; +}; diff --git a/src/components/FullScreenLoader.tsx b/src/components/FullScreenLoader.tsx new file mode 100644 index 0000000..ff4ad25 --- /dev/null +++ b/src/components/FullScreenLoader.tsx @@ -0,0 +1,35 @@ +import { Loader2 } from "lucide-react"; +import { useEffect, useState } from "react"; + +interface FullScreenLoaderProps { + show: boolean; + text?: string; +} + +export default function FullScreenLoader({ show, text = "Carregando..." }: FullScreenLoaderProps) { + const [visible, setVisible] = useState(show); + + useEffect(() => { + if (show) setVisible(true); + else { + // adiciona um pequeno atraso para suavizar o fade-out + const timeout = setTimeout(() => setVisible(false), 200); + return () => clearTimeout(timeout); + } + }, [show]); + + if (!visible && !show) return null; + + return ( +
+
+ +

{text}

+
+
+ ); +} diff --git a/src/components/agents/CreateAgentDialog.tsx b/src/components/agents/CreateAgentDialog.tsx index 2d74988..ee6adc6 100644 --- a/src/components/agents/CreateAgentDialog.tsx +++ b/src/components/agents/CreateAgentDialog.tsx @@ -49,7 +49,7 @@ export function CreateAgentDialog({ open, onOpenChange }: CreateAgentDialogProps onOpenChange(false); // Navegar para a tela de detalhes do novo agente - navigate(`/agents/${data.agent_id}`); + navigate(`/prompt-labs/agents/${data.agent_id}`); // Reset form setName(""); diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index b1d3963..cd76bf4 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -1,8 +1,23 @@ +import React from "react"; import { AppSidebar } from "./AppSidebar"; import { MobileNav } from "./MobileNav"; -import { Outlet } from "react-router-dom"; +import { Outlet, useLocation } from "react-router-dom"; +import { GlobalFunctions } from "@/GlobalFunctions"; export function AppLayout() { + + const { pathname } = useLocation(); + + // ignora layout se for redirecionamento + if (pathname.startsWith("/redir")) { + return <>; + } + + React.useEffect(() => { + console.log('Aqui'); + if(!GlobalFunctions.isUsuarioLogado())window.location.replace(import.meta.env.VITE_BASE_URL_HGTX_CORE); + },[]); + return (
diff --git a/src/components/layout/AppSidebar.tsx b/src/components/layout/AppSidebar.tsx index 54f2b9f..c48a6d5 100644 --- a/src/components/layout/AppSidebar.tsx +++ b/src/components/layout/AppSidebar.tsx @@ -10,10 +10,11 @@ import { import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { ThemeToggle } from "./ThemeToggle"; +import { GlobalFunctions } from "@/GlobalFunctions"; const menuItems = [ - { title: "Agentes", url: "/", icon: Bot }, - { title: "Testar Prompt", url: "/test", icon: TestTube }, + { title: "Agentes", url: "/prompt-labs", icon: Bot }, + { title: "Testar Prompt", url: "/prompt-labs/test", icon: TestTube }, ]; export function AppSidebar() { @@ -134,6 +135,7 @@ export function AppSidebar() {
); diff --git a/src/pages/Redirect.tsx b/src/pages/Redirect.tsx new file mode 100644 index 0000000..e6523df --- /dev/null +++ b/src/pages/Redirect.tsx @@ -0,0 +1,50 @@ + +import { GlobalFunctions } from "@/GlobalFunctions"; +import { RedirectService } from "./RedirectService"; +import React from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import FullScreenLoader from "@/components/FullScreenLoader"; + + + +export default function Redirect() { + + const navigate = useNavigate(); + let { gsikey } = useParams(); + + React.useEffect(() => { + let error: number = 0; + sessionStorage.setItem('gsikey', gsikey ?? '0'); + RedirectService.loadTransferArea(parseInt(gsikey ?? '0')).then((response) => { + console.log(response); + error = response.data.errorCode; + //msg = response.data.errorMessage; + if(response.data.errorCode == 0){ + sessionStorage.setItem('TRANSFER_AREA',JSON.stringify(response.data.areTrs)); + sessionStorage.setItem('usuarioLogado',JSON.stringify({ + token: response.data.jwt.token, + refreshToken: response.data.jwt.refreshToken + })); + } + // else if(error !== 1401){ + // navigate('/',{replace: true}); + // } + }) + .catch((_err) => { + navigate('/404',{replace: true}); + }) + .finally(() => { + if(error == 0 || error === 1401){ + //navigate(`/${GlobalFunctions.getRedirectComponent()}`,{replace: true}); + window.location.replace(`/${GlobalFunctions.getRedirectComponent()}`); + } + // else if(error === 1401){ + // navigate({pathname: '/',search:`?p=${window.btoa(msg)}`},{replace: true}); + // } + }); + }, []); + + return ( + + ); +} diff --git a/src/pages/RedirectService.ts b/src/pages/RedirectService.ts new file mode 100644 index 0000000..b738b8b --- /dev/null +++ b/src/pages/RedirectService.ts @@ -0,0 +1,32 @@ +import { GlobalFunctions, TransferAreaProperties } from "@/GlobalFunctions"; +import axios from "axios"; +const endPoint: string = 'v1/redirect/'; + +export class RedirectService{ + + public static loadTransferArea(gsikey: number): Promise{ + return axios.get(`${import.meta.env.VITE_BASE_URL_HGTX_CORE_API}${endPoint}get_component/${gsikey}/S`); + } + + public static async validaAcessoApp(urlDestino: string): Promise{ + //let token = await GlobalFunctions.getToken(); + let usuLogado = GlobalFunctions.getUsuarioLogado(); + let usuCod: string = GlobalFunctions.getTransferProperty(TransferAreaProperties.UsuarioCodigo); + let estCod: string = usuLogado.EID; + + let urlDestinoPost: string = urlDestino;//urlDestino.trim().length < 1 ? GlobalFunctions.getTransferProperty(TransferAreaProperties.urlDestino) : urlDestino; + if(urlDestinoPost.trim().length < 1){ + try { + let pathSplit = window.location.pathname.split('/'); + + urlDestinoPost = `/${pathSplit[pathSplit.length-2]}/${pathSplit[pathSplit.length-1]}`; + } catch (error) { + } + } + //const heads = { headers: {'Content-Type':'application/json; charset=utf-8','Authorization': `Bearer ${token}`} }; + const heads = { headers: {'Content-Type':'application/json; charset=utf-8'} }; + return axios.post(`${import.meta.env.VITE_BASE_URL_HGTX_CORE_API}v1/aplicativos/validar_acesso_app?usuCod=${usuCod}&estCod=${estCod}`,{ + "urlDestino": urlDestinoPost + },heads); + } +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..337b5b6 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,13 @@ /// +interface ImportMetaEnv { + readonly VITE_API_BASE_URL: string + readonly VITE_API_KEY: string + readonly VITE_USER_EMAIL: string + readonly VITE_ESTABELECIMENTO_ID: string + readonly VITE_BASE_URL_HGTX_CORE_API: string + readonly VITE_BASE_URL_HGTX_CORE: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} \ No newline at end of file