Guia do Framework Curio + React
Configurando o Curio em um Ambiente React
Pré-requisitos
- Node.js (v16 ou superior)
- npm ou yarn (Versao atual da documentacao utilizando yarn)
- React (v16.8 ou superior, com React 18+ recomendado)
Nesse projeto estamos usando as versões mais atuais das ferramentas [ Node 23v, React 19, MaterialUI 7.1.1]
Configuracoes Iniciais
Primeiro configure o yarn para acessar a biblioteca da Evologica no nexus
yarn config set registry https://nexus.dev.evologica.com.br/repository/npm/
- Instale os pacotes necessários:
# Dependências principais
yarn add react react-dom
# Tipagens
yarn add -D @types/react @types/react-dom
# TypeScript
yarn add -D typescript ts-loader
# Babel
yarn add -D @babel/core @babel/preset-env @babel/preset-react babel-loader
# Webpack
yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin clean-webpack-plugin
yarn add -D ejs html-webpack-plugin
yarn add process --dev
# Outros loaders
yarn add -D css-loader style-loader file-loader
# Express (para server.js)
yarn add express
# Ferramentas extra (igual Cognera)
yarn add -D rimraf ts-node
E entao instale as bibliotecas Curio
yarn add @curio/client
- Criando arquivos de configuração:
No diretorio do seu projeto vamos criar 3 arquivos, tsconfig.json, .babelrc, webpack.config.js e uma pasta public na raiz do seu diretorio
Configuracao do arquivo tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"jsx": "react",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src"
]
}
Configuracao do arquivo .babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
Configuracao do arquivo webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: './src/index.tsx',
devtool: 'eval-source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash].js',
publicPath: '/',
},
mode: 'development',
resolve: {
extensions: ['.ts', '.tsx', '.js'],
fallback: {
"process": require.resolve("process/browser")
}
},
devServer: {
historyApiFallback: true,
port: 3000,
static: path.resolve(__dirname, 'public'),
hot: true,
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.ejs',
filename: 'index.html',
title: 'Treinamento React Curio', // Esse valor será usado no <title>
inject: false // Opcional: se quiser controlar os scripts manualmente (como no seu ejs)
}),
new webpack.ProvidePlugin({
process: 'process/browser',
})
]
};
Na Pasta public adicione o arquivo config.json com o modelo de configuracao a baixo:
{
"service": {
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSONBalanced?version=v3",
"server": "192.168.0.59",
"system": "38", //Faz a conexao para o nosso sistema Treinamento
"port": "5370"
},
"accessToken": "3ab910293193f6d5b2d09c368959be9d9cb7ed68_50",
"resources": {
"get": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/getpr",
"put": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/putpr"
},
"logs": true
}
Configurando os Arquivos Core do Curio
Agora no seu diretorio src crie um módulo core dedicado para integração com o Curio. Este servirá como ponto de entrada para toda a funcionalidade relacionada ao Curio em sua aplicação React.
- Crie um arquivo
Session.tscom a classe Session:
// src/curio/Session.ts
import { MainUseCase } from '@curio/client'
export class Session extends MainUseCase {
module: number | undefined
storageToken: string | undefined
}
Esse arquivo vai ser a base para mapear as informacoes de Sessao de usuarios do Curio
- Crie um arquivo
SessionManager.tscom a classe SessionManager:
// src/curio/SessionManager.ts
import { MainUseCase, RequestDriver, SecurityManager, UseCaseMessageType } from '@curio/client'
import { ConnectionError, DestinataryNotFoundError } from '@curio/client/errors'
import { ABORT } from '@curio/client/utils/constants'
import { Session } from './Session'
export interface ConfigService {
url: string
server: string
system: number
port: number
module: number
version: number
}
export interface Service {
service?: string
port: string
server: string
system: string
isapi?: string
}
export const STORAGEKEY = 'br.com.cognera'
export class SessionManager extends SecurityManager {
public session: MainUseCase | undefined
private _service: ConfigService
private _driver: RequestDriver
constructor(service: any, driver: RequestDriver) {
super(service, driver)
this._service = service
this._driver = driver
}
public async openMainUseCase<U extends typeof MainUseCase>(
username: string,
password: string,
constructor?: U
) {
this.session = await super.openMainUseCase(username, password, constructor)
this.session!.addListener(ABORT, () => this._unauthenticate(false))
this.session!.addListener(UseCaseMessageType.RESPONSE, (message: any) => {
if (
message.error instanceof DestinataryNotFoundError ||
message.error instanceof ConnectionError
) {
this._unauthenticate(true)
}
})
return this.session! as InstanceType<U>
}
public connectMainUseCase(mainUseCase: MainUseCase) {
this.session = mainUseCase
this.session.addListener(ABORT, () => this._unauthenticate(false))
this.session.addListener(UseCaseMessageType.RESPONSE, (message: any) => {
if (
message.error instanceof DestinataryNotFoundError ||
message.error instanceof ConnectionError
) {
this._unauthenticate(true)
}
})
return super.connectMainUseCase(mainUseCase)
}
public anonymousSession() {
return new Session(0, this._service, this._driver)
}
private _unauthenticate(expired: boolean) {
this.session = undefined
if (expired) {
localStorage.removeItem(STORAGEKEY)
}
}
}
- Crie um arquivo
index.tspara configuração do uso de SessionManager:
// src/curio/index.ts
import { FetchLink } from '@curio/client/links'
import { V3RequestParser } from '@curio/client/parsers'
import { createV3Validator } from '@curio/client/validators'
import { Session } from './Session'
import { Service as SV, SessionManager } from './SessionManager'
type Service = SV
export interface Config {
service: {
url: string
server: string
system: number
port: number
}
accessToken: string
resources: {
get: string
put: string
viewer: string
}
logs: boolean
}
const createSessionManager = (configuration: Config) => {
const link = new FetchLink(configuration.service)
const requestParser = new V3RequestParser(configuration.service)
return new SessionManager(configuration.service, request => {
return Promise.resolve(request)
.then(requestParser.parse)
.then(JSON.stringify)
.then(link.parse)
.then(link.post)
.then(response => response.json())
.then(json => json)
.then(createV3Validator(request).validate)
})
}
export const getSessionManager = async () => {
const CONFIG_PATH = './config.json'
const config = await (await fetch(CONFIG_PATH)).json()
const sessionManager = createSessionManager(config)
return sessionManager
}
export { Service, Session }
Integrando com React
Agora, com a nossa conexao ao Curio configurada precisamos implementar um metodo para criar a Sessao referente ao nosso usuario.
- Crie uma pasta
apino seu diretoriosrce nela um arquivosession.ts
import { ConnectionError, DestinataryNotFoundError } from '@curio/client/errors'
import { Session, getSessionManager } from '../core'
import { STORAGEKEY } from '../core/SessionManager'
export interface SessionParams {
channel: string
server: string
port: string
system: string
}
export interface LoginParams {
username: string
password?: string
}
export async function login(params: LoginParams) {
const { username, password } = params
try {
const sessionManager = await getSessionManager()
const session = await sessionManager.openMainUseCase(
username,
password ?? '',
Session
)
session.module = 0
session.storageToken = STORAGEKEY
sessionStorage.setItem(STORAGEKEY, session.token.toString())
return session
} catch (error) {
return error as Error
}
}
export async function connect(token: string) {
try {
const sessionManager = await getSessionManager()
sessionManager.connectMainUseCase(
new Session(token, sessionManager.service, sessionManager.driver)
)
await sessionManager.session!.timeoutCheck()
const session = sessionManager.session! as Session
session.module = 0
session.storageToken = STORAGEKEY
sessionStorage.setItem(STORAGEKEY, session.token.toString())
return session
} catch (error) {
if (error instanceof DestinataryNotFoundError) {
return error as DestinataryNotFoundError
}
if (error instanceof ConnectionError) {
return error as ConnectionError
}
}
}
export async function logoutSession(session?: Session) {
if (session) {
session.abort()
}
localStorage.clear()
}
- Crie uma pasta
contextemsrcpara definir um Contexto de autenticacao Curio:
// src/contexts/CurioContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react'
import { Session } from '../core'
import {
LoginParams,
connect as connectRequest,
login as loginRequest,
} from '../api/session'
import { STORAGEKEY } from '../core/SessionManager'
import { DestinataryNotFoundError } from '@curio/client/errors'
interface AuthContextType {
session?: Session
isLoading: boolean
isAuth: boolean
login: (params: LoginParams) => Promise<void>
logout: () => void
}
const CurioContext = createContext<AuthContextType | undefined>(undefined)
export const useAuth = () => {
const context = useContext(CurioContext)
if (!context) {
throw new Error('useAuth deve ser utilizado dentro de um AuthProvider')
}
return context
}
export const CurioProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [session, setSession] = useState<Session>()
const [isAuth, setIsAuth] = useState<boolean>(false)
const [isLoading, setIsLoading] = useState<boolean>(false)
useEffect(() => {
connect()
}, [])
const connect = async () => {
setIsLoading(true)
const token = sessionStorage.getItem(STORAGEKEY)
if (token) {
setIsAuth(true)
const sessionAPI = await connectRequest(token)
if (sessionAPI instanceof Session) {
setSession(sessionAPI)
setIsAuth(true)
} else if (sessionAPI instanceof DestinataryNotFoundError) {
logout()
}
} else {
logout()
}
setIsLoading(false)
}
const login = async (params: LoginParams) => {
setIsLoading(true)
setIsAuth(false)
const sessionAPI = await loginRequest(params)
if (sessionAPI instanceof Error) {
logout()
alert('Nao foi possivel realizar o login!')
} else {
setSession(sessionAPI)
setIsAuth(true)
alert('Login realizado com sucesso!')
}
setIsLoading(false)
}
const logout = () => {
setIsAuth(false)
setSession(undefined)
sessionStorage.clear()
localStorage.clear()
setIsLoading(false)
}
return (
<CurioContext.Provider value={{ isAuth, session, isLoading, login, logout }}>
{children}
</CurioContext.Provider>
)
}
- Envolva sua aplicação React com o CurioProvider:
import React from 'react';
import App from './App';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!);
root.render(
<App />
);
// src/App.tsx
import React from 'react'
import App from './App'
import { CurioProvider } from './contexts/CurioContext'
export default function App() {
return (
<CurioProvider>
<App />
</CurioProvider>
)
}
- Usando o Curio em um componente do seu Projeto:
Para esse exemplo estamos utilizando uma pagina que deve fazer o login
// src/pages/Login/Login.tsx
import React, { useState } from 'react'
import { useAuth } from '../../context/CurioContext'
const Login: React.FC = () => {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const { login, isLoading } = useAuth()
const [error, setError] = useState<string | null>(null)
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
try {
await login({ username, password })
} catch (err) {
setError('Falha no login: ' + (err instanceof Error ? err.message : String(err)))
}
}
if (isLoading) {
return <div>Carregando Curio...</div>
}
return (
<div className="login-container">
<form onSubmit={handleLogin}>
<h2>Login</h2>
{error && <div className="error">{error}</div>}
<div className="form-group">
<label htmlFor="username">Usuário:</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">Senha:</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Entrando...' : 'Entrar'}
</button>
</form>
</div>
)
}
export default Login
Pronto! Com esses passos, agora temos uma interface em React utilizando o Curio que executa o MainUseCase para poder gerar uma sessão de usuário!
Implementando biblioteca de componentes para estilização MaterialUI
Agora o nosso projeto Curio + React vamos dar inicio a utilizacao da biblioteca de components MaterialUI
- Instalando dependências necessárias
yarn add @mui/material @emotion/react @emotion/styled
yarn add @fontsource/roboto
- Configure o tema do Material UI:
No seu diretório src cria uma pasta theme e nela um arquivo de tema para personalizar a aparência do Material UI:
import { createTheme } from '@mui/material/styles';
// Crie um tema personalizado
const theme = createTheme({
palette: {
primary: {
main: '#00b07b', // Cor primária (pode ser ajustada conforme sua marca)
light: '#4FD1DF',
dark: '#005e42',
},
secondary: {
main: '#316b52', // Cor secundária
},
error: {
main: '#f44336',
},
background: {
default: '#f5f5f5',
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 500,
},
h2: {
fontSize: '2rem',
fontWeight: 500,
},
button: {
textTransform: 'none', // Evita que os botões fiquem em maiúsculas
},
},
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: 8,
},
},
},
},
});
export default theme;
- Aplique o tema ao seu aplicativo:
Modifique o arquivo App.tsx para incluir o provedor de tema:
// src/App.tsx
import React, { Suspense } from 'react';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import theme from './theme';
import Login from './pages/Login/Login';
import { CurioProvider } from './context/CurioContext';
export default function App() {
return (
<CurioProvider>
<ThemeProvider theme={theme}>
{/* CssBaseline normaliza os estilos entre navegadores */}
<CssBaseline />
<Suspense fallback={<div>Carregando...</div>}>
<Login />
</Suspense>
</ThemeProvider>
</CurioProvider>
)
}
Refatorando a Página de Login com Material UI
Vamos refatorar nossa página de login para usar componentes do Material UI, melhorando a aparência e a experiência do usuário.
// src/pages/Login/Login.tsx
import React, { useState } from 'react';
import { useAuth } from '../../context/CurioContext';
import {
Box,
Button,
CircularProgress,
Container,
TextField,
Typography,
Alert,
Paper,
Avatar
} from '@mui/material';
const Login: React.FC = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const { login, isLoading } = useAuth();
const [error, setError] = useState<string | null>(null);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
try {
await login({ username, password });
} catch (err) {
setError('Falha no login: ' + (err instanceof Error ? err.message : String(err)));
}
};
return (
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Paper
elevation={3}
sx={{
padding: 3,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '100%',
}}
>
<Avatar sx={{ m: 1, width: 75, height: 75, bgcolor: 'primary.dark' }} src='/assets/curio.png' />
<Typography component="h1" variant="h5">
Login
</Typography>
{error && (
<Alert severity="error" sx={{ width: '100%', mt: 2 }}>
{error}
</Alert>
)}
<Box component="form" onSubmit={handleLogin} sx={{ mt: 1, width: '100%' }}>
<TextField
margin="normal"
required
fullWidth
id="username"
label="Usuário"
name="username"
autoComplete="username"
autoFocus
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={isLoading}
/>
<TextField
margin="normal"
fullWidth
name="password"
label="Senha"
type="password"
id="password"
autoComplete="current-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
disabled={isLoading}
>
{isLoading ? (
<CircularProgress size={24} color="inherit" />
) : (
'Entrar'
)}
</Button>
</Box>
</Paper>
</Box>
</Container >
);
};
export default Login;
No Comments