Commit 06b6c363 authored by Rafael's avatar Rafael

Adiciona conexões com o serviço e primeiro acesso no simulador.

parent 533c4f22
--registry "https://registry.npmjs.org/" --registry "https://nexus.dev.evologica.com.br/repository/npm"
"@curio:registry" "https://nexus.dev.evologica.com.br/repository/npm" "@curio:registry" "https://nexus.dev.evologica.com.br/repository/npm"
"@dynamo:registry" "https://nexus.dev.evologica.com.br/repository/npm" "@dynamo:registry" "https://nexus.dev.evologica.com.br/repository/npm"
\ No newline at end of file
...@@ -11,14 +11,7 @@ ...@@ -11,14 +11,7 @@
"useBuiltIns": "usage", "useBuiltIns": "usage",
"targets": { "targets": {
"ie": "11", "ie": "11",
"browsers": [ "browsers": ["edge >= 16", "safari >= 9", "firefox >= 57", "ie >= 11", "ios >= 9", "chrome >= 49"]
"edge >= 16",
"safari >= 9",
"firefox >= 57",
"ie >= 11",
"ios >= 9",
"chrome >= 49"
]
} }
} }
], ],
...@@ -58,6 +51,7 @@ ...@@ -58,6 +51,7 @@
"@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-async-to-generator", "@babel/plugin-transform-async-to-generator",
"@babel/plugin-syntax-dynamic-import", "@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-arrow-functions" "@babel/plugin-transform-arrow-functions",
"@babel/plugin-syntax-top-level-await"
] ]
} }
{ {
"service": { "API_URL": "http://172.16.17.3:8080",
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSON?version=4", "APP_NAME_BROKER": "@microcredito-dev/agente",
"server": "192.168.0.34", "APP_NAME_CUSTOMER": "@microcredito-dev/cliente",
"system": 19, "SESSION_KEY_BROKER": "@microcredito-dev/agente",
"port": 9801, "SESSION_KEY_CUSTOMER": "@microcredito-dev/cliente"
"module": 1,
"version": 3
},
"accessToken": "ab4dfc4ab84517f900193f8a2530680c73ad3539f75e1f5661bee7065cfcdd32",
"resources": {
"get": "https://mdk.dev.evologica.com.br/cxClient/cxIsapiClient.dll/getpr",
"put": "https://mdk.dev.evologica.com.br/cxClient/cxIsapiClient.dll/putpr"
},
"logs": true
} }
{
"API_URL": "http://192.168.0.34:8080",
"APP_NAME_BROKER": "@microcredito-dev/agente",
"APP_NAME_CUSTOMER": "@microcredito-dev/cliente",
"SESSION_KEY_BROKER": "@microcredito-dev/agente",
"SESSION_KEY_CUSTOMER": "@microcredito-dev/cliente"
}
\ No newline at end of file
{ {
"service": { "API_URL": "https://microcredito.dev.evologica.com.br",
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSON?version=4", "APP_NAME_BROKER": "@microcredito/agente",
"server": "192.168.0.34", "APP_NAME_CUSTOMER": "@microcredito/cliente",
"system": 19, "SESSION_KEY_BROKER": "@microcredito/agente",
"port": 9801, "SESSION_KEY_CUSTOMER": "@microcredito/cliente"
"module": 1,
"version": 3
},
"accessToken": "ab4dfc4ab84517f900193f8a2530680c73ad3539f75e1f5661bee7065cfcdd32",
"resources": {
"get": "https://mdk.dev.evologica.com.br/cxClient/cxIsapiClient.dll/getpr",
"put": "https://mdk.dev.evologica.com.br/cxClient/cxIsapiClient.dll/putpr"
},
"logs": true
} }
{ {
"service": { "API_URL": "https://microcredito.test.evologica.com.br",
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSON?version=4", "APP_NAME_BROKER": "@microcredito-staging/agente",
"server": "192.168.0.34", "APP_NAME_CUSTOMER": "@microcredito-staging/cliente",
"system": 19, "SESSION_KEY_BROKER": "@microcredito-staging/agente",
"port": 9801, "SESSION_KEY_CUSTOMER": "@microcredito-staging/cliente"
"module": 1,
"version": 3
},
"accessToken": "ab4dfc4ab84517f900193f8a2530680c73ad3539f75e1f5661bee7065cfcdd32",
"resources": {
"get": "https://mdk.dev.evologica.com.br/cxClient/cxIsapiClient.dll/getpr",
"put": "https://mdk.dev.evologica.com.br/cxClient/cxIsapiClient.dll/putpr"
},
"logs": true
} }
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@material-ui/lab": "4.0.0-alpha.58", "@material-ui/lab": "4.0.0-alpha.58",
"@material-ui/pickers": "^3.3.10", "@material-ui/pickers": "^3.3.10",
"@microcredito/client": "^0.7.11",
"@reduxjs/toolkit": "^1.2.5", "@reduxjs/toolkit": "^1.2.5",
"@types/react-swipeable-views": "^0.13.1", "@types/react-swipeable-views": "^0.13.1",
"@types/react-swipeable-views-utils": "^0.13.3", "@types/react-swipeable-views-utils": "^0.13.3",
......
{ {
"service": { "API_URL": "http://172.16.17.3:8080",
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSON?version=4", "APP_NAME_BROKER": "@microcredito-dev/agente",
"server": "192.168.0.34", "APP_NAME_CUSTOMER": "@microcredito-dev/cliente",
"system": 19, "SESSION_KEY_BROKER": "@microcredito-dev/agente",
"port": 9801, "SESSION_KEY_CUSTOMER": "@microcredito-dev/cliente"
"module": 1,
"version": 3
},
"accessToken": "ab4dfc4ab84517f900193f8a2530680c73ad3539f75e1f5661bee7065cfcdd32",
"resources": {
"get": "https://mdk.dev.evologica.com.br/cxClient/cxIsapiClient.dll/getpr",
"put": "https://mdk.dev.evologica.com.br/cxClient/cxIsapiClient.dll/putpr"
},
"logs": true
} }
\ No newline at end of file
...@@ -8,6 +8,6 @@ server.get('*', (req, res) => { ...@@ -8,6 +8,6 @@ server.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html')) res.sendFile(path.join(__dirname, 'dist', 'index.html'))
}) })
server.listen(3010, () => { server.listen(3000, () => {
console.log('Listening at http://localhost:3010/') console.log('Listening at http://localhost:3000/')
}) })
import { Entity } from '@agiliza/utils/method'
import { Customer } from '../'
export interface City extends Entity {
name: string
state: string
}
export interface State extends Entity {
name: string
acronym: string
}
export interface AuthenticationContext {
states: State[]
cities: City[]
}
type OutputGetContext = AuthenticationContext
export interface GetContext {
Input: void
Output: OutputGetContext
}
interface InputVerifyCPF {
cpf: string
}
export interface VerifyCPF {
Input: InputVerifyCPF
Output: void
}
interface InputCreateCustomer {
cpf: string
name: string
email: string
phone: string
cep: string
street: string
number: string
complement: string
district: string
state: string
city: string
}
interface OutputCreateCustomer {
customer: Customer
}
export interface CreateCustomer {
Input: InputCreateCustomer
Output: OutputCreateCustomer
}
interface InputSendCode {
cpf: string
email: string
}
export interface SendCode {
Input: InputSendCode
Output: void
}
interface InputVerifyCode {
code: string
cpf: string
}
export interface VerifyCode {
Input: InputVerifyCode
Output: void
}
interface InputCreatePassword {
password: string
code: string
cpf: string
}
export interface CreatePassword {
Input: InputCreatePassword
Output: void
}
export interface GetLoggedCustomer {
Input: void
Output: Customer
}
import { Entity } from '@agiliza/utils/method'
export interface City extends Entity {
name: string
state: string
}
export interface State extends Entity {
name: string
acronym: string
}
import { ErrorTypes } from '@agiliza/constants/error'
interface Error {
type: ErrorTypes
omit?: boolean
}
// Usuário não está autorizado (Login)
export interface UnauthorizedError extends Error {
type: ErrorTypes.UNAUTHORIZED
message?: string
}
// Token de usuário expirou
export interface ExpiredSessionError extends Error {
type: ErrorTypes.EXPIRED_SESSION
message: string
}
// Validação de campos (422, resposta padrão para esse status)
export interface InvalidFields {
[field: string]: string
}
export interface FormValidationError extends Error {
type: ErrorTypes.FORM_VALIDATION
invalidFields: InvalidFields
detail: string
}
// Campo ausente do requerido na API (não deve aparecer normalmente)
export interface MissingInputError extends Error {
type: ErrorTypes.MISSING_INPUT
missingFields: string[]
detail: string
}
// Erros de servidor (500) e quais outros erros não mapeados
export interface InternalServerError extends Error {
type: ErrorTypes.INTERNAL_SERVER
message: string
stack: string
}
// Código de Primeiro Acesso/Esqueci a Senha é inválido
export interface InvalidVerificationCodeError extends Error {
type: ErrorTypes.INVALID_EMAIL_CODE
message: string
}
// Usuário já existe disponível no sistema (Primeiro Acesso)
export interface AlreadyEnabledError extends Error {
type: ErrorTypes.ALREADY_ENABLED
message?: string
}
// Usuário não encontrado (404)
export interface UserNotFoundError extends Error {
type: ErrorTypes.USER_NOT_FOUND
message?: string
}
// Usuário não pode agendar uma nova visita
export interface ScheduleForbiddenError extends Error {
type: ErrorTypes.SCHEDULE_FORBIDDEN
message?: string
}
export type ApiError =
| UnauthorizedError
| ExpiredSessionError
| FormValidationError
| InternalServerError
| MissingInputError
| InvalidVerificationCodeError
| AlreadyEnabledError
| UserNotFoundError
| ScheduleForbiddenError
export * from './proposalData'
export * from './context'
export * from './error'
export * from './simulation'
export * from './authentication'
export interface Customer {
name: string
email: string
ownerName?: string
ownerCPF?: string
cpfcnpj: string
phone: string
avatarUrl?: string
address?: Partial<Address>
profilePicture?: string
}
export * from './proposalData'
import { Entity } from '@agiliza/utils/method'
export interface SimulationCategory extends Entity {
description: string
fullDescription: string
maxInstallment: number
maxGraceMonths: number
}
export interface SimulationContext {
gracePeriods: CreditGracePeriod[]
installments: CreditInstallment[]
}
//Prestação
export interface CreditInstallment extends Entity {
description: string
}
// Carência
export interface CreditGracePeriod extends Entity {
description: string
}
export * from './context'
export * from './simulation'
import { Entity } from '@agiliza/utils/method'
export interface SubProduct extends Entity {
description?: string
fee?: number
IOF?: number
maxAmountInstallment?: number
installementOptions: InstallmentOption[]
TAC?: number
}
export interface GetSubProducts {
Input: InputGetSubProducts
Output: OutputGetSubProducts
}
interface InputGetSubProducts {
creditValue: number
idModality: string
idGracePeriod: string
installmentsNumber: number
}
type OutputGetSubProducts = SubProduct[]
export interface InstallmentOption extends Entity {
installmentAmount?: number
installmentValue?: number
netValue?: number
}
import { getSessionManager } from '@agiliza/curio'
import { System as ApiSystem } from '../interfaces/System'
import { DYNAMO_TRANSITIONS } from '../useCases/transitions'
export interface SearchParams {
values: Record<string, any>
}
export function createMenuAPI() {
return {
fetchMenu: async function () {
const sessionManger = await getSessionManager()
const response = await sessionManger.session!.sendRequest(DYNAMO_TRANSITIONS.menu, {
Level: {
_: sessionManger.session!.module,
},
Version: {
_: sessionManger.session!.version,
},
})
return response.System as ApiSystem
},
}
}
interface JavaConfig {
API_URL: string
APP_NAME_BROKER: string
APP_NAME_CUSTOMER: string
SESSION_KEY_BROKER: string
SESSION_KEY_CUSTOMER: string
}
const JAVA_CONFIG_PATH = './config.json'
let javaConfig: JavaConfig
export const getJavaConfig = async () => {
if (!javaConfig) {
try {
javaConfig = await (await fetch(JAVA_CONFIG_PATH)).json()
return javaConfig
} catch (e) {
if (e instanceof Response) {
//Not found, most likely
throw process.env.NODE_ENV === 'production'
? 'Não foi encontrado o arquivo config.json na raiz da aplicação.'
: 'Arquivo config.json faltando na pasta public.'
}
throw e
}
}
}
import { CreateCustomer } from '@agiliza/api/domain'
import { extractNumbers } from '@agiliza/utils/extractors'
import { CriarUsuarioPrimeiroAcessoRequest } from '@microcredito/client/dist/apis/ClienteApi'
import { ApiAdapter } from './shared'
export class CreateCustomerApiMapper implements ApiAdapter<CreateCustomer['Input'], CriarUsuarioPrimeiroAcessoRequest> {
public mapDomainToApiModel = (input: CreateCustomer['Input']): CriarUsuarioPrimeiroAcessoRequest => ({
criarUsuarioPrimeiroAcessoRequestApiModel: {
usuario: {
usuarioCliente: {
cpfcnpj: extractNumbers(input.cpf),
nome: input.name,
email: input.email,
celular: extractNumbers(input.phone),
endereco: {
cep: extractNumbers(input.cep),
logradouro: input.street,
numero: input.number,
estado: input.state,
municipio: input.city,
complemento: input.complement,
bairro: input.district,
},
},
},
},
})
}
import * as AuthenticationApiMappers from './authentication'
import * as SessionApiAdapters from './session'
import * as SimulationApiMappers from './simulation'
export { SessionApiAdapters, SimulationApiMappers, AuthenticationApiMappers }
import { ClienteApiModels } from '@microcredito/client'
import { ApiAdapter } from './shared'
interface LoginParams {
username: string
password: string
}
export class LoginApiAdapter implements ApiAdapter<LoginParams, ClienteApiModels.LoginRequest> {
public mapDomainToApiModel = (params: LoginParams): ClienteApiModels.LoginRequest => {
const result: ClienteApiModels.LoginRequest = {
loginRequestApiModel: {
login: params.username,
senha: params.password,
agente: false,
},
}
return result
}
}
export interface ApiAdapter<A, B> {
mapDomainToApiModel(domain: A): B
}
import { GetSubProducts } from '@agiliza/api/domain/simulation/simulation'
import { SimularCreditoRequestApiModel } from '@microcredito/client'
import { ApiAdapter } from '../shared'
export class GetSubProductsApiMapper implements ApiAdapter<GetSubProducts['Input'], SimularCreditoRequestApiModel> {
public mapDomainToApiModel = (input: GetSubProducts['Input']): SimularCreditoRequestApiModel => ({
valorCredito: input.creditValue,
codigoCarencia: input.idGracePeriod,
codigoModalidade: input.idModality,
numeroParcelas: input.installmentsNumber,
})
}
import { EstadoApiModel, MunicipioApiModel } from '@microcredito/client'
import { City, State } from '../../domain'
import { DomainAdapter } from './shared'
export class CityMapper implements DomainAdapter<MunicipioApiModel, City> {
public mapApiModelToDomain = (apimodel: MunicipioApiModel): City => {
return {
id: apimodel.codigo,
name: apimodel.nome,
state: apimodel.estado,
}
}
}
export class StateMapper implements DomainAdapter<EstadoApiModel, State> {
public mapApiModelToDomain = (apimodel: EstadoApiModel): State => {
return {
id: apimodel.codigo,
name: apimodel.nome,
acronym: apimodel.sigla,
}
}
}
import { EstadoApiModel, MunicipioApiModel } from '@microcredito/client'
import { City, State } from '../../domain'
import { DomainAdapter } from './shared'
export class CityAdapter implements DomainAdapter<MunicipioApiModel, City> {
public mapApiModelToDomain = (apimodel: MunicipioApiModel): City => {
return {
id: apimodel.codigo,
name: apimodel.nome,
state: apimodel.estado,
}
}
}
export class StateAdapter implements DomainAdapter<EstadoApiModel, State> {
public mapApiModelToDomain = (apimodel: EstadoApiModel): State => {
return {
id: apimodel.codigo,
name: apimodel.nome,
acronym: apimodel.sigla,
}
}
}
import { Customer } from '@agiliza/api/domain'
import { UsuarioApiModel } from '@microcredito/client'
import { DomainAdapter } from './shared'
export class CustomerMapper implements DomainAdapter<UsuarioApiModel, Customer> {
public mapApiModelToDomain = ({ usuarioCliente }: UsuarioApiModel): Customer => ({
profilePicture: usuarioCliente?.fotoPerfil,
cpfcnpj: usuarioCliente?.cpfcnpj || '',
name: usuarioCliente?.nome || '',
phone: usuarioCliente?.celular || '',
ownerCPF: usuarioCliente?.cpfResponsavel,
ownerName: usuarioCliente?.nomeResponsavel,
address: usuarioCliente?.endereco
? {
additionalInfo: usuarioCliente?.endereco.complemento,
cep: usuarioCliente?.endereco.cep,
city: usuarioCliente?.endereco.municipio,
state: usuarioCliente?.endereco.estado,
district: usuarioCliente?.endereco.bairro,
number: usuarioCliente?.endereco.numero,
street: usuarioCliente?.endereco.logradouro,
id: usuarioCliente?.endereco.id,
}
: undefined,
email: usuarioCliente?.email || '',
})
}
import { ApiError, InternalServerError } from '@agiliza/api/domain'
import { ErrorTypes } from '@agiliza/constants/error'
import { DomainAdapter } from './shared'
/** Default response error adapter */
export class ResponseErrorAdapter implements DomainAdapter<Response, Promise<ApiError>> {
public async mapApiModelToDomain(error: Response): Promise<ApiError> {
let message: string
console.log('ERROR', error)
if (error instanceof Error) {
console.log(error)
if (error instanceof TypeError && error.message.includes('Network')) {
// Network error
message = 'Ocorreu um problema na conexão ao servidor. Verifique se você está conectado à rede.'
} else {
message = error.message
}
} else {
try {
// console.log('Request:', error)
const body = await error.json()
console.log(body)
if (body.message) {
message = body.message
} else if (body.erros) {
message = body.erros[0].motivo
} else {
if (body.error)
if (Array.isArray(body.error)) message = body.error[0].motivo
else if (body.status && body.status === 404) message = `${body.error}\nPath: ${body.path}`
else message = body.error
else message = body.toString()
}
} catch (e) {
// console.log(error)
message = error.statusText
}
}
return {
type: ErrorTypes.INTERNAL_SERVER,
message: message || 'Ocorreu um problema na requisição ao servidor.',
} as InternalServerError
}
}
/** Default authenticated response error adapter */
export class AuthResponseErrorAdapter extends ResponseErrorAdapter {
public async mapApiModelToDomain(error: Response): Promise<ApiError> {
// if (error instanceof Response && error.status === 403) {
// return {
// type: ErrorTypes.EXPIRED_SESSION,
// message: 'A sua sessão expirou.',
// }
// }
return super.mapApiModelToDomain(error)
}
}
// export class ValidateTokenErrorAdapter extends ResponseErrorAdapter {
// public mapApiModelToDomain = (error: Response): ApiError | undefined => {
// if (error instanceof Response && error.status === 401) {
// return {
// type: ErrorTypes.INVALID_EMAIL_CODE,
// message: 'Código de verificação inválido.',
// }
// }
// }
// }
import * as AuthenticationMappers from './authentication'
import * as ContextMappers from './context'
import * as CustomerMappers from './customer'
import * as ErrorMappers from './error'
import * as SessionAdapters from './session'
import * as SimulationMappers from './simulation'
export { ContextMappers, ErrorMappers, SessionAdapters, SimulationMappers, AuthenticationMappers, CustomerMappers }
import type { ApiError } from '@agiliza/api/domain'
import { ErrorTypes } from '@agiliza/constants/error'
import { ResponseErrorAdapter } from './error'
export class LoginErrorAdapter extends ResponseErrorAdapter {
public mapApiModelToDomain = async (error: Response): Promise<ApiError> => {
if (error instanceof Response) {
if (error.status === 403) {
// Unauthorized
return {
type: ErrorTypes.UNAUTHORIZED,
message: 'As credenciais fornecidas não são válidas.',
}
}
}
return super.mapApiModelToDomain(error)
}
}
export interface DomainAdapter<A, B> {
mapApiModelToDomain(apiModel: A): B
}
import { SimulationCategory } from '@agiliza/api/domain'
import { CategoriaSimulacaoApiModel } from '@microcredito/client'
import { DomainAdapter } from '../shared'
export class SimulationCategoryMapper implements DomainAdapter<CategoriaSimulacaoApiModel, SimulationCategory> {
public mapApiModelToDomain = (apimodel: CategoriaSimulacaoApiModel): SimulationCategory => {
return {
id: apimodel.id || '',
description: apimodel.descricao || '',
fullDescription: apimodel.descricaoLonga || '',
maxInstallment: apimodel.numeroMaximoParcelas,
maxGraceMonths: apimodel.carenciaMaxima,
}
}
}
export * from './context'
export * from './simulation'
import { InstallmentOption, SubProduct } from '@agiliza/api/domain'
import { OpcaoPrestacaoApiModel, SubprodutoApiModel } from '@microcredito/client'
import { DomainAdapter } from '../shared'
export class SubProductMapper implements DomainAdapter<SubprodutoApiModel, SubProduct> {
public mapInstallmentOption = (opcaoPrestacao: OpcaoPrestacaoApiModel): InstallmentOption => ({
id: opcaoPrestacao.id || '',
installmentAmount: opcaoPrestacao.quantidadePrestacoes,
installmentValue: opcaoPrestacao.valor,
netValue: opcaoPrestacao.valorLiquido,
})
public mapApiModelToDomain = (subproduto: SubprodutoApiModel): SubProduct => ({
id: subproduto.id || '',
description: subproduto.descricao,
IOF: subproduto.iof,
TAC: subproduto.tac,
fee: subproduto.taxa,
installementOptions: subproduto.opcoesPrestacao?.map(this.mapInstallmentOption) || [],
maxAmountInstallment: subproduto.qntMaxPrestacoes,
})
}
import * as error from './error' import * as error from './error'
export * from './domain'
export * from './api'
export { error } export { error }
import {
CreateCustomer,
CreatePassword,
GetContext,
GetLoggedCustomer,
SendCode,
VerifyCode,
VerifyCPF
} from '@agiliza/api/domain'
import { extractNumbers } from '@agiliza/utils/extractors'
import { ClienteApi, Configuration, DominioApi } from '@microcredito/client'
import {
AuthenticationApiMappers,
AuthenticationMappers,
CustomerMappers,
ErrorMappers
} from '../mappers'
import { API_URL, mapUserAgentToString, UserAgent } from './shared'
export interface AuthenticationRepository {
getContext(): Promise<GetContext['Output']>
verifyCPF(input: VerifyCPF['Input']): Promise<VerifyCPF['Output']>
createCustomer(input: CreateCustomer['Input']): Promise<CreateCustomer['Output']>
sendCode(input: SendCode['Input']): Promise<SendCode['Output']>
verifyCode(input: VerifyCode['Input']): Promise<VerifyCode['Output']>
createPassword(input: CreatePassword['Input']): Promise<CreatePassword['Output']>
getLoggedCustomer(): Promise<GetLoggedCustomer['Output']>
}
export class AuthenticationRepositoryImpl implements AuthenticationRepository {
private api: DominioApi
private clienteApi: ClienteApi
private errorAdapter: ErrorMappers.ResponseErrorAdapter
private stateMapper: AuthenticationMappers.StateMapper
private cityMapper: AuthenticationMappers.CityMapper
private customerMapper: CustomerMappers.CustomerMapper
private authenticationApiMapper: AuthenticationApiMappers.CreateCustomerApiMapper
constructor(userAgent: string) {
this.errorAdapter = new ErrorMappers.ResponseErrorAdapter()
this.stateMapper = new AuthenticationMappers.StateMapper()
this.cityMapper = new AuthenticationMappers.CityMapper()
this.customerMapper = new CustomerMappers.CustomerMapper()
this.authenticationApiMapper = new AuthenticationApiMappers.CreateCustomerApiMapper()
this.api = new DominioApi(
new Configuration({
basePath: API_URL,
headers: {
'User-Agent': userAgent,
},
})
)
this.clienteApi = new ClienteApi(
new Configuration({
basePath: API_URL,
headers: {
'User-Agent': userAgent,
},
})
)
}
public getContext = async (): Promise<GetContext['Output']> => {
try {
const estados = await this.api.obterEstados()
const cidades = await this.api.obterMunicipios()
return { states: estados.map(this.stateMapper.mapApiModelToDomain), cities: cidades.map(this.cityMapper.mapApiModelToDomain) }
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
public verifyCPF = async (input: VerifyCPF['Input']): Promise<VerifyCPF['Output']> => {
try {
await this.clienteApi.iniciarPrimeiroAcessoCliente({ iniciarPrimeiroAcessoRequestApiModel: { cpfcnpj: extractNumbers(input.cpf) } })
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
public createCustomer = async (input: CreateCustomer['Input']): Promise<CreateCustomer['Output']> => {
try {
const cliente = await this.clienteApi.criarUsuarioPrimeiroAcesso(this.authenticationApiMapper.mapDomainToApiModel(input))
return { customer: this.customerMapper.mapApiModelToDomain(cliente) }
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
public sendCode = async (input: SendCode['Input']): Promise<SendCode['Output']> => {
try {
await this.clienteApi.enviarCodigoPrimeiroAcessoCliente({
enviarCodigoPrimeiroAcessoRequestApiModel: { cpfcnpj: extractNumbers(input.cpf), email: input.email },
})
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
public verifyCode = async (input: VerifyCode['Input']): Promise<VerifyCode['Output']> => {
try {
await this.clienteApi.validarCodigoPrimeiroAcessoCliente({
validarCodigoPrimeiroAcessoRequestApiModel: { cpfcnpj: extractNumbers(input.cpf), codigo: extractNumbers(input.code) },
})
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
public createPassword = async (input: CreatePassword['Input']): Promise<CreatePassword['Output']> => {
try {
await this.clienteApi.definirSenhaPrimeiroAcessoCliente({
definirSenhaPrimeiroAcessoRequestApiModel: { cpfcnpj: extractNumbers(input.cpf), codigo: extractNumbers(input.code), senha: input.password },
})
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
public getLoggedCustomer = async (): Promise<GetLoggedCustomer['Output']> => {
try {
const usuario = await this.clienteApi.obterUsuarioLogado()
return this.customerMapper.mapApiModelToDomain(usuario)
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
}
export class AuthenticationRepositoryImplFactory {
static create(userAgent: UserAgent) {
const repository = new AuthenticationRepositoryImpl(mapUserAgentToString(userAgent))
return repository
}
}
import { Configuration, DominioApi } from '@microcredito/client'
import { City, State } from '../domain'
import { ContextMappers, ErrorMappers } from '../mappers'
import { API_URL, mapUserAgentToString, UserAgent } from './shared'
export interface ContextRepository {
fetchCities(): Promise<City[]>
fetchStates(): Promise<State[]>
}
export class ContextRepositoryImpl implements ContextRepository {
private api: DominioApi
private stateAdapter: ContextMappers.StateAdapter
private cityAdapter: ContextMappers.CityAdapter
private errorAdapter: ErrorMappers.ResponseErrorAdapter
constructor(userAgent: string) {
this.errorAdapter = new ErrorMappers.ResponseErrorAdapter()
this.stateAdapter = new ContextMappers.StateAdapter()
this.cityAdapter = new ContextMappers.CityAdapter()
this.api = new DominioApi(
new Configuration({
basePath: API_URL,
headers: {
'User-Agent': userAgent,
},
})
)
}
public fetchCities = async (): Promise<City[]> => {
try {
const result = await this.api.obterMunicipios()
return result.map(this.cityAdapter.mapApiModelToDomain)
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
public fetchStates = async (): Promise<State[]> => {
try {
const result = await this.api.obterEstados()
return result.map(this.stateAdapter.mapApiModelToDomain)
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
}
export class ContextRepositoryImplFactory {
static create(userAgent: UserAgent) {
const repository = new ContextRepositoryImpl(mapUserAgentToString(userAgent))
return repository
}
}
import pMinDelay from 'p-min-delay' export * from './session'
export * from './simulation'
import * as apiSession from './session' export * from './authentication'
type ApiSessionManager<A> = (...args: any[]) => Promise<A | void>
interface ApiMapObject<A> {
[key: string]: ApiSessionManager<A>
}
export const applyDelayToApi = <
A,
M extends ApiMapObject<A> | ApiSessionManager<A>
>(
apiMapObject: M,
ms = 600
): M =>
Object.entries(apiMapObject).reduce(
(acc, [k, v]: [string, ApiSessionManager<any>]) => ({
...acc,
[k]: (...args: any[]) => pMinDelay(v(...args), ms)
}),
{} as M
)
export const session = applyDelayToApi(apiSession)
import { getSessionManager } from '@agiliza/curio' import { extractNumbers } from '@agiliza/utils/extractors'
import { ClienteApi, Configuration } from '@microcredito/client'
import { createMenuAPI } from '../dynamo' import { Customer } from '../domain'
import { CustomerMappers, SessionAdapters, SessionApiAdapters as Adapters } from '../mappers'
import { API_URL, mapUserAgentToString, SESSION_KEY, UserAgent } from './shared'
// import { getSessionManager } from '@agiliza/curio'
// import { createMenuAPI } from '../dynamo' // import { createMenuAPI } from '../dynamo'
// import { SolicitarRedefinicao, VerificarCodigo, RedefinirSenha } from '../interfaces/Login' // // import { createMenuAPI } from '../dynamo'
// const ucRecuperarSenha = { // // import { SolicitarRedefinicao, VerificarCodigo, RedefinirSenha } from '../interfaces/Login'
// id: '3522',
// SOLICITAR_REDEFINICAO: 'RM_SOLICITAR_REDEFINICAO',
// VERIFICAR_CODIGO: 'RM_VERIFICAR_CODIGO',
// DEFIINIR_SENHA: 'RM_DEFINIR_SENHA'
// }
interface LoginParams { // // const ucRecuperarSenha = {
username: string // // id: '3522',
password: string // // SOLICITAR_REDEFINICAO: 'RM_SOLICITAR_REDEFINICAO',
} // // VERIFICAR_CODIGO: 'RM_VERIFICAR_CODIGO',
// // DEFIINIR_SENHA: 'RM_DEFINIR_SENHA'
// // }
export const initialize = async () => { // interface LoginParams {
await getSessionManager() // username: string
} // password: string
// }
export const login = async ({ username, password }: LoginParams) => { // export const initialize = async () => {
const sessionManager = await getSessionManager() // await getSessionManager()
return sessionManager.openMainUseCase(username, password) // }
}
export const logout = async () => { // export const login = async ({ username, password }: LoginParams) => {
const sessionManager = await getSessionManager() // const sessionManager = await getSessionManager()
sessionManager?.session?.abort() // return sessionManager.openMainUseCase(username, password)
} // }
// export const logout = async () => {
// const sessionManager = await getSessionManager()
// sessionManager?.session?.abort()
// }
export const { fetchMenu } = createMenuAPI() // export const { fetchMenu } = createMenuAPI()
// export const solicitarRedefinicao = async (funcionario: SolicitarRedefinicao) => { // export const solicitarRedefinicao = async (funcionario: SolicitarRedefinicao) => {
// const mainUseCase = await sessionManager.anonymousConnection() // const mainUseCase = await sessionManager.anonymousConnection()
...@@ -72,3 +78,70 @@ export const { fetchMenu } = createMenuAPI() ...@@ -72,3 +78,70 @@ export const { fetchMenu } = createMenuAPI()
// enviarCodigoSenha, // enviarCodigoSenha,
// redefinirSenha // redefinirSenha
// } // }
export interface SessionRepository {
login(username: string, password: string): Promise<Customer>
logout(): void
connect(): void
}
export class SessionRepositoryImpl implements SessionRepository {
private customer: string
private api: ClienteApi
private loginApiAdapter: Adapters.LoginApiAdapter
private errorAdapter: SessionAdapters.LoginErrorAdapter
private customerMapper: CustomerMappers.CustomerMapper
constructor(userAgent: string) {
this.customer = userAgent
this.errorAdapter = new SessionAdapters.LoginErrorAdapter()
this.loginApiAdapter = new Adapters.LoginApiAdapter()
this.customerMapper = new CustomerMappers.CustomerMapper()
this.api = new ClienteApi(
new Configuration({
basePath: API_URL,
headers: {
'User-Agent': userAgent,
},
})
)
}
public login = async (username: string, password: string) => {
try {
const params = this.loginApiAdapter.mapDomainToApiModel({ username: extractNumbers(username), password })
const accessToken = await this.api.login(params)
const clienteApi = new ClienteApi(
new Configuration({
basePath: API_URL,
accessToken: accessToken.token,
headers: {
'User-Agent': this.customer,
},
})
)
const cliente = await clienteApi.obterUsuarioLogado()
return this.customerMapper.mapApiModelToDomain(cliente)
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
public logout = () => {
localStorage.removeItem(SESSION_KEY)
}
public connect = () => {
const sessionKey = localStorage.getItem(SESSION_KEY)
if (sessionKey) return
else throw new Error()
// return await new Promise<void>((res, rej) => {
// })
}
}
export class SessionRepositoryImplFactory {
static create(userAgent: UserAgent) {
const repository = new SessionRepositoryImpl(mapUserAgentToString(userAgent))
return repository
}
}
const config = await fetch('./config.json').then((res) => res.json())
const { API_URL, APP_NAME_CUSTOMER, SESSION_KEY_CUSTOMER } = config
export { API_URL }
export const SESSION_KEY = SESSION_KEY_CUSTOMER
export const APP_NAME = APP_NAME_CUSTOMER
export interface UserAgent {
appVersion: string
platform: {
name: string
version: string
}
}
export function mapUserAgentToString(agent: UserAgent) {
return `${APP_NAME}/${agent.appVersion} (${agent.platform.name}/${agent.platform.version})`
}
import { SimulationCategory } from '@agiliza/api/domain'
import { Configuration, DominioApi } from '@microcredito/client'
import { ErrorMappers, SimulationMappers } from '../../mappers'
import { API_URL, mapUserAgentToString, UserAgent } from '../shared'
export interface SimulationContextRepository {
fetchSimulationCategories(): Promise<SimulationCategory[]>
}
export class SimulationContextRepositoryImpl implements SimulationContextRepository {
private api: DominioApi
private simulationCategoryMapper: SimulationMappers.SimulationCategoryMapper
private errorAdapter: ErrorMappers.ResponseErrorAdapter
constructor(userAgent: string) {
this.errorAdapter = new ErrorMappers.ResponseErrorAdapter()
this.simulationCategoryMapper = new SimulationMappers.SimulationCategoryMapper()
this.api = new DominioApi(
new Configuration({
basePath: API_URL,
headers: {
'User-Agent': userAgent,
},
})
)
}
public fetchSimulationCategories = async (): Promise<SimulationCategory[]> => {
try {
const result = await this.api.obterCategoriasSimulacao()
return result.map(this.simulationCategoryMapper.mapApiModelToDomain)
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
}
export class SimulationContextRepositoryImplFactory {
static create(userAgent: UserAgent) {
const repository = new SimulationContextRepositoryImpl(mapUserAgentToString(userAgent))
return repository
}
}
import { GetSubProducts } from '@agiliza/api/domain/simulation/simulation'
import { Configuration } from '@microcredito/client'
import { AnonimoApi } from '@microcredito/client/dist/apis/AnonimoApi'
import { ErrorMappers, SimulationApiMappers, SimulationMappers } from '../../mappers'
import { API_URL, mapUserAgentToString, UserAgent } from '../shared'
export interface SimulationRepository {
getSubproducts(input: GetSubProducts['Input']): Promise<GetSubProducts['Output']>
}
export class SimulationRepositoryImpl implements SimulationRepository {
private api: AnonimoApi
private getSubproductsApiMapper: SimulationApiMappers.GetSubProductsApiMapper
private subproductMapper: SimulationMappers.SubProductMapper
private errorAdapter: ErrorMappers.ResponseErrorAdapter
constructor(userAgent: string) {
this.errorAdapter = new ErrorMappers.ResponseErrorAdapter()
this.getSubproductsApiMapper = new SimulationApiMappers.GetSubProductsApiMapper()
this.subproductMapper = new SimulationMappers.SubProductMapper()
this.api = new AnonimoApi(
new Configuration({
basePath: API_URL,
headers: {
'User-Agent': userAgent,
},
})
)
}
public getSubproducts = async (input: GetSubProducts['Input']): Promise<GetSubProducts['Output']> => {
try {
const result = await this.api.anonimoObterSubprodutos({
simularCreditoRequestApiModel: this.getSubproductsApiMapper.mapDomainToApiModel(input),
})
return result.map(this.subproductMapper.mapApiModelToDomain)
} catch (e) {
const result = await this.errorAdapter.mapApiModelToDomain(e)
throw result
}
}
}
export class SimulationRepositoryImplFactory {
static create(userAgent: UserAgent) {
const repository = new SimulationRepositoryImpl(mapUserAgentToString(userAgent))
return repository
}
}
...@@ -7,23 +7,16 @@ import { withStyles, WithStyles } from '@material-ui/core/styles' ...@@ -7,23 +7,16 @@ import { withStyles, WithStyles } from '@material-ui/core/styles'
import { styles } from './styles' import { styles } from './styles'
type ExtendedTypes = WithStyles<typeof styles> & type ExtendedTypes = WithStyles<typeof styles> &
Pick<ButtonProps, 'disabled' | 'onClick' | 'type' | 'children' | 'className' | 'color'> Pick<ButtonProps, 'disabled' | 'onClick' | 'type' | 'children' | 'className' | 'color' | 'size' | 'variant'>
interface Props extends ExtendedTypes { interface Props extends ExtendedTypes {
fetching?: boolean fetching?: boolean
} }
const ButtonWithProgress = (props: Props) => { const ButtonWithProgress = (props: Props) => {
const { className, classes, fetching, disabled, onClick, type, children, color = 'primary' } = props const { className, classes, fetching, disabled, onClick, type, children, color = 'primary', size, variant = 'contained' } = props
return ( return (
<Button <Button className={className} disabled={disabled || fetching} onClick={onClick} variant={variant} color={color} type={type} size={size}>
className={className}
disabled={disabled || fetching}
onClick={onClick}
variant="contained"
color={color}
type={type}
>
{fetching && <LinearProgress className={classes.progress} />} {fetching && <LinearProgress className={classes.progress} />}
{children} {children}
</Button> </Button>
......
...@@ -9,7 +9,7 @@ import withStyles, { WithStyles } from '@material-ui/styles/withStyles' ...@@ -9,7 +9,7 @@ import withStyles, { WithStyles } from '@material-ui/styles/withStyles'
import styles from './styles' import styles from './styles'
type BaseProps = WithStyles<typeof styles> & type BaseProps = WithStyles<typeof styles> &
Pick<TextFieldProps, 'value' | 'onChange' | 'required' | 'label' | 'type' | 'className' | 'InputProps' | 'autoFocus'> Pick<TextFieldProps, 'value' | 'onChange' | 'required' | 'label' | 'type' | 'className' | 'InputProps' | 'autoFocus' | 'inputProps'>
interface Props extends BaseProps {} interface Props extends BaseProps {}
...@@ -30,11 +30,7 @@ const TextFieldWithIcon = (props: Props) => { ...@@ -30,11 +30,7 @@ const TextFieldWithIcon = (props: Props) => {
InputProps || { InputProps || {
startAdornment: startAdornment:
type === 'password' ? ( type === 'password' ? (
<InputAdornment <InputAdornment position="start" onClick={() => setIsPasswordVisible(!isPasswordVisible)} className={classes.inputAdorment}>
position="start"
onClick={() => setIsPasswordVisible(!isPasswordVisible)}
className={classes.inputAdorment}
>
{isPasswordVisible ? <Visibility /> : <VisibilityOff />} {isPasswordVisible ? <Visibility /> : <VisibilityOff />}
</InputAdornment> </InputAdornment>
) : null, ) : null,
......
...@@ -3,7 +3,7 @@ import React from 'react' ...@@ -3,7 +3,7 @@ import React from 'react'
import { RouteComponentProps } from 'react-router' import { RouteComponentProps } from 'react-router'
import logo from '@agiliza/public/images/logo.svg' import logo from '@agiliza/public/images/logo.svg'
import { actions as sessionActions } from '@agiliza/redux/session' import { actions as loginActions } from '@agiliza/redux/ui/login'
import Divider from '@material-ui/core/Divider' import Divider from '@material-ui/core/Divider'
import MUIDrawer from '@material-ui/core/Drawer' import MUIDrawer from '@material-ui/core/Drawer'
...@@ -22,7 +22,7 @@ type BaseProps = RouteComponentProps ...@@ -22,7 +22,7 @@ type BaseProps = RouteComponentProps
export interface Props extends BaseProps { export interface Props extends BaseProps {
drawerOpen: boolean drawerOpen: boolean
toggleDrawer: () => void toggleDrawer: () => void
logout: typeof sessionActions.logout logout: typeof loginActions.logout
Items?: React.ComponentType<DrawerItemsProps> Items?: React.ComponentType<DrawerItemsProps>
} }
......
import React, { useEffect, useState } from 'react' import React, { useState } from 'react'
import { ConfigService } from '@agiliza/curio/SessionManager'
import Popover from '@material-ui/core/Popover' import Popover from '@material-ui/core/Popover'
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography'
import InfoIcon from '@material-ui/icons/InfoRounded' import InfoIcon from '@material-ui/icons/InfoRounded'
...@@ -8,9 +7,7 @@ import { WithStyles, withStyles } from '@material-ui/styles' ...@@ -8,9 +7,7 @@ import { WithStyles, withStyles } from '@material-ui/styles'
import { styles } from './styles' import { styles } from './styles'
interface Props extends WithStyles<typeof styles> { interface Props extends WithStyles<typeof styles> {}
service?: ConfigService
}
interface PopItem { interface PopItem {
key: string key: string
...@@ -19,22 +16,22 @@ interface PopItem { ...@@ -19,22 +16,22 @@ interface PopItem {
} }
const PopoverVersion = (props: Props) => { const PopoverVersion = (props: Props) => {
const { classes, service } = props const { classes } = props
const [popItems, setPopItems] = useState<PopItem[]>([]) const [popItems] = useState<PopItem[]>([])
const [anchor, setAnchor] = useState<HTMLDivElement | null>(null) const [anchor, setAnchor] = useState<HTMLDivElement | null>(null)
useEffect(() => { // useEffect(() => {
let items: PopItem[] = [{ key: 'Versão', value: VERSION }] // let items: PopItem[] = [{ key: 'Versão', value: VERSION }]
if (service) { // if (service) {
items = items.concat( // items = items.concat(
{ key: 'ISAPI', value: service?.url, underline: true }, // { key: 'ISAPI', value: service?.url, underline: true },
{ key: 'Serviço', value: service?.server }, // { key: 'Serviço', value: service?.server },
{ key: 'Porta', value: service?.port }, // { key: 'Porta', value: service?.port },
{ key: 'Sistema', value: service?.system } // { key: 'Sistema', value: service?.system }
) // )
} // }
setPopItems(items) // setPopItems(items)
}, [service]) // }, [service])
const createPopItems = (items: PopItem[]) => const createPopItems = (items: PopItem[]) =>
items.map((item, i) => ( items.map((item, i) => (
......
import React, { useMemo } from 'react'
import { City, State } from '@agiliza/api/domain'
import { ActionType } from '@agiliza/utils/hooks/state'
import { maskCEP } from '@agiliza/utils/masks'
import { SelectField, SelectFieldProps } from '@curio/components'
import { TextField, TextFieldProps } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import { AddressState as FormState } from './state'
import styles from './styles'
type ExtendedProps = WithStyles<typeof styles>
interface Props extends ExtendedProps {
states: State[]
cities: City[]
state: FormState
actions: ActionType<FormState>
}
const Address = (props: Props) => {
const { classes, state: _state, actions, states, cities } = props
const { cep, street, number, complement, state, city, district } = _state
const handleChange =
(key: keyof FormState): TextFieldProps['onChange'] =>
(evt) => {
actions.update({ [key]: evt.target.value })
}
const handleChangeSelect =
(key: keyof FormState): SelectFieldProps['onChange'] =>
(value) => {
actions.update({ [key]: value })
}
const availableCities = useMemo(() => {
if (state === '') return []
return cities.filter((ct) => ct.state === state)
}, [state])
return (
<>
<TextField variant="outlined" label="CEP" value={maskCEP(cep)} onChange={handleChange('cep')} inputProps={{ maxLength: 9 }} />
<TextField variant="outlined" label="Rua / Avenida" value={street} onChange={handleChange('street')} />
<TextField variant="outlined" label="Número" value={number} onChange={handleChange('number')} />
<TextField variant="outlined" label="Complemento" value={complement} onChange={handleChange('complement')} />
<SelectField
id="state-select"
variant="outlined"
label="Estado"
value={state}
onChange={handleChangeSelect('state')}
items={states.map((st) => ({ label: st.name, value: st.id }))}
shrink={false}
className={classes.selectField}
/>
<SelectField
id="state-select"
variant="outlined"
disabled={!state}
label="Cidade"
value={city}
onChange={handleChangeSelect('city')}
items={availableCities.map((st) => ({ label: st.name, value: st.id }))}
shrink={false}
className={classes.selectField}
/>
<TextField variant="outlined" label="Bairro" value={district} onChange={handleChange('district')} />
</>
)
}
export default withStyles(styles)(Address)
export { default } from './Address'
export * from './Address'
export * from './state'
export interface AddressState {
cep: string
street: string
number: string
complement: string
district: string
state: string
city: string
}
export const initStateAddress: AddressState = {
cep: '',
street: '',
number: '',
complement: '',
district: '',
state: '',
city: '',
}
import { Theme } from '@material-ui/core/styles'
import { createStyles } from '@material-ui/styles'
// eslint-disable-next-line
export default (theme: Theme) =>
createStyles({
form: { display: 'flex', flexDirection: 'column' },
selectField: { marginBottom: theme.spacing(2) },
})
import React from 'react'
import { City, State } from '@agiliza/api/domain/authentication'
import { getErrorProps, useErrorValidator } from '@agiliza/utils/hooks/errorValidation'
import { ActionType } from '@agiliza/utils/hooks/state'
import { maskCPFCNPJ, maskPhone } from '@agiliza/utils/masks'
import { TextField, TextFieldProps } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import AddressForm from '../Address'
import { initState, State as FirstAccessState } from './state'
import styles from './styles'
type ExtendedProps = WithStyles<typeof styles>
interface Props extends ExtendedProps {
states: State[]
cities: City[]
state: FirstAccessState
actions: ActionType<FirstAccessState>
}
const FirstAccess = (props: Props) => {
const { classes, states, cities, state, actions } = props
const { cpf, name, email, phone, ...addressState } = state
const { errorState, actions: errorActions } = useErrorValidator(initState)
const handleChange =
(key: keyof FirstAccessState): TextFieldProps['onChange'] =>
(evt) => {
actions.update({ [key]: evt.target.value })
errorActions.validate({ [key]: evt.target.value })
}
return (
<form className={classes.form}>
<TextField
variant="outlined"
label="CPF"
value={maskCPFCNPJ(cpf)}
onChange={handleChange('cpf')}
inputProps={{ maxLength: 14 }}
disabled
{...getErrorProps(errorState.cpf)}
/>
<TextField variant="outlined" label="Nome" value={name} onChange={handleChange('name')} />
<TextField variant="outlined" label="Email" value={email} onChange={handleChange('email')} />
<TextField variant="outlined" label="Telefone" value={maskPhone(phone)} onChange={handleChange('phone')} inputProps={{ maxLength: 15 }} />
<AddressForm state={addressState} actions={actions} states={states} cities={cities} />
</form>
)
}
export default withStyles(styles)(FirstAccess)
export { default } from './FirstAccess'
export * from './FirstAccess'
export * from './state'
export interface State {
cpf: string
name: string
email: string
phone: string
cep: string
street: string
number: string
complement: string
district: string
state: string
city: string
}
export const initState: State = {
cpf: '',
email: '',
name: '',
phone: '',
cep: '',
street: '',
number: '',
complement: '',
district: '',
state: '',
city: '',
}
import { Theme } from '@material-ui/core/styles'
import { createStyles } from '@material-ui/styles'
// eslint-disable-next-line
export default (theme: Theme) =>
createStyles({
form: {
display: 'flex',
flexDirection: 'column',
'& .MuiTextField-root': {
marginBottom: theme.spacing(2),
},
},
})
import React from 'react'
import TextFieldWithIcon from '@agiliza/components/atoms/TextFieldWithIcon'
import { InputAdornment, TextFieldProps } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import { AccountCircle } from '@material-ui/icons'
import styles from './styles'
type ExtendedProps = WithStyles<typeof styles>
interface Props extends ExtendedProps {
username: string
password: string
onChange(type: 'username' | 'password'): TextFieldProps['onChange']
}
const Login = (props: Props) => {
const { classes, username, password, onChange } = props
return (
<form className={classes.form}>
<TextFieldWithIcon
autoFocus
required
value={username}
onChange={onChange('username')}
label="CPF"
className={classes.usernameTxtField}
inputProps={{ maxLength: 14 }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
/>
<TextFieldWithIcon required value={password} onChange={onChange('password')} label="Senha" type="password" />
</form>
)
}
export default withStyles(styles)(Login)
export { default } from './Login'
export * from './Login'
import { Theme } from '@material-ui/core/styles'
import { createStyles } from '@material-ui/styles'
// eslint-disable-next-line
export default (theme: Theme) =>
createStyles({
title: {
fontSize: '150%',
fontWeight: 'bold',
overflowWrap: 'normal',
},
formContainer: {
height: '40%',
textAlign: 'center',
justifyContent: 'space-between',
display: 'flex',
flexDirection: 'column',
},
container: {
display: 'flex',
height: '100vh',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
},
form: {
display: 'flex',
flexDirection: 'column',
width: '100%',
margin: theme.spacing(2),
},
usernameTxtField: { marginBottom: 10 },
})
export * from './NotFound' export * from './NotFound'
export * from './AccountInputsPhoto' export * from './AccountInputsPhoto'
export * from './Login'
export * from './Address'
export * from './FirstAccess'
export enum ErrorTypes {
UNAUTHORIZED,
INTERNAL_SERVER,
FORM_VALIDATION,
MISSING_INPUT,
EXPIRED_SESSION,
INVALID_EMAIL_CODE,
ALREADY_ENABLED,
USER_NOT_FOUND,
SCHEDULE_FORBIDDEN,
}
import { UserAgent } from '@agiliza/api/useCases/shared'
import packagejson from '../../package.json'
const { version } = packagejson
export const appPlatform: UserAgent = {
appVersion: version,
platform: {
name: 'Web',
version: '1',
},
}
import { Dispatch } from 'redux'
import session from '@agiliza/redux/session'
import { MainUseCase, RequestDriver, SecurityManager, UseCaseMessageType } from '@curio/client'
import { ConnectionError, DestinataryNotFoundError } from '@curio/client/errors'
import { ABORT } from '@curio/client/utils/constants'
const STORAGE_SESSION_KEY = '@agiliza-session-token'
export interface ConfigService {
url: string
server: string
system: number
port: number
module: number
version: number
}
class Session extends MainUseCase {
module: number | undefined
version: number | undefined
constructor(token, service, driver) {
super(token, service, driver)
this.module = service.module
this.version = service.version
}
}
export class AnonymousSession extends MainUseCase {}
export class SessionManager extends SecurityManager {
public session: Session | undefined
private _dispatch: Dispatch
private _storage: Storage
private _service: ConfigService
constructor(storage: Storage, service: ConfigService, driver: RequestDriver, dispatch: Dispatch) {
super(service, driver)
this._dispatch = dispatch
this._storage = storage
this._service = service
const token = this._storage.getItem(STORAGE_SESSION_KEY)
const { actions } = session
if (process.env.NODE_ENV === 'development') {
dispatch(actions.setServiceInfo(service))
}
if (token) {
dispatch(actions.authRequest())
this.connectMainUseCase(new Session(token, this.service, driver))
this.session!.timeoutCheck()
.then(() => dispatch(actions.authSuccess()))
.catch((err) => {})
}
}
public anonymousConnection() {
return super.openMainUseCase('Administrador', '', AnonymousSession)
}
public async openMainUseCase<U extends typeof MainUseCase>(username: string, password: string) {
this.session = await super.openMainUseCase(username, password, Session)
this.session.module = this._service.module
this.session.version = this._service.version
this._storage.setItem(STORAGE_SESSION_KEY, this.session.token.toString())
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: Session) {
this.session = mainUseCase
if (!(mainUseCase instanceof AnonymousSession)) {
this._storage.setItem(STORAGE_SESSION_KEY, this.session.token.toString())
}
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)
}
private _unauthenticate(expired: boolean) {
this.session = undefined
this._storage.removeItem(STORAGE_SESSION_KEY)
if (expired) {
this._dispatch(session.actions.logout())
}
}
}
import { Dispatch } from 'redux'
import { store } from '@agiliza/redux'
import { FetchLink } from '@curio/client/links'
import { V3RequestParser } from '@curio/client/parsers'
import { createV3Validator } from '@curio/client/validators'
// import config from '../../config/config.json'
import { ConfigService, SessionManager } from './SessionManager'
interface Config {
service: ConfigService
accessToken: string
resources: {
get: string
put: string
}
logs: boolean
externalApiUrl: string
}
const createSessionManager = (config: Config, dispatch: Dispatch) => {
const link = new FetchLink(config.service)
const requestParser = new V3RequestParser(config.service)
const sessionManager = new SessionManager(
localStorage,
config.service,
(request) => {
return Promise.resolve(request)
.then(requestParser.parse)
.then(JSON.stringify)
.then(link.parse)
.then(link.post)
.then((response) => response.json())
.then(createV3Validator(request).validate)
},
dispatch
)
interface Attributes {
[index: string]: string
_Id: string
_Recipient: string
_Sender: string
_SerialNumber: string
_SignalName: string
}
if (config.logs) {
sessionManager[Symbol.observable]().subscribe({
next: (msg) => {
if (msg.signalName === '116' || msg.signalName === '120' || msg.signalName === '134' || !Number(msg.signalName)) {
const signalName = msg.signalName.substr(msg.signalName.indexOf('_') + 1).replace(/_/g, ' ')
const filteredBody = Object.keys(msg.body ? (msg.body as Attributes) : {}).reduce((acc, curr) => {
if (curr.indexOf('_') < 0) return { ...acc, [curr]: (msg.body as Attributes)[curr] }
else return acc
}, {})
const colorType = msg.type === 'response' ? 'green' : 'blue'
console.log(
`%c ${msg.type.toUpperCase()} %c ${signalName}`,
`color: white; font-weight: bolder; background: ${colorType}`,
'font-weight: bolder; font-style: italic;',
Object.keys(filteredBody).length !== 0 ? filteredBody : ''
)
}
},
error: console.error,
complete: () => console.log('complete'),
})
}
return sessionManager
}
const CONFIG_PATH = './config.json'
let sm: SessionManager
let config: Config
async function getSessionManager() {
if (sm) {
return sm
} else {
try {
const cnfg = await (await fetch(CONFIG_PATH)).json()
config = cnfg
sm = createSessionManager(config, store.dispatch)
return sm
} catch (e) {
if (e instanceof Response) {
//Not found, most likely
throw process.env.NODE_ENV === 'production'
? 'Não foi encontrado o arquivo config.json na raiz da aplicação.'
: 'Arquivo config.json faltando na pasta public.'
}
throw e
}
}
}
const wd: any = window
wd.__CURIO_GET_SESSION__ = () => getSessionManager().then((sm) => sm.session)
export { createSessionManager, getSessionManager, config }
import sessionEpic from './session' // import sessionEpic from './session'
import systemEpic from './system' // import systemEpic from './system'
export { systemEpic, sessionEpic } export {}
import { AnyAction } from 'redux' // import { AnyAction } from 'redux'
import { ActionsObservable, combineEpics, ofType } from 'redux-observable' // import { ActionsObservable, combineEpics, ofType } from 'redux-observable'
import { from, of } from 'rxjs' // import { from, of } from 'rxjs'
import { catchError, switchMap, tap } from 'rxjs/operators' // import { catchError, switchMap, tap } from 'rxjs/operators'
import { session } from '@agiliza/api/useCases' // import { session } from '@agiliza/api/useCases'
import { actions as sessionActions, types as sessionTypes } from '@agiliza/redux/session' // import { actions as sessionActions, types as sessionTypes } from '@agiliza/redux/session'
import { actions as loginActions, types as loginTypes } from '@agiliza/redux/ui/login' // import { actions as loginActions, types as loginTypes } from '@agiliza/redux/ui/login'
type LoginRequestAction = ReturnType<typeof loginActions.login> // type LoginRequestAction = ReturnType<typeof loginActions.login>
const loginEpic = (action$: ActionsObservable<LoginRequestAction>) => // const loginEpic = (action$: ActionsObservable<LoginRequestAction>) =>
action$.pipe( // action$.pipe(
ofType<AnyAction, LoginRequestAction>(loginTypes.login), // ofType<AnyAction, LoginRequestAction>(loginTypes.login),
switchMap((action) => // switchMap((action) =>
from(session.login(action.payload)).pipe( // from(session.login(action.payload)).pipe(
switchMap(() => { // switchMap(() => {
return of(loginActions.loginSuccess(), sessionActions.authSuccess()) // return of(loginActions.loginSuccess(), sessionActions.authSuccess())
}), // }),
catchError((error) => of(loginActions.loginError(error))) // catchError((error) => of(loginActions.loginError(error)))
) // )
) // )
) // )
type LogoutRequestAction = ReturnType<typeof sessionActions.logout> // type LogoutRequestAction = ReturnType<typeof sessionActions.logout>
const logoutEpic = (action$: ActionsObservable<AnyAction>) => // const logoutEpic = (action$: ActionsObservable<AnyAction>) =>
action$.pipe( // action$.pipe(
ofType<AnyAction, LogoutRequestAction>(sessionTypes.logout), // ofType<AnyAction, LogoutRequestAction>(sessionTypes.logout),
tap(() => void session.logout()), // tap(() => void session.logout()),
switchMap(() => of(sessionActions.logoutSuccess(), { type: 'RESET_STORE' })) // switchMap(() => of(sessionActions.logoutSuccess(), { type: 'RESET_STORE' }))
) // )
type InitializeRequestAction = ReturnType<typeof sessionActions.initialize> // type InitializeRequestAction = ReturnType<typeof sessionActions.initialize>
const initializeEpic = (action$: ActionsObservable<InitializeRequestAction>) => // const initializeEpic = (action$: ActionsObservable<InitializeRequestAction>) =>
action$.pipe( // action$.pipe(
ofType<AnyAction, InitializeRequestAction>(sessionTypes.initialize), // ofType<AnyAction, InitializeRequestAction>(sessionTypes.initialize),
switchMap(() => // switchMap(() =>
from(session.initialize()).pipe( // from(session.initialize()).pipe(
switchMap(() => { // switchMap(() => {
return of(sessionActions.initializeSuccess()) // return of(sessionActions.initializeSuccess())
}), // }),
catchError((error) => of(sessionActions.initializeError(error))) // catchError((error) => of(sessionActions.initializeError(error)))
) // )
) // )
) // )
export default combineEpics(loginEpic, logoutEpic, initializeEpic) // export default combineEpics(loginEpic, logoutEpic, initializeEpic)
import { AnyAction } from 'redux' // import { AnyAction } from 'redux'
import { ActionsObservable, combineEpics, ofType } from 'redux-observable' // import { ActionsObservable, combineEpics, ofType } from 'redux-observable'
import { from, of } from 'rxjs' // import { from, of } from 'rxjs'
import { catchError, switchMap } from 'rxjs/operators' // import { catchError, switchMap } from 'rxjs/operators'
import { mapSystemApiToStore } from '@agiliza/api/mappers/system' // import { mapSystemApiToStore } from '@agiliza/api/mappers/system'
import { session } from '@agiliza/api/useCases' // import { session } from '@agiliza/api/useCases'
import { actions, types } from '@agiliza/redux/ui/system' // import { actions, types } from '@agiliza/redux/ui/system'
import { getError } from '@agiliza/utils/method' // import { getError } from '@agiliza/utils/method'
type FetchMenuAction = ReturnType<typeof actions.fetchMenu> // type FetchMenuAction = ReturnType<typeof actions.fetchMenu>
const fetchMenuEpic = (action$: ActionsObservable<AnyAction>) => // const fetchMenuEpic = (action$: ActionsObservable<AnyAction>) =>
action$.pipe( // action$.pipe(
ofType<AnyAction, FetchMenuAction>(types.fetchMenu), // ofType<AnyAction, FetchMenuAction>(types.fetchMenu),
switchMap(() => // switchMap(() =>
from(session.fetchMenu()).pipe( // from(session.fetchMenu()).pipe(
switchMap((menu) => of(actions.fetchMenuSuccess(mapSystemApiToStore(menu)))), // switchMap((menu) => of(actions.fetchMenuSuccess(mapSystemApiToStore(menu)))),
catchError((error) => of(actions.fetchMenuError(getError(error)))) // catchError((error) => of(actions.fetchMenuError(getError(error))))
) // )
) // )
) // )
export default combineEpics(fetchMenuEpic) // export default combineEpics(fetchMenuEpic)
import { getActionTypes } from '@agiliza/utils/method'
import * as selectors from './selectors'
import * as shared from './shared'
import slice from './slice'
export * from './slice'
const actions = slice.actions
const reducer = slice.reducer
const types = getActionTypes(slice.actions)
export { actions, types, selectors, shared }
export default reducer
import { values } from '@agiliza/utils/method'
import { Store } from './slice'
export const getContext = (state: Store) => state
export const getContextEntities = (state: Store) => ({ states: values(state.states.byId), cities: values(state.cities.byId) })
import { normalize, schema } from 'normalizr'
import { City, GetContext, State } from '@agiliza/api/domain/authentication'
import { NormalizedEntity } from '@agiliza/utils/method'
export interface NormalizedContext {
states: NormalizedEntity<State>
cities: NormalizedEntity<City>
}
const states = new schema.Array(new schema.Entity('states'))
const cities = new schema.Array(new schema.Entity('cities'))
const contextEntities = new schema.Object({ states, cities })
export const normalizeContext = (context: GetContext['Output']) => {
const { entities } = normalize(context, contextEntities)
const normalizedEntities = entities as unknown as NormalizedContext
return normalizedEntities
}
import { City, GetContext, State } from '@agiliza/api/domain/authentication'
import { types as fetchTypes } from '@agiliza/redux/useCases/authentication'
import { EntityStore, keys } from '@agiliza/utils/method'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { PREFIX } from '../shared'
import { normalizeContext } from './shared'
export interface Store {
states: EntityStore<State>
cities: EntityStore<City>
}
export const initialState: Store = {
states: { byId: {}, allIds: [] },
cities: { byId: {}, allIds: [] },
}
export default createSlice({
name: `${PREFIX}/authentication`,
initialState,
reducers: {},
extraReducers: {
[fetchTypes.getContext.fulfilled]: (state, action: PayloadAction<GetContext['Output']>) => {
const normalized = normalizeContext(action.payload)
console.log(normalized)
state.states.byId = normalized.states
state.states.allIds = keys(normalized.states)
state.cities.byId = normalized.cities
state.cities.allIds = keys(normalized.cities)
},
},
})
// import { initialState as profileInitState, reducer as profile, State as Profiles } from './profiles' import authentication, {
initialState as authenticationInitState,
Store as AuthenticationState
} from './authentication'
import simulation, {
initialState as simulationInitState,
State as SimulationState
} from './simulation'
import { initialState as systemInitState, reducer as system, State as System } from './system' import { initialState as systemInitState, reducer as system, State as System } from './system'
// import { initialState as userInitState, reducer as user, State as Users } from './users'
export interface EntitiesState { export interface EntitiesState {
// user: Users
// profile: Profiles
system: System system: System
simulation: SimulationState
authentication: AuthenticationState
} }
const reducers = {
// user,
// profile,
system
}
export const initialState: EntitiesState = { export const initialState: EntitiesState = {
// user: userInitState, simulation: simulationInitState,
// profile: profileInitState, system: systemInitState,
system: systemInitState authentication: authenticationInitState,
}
const reducers = {
simulation,
system,
authentication,
} }
export default reducers export default reducers
export const PREFIX = 'entities'
import { getActionTypes } from '@agiliza/utils/method'
import * as selectors from './selectors'
import slice from './slice'
export * from './slice'
const actions = slice.actions
const reducer = slice.reducer
const types = getActionTypes(slice.actions)
export { actions, types, selectors }
export default reducer
import { createSelector } from '@reduxjs/toolkit'
import { Store } from './slice'
export const getAllIds = (key: keyof Store) => (state: Store) => {
return state[key].allIds
}
export const getById = (key: keyof Store) => (state: Store) => {
return state[key].byId
}
export const getSimulationCategories = createSelector(getById('simulationCategories'), getAllIds('simulationCategories'), (byId, allIds) =>
allIds.map((id) => byId[id])
)
import { SimulationCategory } from '@agiliza/api/domain'
import { types as fetchTypes } from '@agiliza/redux/useCases/simulation'
import { EntityStore, syncAllIds, syncById } from '@agiliza/utils/method'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { normalizeSimulationCategories, SIMULATION_PREFIX } from '../shared'
export interface Store {
simulationCategories: EntityStore<SimulationCategory>
}
export const initialState: Store = {
simulationCategories: { byId: {}, allIds: [] },
}
export default createSlice({
name: `${SIMULATION_PREFIX}/context`,
initialState,
reducers: {},
extraReducers: {
[fetchTypes.fetchSimulationCategories.fulfilled]: (state, action: PayloadAction<SimulationCategory[]>) => {
const normalized = normalizeSimulationCategories(action.payload)
state.simulationCategories.byId = syncById(state.simulationCategories, normalized.simulationCategories)
state.simulationCategories.allIds = syncAllIds(state.simulationCategories, normalized.simulationCategories)
},
},
})
import { combineReducers } from 'redux'
import context, { initialState as contextInitState, Store as ContextStore } from './context'
import simulation, {
initialState as simulationInitState,
Store as SimulationStore
} from './simulation'
export interface State {
context: ContextStore
simulation: SimulationStore
}
export const initialState: State = {
context: contextInitState,
simulation: simulationInitState,
}
export default combineReducers({
context,
simulation,
})
import { normalize, schema } from 'normalizr'
import { SimulationCategory, SubProduct } from '@agiliza/api/domain'
import { NormalizedEntity } from '@agiliza/utils/method'
export const SIMULATION_PREFIX = 'entities/simulation'
export interface NormalizedSimulationCategories {
simulationCategories: NormalizedEntity<SimulationCategory>
}
export interface NormalizedSubproducts {
subproducts: NormalizedEntity<SubProduct>
}
const simulationCategory = new schema.Array(new schema.Entity('simulationCategories'))
const subProduct = new schema.Array(new schema.Entity('subproducts'))
export const normalizeSimulationCategories = (sCs: SimulationCategory[]) => {
const { entities } = normalize(sCs, simulationCategory)
const normalizedEntities = entities as unknown as NormalizedSimulationCategories
return normalizedEntities
}
export const normalizeSubproducts = (sPs: SubProduct[]) => {
const { entities } = normalize(sPs, subProduct)
const normalizedEntities = entities as unknown as NormalizedSubproducts
return normalizedEntities
}
import { getActionTypes } from '@agiliza/utils/method'
import * as selectors from './selectors'
import slice from './slice'
export * from './slice'
const actions = slice.actions
const reducer = slice.reducer
const types = getActionTypes(slice.actions)
export { actions, types, selectors }
export default reducer
import { createSelector } from '@reduxjs/toolkit'
import { Store } from './slice'
export const getAllIds = (key: keyof Store) => (state: Store) => {
return state[key].allIds
}
export const getById = (key: keyof Store) => (state: Store) => {
return state[key].byId
}
export const getSubproducts = createSelector(getById('subproducts'), getAllIds('subproducts'), (byId, allIds) => allIds.map((id) => byId[id]))
import { GetSubProducts, SubProduct } from '@agiliza/api/domain'
import { types as fetchTypes } from '@agiliza/redux/useCases/simulation'
import { EntityStore, syncAllIds, syncById } from '@agiliza/utils/method'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { normalizeSubproducts, SIMULATION_PREFIX } from '../shared'
export interface Store {
subproducts: EntityStore<SubProduct>
}
export const initialState: Store = {
subproducts: { byId: {}, allIds: [] },
}
export default createSlice({
name: `${SIMULATION_PREFIX}/simulation`,
initialState,
reducers: {},
extraReducers: {
[fetchTypes.getSubproducts.fulfilled]: (state, action: PayloadAction<GetSubProducts['Output']>) => {
const normalized = normalizeSubproducts(action.payload)
state.subproducts.byId = syncById(state.subproducts, normalized.subproducts)
state.subproducts.allIds = syncAllIds(state.subproducts, normalized.subproducts)
},
},
})
import deepmerge from 'deepmerge' import deepmerge from 'deepmerge'
import { AnyAction, combineReducers, Middleware, Reducer } from 'redux' import { AnyAction, combineReducers, Middleware, Reducer } from 'redux'
import { createLogger } from 'redux-logger'
import { combineEpics, createEpicMiddleware, Epic } from 'redux-observable' import { combineEpics, createEpicMiddleware, Epic } from 'redux-observable'
import { sessionEpic } from '@agiliza/epics' // import { sessionEpic } from '@agiliza/epics'
import { configureStore } from '@reduxjs/toolkit' import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import entitiesReducer, { EntitiesState, initialState as entitiesInitState } from './entities' import entitiesReducer, { EntitiesState, initialState as entitiesInitState } from './entities'
import { errorMiddleware } from './middlewares' import { errorMiddleware, printMiddleware } from './middlewares'
import { import {
initialState as sessionInitState, initialState as sessionInitState,
reducer as sessionReducer, reducer as sessionReducer,
State as SessionState State as SessionState
} from './session' } from './session'
import uiReducer, { initialState as uiInitState, UIState } from './ui' import uiReducer, { initialState as uiInitState, UIState } from './ui'
import useCases, { initialState as useCasesInitState, UseCasesState } from './useCases'
type ReducerMap = Record<string, Reducer> type ReducerMap = Record<string, Reducer>
type Reducers = Record<string, Reducer | ReducerMap> type Reducers = Record<string, Reducer | ReducerMap>
...@@ -22,6 +22,7 @@ let registeredReducers: Reducers = { ...@@ -22,6 +22,7 @@ let registeredReducers: Reducers = {
entities: entitiesReducer, entities: entitiesReducer,
session: sessionReducer, session: sessionReducer,
ui: uiReducer, ui: uiReducer,
useCases,
} }
function recombineReducers(reducers: Reducers) { function recombineReducers(reducers: Reducers) {
...@@ -42,17 +43,23 @@ const rootReducer = (state, action: AnyAction) => { ...@@ -42,17 +43,23 @@ const rootReducer = (state, action: AnyAction) => {
return combinedReducers(state, action) return combinedReducers(state, action)
} }
const middlewares = !process.env.PRODUCTION ? [createLogger({ collapsed: true }), errorMiddleware] : [errorMiddleware]
const defaultMiddleware = getDefaultMiddleware({
serializableCheck: false,
})
const middlewares = !process.env.PRODUCTION ? [/*createLogger({ collapsed: true }), */ errorMiddleware, printMiddleware] : [errorMiddleware]
const epicMiddleware = createEpicMiddleware() const epicMiddleware = createEpicMiddleware()
const store = configureStore({ const store = configureStore({
reducer: rootReducer, reducer: rootReducer,
middleware: [...middlewares, epicMiddleware] as Middleware[], middleware: [...defaultMiddleware, ...middlewares, epicMiddleware] as Middleware[],
preloadedState: { preloadedState: {
session: sessionInitState, session: sessionInitState,
ui: uiInitState, ui: uiInitState,
entities: entitiesInitState, entities: entitiesInitState,
useCases: useCasesInitState,
}, },
devTools: process.env.NODE_ENV === 'development', devTools: process.env.NODE_ENV === 'development',
}) })
...@@ -61,7 +68,7 @@ type EpicMap = Record<string, Epic> ...@@ -61,7 +68,7 @@ type EpicMap = Record<string, Epic>
type Epics = Record<string, Epic | EpicMap> type Epics = Record<string, Epic | EpicMap>
let registeredEpics: Epics = { let registeredEpics: Epics = {
session: sessionEpic, // session: sessionEpic,
} }
function recombineEpics(epics: Epics) { function recombineEpics(epics: Epics) {
...@@ -98,6 +105,7 @@ export interface StoreState { ...@@ -98,6 +105,7 @@ export interface StoreState {
ui: UIState ui: UIState
session: SessionState session: SessionState
entities: EntitiesState entities: EntitiesState
useCases: UseCasesState
} }
const wd: any = window const wd: any = window
......
import { getError } from '@agiliza/utils/method' import { ApiError } from '@agiliza/api/domain'
import { Dispatch, Middleware, PayloadAction } from '@reduxjs/toolkit' import { ErrorTypes } from '@agiliza/constants/error'
import * as ucLogout from '@agiliza/redux/ui/login'
import { Middleware, PayloadAction, ThunkDispatch } from '@reduxjs/toolkit'
import { actions as errorActions } from './ui/error' import { actions as errorActions } from './ui/error'
export const errorMiddleware: Middleware = // export const errorMiddleware: Middleware =
({ dispatch }) => // ({ dispatch }) =>
(next: Dispatch<PayloadAction<any>>) => // (next: Dispatch<PayloadAction<any>>) =>
((action: PayloadAction<any>) => { // ((action: PayloadAction<any>) => {
if (/Error$/.test(action.type) && action.payload) { // if (/Error$/.test(action.type) && action.payload) {
const error = getError(action.payload) // const error = getError(action.payload)
dispatch(errorActions.setErrorMessage(error)) // dispatch(errorActions.setErrorMessage(error))
// }
// return next(action)
// }) as Dispatch<PayloadAction>
export const errorMiddleware: Middleware = (api) => (next: ThunkDispatch<any, any, any>) => (action: PayloadAction<ApiError>) => {
if (action.type.includes('/rejected')) {
const error = action.payload
// console.log(action);
// console.log(error)
// Todo erro de sessão expirada deve imediatamente remover a autenticação do usuário.
if (error.type === ErrorTypes.EXPIRED_SESSION) {
api.dispatch(ucLogout.actions.logout() as any)
} else if (!action.type.includes('/connect') && (action.type.includes('useCases') || action.type.includes('ui'))) {
if (error.type !== ErrorTypes.FORM_VALIDATION && error.type !== ErrorTypes.MISSING_INPUT) {
api.dispatch(errorActions.setErrorMessage(error.message || 'Erro de autenticação'))
}
}
}
return next(action)
}
export const printMiddleware: Middleware =
(api) => (next: ThunkDispatch<any, any, any>) => (action: PayloadAction<any, string, { arg: Record<string, unknown> | string }>) => {
if (action.type.includes('/pending')) {
console.log(
`%c ${action.type.substring(0, action.type.lastIndexOf('/'))} %c`,
`color: white; font-weight: bolder; background: blue`,
'font-weight: bolder; font-style: italic;',
action.meta.arg || ''
)
}
if (action.type.includes('/fulfilled')) {
console.log(
`%c ${action.type.substring(0, action.type.lastIndexOf('/'))} %c`,
`color: white; font-weight: bolder; background: green`,
'font-weight: bolder; font-style: italic;',
action.payload ? (typeof action.payload === 'object' ? (Object.keys(action.payload).length !== 0 ? action.payload : '') : action.payload) : ''
)
} }
return next(action) return next(action)
}) as Dispatch<PayloadAction> }
import { getTypesActions } from '@agiliza/utils/method' import { getTypesActions } from '@agiliza/utils/method'
import session from './reducer' import session from './reducer'
import * as selectors from './selectors'
export * from './reducer' export * from './reducer'
...@@ -9,3 +10,5 @@ export const reducer = session.reducer ...@@ -9,3 +10,5 @@ export const reducer = session.reducer
export const types = getTypesActions(session.actions) export const types = getTypesActions(session.actions)
export default session export default session
export { selectors }
import { ConfigService } from '@agiliza/curio/SessionManager' import { Customer } from '@agiliza/api/domain'
import { types as loginTypes } from '@agiliza/redux/ui/login'
import { types as authenticationTypes } from '@agiliza/redux/useCases/authentication'
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
export interface State { export interface State {
...@@ -6,7 +8,7 @@ export interface State { ...@@ -6,7 +8,7 @@ export interface State {
authenticating: boolean authenticating: boolean
authenticated: boolean authenticated: boolean
error?: string error?: string
serviceInfo?: ConfigService customer?: Customer
} }
export const initialState: State = { export const initialState: State = {
...@@ -15,42 +17,27 @@ export const initialState: State = { ...@@ -15,42 +17,27 @@ export const initialState: State = {
initializing: false, initializing: false,
} }
export type Actions = 'setServiceInfo' | 'authRequest' | 'authSuccess' | 'authError' | 'logout' | 'logoutSuccess'
const session = createSlice({ const session = createSlice({
name: 'session', name: 'session',
initialState, initialState,
reducers: { reducers: {},
initialize: (state) => { extraReducers: {
state.initializing = true [loginTypes.login.fulfilled]: (state, action: PayloadAction<Customer>) => {
},
initializeSuccess: (state) => {
state.initializing = false
},
initializeError: (state, action: PayloadAction<string | undefined>) => {
state.initializing = false
state.error = action.payload
},
authRequest: (state) => {
state.authenticating = true
state.error = undefined
},
authSuccess: (state) => {
state.authenticating = false
state.authenticated = true state.authenticated = true
state.customer = action.payload
console.log(action.payload)
}, },
authError: (state, action: PayloadAction<string | undefined>) => { [loginTypes.logout.fulfilled]: (state) => {
state.authenticating = false
state.error = action.payload
},
logout: (state) => {
state.error = undefined
},
logoutSuccess: (state) => {
state.authenticated = false state.authenticated = false
}, },
setServiceInfo: (state, action: PayloadAction<ConfigService>) => { [loginTypes.connect.fulfilled]: (state) => {
state.serviceInfo = action.payload state.authenticated = true
},
// [authenticationTypes.createCustomer.fulfilled]: (state, action: PayloadAction<Customer>) => {
// state.customer = action.payload
// },
[authenticationTypes.getLoggedCustomer.fulfilled]: (state, action: PayloadAction<Customer>) => {
state.customer = action.payload
}, },
}, },
}) })
......
...@@ -4,4 +4,4 @@ export const isAuthenticating = (state: State) => state.authenticating ...@@ -4,4 +4,4 @@ export const isAuthenticating = (state: State) => state.authenticating
export const isAuthenticated = (state: State) => state.authenticated export const isAuthenticated = (state: State) => state.authenticated
export const isInitializing = (state: State) => state.initializing export const isInitializing = (state: State) => state.initializing
export const getError = (state: State) => state.error export const getError = (state: State) => state.error
export const getServiceInfo = (state: State) => state.serviceInfo export const getCustomer = (state: State) => state.customer
...@@ -4,7 +4,7 @@ import { ...@@ -4,7 +4,7 @@ import {
State as DrawerState State as DrawerState
} from './drawer' } from './drawer'
import { initialState as errorInitialState, reducer as error, State as ErrorState } from './error' import { initialState as errorInitialState, reducer as error, State as ErrorState } from './error'
import { initialState as loginInitialState, reducer as login, State as LoginState } from './login' import login, { initialState as loginInitialState, State as LoginState } from './login'
// import { // import {
// initialState as profileInitialState, // initialState as profileInitialState,
// reducer as profile, // reducer as profile,
...@@ -35,7 +35,7 @@ const reducers = { ...@@ -35,7 +35,7 @@ const reducers = {
drawer, drawer,
// user, // user,
// profile, // profile,
system system,
} }
export const initialState: UIState = { export const initialState: UIState = {
...@@ -44,7 +44,7 @@ export const initialState: UIState = { ...@@ -44,7 +44,7 @@ export const initialState: UIState = {
drawer: drawerInitialState, drawer: drawerInitialState,
// user: userInitialState, // user: userInitialState,
// profile: profileInitialState, // profile: profileInitialState,
system: systemInitialState system: systemInitialState,
} }
export default reducers export default reducers
import { getTypesActions } from '@agiliza/utils/method'
import login from './reducer'
import * as selectors from './selectors' import * as selectors from './selectors'
import slice from './slice'
export * from './reducer' export * from './slice'
const actions = login.actions export { selectors }
const reducer = login.reducer export default slice.reducer
const types = getTypesActions(login.actions)
export { actions, reducer, types, selectors }
export default login
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
export interface State {
fetching: boolean
}
export const initialState: State = {
fetching: false,
}
interface Login {
username: string
password: string
}
const login = createSlice({
name: 'ui/login',
initialState,
reducers: {
login(state, action: PayloadAction<Login>) {
state.fetching = true
},
loginSuccess(state) {
state.fetching = false
},
loginError(state, action: PayloadAction<string>) {
state.fetching = false
},
},
})
export default login
import { State } from './reducer' import { State } from './slice'
export const isFetching = (state: State) => state.fetching export const isFetching = (state: State) => state.fetching
import { SessionRepositoryImplFactory } from '@agiliza/api/useCases/session'
import { appPlatform } from '@agiliza/constants/platform'
import {
createAsyncReducers,
getTypesThunkActions,
values,
WithSuccess
} from '@agiliza/utils/method'
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
const prefix = 'ui/login'
export interface State {
fetching: boolean
}
export const initialState: State = {
fetching: false,
}
interface Login {
username: string
password: string
}
export const actions = {
login: createAsyncThunk(`${prefix}/login`, async (args: WithSuccess<{ username: string; password: string }>, thunkApi) => {
const useCase = SessionRepositoryImplFactory.create(appPlatform)
try {
const { username, password, onSuccess } = args
const customer = await useCase.login(username, password)
onSuccess && onSuccess()
return customer
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
logout: createAsyncThunk(`${prefix}/logout`, (_, thunkApi) => {
const useCase = SessionRepositoryImplFactory.create(appPlatform)
useCase.logout()
thunkApi.dispatch({ type: 'RESET_STORE' })
return
}),
connect: createAsyncThunk(`${prefix}/connect`, (_, thunkApi) => {
const useCase = SessionRepositoryImplFactory.create(appPlatform)
try {
useCase.connect()
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
} as const
export const types = getTypesThunkActions(actions)
const login = createSlice({
name: prefix,
initialState,
reducers: {
login(state, action: PayloadAction<Login>) {
state.fetching = true
},
loginSuccess(state) {
state.fetching = false
},
loginError(state, action: PayloadAction<string>) {
state.fetching = false
},
},
extraReducers: {
...values(types).reduce((reducers, type) => ({ ...reducers, ...createAsyncReducers(type) }), {}),
},
})
export default login
import * as selectors from './selectors'
import slice from './slice'
export * from './slice'
export { selectors }
export default slice.reducer
import { State } from './slice'
export const isFetching = (state: State) => state.fetching
export const isSendingCode = (state: State) => state.sendingCode
import {
CreateCustomer,
CreatePassword,
GetLoggedCustomer,
SendCode,
VerifyCode,
VerifyCPF
} from '@agiliza/api/domain'
import { AuthenticationRepositoryImplFactory } from '@agiliza/api/useCases'
import { appPlatform } from '@agiliza/constants/platform'
import {
createAsyncReducers,
getTypesThunkActions,
values,
WithSuccess
} from '@agiliza/utils/method'
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
const prefix = 'useCases/authentication'
export interface State {
fetching: boolean
sendingCode: boolean
}
export const initialState: State = {
fetching: false,
sendingCode: false,
}
export const actions = {
getContext: createAsyncThunk(`${prefix}/getContext`, async (_, thunkApi) => {
const useCase = AuthenticationRepositoryImplFactory.create(appPlatform)
try {
return await useCase.getContext()
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
verifyCPF: createAsyncThunk(`${prefix}/verifyCPF`, async (input: WithSuccess<VerifyCPF['Input']>, thunkApi) => {
const useCase = AuthenticationRepositoryImplFactory.create(appPlatform)
try {
await useCase.verifyCPF(input)
input.onSuccess && input.onSuccess()
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
createCustomer: createAsyncThunk(`${prefix}/createCustomer`, async (input: WithSuccess<CreateCustomer['Input']>, thunkApi) => {
const useCase = AuthenticationRepositoryImplFactory.create(appPlatform)
try {
const customer = await useCase.createCustomer(input)
input.onSuccess && input.onSuccess()
return customer
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
sendCode: createAsyncThunk(`${prefix}/sendCode`, async (input: WithSuccess<SendCode['Input']>, thunkApi) => {
const useCase = AuthenticationRepositoryImplFactory.create(appPlatform)
try {
await useCase.sendCode(input)
input.onSuccess && input.onSuccess()
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
verifyCode: createAsyncThunk(`${prefix}/verifyCode`, async (input: WithSuccess<VerifyCode['Input']>, thunkApi) => {
const useCase = AuthenticationRepositoryImplFactory.create(appPlatform)
try {
await useCase.verifyCode(input)
input.onSuccess && input.onSuccess()
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
createPassword: createAsyncThunk(`${prefix}/createPassword`, async (input: WithSuccess<CreatePassword['Input']>, thunkApi) => {
const useCase = AuthenticationRepositoryImplFactory.create(appPlatform)
try {
await useCase.createPassword(input)
input.onSuccess && input.onSuccess()
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
getLoggedCustomer: createAsyncThunk(`${prefix}/getLoggedCustomer`, async (input: WithSuccess<GetLoggedCustomer['Input']>, thunkApi) => {
const useCase = AuthenticationRepositoryImplFactory.create(appPlatform)
try {
const customer = await useCase.getLoggedCustomer()
input.onSuccess && input.onSuccess()
return customer
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
} as const
export const types = getTypesThunkActions(actions)
const slice = createSlice({
name: prefix,
initialState,
reducers: {},
extraReducers: {
...values(types).reduce((reducers, type) => ({ ...reducers, ...createAsyncReducers(type) }), {}),
...createAsyncReducers(types.sendCode, 'sendingCode'),
},
})
export default slice
import authentication, {
initialState as authenticationInitialState,
State as AuthenticationState
} from './authentication'
import simulation, {
initialState as simulationInitialState,
State as SimulationState
} from './simulation'
export interface UseCasesState {
simulation: SimulationState
authentication: AuthenticationState
}
const reducers = {
simulation,
authentication,
}
export const initialState: UseCasesState = {
simulation: simulationInitialState,
authentication: authenticationInitialState,
}
export default reducers
import * as selectors from './selectors'
import slice from './slice'
export * from './slice'
export { selectors }
export default slice.reducer
import { State } from './slice'
export const isFetching = (state: State) => state.fetching
import { GetSubProducts } from '@agiliza/api/domain'
import { SimulationContextRepositoryImplFactory } from '@agiliza/api/useCases'
import { SimulationRepositoryImplFactory } from '@agiliza/api/useCases/simulation/simulation'
import { appPlatform } from '@agiliza/constants/platform'
import {
createAsyncReducers,
getTypesThunkActions,
values,
WithSuccess
} from '@agiliza/utils/method'
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
const prefix = 'useCases/simulation'
export interface State {
fetching: boolean
}
export const initialState: State = {
fetching: false,
}
export const actions = {
fetchSimulationCategories: createAsyncThunk(`${prefix}/fetchSimulationCategories`, async (_, thunkApi) => {
const useCase = SimulationContextRepositoryImplFactory.create(appPlatform)
try {
return await useCase.fetchSimulationCategories()
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}),
getSubproducts: createAsyncThunk(
`${prefix}/getSubproducts`,
async (input: WithSuccess<GetSubProducts['Input'], GetSubProducts['Output']>, thunkApi) => {
const useCase = SimulationRepositoryImplFactory.create(appPlatform)
try {
const response = await useCase.getSubproducts(input)
input.onSuccess && input.onSuccess(response)
return response
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}
),
} as const
export const types = getTypesThunkActions(actions)
const login = createSlice({
name: prefix,
initialState,
reducers: {},
extraReducers: {
...values(types).reduce((reducers, type) => ({ ...reducers, ...createAsyncReducers(type) }), {}),
},
})
export default login
import deepmerge from 'deepmerge' import deepmerge from 'deepmerge'
import { PayloadAction } from '@reduxjs/toolkit' import { createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
export const values = (obj: Record<string, any>) => { export const values = (obj: Record<string, any>) => {
return Object.values(obj || {}) return Object.values(obj || {})
...@@ -129,3 +129,59 @@ type FirstParameter<T extends (...args: any) => any> = Parameters<T>[0] ...@@ -129,3 +129,59 @@ type FirstParameter<T extends (...args: any) => any> = Parameters<T>[0]
type RequiredBy<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>> type RequiredBy<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>
export type { ActionType, FirstParameter, RequiredBy } export type { ActionType, FirstParameter, RequiredBy }
interface AsyncReducers {
pending: string
fulfilled: string
rejected: string
}
export const createAsyncReducers = <Types extends AsyncReducers, State extends { [K: string]: boolean }>(
types: Types,
property: keyof State = 'fetching'
) => {
return {
[types.pending]: (state: State) => {
state[property] = true as any
},
[types.fulfilled]: (state: State) => {
state[property] = false as any
},
[types.rejected]: (state: State) => {
state[property] = false as any
},
}
}
export const getTypesThunkActions = <Actions>(actions: Actions) => {
type Types = {
[key in keyof Actions]: {
pending: string
fulfilled: string
rejected: string
}
}
return keys(actions).reduce((acc, curr) => {
const thunkAction = actions[curr as keyof Actions] as unknown as ReturnType<typeof createAsyncThunk>
return {
...acc,
[curr]: {
pending: thunkAction.pending.type,
fulfilled: thunkAction.fulfilled.type,
rejected: thunkAction.rejected.type,
},
}
}, {} as Types)
}
export const getActionTypes = <Actions>(actions: Actions) => {
type Types = {
[key in keyof Actions]: string
}
return Object.keys(actions).reduce((acc, curr) => {
return { ...acc, [curr]: `${actions[curr as keyof Actions]}` }
}, {} as Types)
}
export type WithSuccess<T, A = void> = T & { onSuccess?: (args?: A) => void }
...@@ -135,6 +135,11 @@ export function validateEmail(field: Field<string>): FieldValidation { ...@@ -135,6 +135,11 @@ export function validateEmail(field: Field<string>): FieldValidation {
} }
} }
export const isValidCPF = (text: string) => {
const cpf = extractNumbers(text)
return cpf.length === 11
}
export function validateDate(field: Field<Date>): FieldValidation { export function validateDate(field: Field<Date>): FieldValidation {
try { try {
if (field.value !== null) field.value.toISOString() if (field.value !== null) field.value.toISOString()
......
...@@ -5,8 +5,6 @@ import { bindActionCreators, Dispatch } from 'redux' ...@@ -5,8 +5,6 @@ import { bindActionCreators, Dispatch } from 'redux'
import ButtonWithProgress from '@agiliza/components/atoms/ButtonWithProgress' import ButtonWithProgress from '@agiliza/components/atoms/ButtonWithProgress'
import TextFieldWithIcon from '@agiliza/components/atoms/TextFieldWithIcon' import TextFieldWithIcon from '@agiliza/components/atoms/TextFieldWithIcon'
import PopoverVersion from '@agiliza/components/molecules/PopoverVersion'
import { ConfigService } from '@agiliza/curio/SessionManager'
import { StoreState } from '@agiliza/redux' import { StoreState } from '@agiliza/redux'
import { isAuthenticated } from '@agiliza/redux/session/selectors' import { isAuthenticated } from '@agiliza/redux/session/selectors'
import { actions } from '@agiliza/redux/ui/login' import { actions } from '@agiliza/redux/ui/login'
...@@ -24,11 +22,10 @@ interface Props extends BaseProps { ...@@ -24,11 +22,10 @@ interface Props extends BaseProps {
authenticated: boolean authenticated: boolean
fetching: boolean fetching: boolean
loginRequest: typeof actions.login loginRequest: typeof actions.login
service?: ConfigService
} }
const Login = (props: Props) => { const Login = (props: Props) => {
const { loginRequest, authenticated, service, fetching } = props const { loginRequest, authenticated, fetching } = props
const classes = useStyles() const classes = useStyles()
...@@ -72,7 +69,7 @@ const Login = (props: Props) => { ...@@ -72,7 +69,7 @@ const Login = (props: Props) => {
Entrar Entrar
</ButtonWithProgress> </ButtonWithProgress>
</div> </div>
<PopoverVersion service={service} /> {/* <PopoverVersion service={service} /> */}
</div> </div>
) )
} }
......
...@@ -4,8 +4,8 @@ import { Redirect, Route, RouteComponentProps, Switch, useLocation } from 'react ...@@ -4,8 +4,8 @@ import { Redirect, Route, RouteComponentProps, Switch, useLocation } from 'react
import AppBar from '@agiliza/components/molecules/AppBar' import AppBar from '@agiliza/components/molecules/AppBar'
import CircularProgress from '@agiliza/components/molecules/CircularProgress' import CircularProgress from '@agiliza/components/molecules/CircularProgress'
import Drawer from '@agiliza/components/molecules/Drawer' import Drawer from '@agiliza/components/molecules/Drawer'
import { actions as sessionActions } from '@agiliza/redux/session'
import { actions as drawerActions } from '@agiliza/redux/ui/drawer' import { actions as drawerActions } from '@agiliza/redux/ui/drawer'
import { actions as loginActions } from '@agiliza/redux/ui/login'
import { withStyles, WithStyles } from '@material-ui/core/styles' import { withStyles, WithStyles } from '@material-ui/core/styles'
import connect, { ConnectedProps } from './connect' import connect, { ConnectedProps } from './connect'
...@@ -20,7 +20,7 @@ type BaseProps = RouteComponentProps & ConnectedProps & WithStyles<typeof styles ...@@ -20,7 +20,7 @@ type BaseProps = RouteComponentProps & ConnectedProps & WithStyles<typeof styles
interface Props extends BaseProps { interface Props extends BaseProps {
toggleDrawer: typeof drawerActions.toggleDrawer toggleDrawer: typeof drawerActions.toggleDrawer
logout: typeof sessionActions.logout logout: typeof loginActions.logout
} }
const Main = (props: Props) => { const Main = (props: Props) => {
...@@ -40,7 +40,7 @@ const Main = (props: Props) => { ...@@ -40,7 +40,7 @@ const Main = (props: Props) => {
<Switch> <Switch>
<Route path={PATHS.creditLines} render={(rProps) => <SimulationCreditLines {...rProps} />} /> <Route path={PATHS.creditLines} render={(rProps) => <SimulationCreditLines {...rProps} />} />
<Route path={PATHS.proposalData} render={(rProps) => <ProposalData {...rProps} />} /> <Route path={PATHS.proposalData} render={(rProps) => <ProposalData {...rProps} />} />
<Redirect from="/" to={PATHS.proposalData} /> <Redirect from="/" to={PATHS.creditLines} />
</Switch> </Switch>
</Suspense> </Suspense>
</div> </div>
......
...@@ -2,9 +2,9 @@ import { connect } from 'react-redux' ...@@ -2,9 +2,9 @@ import { connect } from 'react-redux'
import { bindActionCreators, Dispatch } from 'redux' import { bindActionCreators, Dispatch } from 'redux'
import { StoreState } from '@agiliza/redux' import { StoreState } from '@agiliza/redux'
import { actions as sessionActions } from '@agiliza/redux/session'
import { actions as drawerActions } from '@agiliza/redux/ui/drawer' import { actions as drawerActions } from '@agiliza/redux/ui/drawer'
import { isDrawerOpen } from '@agiliza/redux/ui/drawer/selectors' import { isDrawerOpen } from '@agiliza/redux/ui/drawer/selectors'
import { actions as loginActions } from '@agiliza/redux/ui/login'
import { actions as systemActions, selectors as systemSelectors } from '@agiliza/redux/ui/system' import { actions as systemActions, selectors as systemSelectors } from '@agiliza/redux/ui/system'
export interface ConnectedProps { export interface ConnectedProps {
...@@ -12,7 +12,7 @@ export interface ConnectedProps { ...@@ -12,7 +12,7 @@ export interface ConnectedProps {
fetchingMenu: boolean fetchingMenu: boolean
fetchMenu: typeof systemActions.fetchMenu fetchMenu: typeof systemActions.fetchMenu
toggleDrawer: typeof drawerActions.toggleDrawer toggleDrawer: typeof drawerActions.toggleDrawer
logout: typeof sessionActions.logout logout: typeof loginActions.logout
} }
const mapStateToProps = (state: StoreState) => ({ const mapStateToProps = (state: StoreState) => ({
...@@ -25,7 +25,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ...@@ -25,7 +25,7 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators( bindActionCreators(
{ {
toggleDrawer: drawerActions.toggleDrawer, toggleDrawer: drawerActions.toggleDrawer,
logout: sessionActions.logout, logout: loginActions.logout,
fetchMenu: systemActions.fetchMenu, fetchMenu: systemActions.fetchMenu,
}, },
dispatch dispatch
......
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import { RouteComponentProps } from 'react-router' import { RouteComponentProps } from 'react-router'
import { SimulationCategory } from '@agiliza/api/domain'
import { extractCurrency, extractNumbers } from '@agiliza/utils/extractors' import { extractCurrency, extractNumbers } from '@agiliza/utils/extractors'
import { formatCurrency } from '@agiliza/utils/formatters' import { formatCurrency } from '@agiliza/utils/formatters'
import { import {
Button, Button,
CircularProgress, CircularProgress,
IconButton,
InputAdornment, InputAdornment,
TextareaAutosize, TextareaAutosize,
TextField, TextField,
Typography Typography
} from '@material-ui/core' } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles' import { withStyles, WithStyles } from '@material-ui/core/styles'
import { Refresh as RefreshIcon } from '@material-ui/icons'
import { Autocomplete } from '@material-ui/lab' import { Autocomplete } from '@material-ui/lab'
import { creditLines } from '../../../../__mocks__/creditLines'
import { CREDIT_LINES_PATHS } from '../router' import { CREDIT_LINES_PATHS } from '../router'
import { connected, ConnectedProps } from './connect'
import SliderField from './SliderField' import SliderField from './SliderField'
import styles from './styles' import styles from './styles'
type CreditLine = typeof creditLines[0] type ExtendedProps = WithStyles<typeof styles> & RouteComponentProps & ConnectedProps
type ExtendedProps = WithStyles<typeof styles> & RouteComponentProps
interface Props extends ExtendedProps { interface Props extends ExtendedProps {
fetching: boolean fetching: boolean
} }
const CreditLinesList: React.FC<Props> = (props) => { const CreditLinesList = (props: Props) => {
const { classes, fetching, history, match } = props const { classes, fetching, history, match, simulationCategories } = props
useEffect(() => {
props.fetchSimulationCategories()
}, [])
const [selectedCreditLine, setSelectedCreditLine] = useState<CreditLine | null>(null) const [selectedSimulCategory, setSelectedSimulCategory] = useState<SimulationCategory | null>(null)
const [amount, setAmount] = useState('0') const [amount, setAmount] = useState('0')
const [paymentMonths, setPaymentMonths] = useState(0) const [paymentMonths, setPaymentMonths] = useState(0)
const [graceMonths, setGraceMonths] = useState(0) const [graceMonths, setGraceMonths] = useState(0)
const parentPath = match.path.substring(0, match.path.lastIndexOf('/')) const parentPath = match.path.substring(0, match.path.lastIndexOf('/'))
const handleSimulate = () => { const handleSimulate = () => {
if (selectedCreditLine) history.push({ pathname: parentPath + CREDIT_LINES_PATHS.simulation, state: { id: selectedCreditLine.id } }) if (selectedSimulCategory)
props.getSubproducts({
idModality: selectedSimulCategory.id,
creditValue: Number(amount),
idGracePeriod: graceMonths.toString(),
installmentsNumber: paymentMonths,
onSuccess: (subPdcs) => {
if (subPdcs && subPdcs.length > 0) history.push({ pathname: parentPath + CREDIT_LINES_PATHS.simulation })
else props.setErrorMessage('Nenhum subproduto encontrado. Tente novamente com outros valores...')
},
})
} }
return ( return (
...@@ -47,42 +62,53 @@ const CreditLinesList: React.FC<Props> = (props) => { ...@@ -47,42 +62,53 @@ const CreditLinesList: React.FC<Props> = (props) => {
<Typography variant="h5" className={classes.title}> <Typography variant="h5" className={classes.title}>
Escolha a melhor linha de crédito para você e clique em simular! Escolha a melhor linha de crédito para você e clique em simular!
</Typography> </Typography>
<Autocomplete<CreditLine> <Autocomplete<SimulationCategory>
id="credit-lines" id="credit-lines"
className={classes.creditLinesAutocomplete} className={classes.creditLinesAutocomplete}
options={creditLines} options={simulationCategories}
getOptionLabel={(option) => option.name} getOptionLabel={(option) => option.description}
getOptionSelected={(opt, val) => opt.id === selectedCreditLine?.id} getOptionSelected={(opt, val) => opt.id === selectedSimulCategory?.id}
noOptionsText="Nenhuma linha encontrado" noOptionsText="Nenhuma linha encontrada"
value={selectedCreditLine} value={selectedSimulCategory}
onChange={(_, opt) => { onChange={(_, opt) => {
setSelectedCreditLine(opt || null) setSelectedSimulCategory(opt || null)
setAmount(opt?.amount.min.toString() || '0') // setAmount(opt?.amount.min.toString() || '0')
setPaymentMonths(opt?.paymentMonths.min || 0) setPaymentMonths(opt?.maxInstallment || 0)
setGraceMonths(opt?.graceMonths.min || 0) setGraceMonths(opt?.maxGraceMonths || 0)
}} }}
renderInput={(params) => { renderInput={(params) => {
const isValid = React.isValidElement(params.InputProps.endAdornment) const isValid = React.isValidElement(params.InputProps.endAdornment)
const fetchingEndAdornment = (
<InputAdornment position="end" style={{ flex: '1', display: 'flex', justifyContent: 'flex-end' }}>
<CircularProgress size={20} />
</InputAdornment>
)
const validEndAdornment = React.cloneElement(params.InputProps.endAdornment as React.ReactElement, {
style: { marginRight: '1rem' },
})
const refreshEndAdorment = (
<InputAdornment position="end" style={{ flex: '1', display: 'flex', justifyContent: 'flex-end' }}>
<IconButton onClick={() => props.fetchSimulationCategories()}>
<RefreshIcon />
</IconButton>
</InputAdornment>
)
return ( return (
<TextField <TextField
{...params} {...params}
id="select-patient-id" id="select-patient-id"
label="Selecione ou Digite Id Paciente" label="Linha de crédito"
variant="outlined" variant="outlined"
InputProps={{ InputProps={{
ref: params.InputProps.ref, ref: params.InputProps.ref,
endAdornment: fetching ? ( endAdornment: fetching
<InputAdornment position="end" style={{ flex: '1', display: 'flex', justifyContent: 'flex-end' }}> ? fetchingEndAdornment
<CircularProgress size={20} /> : !simulationCategories.length
</InputAdornment> ? refreshEndAdorment
) : isValid ? ( : isValid
React.cloneElement(params.InputProps.endAdornment as React.ReactElement, { ? validEndAdornment
style: { marginRight: '1rem' }, : params.InputProps.endAdornment,
})
) : (
params.InputProps.endAdornment
),
}} }}
/> />
) )
...@@ -91,7 +117,7 @@ const CreditLinesList: React.FC<Props> = (props) => { ...@@ -91,7 +117,7 @@ const CreditLinesList: React.FC<Props> = (props) => {
<TextField <TextField
className={classes.description} className={classes.description}
label="Descrição da linha de crédito" label="Descrição da linha de crédito"
value={selectedCreditLine?.informations || ''} value={selectedSimulCategory?.fullDescription || ''}
rows={5} rows={5}
multiline multiline
contentEditable={false} contentEditable={false}
...@@ -105,89 +131,93 @@ const CreditLinesList: React.FC<Props> = (props) => { ...@@ -105,89 +131,93 @@ const CreditLinesList: React.FC<Props> = (props) => {
<SliderField <SliderField
className={classes.sliderField} className={classes.sliderField}
title="De quanto você precisa?" title="De quanto você precisa?"
min={selectedCreditLine?.amount.min || 0} // sliderProps={{
max={selectedCreditLine?.amount.max || 5000} // min: 0,
marks={ // max: 5000,
selectedCreditLine // marks: selectedSimulCategory
? [ // ? [
{ value: selectedCreditLine.amount.min, label: formatCurrency(selectedCreditLine.amount.min.toString()) }, // { value: 0, label: formatCurrency('0') },
{ value: selectedCreditLine.amount.max, label: formatCurrency(selectedCreditLine.amount.max.toString()) }, // { value: 99999, label: formatCurrency('99999') },
] // // { value: selectedSimulCategory.amount.min, label: formatCurrency(selectedSimulCategory.amount.min.toString()) },
: [ // // { value: selectedSimulCategory.amount.max, label: formatCurrency(selectedSimulCategory.amount.max.toString()) },
{ value: 0, label: formatCurrency('0') }, // ]
{ value: 5000, label: '' }, // : [
] // { value: 0, label: formatCurrency('0') },
} // { value: 5000, label: '' },
valueSlider={Number(amount)} // ],
// value: Number(amount),
// step: 0.01,
// onChange: (_, value) => setAmount((value as number).toString()),
// }}
valueField={formatCurrency(amount.toString(), true)} valueField={formatCurrency(amount.toString(), true)}
step={0.01} disabled={!selectedSimulCategory}
disabled={!selectedCreditLine}
onChangeSlider={(_, value) => setAmount((value as number).toString())}
onChangeField={(evt) => setAmount(extractCurrency(evt.target.value))} onChangeField={(evt) => setAmount(extractCurrency(evt.target.value))}
onBlurField={(evt) => { onBlurField={(evt) => {
const amountValue = extractCurrency(evt.target.value) // const amountValue = extractCurrency(evt.target.value)
if (selectedCreditLine) // if (selectedSimulCategory)
setAmount(Number(amountValue) <= selectedCreditLine.amount.max ? amountValue : selectedCreditLine.amount.max.toString()) // setAmount(Number(amountValue) <= selectedSimulCategory.amount.max ? amountValue : selectedSimulCategory.amount.max.toString())
}} }}
/> />
<SliderField <SliderField
className={classes.sliderField} className={classes.sliderField}
title="Quantos meses para pagar?" title="Quantos meses para pagar?"
min={selectedCreditLine?.paymentMonths.min || 0} sliderProps={{
max={selectedCreditLine?.paymentMonths.max || 12} min: 0,
marks={ max: selectedSimulCategory?.maxInstallment || 12,
selectedCreditLine marks: selectedSimulCategory
? [ ? [
{ value: selectedCreditLine.paymentMonths.min, label: `${selectedCreditLine.paymentMonths.min} meses` }, { value: 0, label: `${0} meses` },
{ value: selectedCreditLine.paymentMonths.max, label: `${selectedCreditLine.paymentMonths.max} meses` }, { value: selectedSimulCategory?.maxInstallment, label: `${selectedSimulCategory?.maxInstallment} meses` },
] ]
: [ : [
{ value: 0, label: '0 meses' }, { value: 0, label: '0 meses' },
{ value: 12, label: '' }, { value: 12, label: '' },
] ],
} value: Number(paymentMonths),
valueSlider={Number(paymentMonths)} onChange: (_, value) => setPaymentMonths(value as number),
valueField={paymentMonths.toString() + ' meses'} }}
disabled={!selectedCreditLine} valueField={paymentMonths.toString()}
onChangeSlider={(_, value) => setPaymentMonths(value as number)} suffix={`m${paymentMonths > 1 ? 'e' : 'ê'}s${paymentMonths > 1 ? 'es' : ''}`}
onChangeField={(evt) => setPaymentMonths(Number(evt.target.value))} disabled={!selectedSimulCategory}
onChangeField={(evt) => setPaymentMonths(Number(extractNumbers(evt.target.value)))}
onBlurField={(evt) => { onBlurField={(evt) => {
const paymentMonthsValue = Number(extractNumbers(evt.target.value)) const paymentMonthsValue = Number(extractNumbers(evt.target.value))
if (selectedCreditLine) if (selectedSimulCategory)
setPaymentMonths( setPaymentMonths(
paymentMonthsValue <= selectedCreditLine.paymentMonths.max ? paymentMonthsValue : selectedCreditLine.paymentMonths.max paymentMonthsValue <= selectedSimulCategory.maxInstallment ? paymentMonthsValue : selectedSimulCategory.maxInstallment
) )
}} }}
/> />
<SliderField <SliderField
className={classes.sliderField} className={classes.sliderField}
title="Quantos meses de carência?" title="Quantos dias de carência?"
min={selectedCreditLine?.graceMonths.min || 0} sliderProps={{
max={selectedCreditLine?.graceMonths.max || 12} min: 0,
marks={ max: selectedSimulCategory?.maxGraceMonths || 12,
selectedCreditLine marks: selectedSimulCategory
? [ ? [
{ value: selectedCreditLine.graceMonths.min, label: `${selectedCreditLine.graceMonths.min} meses` }, { value: 0, label: `${0} dias` },
{ value: selectedCreditLine.graceMonths.max, label: `${selectedCreditLine.graceMonths.max} meses` }, { value: selectedSimulCategory?.maxGraceMonths, label: `${selectedSimulCategory?.maxGraceMonths} dias` },
] ]
: [ : [
{ value: 0, label: '0 meses' }, { value: 0, label: '0 dias' },
{ value: 12, label: '' }, { value: 12, label: '' },
] ],
} value: Number(graceMonths),
valueSlider={Number(graceMonths)} onChange: (_, value) => setGraceMonths(value as number),
valueField={graceMonths.toString() + ' meses'} }}
disabled={!selectedCreditLine} valueField={graceMonths.toString()}
onChangeSlider={(_, value) => setGraceMonths(value as number)} suffix={`dia${graceMonths > 1 ? 's' : ''}`}
onChangeField={(evt) => setGraceMonths(Number(evt.target.value))} disabled={!selectedSimulCategory}
onChangeField={(evt) => setGraceMonths(Number(extractNumbers(evt.target.value)))}
onBlurField={(evt) => { onBlurField={(evt) => {
const graceMonthsValue = Number(extractNumbers(evt.target.value)) const graceMonthsValue = Number(extractNumbers(evt.target.value))
if (selectedCreditLine) if (selectedSimulCategory)
setGraceMonths(graceMonthsValue <= selectedCreditLine.graceMonths.max ? graceMonthsValue : selectedCreditLine.graceMonths.max) setGraceMonths(graceMonthsValue <= selectedSimulCategory.maxGraceMonths ? graceMonthsValue : selectedSimulCategory.maxGraceMonths)
}} }}
/> />
<div className={classes.btnContainer}> <div className={classes.btnContainer}>
<Button disabled={!selectedCreditLine} variant="contained" color="secondary" onClick={handleSimulate}> <Button disabled={!selectedSimulCategory} variant="contained" color="secondary" onClick={handleSimulate}>
Simular Simular
</Button> </Button>
</div> </div>
...@@ -197,4 +227,4 @@ const CreditLinesList: React.FC<Props> = (props) => { ...@@ -197,4 +227,4 @@ const CreditLinesList: React.FC<Props> = (props) => {
) )
} }
export default withStyles(styles)(CreditLinesList) export default connected(withStyles(styles)(CreditLinesList))
...@@ -6,35 +6,33 @@ type ExtendedProps = Record<string, unknown> ...@@ -6,35 +6,33 @@ type ExtendedProps = Record<string, unknown>
interface Props extends ExtendedProps { interface Props extends ExtendedProps {
title: string title: string
min: SliderProps['min']
max: SliderProps['max']
marks?: SliderProps['marks']
valueSlider: number
valueField: string valueField: string
step?: SliderProps['step'] sliderProps?: Pick<SliderProps, 'min' | 'max' | 'marks' | 'step'> & {
onChangeSlider: (event: React.ChangeEvent<Record<string, unknown>>, value: number | number[]) => void onChange: (event: React.ChangeEvent<Record<string, unknown>>, value: number | number[]) => void
value: number
}
disabled: boolean
onChangeField: TextFieldProps['onChange'] onChangeField: TextFieldProps['onChange']
onBlurField: TextFieldProps['onBlur'] onBlurField: TextFieldProps['onBlur']
disabled: boolean
className?: string className?: string
suffix?: string
} }
const SliderField = (props: Props) => { const SliderField = (props: Props) => {
const { className, title, min, max, marks, valueSlider, valueField, step = 1, disabled, onChangeSlider, onChangeField, onBlurField } = props const { className, title, valueField, disabled, onChangeField, onBlurField, suffix, sliderProps } = props
const { step = 1 } = sliderProps || { step: 1 }
return ( return (
<div className={className}> <div className={className}>
<Typography gutterBottom>{title}</Typography> <Typography gutterBottom>{title}</Typography>
<Slider {sliderProps && <Slider {...sliderProps} disabled={disabled} step={step} />}
<TextField
style={{ width: '18%' }}
disabled={disabled} disabled={disabled}
min={min} value={valueField}
max={max} onChange={onChangeField}
value={valueSlider} onBlur={onBlurField}
onChange={onChangeSlider} InputProps={{ endAdornment: suffix && <Typography>{suffix}</Typography> }}
// valueLabelDisplay="auto"
step={step}
marks={marks}
/> />
<TextField style={{ width: '12%' }} disabled={disabled} value={valueField} onChange={onChangeField} onBlur={onBlurField} />
</div> </div>
) )
} }
......
import { connect } from 'react-redux'
import { SimulationCategory } from '@agiliza/api/domain'
import { StoreState } from '@agiliza/redux'
import * as entSimulationContext from '@agiliza/redux/entities/simulation/context'
import { actions as errorActions } from '@agiliza/redux/ui/error'
import * as ucSimulation from '@agiliza/redux/useCases/simulation'
import { bindActionCreators, Dispatch } from '@reduxjs/toolkit'
export interface ConnectedProps {
fetching: boolean
simulationCategories: SimulationCategory[]
fetchSimulationCategories: typeof ucSimulation.actions.fetchSimulationCategories
getSubproducts: typeof ucSimulation.actions.getSubproducts
setErrorMessage: typeof errorActions.setErrorMessage
}
type StateProps = Pick<ConnectedProps, 'fetching' | 'simulationCategories'>
type DispatchProps = Pick<ConnectedProps, 'fetchSimulationCategories' | 'getSubproducts' | 'setErrorMessage'>
const mapStateToProps = (state: StoreState): StateProps => ({
fetching: ucSimulation.selectors.isFetching(state.useCases.simulation),
simulationCategories: entSimulationContext.selectors.getSimulationCategories(state.entities.simulation.context),
})
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps =>
bindActionCreators(
{
fetchSimulationCategories: ucSimulation.actions.fetchSimulationCategories,
getSubproducts: ucSimulation.actions.getSubproducts,
setErrorMessage: errorActions.setErrorMessage,
},
dispatch
)
export const connected = connect(mapStateToProps, mapDispatchToProps)
import React, { useEffect, useMemo } from 'react' import React, { useEffect, useState } from 'react'
import { SubProduct } from '@agiliza/api/domain'
import { formatCurrency } from '@agiliza/utils/formatters' import { formatCurrency } from '@agiliza/utils/formatters'
import { Grid } from '@material-ui/core' import { SelectField } from '@curio/components'
import { Button, Grid } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles' import { withStyles, WithStyles } from '@material-ui/core/styles'
import { creditLinesData } from '../../../../../__mocks__/creditLines' import { connected, ConnectedProps } from './connect'
import GridLine from './GridLine' import GridLine from './GridLine'
import InstallmentsDialog from './InstallmentsDialog'
import styles from './styles' import styles from './styles'
type ExtendedProps = WithStyles<typeof styles> type ExtendedProps = WithStyles<typeof styles> & ConnectedProps
interface Props extends ExtendedProps { interface Props extends ExtendedProps {
id: string
onChangeCanGoForward: (vl: boolean) => void onChangeCanGoForward: (vl: boolean) => void
} }
const CreditLinesInfo = (props: Props) => { const CreditLinesInfo = (props: Props) => {
const { classes, id, onChangeCanGoForward } = props const { classes, onChangeCanGoForward, subproducts } = props
const creditLine = useMemo(() => creditLinesData[id], [id]) const [selectedSP, setSelectedSP] = useState<SubProduct | undefined>()
const [open, setOpen] = useState(false)
useEffect(() => { useEffect(() => {
onChangeCanGoForward(true) onChangeCanGoForward(!!selectedSP)
}, []) }, [selectedSP])
return ( return (
<div className={classes.contentContainer}> <div className={classes.contentContainer}>
<div className={classes.content}> <div className={classes.content}>
<SelectField
id="subproducts"
className={classes.selectField}
label="Opções de Subproduto"
items={subproducts.map((sp) => ({ label: sp.description || '', value: sp.id }))}
value={selectedSP?.id || ''}
onChange={(value) => {
const subprd = subproducts.find((sp) => sp.id === value)
setSelectedSP(subprd)
}}
shrink={false}
/>
<Grid container className={classes.gridContainer}> <Grid container className={classes.gridContainer}>
<GridLine label="Número de parcelas" value={creditLine?.paymentMonths} /> <GridLine label="Número de parcelas" value={selectedSP?.maxAmountInstallment || ''} />
<GridLine label="Valor das parcelas" value={formatCurrency(creditLine?.amount.toFixed(2))} /> {/* <GridLine label="Valor das parcelas" value={formatCurrency(selectedSP?.amount.toFixed(2))} /> */}
<GridLine label="Taxa de juros" value={`${creditLine?.interestRate} %`} /> <GridLine label="Taxa de juros" value={selectedSP?.fee ? `${selectedSP?.fee} %` : ''} />
<GridLine label="Custo efetivo total (CET)" value={`${creditLine?.effectiveTotalCost} %`} /> <GridLine label="Custo efetivo total (CET)" value={selectedSP?.IOF !== undefined ? `${selectedSP?.IOF} %` : ''} />
<GridLine label="Taxa abertura crédito (TAC)" value={formatCurrency(creditLine?.openingCreditRate.toFixed(2))} /> <GridLine label="Taxa abertura crédito (TAC)" value={formatCurrency(selectedSP?.TAC?.toFixed(2))} />
</Grid> </Grid>
<Button variant="text" onClick={() => setOpen(true)} disabled={!selectedSP}>
Exibir opções de parcelamento
</Button>
<InstallmentsDialog open={open} onClose={() => setOpen(false)} installmentOptions={selectedSP?.installementOptions} />
</div> </div>
</div> </div>
) )
} }
export default withStyles(styles)(CreditLinesInfo) export default connected(withStyles(styles)(CreditLinesInfo))
import React from 'react'
import { InstallmentOption } from '@agiliza/api/domain'
import { formatCurrency } from '@agiliza/utils/formatters'
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogProps,
DialogTitle,
Grid,
Typography
} from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import styles from './styles'
type ExtendedProps = Pick<DialogProps, 'open'> & WithStyles<typeof styles>
interface Props extends ExtendedProps {
onClose: () => void
installmentOptions?: InstallmentOption[]
}
const InstallmentsDialog = (props: Props) => {
const { classes, open, onClose, installmentOptions } = props
return (
<Dialog onClose={onClose} aria-labelledby="simple-dialog-title" open={open} PaperProps={{ className: classes.dialog }}>
<DialogTitle>Tipos de parcelamento</DialogTitle>
<DialogContent>
<Grid container className={classes.gridContainer}>
<Grid item xs={6}>
<Typography className={classes.gridHeaderText}>Número de parcelas</Typography>
</Grid>
<Grid item xs={3}>
<Typography className={classes.gridHeaderText}>Valor parcela</Typography>
</Grid>
<Grid item xs={3}>
<Typography className={classes.gridHeaderText}>Valor total</Typography>
</Grid>
</Grid>
{installmentOptions?.map((iO) => (
<Grid container item xs={12} key={iO.id}>
<Grid item xs={6}>
{iO.installmentAmount && `${iO.installmentAmount || ''} parcela${iO.installmentAmount > 1 ? 's' : ''}`}
</Grid>
<Grid item xs={3}>
{formatCurrency(iO.installmentValue?.toString() || '')}
</Grid>
<Grid item xs={3}>
{formatCurrency(iO.netValue?.toString() || '')}
</Grid>
{/* <GridLine label="Número de parcelas" value={iO.installmentAmount || ''} />
<GridLine label="Valor de cada parcela" value={iO.installmentValue || ''} />
<GridLine label="Valor total" value={iO.netValue || ''} /> */}
</Grid>
))}
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Fechar</Button>
</DialogActions>
</Dialog>
)
}
export default withStyles(styles)(InstallmentsDialog)
export { default } from './InstallmentsDialog'
export * from './InstallmentsDialog'
import { Theme } from '@material-ui/core/styles'
import { createStyles } from '@material-ui/styles'
import sharedStyles from '../styles'
// eslint-disable-next-line
export default (theme: Theme) =>
createStyles({
...sharedStyles(theme),
gridContainer: { marginBottom: theme.spacing(1) },
dialog: { height: '45%', width: '65%' },
gridHeaderText: { fontWeight: 'bold' },
})
import { connect } from 'react-redux'
import { SubProduct } from '@agiliza/api/domain'
import { StoreState } from '@agiliza/redux'
import * as entSimulation from '@agiliza/redux/entities/simulation/simulation'
export interface ConnectedProps {
subproducts: SubProduct[]
}
type StateProps = Pick<ConnectedProps, 'subproducts'>
// type DispatchProps = Record<string, any>
const mapStateToProps = (state: StoreState): StateProps => ({
subproducts: entSimulation.selectors.getSubproducts(state.entities.simulation.simulation),
})
// const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => bindActionCreators({}, dispatch)
export const connected = connect(mapStateToProps)
...@@ -7,6 +7,7 @@ import sharedStyles from '../styles' ...@@ -7,6 +7,7 @@ import sharedStyles from '../styles'
export default (theme: Theme) => export default (theme: Theme) =>
createStyles({ createStyles({
...sharedStyles(theme), ...sharedStyles(theme),
gridContainer: {}, gridContainer: { marginBottom: theme.spacing(2) },
gridItem: { whiteSpace: 'nowrap' }, gridItem: { whiteSpace: 'nowrap' },
selectField: { marginBottom: theme.spacing(2) },
}) })
import React, { useMemo, useState } from 'react' import React, { useMemo, useState } from 'react'
import { RouteComponentProps, useLocation } from 'react-router' import { RouteComponentProps } from 'react-router'
import { PATHS } from '@agiliza/views/Main/DrawerItems' import { PATHS } from '@agiliza/views/Main/DrawerItems'
import { Button, Step, StepLabel, Stepper } from '@material-ui/core' import { Button, Step, StepLabel, Stepper } from '@material-ui/core'
...@@ -12,16 +12,15 @@ import { ...@@ -12,16 +12,15 @@ import {
import CreditLinesInfo from './CreditLinesInfo' import CreditLinesInfo from './CreditLinesInfo'
import styles from './styles' import styles from './styles'
import UserForm from './UserForm' import UserForm from './UserForm'
import VerificationCode from './VerificationCode'
type ExtendedProps = WithStyles<typeof styles> & RouteComponentProps type ExtendedProps = WithStyles<typeof styles> & RouteComponentProps
interface Props extends ExtendedProps {} interface Props extends ExtendedProps {}
const Simulation = (props: Props) => { const Simulation = (props: Props) => {
const { classes, history } = props const { classes, ...routerProps } = props
const { history } = routerProps
// const theme = useTheme() // const theme = useTheme()
const { state: lState } = useLocation<{ id: string }>()
const [activeStep, setActiveStep] = useState(0) const [activeStep, setActiveStep] = useState(0)
const [canGoForward, setCanGoForward] = useState(true) const [canGoForward, setCanGoForward] = useState(true)
...@@ -32,11 +31,9 @@ const Simulation = (props: Props) => { ...@@ -32,11 +31,9 @@ const Simulation = (props: Props) => {
const renderStep = (step: number) => { const renderStep = (step: number) => {
switch (step) { switch (step) {
case 0: case 0:
return <CreditLinesInfo id={lState.id} onChangeCanGoForward={handleChangeCanGoForward} /> return <CreditLinesInfo onChangeCanGoForward={handleChangeCanGoForward} />
case 1: case 1:
return <UserForm onChangeCanGoForward={handleChangeCanGoForward} /> return <UserForm onChangeCanGoForward={handleChangeCanGoForward} />
case 2:
return <VerificationCode onChangeCanGoForward={handleChangeCanGoForward} />
} }
} }
...@@ -49,7 +46,7 @@ const Simulation = (props: Props) => { ...@@ -49,7 +46,7 @@ const Simulation = (props: Props) => {
setActiveStep((prevActiveStep) => prevActiveStep - 1) setActiveStep((prevActiveStep) => prevActiveStep - 1)
} }
const steps = useMemo(() => 3, []) const steps = useMemo(() => 2, [])
return ( return (
<div className={classes.pageContent}> <div className={classes.pageContent}>
...@@ -66,9 +63,6 @@ const Simulation = (props: Props) => { ...@@ -66,9 +63,6 @@ const Simulation = (props: Props) => {
<Step> <Step>
<StepLabel>Dados pessoais</StepLabel> <StepLabel>Dados pessoais</StepLabel>
</Step> </Step>
<Step>
<StepLabel>Código de verificação</StepLabel>
</Step>
</Stepper> </Stepper>
<Button size="large" onClick={handleNext} disabled={!canGoForward} className={classes.stepperBtn}> <Button size="large" onClick={handleNext} disabled={!canGoForward} className={classes.stepperBtn}>
{activeStep === steps - 1 ? ( {activeStep === steps - 1 ? (
......
import React from 'react'
import TextFieldWithIcon from '@agiliza/components/atoms/TextFieldWithIcon'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import styles from './styles'
type ExtendedProps = WithStyles<typeof styles>
interface Props extends ExtendedProps {
password: string
confirmPassword: string
onChange(type: 'password' | 'confirmPassword'): (value: string) => void
}
const CreatePassword = (props: Props) => {
const { classes, password, confirmPassword, onChange } = props
return (
<div className={classes.dialogContent}>
<TextFieldWithIcon label="Senha" value={password} onChange={(evt) => onChange('password')(evt.target.value)} type="password" />
<TextFieldWithIcon
label="Confirmar senha"
value={confirmPassword}
onChange={(evt) => onChange('confirmPassword')(evt.target.value)}
type="password"
/>
</div>
)
}
export default withStyles(styles)(CreatePassword)
export { default } from './CreatePassword'
export * from './CreatePassword'
...@@ -7,5 +7,5 @@ import sharedStyles from '../styles' ...@@ -7,5 +7,5 @@ import sharedStyles from '../styles'
export default (theme: Theme) => export default (theme: Theme) =>
createStyles({ createStyles({
...sharedStyles(theme), ...sharedStyles(theme),
codeField: { marginTop: theme.spacing(5) }, title: { marginBottom: theme.spacing(2) },
}) })
import React, { useEffect, useMemo, useState } from 'react'
import ButtonWithProgress from '@agiliza/components/atoms/ButtonWithProgress'
import FirstAccessTemplate, { initState } from '@agiliza/components/templates/FirstAccess'
import { useFormState } from '@agiliza/utils/hooks/state'
import { values } from '@agiliza/utils/method'
import { isValidCPF } from '@agiliza/utils/validators'
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogProps,
DialogTitle,
MobileStepper
} from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import {
KeyboardArrowLeft as KeyboardArrowLeftIcon,
KeyboardArrowRight as KeyboardArrowRightIcon
} from '@material-ui/icons'
import { connected, ConnectedProps } from './connect'
import CreatePassword from './CreatePassword'
import styles from './styles'
import VerifyCode from './VerifyCode'
import VerifyCPF from './VerifyCPF'
type ExtendedProps = Pick<DialogProps, 'open'> & WithStyles<typeof styles> & ConnectedProps
interface Props extends ExtendedProps {
onClose: () => void
}
const FirstAccessDialog = (props: Props) => {
const { classes, open, onClose, context, fetching, sendingCode } = props
const { state, actions } = useFormState('firstAccessForm', initState)
const [code, setCode] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
useEffect(() => {
if (!context.states.length) props.getContext()
}, [])
const { states, cities } = context
const [activeStep, setActiveStep] = useState(0)
const { cpf, email } = state
const handleNext = () => {
switch (activeStep) {
case 0:
props.verifyCPF({
cpf,
onSuccess: () => setActiveStep((prevActiveStep) => prevActiveStep + 1),
})
break
case 1:
props.createCustomer({
...state,
onSuccess: () => {
props.sendCode({ cpf, email, onSuccess: () => setActiveStep((prevActiveStep) => prevActiveStep + 1) })
},
})
break
case 2:
props.verifyCode({ cpf, code, onSuccess: () => setActiveStep((prevActiveStep) => prevActiveStep + 1) })
break
case 3:
props.createPassword({
cpf,
code,
password,
onSuccess: () => {
props.login({ username: cpf, password, onSuccess: onClose })
},
})
break
default:
setActiveStep((prevActiveStep) => prevActiveStep + 1)
}
}
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1)
}
const steps = useMemo(() => 4, [])
const canGoForward = useMemo(() => {
switch (activeStep) {
case 0:
return isValidCPF(state.cpf)
case 1:
return values(state).every((st) => !!st)
case 2:
return !!code
case 3:
return password && confirmPassword && password === confirmPassword
}
}, [state, code, activeStep, confirmPassword, password])
const renderStep = (step: number) => {
switch (step) {
case 0:
return <VerifyCPF cpf={state.cpf} onChange={(cpf) => actions.update({ cpf })} />
case 1:
return <FirstAccessTemplate state={state} actions={actions} states={states} cities={cities} />
case 2:
return <VerifyCode code={code} onChange={(value) => setCode(value)} onResendCode={() => props.sendCode({ cpf, email })} />
case 3:
return (
<CreatePassword
password={password}
confirmPassword={confirmPassword}
onChange={(type) => (value) => {
if (type === 'password') setPassword(value)
if (type === 'confirmPassword') setConfirmPassword(value)
}}
/>
)
}
}
return (
<Dialog onClose={onClose} aria-labelledby="simple-dialog-title" open={open} PaperProps={{ className: classes.dialog }}>
<DialogTitle>Primeiro acesso</DialogTitle>
<DialogContent>{renderStep(activeStep)}</DialogContent>
<DialogActions>
<MobileStepper
variant="dots"
steps={steps}
position="static"
activeStep={activeStep}
className={classes.stepper}
nextButton={
<ButtonWithProgress
fetching={fetching || sendingCode}
size="small"
variant="text"
onClick={handleNext}
disabled={!canGoForward || activeStep === steps}
>
{activeStep === steps - 1 ? (
'Finalizar'
) : (
<>
Próximo
<KeyboardArrowRightIcon />
</>
)}
</ButtonWithProgress>
}
backButton={
<Button size="small" onClick={handleBack} disabled={activeStep === 0}>
<KeyboardArrowLeftIcon />
Anterior
</Button>
}
/>
</DialogActions>
</Dialog>
)
}
export default connected(withStyles(styles)(FirstAccessDialog))
import React from 'react'
import { maskCPFCNPJ } from '@agiliza/utils/masks'
import { TextField, Typography } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import styles from './styles'
type ExtendedProps = WithStyles<typeof styles>
interface Props extends ExtendedProps {
cpf: string
onChange: (value: string) => void
}
const VerifyCPF = (props: Props) => {
const { classes, cpf, onChange } = props
return (
<div className={classes.dialogContent}>
<Typography className={classes.title}>Informe o seu CPF</Typography>
<TextField
variant="outlined"
// label="CPF"
value={maskCPFCNPJ(cpf)}
onChange={(evt) => onChange(evt.target.value)}
inputProps={{ maxLength: 14 }}
/>
</div>
)
}
export default withStyles(styles)(VerifyCPF)
import { Theme } from '@material-ui/core/styles'
import { createStyles } from '@material-ui/styles'
import sharedStyles from '../styles'
// eslint-disable-next-line
export default (theme: Theme) =>
createStyles({
...sharedStyles(theme),
title: { marginBottom: theme.spacing(2) },
})
import React from 'react'
import { extractNumbers } from '@agiliza/utils/method'
import { Button, TextField, Typography } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import styles from './styles'
type ExtendedProps = WithStyles<typeof styles>
interface Props extends ExtendedProps {
code: string
onChange: (value: string) => void
onResendCode: () => void
}
const VerifyCode = (props: Props) => {
const { classes, code, onChange } = props
return (
<div className={classes.dialogContent}>
<Typography className={classes.title}>Foi enviado um código de verificação por email.</Typography>
<TextField variant="outlined" value={code} onChange={(evt) => onChange(extractNumbers(evt.target.value))} inputProps={{ maxLength: 14 }} />
<div style={{ display: 'flex', width: '100%', justifyContent: 'flex-end' }} onClick={props.onResendCode}>
<Button>Re-enviar código</Button>
</div>
</div>
)
}
export default withStyles(styles)(VerifyCode)
import { Theme } from '@material-ui/core/styles'
import { createStyles } from '@material-ui/styles'
import sharedStyles from '../styles'
// eslint-disable-next-line
export default (theme: Theme) =>
createStyles({
...sharedStyles(theme),
title: { marginBottom: theme.spacing(2) },
})
import { connect } from 'react-redux'
import { bindActionCreators, Dispatch } from 'redux'
import { AuthenticationContext } from '@agiliza/api/domain'
import { StoreState } from '@agiliza/redux'
import * as entAuthentication from '@agiliza/redux/entities/authentication'
import * as uiLogin from '@agiliza/redux/ui/login'
import * as ucAuthentication from '@agiliza/redux/useCases/authentication'
export interface ConnectedProps {
fetching: boolean
sendingCode: boolean
context: AuthenticationContext
getContext: typeof ucAuthentication.actions.getContext
verifyCPF: typeof ucAuthentication.actions.verifyCPF
createCustomer: typeof ucAuthentication.actions.createCustomer
sendCode: typeof ucAuthentication.actions.sendCode
verifyCode: typeof ucAuthentication.actions.verifyCode
createPassword: typeof ucAuthentication.actions.createPassword
login: typeof uiLogin.actions.login
}
type StateProps = Pick<ConnectedProps, 'fetching' | 'context' | 'sendingCode'>
type DispatchProps = Pick<ConnectedProps, 'getContext' | 'verifyCPF' | 'createCustomer' | 'verifyCode' | 'createPassword' | 'sendCode' | 'login'>
const mapStateToProps = (state: StoreState): StateProps => ({
fetching: ucAuthentication.selectors.isFetching(state.useCases.authentication),
context: entAuthentication.selectors.getContextEntities(state.entities.authentication),
sendingCode: ucAuthentication.selectors.isSendingCode(state.useCases.authentication),
})
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps =>
bindActionCreators(
{
getContext: ucAuthentication.actions.getContext,
verifyCPF: ucAuthentication.actions.verifyCPF,
createCustomer: ucAuthentication.actions.createCustomer,
sendCode: ucAuthentication.actions.sendCode,
verifyCode: ucAuthentication.actions.verifyCode,
createPassword: ucAuthentication.actions.createPassword,
login: uiLogin.actions.login,
},
dispatch
)
export const connected = connect(mapStateToProps, mapDispatchToProps)
export { default } from './FirstAccessDialog'
export * from './FirstAccessDialog'
import { Theme } from '@material-ui/core/styles'
import { createStyles } from '@material-ui/styles'
import sharedStyles from '../styles'
// eslint-disable-next-line
export default (theme: Theme) =>
createStyles({
...sharedStyles(theme),
gridContainer: { marginBottom: theme.spacing(1) },
dialog: { height: '60%', width: '65%' },
gridHeaderText: { fontWeight: 'bold' },
stepper: { ...sharedStyles(theme).stepper, background: 'white' },
dialogContent: {
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
'& .MuiTextField-root': {
marginBottom: theme.spacing(2),
},
},
})
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { formatPhone } from '@agiliza/utils/formatters' import ButtonWithProgress from '@agiliza/components/atoms/ButtonWithProgress'
import { import LoginTemplate from '@agiliza/components/templates/Login'
getErrorProps, import { formatCPF } from '@agiliza/utils/formatters'
invalidState, import { extractNumbers } from '@agiliza/utils/method'
useErrorValidator, import { Button, TextFieldProps, Typography } from '@material-ui/core'
Validators,
validState
} from '@agiliza/utils/hooks/errorValidation'
import { isValidEmail, isValidPhone } from '@agiliza/utils/validators'
import { TextField, TextFieldProps } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles' import { withStyles, WithStyles } from '@material-ui/core/styles'
import { connected, ConnectedProps } from './connect'
import FirstAccessDialog from './FirstAccessDialog'
import styles from './styles' import styles from './styles'
interface UserFormFieldProps extends Pick<TextFieldProps, 'label' | 'value' | 'onChange' | 'error' | 'helperText' | 'inputProps'> {} // interface UserFormFieldProps extends Pick<TextFieldProps, 'label' | 'value' | 'onChange' | 'error' | 'helperText' | 'inputProps'> {}
const UserFormField = ({ helperText, ...props }: UserFormFieldProps) => <TextField fullWidth margin="normal" variant="outlined" {...props} /> // const UserFormField = ({ helperText, ...props }: UserFormFieldProps) => <TextField fullWidth margin="normal" variant="outlined" {...props} />
interface State { interface State {
name: string username: string
phoneNumber: string password: string
email: string
} }
const validators: Validators<Partial<State>> = { // const validators: Validators<Partial<State>> = {
phoneNumber: (vl) => (isValidPhone(vl) ? validState(vl) : invalidState(vl, '')), // phoneNumber: (vl) => (isValidPhone(vl) ? validState(vl) : invalidState(vl, '')),
email: (vl) => (isValidEmail(vl) ? validState(vl) : invalidState(vl, '')), // email: (vl) => (isValidEmail(vl) ? validState(vl) : invalidState(vl, '')),
} // }
type ExtendedProps = WithStyles<typeof styles> type ExtendedProps = WithStyles<typeof styles> & ConnectedProps
interface Props extends ExtendedProps { interface Props extends ExtendedProps {
onChangeCanGoForward: (vl: boolean) => void onChangeCanGoForward: (vl: boolean) => void
} }
const UserForm = (props: Props) => { const UserForm = (props: Props) => {
const { classes, onChangeCanGoForward } = props const { classes, fetching, onChangeCanGoForward, customer } = props
const [state, setState] = useState<State>({ email: '', phoneNumber: '', name: '' }) const [state, setState] = useState<State>({ username: '', password: '' })
const { name, phoneNumber, email } = state const { username, password } = state
const [open, setOpen] = useState(false)
const { errorState, validState: isValidState, actions } = useErrorValidator({ email: '', phoneNumber: '', name: '' }, validators) // const { errorState, validState: isValidState, actions } = useErrorValidator({ username: '', password: '' })
useEffect(() => { useEffect(() => {
onChangeCanGoForward(isValidState) onChangeCanGoForward(false)
}, []) }, [])
useEffect(() => { // useEffect(() => {
onChangeCanGoForward(isValidState) // onChangeCanGoForward(isValidState)
}, [isValidState]) // }, [isValidState])
const handleChange = const handleChange =
(key: keyof State): TextFieldProps['onChange'] => (key: keyof State): TextFieldProps['onChange'] =>
(evt) => { (evt) => {
setState({ ...state, [key]: evt.target.value }) setState({ ...state, [key]: evt.target.value })
actions.validate({ [key]: evt.target.value }) // actions.validate({ [key]: evt.target.value })
} }
return ( return (
<div className={classes.contentContainer}> <div className={classes.contentContainer}>
<div className={classes.content}> <div className={classes.content}>
<UserFormField label="Nome" value={name} onChange={handleChange('name')} {...getErrorProps(errorState.name)} /> <Typography>Identifique-se</Typography>
<LoginTemplate username={formatCPF(username)} password={password} onChange={handleChange} />
<ButtonWithProgress
className={classes.btn}
fetching={fetching}
disabled={!username || !!customer}
color="secondary"
onClick={() =>
props.login({
username: extractNumbers(username),
password,
onSuccess: () => {
onChangeCanGoForward(true)
},
})
}
>
{!customer ? 'Identificar' : 'Identificado'}
</ButtonWithProgress>
<Button variant="contained" className={classes.btn} color="secondary" onClick={() => setOpen(true)} disabled={!!customer}>
Primeiro aceeso
</Button>
{/* <UserFormField label="Nome" value={name} onChange={handleChange('name')} {...getErrorProps(errorState.name)} />
<UserFormField <UserFormField
label="Número de celular" label="Número de celular"
value={formatPhone(phoneNumber)} value={formatPhone(phoneNumber)}
...@@ -68,10 +87,11 @@ const UserForm = (props: Props) => { ...@@ -68,10 +87,11 @@ const UserForm = (props: Props) => {
inputProps={{ maxLength: 15 }} inputProps={{ maxLength: 15 }}
{...getErrorProps(errorState.phoneNumber)} {...getErrorProps(errorState.phoneNumber)}
/> />
<UserFormField label="Email" value={email} onChange={handleChange('email')} {...getErrorProps(errorState.email)} /> <UserFormField label="Email" value={email} onChange={handleChange('email')} {...getErrorProps(errorState.email)} /> */}
</div> </div>
<FirstAccessDialog open={open} onClose={() => setOpen(false)} />
</div> </div>
) )
} }
export default withStyles(styles)(UserForm) export default connected(withStyles(styles)(UserForm))
import { connect } from 'react-redux'
import { bindActionCreators, Dispatch } from 'redux'
import { Customer } from '@agiliza/api/domain'
import { StoreState } from '@agiliza/redux'
import * as session from '@agiliza/redux/session'
import * as uiLogin from '@agiliza/redux/ui/login'
export interface ConnectedProps {
fetching: boolean
login: typeof uiLogin.actions.login
customer?: Customer
}
type StateProps = Pick<ConnectedProps, 'fetching' | 'customer'>
type DispatchProps = Pick<ConnectedProps, 'login'>
const mapStateToProps = (state: StoreState): StateProps => ({
fetching: uiLogin.selectors.isFetching(state.ui.login),
customer: session.selectors.getCustomer(state.session),
})
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps =>
bindActionCreators(
{
login: uiLogin.actions.login,
},
dispatch
)
export const connected = connect(mapStateToProps, mapDispatchToProps)
...@@ -7,6 +7,8 @@ import sharedStyles from '../styles' ...@@ -7,6 +7,8 @@ import sharedStyles from '../styles'
export default (theme: Theme) => export default (theme: Theme) =>
createStyles({ createStyles({
...sharedStyles(theme), ...sharedStyles(theme),
content: { ...sharedStyles(theme).content, width: '15%' },
gridContainer: {}, gridContainer: {},
gridItem: { whiteSpace: 'nowrap' }, gridItem: { whiteSpace: 'nowrap' },
btn: { width: '100%', marginBottom: theme.spacing(1) },
}) })
import React, { useEffect, useState } from 'react'
import { extractNumbers } from '@agiliza/utils/extractors'
import { TextField, TextFieldProps, Typography } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import styles from './styles'
type ExtendedProps = WithStyles<typeof styles>
interface Props extends ExtendedProps {
onChangeCanGoForward: (vl: boolean) => void
}
const VerificationCode = (props: Props) => {
const { classes, onChangeCanGoForward } = props
const [code, setCode] = useState('')
useEffect(() => {
onChangeCanGoForward(code.length === 6)
}, [code])
const handleChange: TextFieldProps['onChange'] = (evt) => {
setCode(extractNumbers(evt.target.value))
}
return (
<div className={classes.contentContainer}>
<div className={classes.content}>
<Typography variant="h5">Foi enviado um código de verificação por email.</Typography>
<TextField
label="Código de verificação"
value={code}
onChange={handleChange}
variant="outlined"
className={classes.codeField}
inputProps={{ maxLength: 6 }}
/>
</div>
</div>
)
}
export default withStyles(styles)(VerificationCode)
export { default } from './VerificationCode'
export * from './VerificationCode'
import React, { lazy, Suspense } from 'react' import React, { lazy, Suspense } from 'react'
import { Redirect, Route, RouteComponentProps, Switch } from 'react-router' import { Redirect, Route, RouteComponentProps, Switch } from 'react-router'
import NotFound from '@agiliza/components/templates/NotFound'
import { CircularProgress } from '@material-ui/core' import { CircularProgress } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles' import { withStyles, WithStyles } from '@material-ui/core/styles'
...@@ -25,14 +24,15 @@ const CreditLinesRouter = (props: Props) => { ...@@ -25,14 +24,15 @@ const CreditLinesRouter = (props: Props) => {
<div className={classes.pageContent}> <div className={classes.pageContent}>
<Suspense fallback={<CircularProgress className={classes.circularProgress} />}> <Suspense fallback={<CircularProgress className={classes.circularProgress} />}>
<Switch> <Switch>
<Route exact path={match.path + CREDIT_LINES_PATHS.creditLinesList} render={(rProps) => <CreditLinesList fetching={false} {...rProps} />} /> <Route exact path={match.path + CREDIT_LINES_PATHS.creditLinesList} render={(rProps) => <CreditLinesList {...rProps} />} />
<Route <Route
exact exact
path={match.path + CREDIT_LINES_PATHS.simulation} path={match.path + CREDIT_LINES_PATHS.simulation}
render={(rProps) => { render={(rProps) => {
const { location } = rProps as RouteComponentProps<any, any, { id: string }> return <Simulation {...rProps} />
if (location.state?.id) return <Simulation {...rProps} /> // const { location } = rProps as RouteComponentProps<any, any, { id: string }>
else return <NotFound {...rProps} /> // if (location.state?.id) return <Simulation {...rProps} />
// else return <NotFound {...rProps} />
}} }}
/> />
<Redirect from={match.path} to={match.path + CREDIT_LINES_PATHS.creditLinesList} /> <Redirect from={match.path} to={match.path + CREDIT_LINES_PATHS.creditLinesList} />
......
...@@ -6,18 +6,12 @@ import { bindActionCreators, Dispatch } from 'redux' ...@@ -6,18 +6,12 @@ import { bindActionCreators, Dispatch } from 'redux'
import AuthRoute from '@agiliza/components/atoms/AuthRoute' import AuthRoute from '@agiliza/components/atoms/AuthRoute'
import CircularProgress from '@agiliza/components/molecules/CircularProgress' import CircularProgress from '@agiliza/components/molecules/CircularProgress'
import { ConfigService } from '@agiliza/curio/SessionManager'
import { StoreState } from '@agiliza/redux' import { StoreState } from '@agiliza/redux'
import { actions as sessionActions } from '@agiliza/redux/session' import { isAuthenticated, isAuthenticating, isInitializing } from '@agiliza/redux/session/selectors'
import {
getServiceInfo,
isAuthenticated,
isAuthenticating,
isInitializing
} from '@agiliza/redux/session/selectors'
import { actions as drawerActions } from '@agiliza/redux/ui/drawer' import { actions as drawerActions } from '@agiliza/redux/ui/drawer'
import { isDrawerOpen } from '@agiliza/redux/ui/drawer/selectors' import { isDrawerOpen } from '@agiliza/redux/ui/drawer/selectors'
import { getError } from '@agiliza/redux/ui/error/selectors' import { getError } from '@agiliza/redux/ui/error/selectors'
import { actions as loginActions } from '@agiliza/redux/ui/login'
const Login = lazy(() => import('./Login')) const Login = lazy(() => import('./Login'))
const Main = lazy(() => import('./Main')) const Main = lazy(() => import('./Main'))
...@@ -28,15 +22,14 @@ interface Props { ...@@ -28,15 +22,14 @@ interface Props {
error: string error: string
authenticated: boolean authenticated: boolean
authenticating: boolean authenticating: boolean
initialize: typeof sessionActions.initialize connect: typeof loginActions.connect
serviceInfo?: ConfigService
} }
const Views = (props: Props) => { const Views = (props: Props) => {
const { authenticating, serviceInfo, initialize } = props const { authenticating } = props
useEffect(() => { useEffect(() => {
initialize() props.connect()
}, []) }, [])
if (authenticating) return <CircularProgress root /> if (authenticating) return <CircularProgress root />
...@@ -45,7 +38,7 @@ const Views = (props: Props) => { ...@@ -45,7 +38,7 @@ const Views = (props: Props) => {
<Router history={history}> <Router history={history}>
<Suspense fallback={<CircularProgress />}> <Suspense fallback={<CircularProgress />}>
<Switch> <Switch>
<Route path="/login" render={(renderProps) => <Login {...renderProps} service={serviceInfo} />} /> <Route path="/login" render={(renderProps) => <Login {...renderProps} />} />
<AuthRoute path="/" component={Main} /> <AuthRoute path="/" component={Main} />
</Switch> </Switch>
</Suspense> </Suspense>
...@@ -57,7 +50,6 @@ const mapStateToProps = (state: StoreState) => ({ ...@@ -57,7 +50,6 @@ const mapStateToProps = (state: StoreState) => ({
error: getError(state.ui.error), error: getError(state.ui.error),
authenticated: isAuthenticated(state.session), authenticated: isAuthenticated(state.session),
authenticating: isAuthenticating(state.session) || isInitializing(state.session), authenticating: isAuthenticating(state.session) || isInitializing(state.session),
serviceInfo: getServiceInfo(state.session),
drawerOpen: isDrawerOpen(state.ui.drawer), drawerOpen: isDrawerOpen(state.ui.drawer),
}) })
...@@ -65,7 +57,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ...@@ -65,7 +57,7 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators( bindActionCreators(
{ {
toggleDrawer: drawerActions.toggleDrawer, toggleDrawer: drawerActions.toggleDrawer,
initialize: sessionActions.initialize, connect: loginActions.connect,
}, },
dispatch dispatch
) )
......
...@@ -57,5 +57,5 @@ ...@@ -57,5 +57,5 @@
"typeRoots": ["node_modules/@types", "src/typings"] "typeRoots": ["node_modules/@types", "src/typings"]
}, },
"include": ["src"], "include": ["src"],
"exclude": ["node_modules", "dist", "public"] "exclude": ["node_modules", "dist"]
} }
...@@ -113,6 +113,7 @@ const makeCommonPlugins = (env) => [ ...@@ -113,6 +113,7 @@ const makeCommonPlugins = (env) => [
new webpack.EnvironmentPlugin({ new webpack.EnvironmentPlugin({
PUBLIC_PATH: '/', PUBLIC_PATH: '/',
NODE_ENV: env.production ? 'production' : 'development', NODE_ENV: env.production ? 'production' : 'development',
PRODUCTION: env.production,
APP_TITLE: 'Agiliza', APP_TITLE: 'Agiliza',
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
...@@ -188,5 +189,8 @@ module.exports = (env) => { ...@@ -188,5 +189,8 @@ module.exports = (env) => {
new CssMinimizerPlugin(), new CssMinimizerPlugin(),
], ],
}, },
experiments: {
topLevelAwait: true,
},
} }
} }
...@@ -1264,6 +1264,11 @@ ...@@ -1264,6 +1264,11 @@
prop-types "^15.7.2" prop-types "^15.7.2"
react-is "^16.8.0 || ^17.0.0" react-is "^16.8.0 || ^17.0.0"
"@microcredito/client@^0.7.11":
version "0.7.11"
resolved "https://nexus.dev.evologica.com.br/repository/npm/@microcredito/client/-/client-0.7.11.tgz#6601c681c6452c71d18e82f9ddca8bddeedf44a7"
integrity sha1-ZgHGgcZFLHHRjoL53cqL3e7fRKc=
"@nodelib/fs.scandir@2.1.4": "@nodelib/fs.scandir@2.1.4":
version "2.1.4" version "2.1.4"
resolved "https://nexus.dev.evologica.com.br/repository/npm/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" resolved "https://nexus.dev.evologica.com.br/repository/npm/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment