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"
"@dynamo:registry" "https://nexus.dev.evologica.com.br/repository/npm"
\ No newline at end of file
......@@ -11,14 +11,7 @@
"useBuiltIns": "usage",
"targets": {
"ie": "11",
"browsers": [
"edge >= 16",
"safari >= 9",
"firefox >= 57",
"ie >= 11",
"ios >= 9",
"chrome >= 49"
]
"browsers": ["edge >= 16", "safari >= 9", "firefox >= 57", "ie >= 11", "ios >= 9", "chrome >= 49"]
}
}
],
......@@ -58,6 +51,7 @@
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-async-to-generator",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-arrow-functions"
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-syntax-top-level-await"
]
}
{
"service": {
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSON?version=4",
"server": "192.168.0.34",
"system": 19,
"port": 9801,
"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://172.16.17.3: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"
}
{
"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": {
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSON?version=4",
"server": "192.168.0.34",
"system": 19,
"port": 9801,
"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": "https://microcredito.dev.evologica.com.br",
"APP_NAME_BROKER": "@microcredito/agente",
"APP_NAME_CUSTOMER": "@microcredito/cliente",
"SESSION_KEY_BROKER": "@microcredito/agente",
"SESSION_KEY_CUSTOMER": "@microcredito/cliente"
}
{
"service": {
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSON?version=4",
"server": "192.168.0.34",
"system": 19,
"port": 9801,
"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": "https://microcredito.test.evologica.com.br",
"APP_NAME_BROKER": "@microcredito-staging/agente",
"APP_NAME_CUSTOMER": "@microcredito-staging/cliente",
"SESSION_KEY_BROKER": "@microcredito-staging/agente",
"SESSION_KEY_CUSTOMER": "@microcredito-staging/cliente"
}
......@@ -90,6 +90,7 @@
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "4.0.0-alpha.58",
"@material-ui/pickers": "^3.3.10",
"@microcredito/client": "^0.7.11",
"@reduxjs/toolkit": "^1.2.5",
"@types/react-swipeable-views": "^0.13.1",
"@types/react-swipeable-views-utils": "^0.13.3",
......
{
"service": {
"url": "https://srvd1.dev.evologica.com.br/cxClient/cxIsapiClient.dll/gatewayJSON?version=4",
"server": "192.168.0.34",
"system": 19,
"port": 9801,
"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://172.16.17.3: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
......@@ -8,6 +8,6 @@ server.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'))
})
server.listen(3010, () => {
console.log('Listening at http://localhost:3010/')
server.listen(3000, () => {
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'
export * from './domain'
export * from './api'
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'
import * as apiSession from './session'
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)
export * from './session'
export * from './simulation'
export * from './authentication'
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 { SolicitarRedefinicao, VerificarCodigo, RedefinirSenha } from '../interfaces/Login'
// // import { createMenuAPI } from '../dynamo'
// const ucRecuperarSenha = {
// id: '3522',
// SOLICITAR_REDEFINICAO: 'RM_SOLICITAR_REDEFINICAO',
// VERIFICAR_CODIGO: 'RM_VERIFICAR_CODIGO',
// DEFIINIR_SENHA: 'RM_DEFINIR_SENHA'
// }
// // import { SolicitarRedefinicao, VerificarCodigo, RedefinirSenha } from '../interfaces/Login'
interface LoginParams {
username: string
password: string
}
// // const ucRecuperarSenha = {
// // id: '3522',
// // SOLICITAR_REDEFINICAO: 'RM_SOLICITAR_REDEFINICAO',
// // VERIFICAR_CODIGO: 'RM_VERIFICAR_CODIGO',
// // DEFIINIR_SENHA: 'RM_DEFINIR_SENHA'
// // }
export const initialize = async () => {
await getSessionManager()
}
// interface LoginParams {
// username: string
// password: string
// }
export const login = async ({ username, password }: LoginParams) => {
const sessionManager = await getSessionManager()
return sessionManager.openMainUseCase(username, password)
}
// export const initialize = async () => {
// await getSessionManager()
// }
export const logout = async () => {
const sessionManager = await getSessionManager()
sessionManager?.session?.abort()
}
// export const login = async ({ username, password }: LoginParams) => {
// const sessionManager = await getSessionManager()
// 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) => {
// const mainUseCase = await sessionManager.anonymousConnection()
......@@ -72,3 +78,70 @@ export const { fetchMenu } = createMenuAPI()
// enviarCodigoSenha,
// 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'
import { styles } from './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 {
fetching?: boolean
}
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 (
<Button
className={className}
disabled={disabled || fetching}
onClick={onClick}
variant="contained"
color={color}
type={type}
>
<Button className={className} disabled={disabled || fetching} onClick={onClick} variant={variant} color={color} type={type} size={size}>
{fetching && <LinearProgress className={classes.progress} />}
{children}
</Button>
......
......@@ -9,7 +9,7 @@ import withStyles, { WithStyles } from '@material-ui/styles/withStyles'
import styles from './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 {}
......@@ -30,11 +30,7 @@ const TextFieldWithIcon = (props: Props) => {
InputProps || {
startAdornment:
type === 'password' ? (
<InputAdornment
position="start"
onClick={() => setIsPasswordVisible(!isPasswordVisible)}
className={classes.inputAdorment}
>
<InputAdornment position="start" onClick={() => setIsPasswordVisible(!isPasswordVisible)} className={classes.inputAdorment}>
{isPasswordVisible ? <Visibility /> : <VisibilityOff />}
</InputAdornment>
) : null,
......
......@@ -3,7 +3,7 @@ import React from 'react'
import { RouteComponentProps } from 'react-router'
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 MUIDrawer from '@material-ui/core/Drawer'
......@@ -22,7 +22,7 @@ type BaseProps = RouteComponentProps
export interface Props extends BaseProps {
drawerOpen: boolean
toggleDrawer: () => void
logout: typeof sessionActions.logout
logout: typeof loginActions.logout
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 Typography from '@material-ui/core/Typography'
import InfoIcon from '@material-ui/icons/InfoRounded'
......@@ -8,9 +7,7 @@ import { WithStyles, withStyles } from '@material-ui/styles'
import { styles } from './styles'
interface Props extends WithStyles<typeof styles> {
service?: ConfigService
}
interface Props extends WithStyles<typeof styles> {}
interface PopItem {
key: string
......@@ -19,22 +16,22 @@ interface PopItem {
}
const PopoverVersion = (props: Props) => {
const { classes, service } = props
const [popItems, setPopItems] = useState<PopItem[]>([])
const { classes } = props
const [popItems] = useState<PopItem[]>([])
const [anchor, setAnchor] = useState<HTMLDivElement | null>(null)
useEffect(() => {
let items: PopItem[] = [{ key: 'Versão', value: VERSION }]
if (service) {
items = items.concat(
{ key: 'ISAPI', value: service?.url, underline: true },
{ key: 'Serviço', value: service?.server },
{ key: 'Porta', value: service?.port },
{ key: 'Sistema', value: service?.system }
)
}
setPopItems(items)
}, [service])
// useEffect(() => {
// let items: PopItem[] = [{ key: 'Versão', value: VERSION }]
// if (service) {
// items = items.concat(
// { key: 'ISAPI', value: service?.url, underline: true },
// { key: 'Serviço', value: service?.server },
// { key: 'Porta', value: service?.port },
// { key: 'Sistema', value: service?.system }
// )
// }
// setPopItems(items)
// }, [service])
const createPopItems = (items: PopItem[]) =>
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 './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 systemEpic from './system'
// import sessionEpic from './session'
// import systemEpic from './system'
export { systemEpic, sessionEpic }
export {}
import { AnyAction } from 'redux'
import { ActionsObservable, combineEpics, ofType } from 'redux-observable'
import { from, of } from 'rxjs'
import { catchError, switchMap, tap } from 'rxjs/operators'
// import { AnyAction } from 'redux'
// import { ActionsObservable, combineEpics, ofType } from 'redux-observable'
// import { from, of } from 'rxjs'
// import { catchError, switchMap, tap } from 'rxjs/operators'
import { session } from '@agiliza/api/useCases'
import { actions as sessionActions, types as sessionTypes } from '@agiliza/redux/session'
import { actions as loginActions, types as loginTypes } from '@agiliza/redux/ui/login'
// import { session } from '@agiliza/api/useCases'
// import { actions as sessionActions, types as sessionTypes } from '@agiliza/redux/session'
// 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>) =>
action$.pipe(
ofType<AnyAction, LoginRequestAction>(loginTypes.login),
switchMap((action) =>
from(session.login(action.payload)).pipe(
switchMap(() => {
return of(loginActions.loginSuccess(), sessionActions.authSuccess())
}),
catchError((error) => of(loginActions.loginError(error)))
)
)
)
type LogoutRequestAction = ReturnType<typeof sessionActions.logout>
// const loginEpic = (action$: ActionsObservable<LoginRequestAction>) =>
// action$.pipe(
// ofType<AnyAction, LoginRequestAction>(loginTypes.login),
// switchMap((action) =>
// from(session.login(action.payload)).pipe(
// switchMap(() => {
// return of(loginActions.loginSuccess(), sessionActions.authSuccess())
// }),
// catchError((error) => of(loginActions.loginError(error)))
// )
// )
// )
// type LogoutRequestAction = ReturnType<typeof sessionActions.logout>
const logoutEpic = (action$: ActionsObservable<AnyAction>) =>
action$.pipe(
ofType<AnyAction, LogoutRequestAction>(sessionTypes.logout),
tap(() => void session.logout()),
switchMap(() => of(sessionActions.logoutSuccess(), { type: 'RESET_STORE' }))
)
// const logoutEpic = (action$: ActionsObservable<AnyAction>) =>
// action$.pipe(
// ofType<AnyAction, LogoutRequestAction>(sessionTypes.logout),
// tap(() => void session.logout()),
// switchMap(() => of(sessionActions.logoutSuccess(), { type: 'RESET_STORE' }))
// )
type InitializeRequestAction = ReturnType<typeof sessionActions.initialize>
// type InitializeRequestAction = ReturnType<typeof sessionActions.initialize>
const initializeEpic = (action$: ActionsObservable<InitializeRequestAction>) =>
action$.pipe(
ofType<AnyAction, InitializeRequestAction>(sessionTypes.initialize),
switchMap(() =>
from(session.initialize()).pipe(
switchMap(() => {
return of(sessionActions.initializeSuccess())
}),
catchError((error) => of(sessionActions.initializeError(error)))
)
)
)
// const initializeEpic = (action$: ActionsObservable<InitializeRequestAction>) =>
// action$.pipe(
// ofType<AnyAction, InitializeRequestAction>(sessionTypes.initialize),
// switchMap(() =>
// from(session.initialize()).pipe(
// switchMap(() => {
// return of(sessionActions.initializeSuccess())
// }),
// catchError((error) => of(sessionActions.initializeError(error)))
// )
// )
// )
export default combineEpics(loginEpic, logoutEpic, initializeEpic)
// export default combineEpics(loginEpic, logoutEpic, initializeEpic)
import { AnyAction } from 'redux'
import { ActionsObservable, combineEpics, ofType } from 'redux-observable'
import { from, of } from 'rxjs'
import { catchError, switchMap } from 'rxjs/operators'
// import { AnyAction } from 'redux'
// import { ActionsObservable, combineEpics, ofType } from 'redux-observable'
// import { from, of } from 'rxjs'
// import { catchError, switchMap } from 'rxjs/operators'
import { mapSystemApiToStore } from '@agiliza/api/mappers/system'
import { session } from '@agiliza/api/useCases'
import { actions, types } from '@agiliza/redux/ui/system'
import { getError } from '@agiliza/utils/method'
// import { mapSystemApiToStore } from '@agiliza/api/mappers/system'
// import { session } from '@agiliza/api/useCases'
// import { actions, types } from '@agiliza/redux/ui/system'
// import { getError } from '@agiliza/utils/method'
type FetchMenuAction = ReturnType<typeof actions.fetchMenu>
// type FetchMenuAction = ReturnType<typeof actions.fetchMenu>
const fetchMenuEpic = (action$: ActionsObservable<AnyAction>) =>
action$.pipe(
ofType<AnyAction, FetchMenuAction>(types.fetchMenu),
switchMap(() =>
from(session.fetchMenu()).pipe(
switchMap((menu) => of(actions.fetchMenuSuccess(mapSystemApiToStore(menu)))),
catchError((error) => of(actions.fetchMenuError(getError(error))))
)
)
)
// const fetchMenuEpic = (action$: ActionsObservable<AnyAction>) =>
// action$.pipe(
// ofType<AnyAction, FetchMenuAction>(types.fetchMenu),
// switchMap(() =>
// from(session.fetchMenu()).pipe(
// switchMap((menu) => of(actions.fetchMenuSuccess(mapSystemApiToStore(menu)))),
// 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 userInitState, reducer as user, State as Users } from './users'
export interface EntitiesState {
// user: Users
// profile: Profiles
system: System
simulation: SimulationState
authentication: AuthenticationState
}
const reducers = {
// user,
// profile,
system
}
export const initialState: EntitiesState = {
// user: userInitState,
// profile: profileInitState,
system: systemInitState
simulation: simulationInitState,
system: systemInitState,
authentication: authenticationInitState,
}
const reducers = {
simulation,
system,
authentication,
}
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 { AnyAction, combineReducers, Middleware, Reducer } from 'redux'
import { createLogger } from 'redux-logger'
import { combineEpics, createEpicMiddleware, Epic } from 'redux-observable'
import { sessionEpic } from '@agiliza/epics'
import { configureStore } from '@reduxjs/toolkit'
// import { sessionEpic } from '@agiliza/epics'
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import entitiesReducer, { EntitiesState, initialState as entitiesInitState } from './entities'
import { errorMiddleware } from './middlewares'
import { errorMiddleware, printMiddleware } from './middlewares'
import {
initialState as sessionInitState,
reducer as sessionReducer,
State as SessionState
} from './session'
import uiReducer, { initialState as uiInitState, UIState } from './ui'
import useCases, { initialState as useCasesInitState, UseCasesState } from './useCases'
type ReducerMap = Record<string, Reducer>
type Reducers = Record<string, Reducer | ReducerMap>
......@@ -22,6 +22,7 @@ let registeredReducers: Reducers = {
entities: entitiesReducer,
session: sessionReducer,
ui: uiReducer,
useCases,
}
function recombineReducers(reducers: Reducers) {
......@@ -42,17 +43,23 @@ const rootReducer = (state, action: AnyAction) => {
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 store = configureStore({
reducer: rootReducer,
middleware: [...middlewares, epicMiddleware] as Middleware[],
middleware: [...defaultMiddleware, ...middlewares, epicMiddleware] as Middleware[],
preloadedState: {
session: sessionInitState,
ui: uiInitState,
entities: entitiesInitState,
useCases: useCasesInitState,
},
devTools: process.env.NODE_ENV === 'development',
})
......@@ -61,7 +68,7 @@ type EpicMap = Record<string, Epic>
type Epics = Record<string, Epic | EpicMap>
let registeredEpics: Epics = {
session: sessionEpic,
// session: sessionEpic,
}
function recombineEpics(epics: Epics) {
......@@ -98,6 +105,7 @@ export interface StoreState {
ui: UIState
session: SessionState
entities: EntitiesState
useCases: UseCasesState
}
const wd: any = window
......
import { getError } from '@agiliza/utils/method'
import { Dispatch, Middleware, PayloadAction } from '@reduxjs/toolkit'
import { ApiError } from '@agiliza/api/domain'
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'
export const errorMiddleware: Middleware =
({ dispatch }) =>
(next: Dispatch<PayloadAction<any>>) =>
((action: PayloadAction<any>) => {
if (/Error$/.test(action.type) && action.payload) {
const error = getError(action.payload)
dispatch(errorActions.setErrorMessage(error))
// export const errorMiddleware: Middleware =
// ({ dispatch }) =>
// (next: Dispatch<PayloadAction<any>>) =>
// ((action: PayloadAction<any>) => {
// if (/Error$/.test(action.type) && action.payload) {
// const error = getError(action.payload)
// 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)
}) as Dispatch<PayloadAction>
}
import { getTypesActions } from '@agiliza/utils/method'
import session from './reducer'
import * as selectors from './selectors'
export * from './reducer'
......@@ -9,3 +10,5 @@ export const reducer = session.reducer
export const types = getTypesActions(session.actions)
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'
export interface State {
......@@ -6,7 +8,7 @@ export interface State {
authenticating: boolean
authenticated: boolean
error?: string
serviceInfo?: ConfigService
customer?: Customer
}
export const initialState: State = {
......@@ -15,42 +17,27 @@ export const initialState: State = {
initializing: false,
}
export type Actions = 'setServiceInfo' | 'authRequest' | 'authSuccess' | 'authError' | 'logout' | 'logoutSuccess'
const session = createSlice({
name: 'session',
initialState,
reducers: {
initialize: (state) => {
state.initializing = true
},
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
reducers: {},
extraReducers: {
[loginTypes.login.fulfilled]: (state, action: PayloadAction<Customer>) => {
state.authenticated = true
state.customer = action.payload
console.log(action.payload)
},
authError: (state, action: PayloadAction<string | undefined>) => {
state.authenticating = false
state.error = action.payload
},
logout: (state) => {
state.error = undefined
},
logoutSuccess: (state) => {
[loginTypes.logout.fulfilled]: (state) => {
state.authenticated = false
},
setServiceInfo: (state, action: PayloadAction<ConfigService>) => {
state.serviceInfo = action.payload
[loginTypes.connect.fulfilled]: (state) => {
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
export const isAuthenticated = (state: State) => state.authenticated
export const isInitializing = (state: State) => state.initializing
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 {
State as DrawerState
} from './drawer'
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 {
// initialState as profileInitialState,
// reducer as profile,
......@@ -35,7 +35,7 @@ const reducers = {
drawer,
// user,
// profile,
system
system,
}
export const initialState: UIState = {
......@@ -44,7 +44,7 @@ export const initialState: UIState = {
drawer: drawerInitialState,
// user: userInitialState,
// profile: profileInitialState,
system: systemInitialState
system: systemInitialState,
}
export default reducers
import { getTypesActions } from '@agiliza/utils/method'
import login from './reducer'
import * as selectors from './selectors'
import slice from './slice'
export * from './reducer'
const actions = login.actions
const reducer = login.reducer
const types = getTypesActions(login.actions)
export { actions, reducer, types, selectors }
export default login
export * from './slice'
export { selectors }
export default slice.reducer
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
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 { PayloadAction } from '@reduxjs/toolkit'
import { createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
export const values = (obj: Record<string, any>) => {
return Object.values(obj || {})
......@@ -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>>
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 {
}
}
export const isValidCPF = (text: string) => {
const cpf = extractNumbers(text)
return cpf.length === 11
}
export function validateDate(field: Field<Date>): FieldValidation {
try {
if (field.value !== null) field.value.toISOString()
......
......@@ -5,8 +5,6 @@ import { bindActionCreators, Dispatch } from 'redux'
import ButtonWithProgress from '@agiliza/components/atoms/ButtonWithProgress'
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 { isAuthenticated } from '@agiliza/redux/session/selectors'
import { actions } from '@agiliza/redux/ui/login'
......@@ -24,11 +22,10 @@ interface Props extends BaseProps {
authenticated: boolean
fetching: boolean
loginRequest: typeof actions.login
service?: ConfigService
}
const Login = (props: Props) => {
const { loginRequest, authenticated, service, fetching } = props
const { loginRequest, authenticated, fetching } = props
const classes = useStyles()
......@@ -72,7 +69,7 @@ const Login = (props: Props) => {
Entrar
</ButtonWithProgress>
</div>
<PopoverVersion service={service} />
{/* <PopoverVersion service={service} /> */}
</div>
)
}
......
......@@ -4,8 +4,8 @@ import { Redirect, Route, RouteComponentProps, Switch, useLocation } from 'react
import AppBar from '@agiliza/components/molecules/AppBar'
import CircularProgress from '@agiliza/components/molecules/CircularProgress'
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 loginActions } from '@agiliza/redux/ui/login'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import connect, { ConnectedProps } from './connect'
......@@ -20,7 +20,7 @@ type BaseProps = RouteComponentProps & ConnectedProps & WithStyles<typeof styles
interface Props extends BaseProps {
toggleDrawer: typeof drawerActions.toggleDrawer
logout: typeof sessionActions.logout
logout: typeof loginActions.logout
}
const Main = (props: Props) => {
......@@ -40,7 +40,7 @@ const Main = (props: Props) => {
<Switch>
<Route path={PATHS.creditLines} render={(rProps) => <SimulationCreditLines {...rProps} />} />
<Route path={PATHS.proposalData} render={(rProps) => <ProposalData {...rProps} />} />
<Redirect from="/" to={PATHS.proposalData} />
<Redirect from="/" to={PATHS.creditLines} />
</Switch>
</Suspense>
</div>
......
......@@ -2,9 +2,9 @@ import { connect } from 'react-redux'
import { bindActionCreators, Dispatch } from 'redux'
import { StoreState } from '@agiliza/redux'
import { actions as sessionActions } from '@agiliza/redux/session'
import { actions as drawerActions } from '@agiliza/redux/ui/drawer'
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'
export interface ConnectedProps {
......@@ -12,7 +12,7 @@ export interface ConnectedProps {
fetchingMenu: boolean
fetchMenu: typeof systemActions.fetchMenu
toggleDrawer: typeof drawerActions.toggleDrawer
logout: typeof sessionActions.logout
logout: typeof loginActions.logout
}
const mapStateToProps = (state: StoreState) => ({
......@@ -25,7 +25,7 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
{
toggleDrawer: drawerActions.toggleDrawer,
logout: sessionActions.logout,
logout: loginActions.logout,
fetchMenu: systemActions.fetchMenu,
},
dispatch
......
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import { RouteComponentProps } from 'react-router'
import { SimulationCategory } from '@agiliza/api/domain'
import { extractCurrency, extractNumbers } from '@agiliza/utils/extractors'
import { formatCurrency } from '@agiliza/utils/formatters'
import {
Button,
CircularProgress,
IconButton,
InputAdornment,
TextareaAutosize,
TextField,
Typography
} from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import { Refresh as RefreshIcon } from '@material-ui/icons'
import { Autocomplete } from '@material-ui/lab'
import { creditLines } from '../../../../__mocks__/creditLines'
import { CREDIT_LINES_PATHS } from '../router'
import { connected, ConnectedProps } from './connect'
import SliderField from './SliderField'
import styles from './styles'
type CreditLine = typeof creditLines[0]
type ExtendedProps = WithStyles<typeof styles> & RouteComponentProps
type ExtendedProps = WithStyles<typeof styles> & RouteComponentProps & ConnectedProps
interface Props extends ExtendedProps {
fetching: boolean
}
const CreditLinesList: React.FC<Props> = (props) => {
const { classes, fetching, history, match } = props
const CreditLinesList = (props: 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 [paymentMonths, setPaymentMonths] = useState(0)
const [graceMonths, setGraceMonths] = useState(0)
const parentPath = match.path.substring(0, match.path.lastIndexOf('/'))
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 (
......@@ -47,42 +62,53 @@ const CreditLinesList: React.FC<Props> = (props) => {
<Typography variant="h5" className={classes.title}>
Escolha a melhor linha de crédito para você e clique em simular!
</Typography>
<Autocomplete<CreditLine>
<Autocomplete<SimulationCategory>
id="credit-lines"
className={classes.creditLinesAutocomplete}
options={creditLines}
getOptionLabel={(option) => option.name}
getOptionSelected={(opt, val) => opt.id === selectedCreditLine?.id}
noOptionsText="Nenhuma linha encontrado"
value={selectedCreditLine}
options={simulationCategories}
getOptionLabel={(option) => option.description}
getOptionSelected={(opt, val) => opt.id === selectedSimulCategory?.id}
noOptionsText="Nenhuma linha encontrada"
value={selectedSimulCategory}
onChange={(_, opt) => {
setSelectedCreditLine(opt || null)
setSelectedSimulCategory(opt || null)
setAmount(opt?.amount.min.toString() || '0')
setPaymentMonths(opt?.paymentMonths.min || 0)
setGraceMonths(opt?.graceMonths.min || 0)
// setAmount(opt?.amount.min.toString() || '0')
setPaymentMonths(opt?.maxInstallment || 0)
setGraceMonths(opt?.maxGraceMonths || 0)
}}
renderInput={(params) => {
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 (
<TextField
{...params}
id="select-patient-id"
label="Selecione ou Digite Id Paciente"
label="Linha de crédito"
variant="outlined"
InputProps={{
ref: params.InputProps.ref,
endAdornment: fetching ? (
<InputAdornment position="end" style={{ flex: '1', display: 'flex', justifyContent: 'flex-end' }}>
<CircularProgress size={20} />
</InputAdornment>
) : isValid ? (
React.cloneElement(params.InputProps.endAdornment as React.ReactElement, {
style: { marginRight: '1rem' },
})
) : (
params.InputProps.endAdornment
),
endAdornment: fetching
? fetchingEndAdornment
: !simulationCategories.length
? refreshEndAdorment
: isValid
? validEndAdornment
: params.InputProps.endAdornment,
}}
/>
)
......@@ -91,7 +117,7 @@ const CreditLinesList: React.FC<Props> = (props) => {
<TextField
className={classes.description}
label="Descrição da linha de crédito"
value={selectedCreditLine?.informations || ''}
value={selectedSimulCategory?.fullDescription || ''}
rows={5}
multiline
contentEditable={false}
......@@ -105,89 +131,93 @@ const CreditLinesList: React.FC<Props> = (props) => {
<SliderField
className={classes.sliderField}
title="De quanto você precisa?"
min={selectedCreditLine?.amount.min || 0}
max={selectedCreditLine?.amount.max || 5000}
marks={
selectedCreditLine
? [
{ value: selectedCreditLine.amount.min, label: formatCurrency(selectedCreditLine.amount.min.toString()) },
{ value: selectedCreditLine.amount.max, label: formatCurrency(selectedCreditLine.amount.max.toString()) },
]
: [
{ value: 0, label: formatCurrency('0') },
{ value: 5000, label: '' },
]
}
valueSlider={Number(amount)}
// sliderProps={{
// min: 0,
// max: 5000,
// marks: selectedSimulCategory
// ? [
// { value: 0, label: formatCurrency('0') },
// { 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: Number(amount),
// step: 0.01,
// onChange: (_, value) => setAmount((value as number).toString()),
// }}
valueField={formatCurrency(amount.toString(), true)}
step={0.01}
disabled={!selectedCreditLine}
onChangeSlider={(_, value) => setAmount((value as number).toString())}
disabled={!selectedSimulCategory}
onChangeField={(evt) => setAmount(extractCurrency(evt.target.value))}
onBlurField={(evt) => {
const amountValue = extractCurrency(evt.target.value)
if (selectedCreditLine)
setAmount(Number(amountValue) <= selectedCreditLine.amount.max ? amountValue : selectedCreditLine.amount.max.toString())
// const amountValue = extractCurrency(evt.target.value)
// if (selectedSimulCategory)
// setAmount(Number(amountValue) <= selectedSimulCategory.amount.max ? amountValue : selectedSimulCategory.amount.max.toString())
}}
/>
<SliderField
className={classes.sliderField}
title="Quantos meses para pagar?"
min={selectedCreditLine?.paymentMonths.min || 0}
max={selectedCreditLine?.paymentMonths.max || 12}
marks={
selectedCreditLine
sliderProps={{
min: 0,
max: selectedSimulCategory?.maxInstallment || 12,
marks: selectedSimulCategory
? [
{ value: selectedCreditLine.paymentMonths.min, label: `${selectedCreditLine.paymentMonths.min} meses` },
{ value: selectedCreditLine.paymentMonths.max, label: `${selectedCreditLine.paymentMonths.max} meses` },
{ value: 0, label: `${0} meses` },
{ value: selectedSimulCategory?.maxInstallment, label: `${selectedSimulCategory?.maxInstallment} meses` },
]
: [
{ value: 0, label: '0 meses' },
{ value: 12, label: '' },
]
}
valueSlider={Number(paymentMonths)}
valueField={paymentMonths.toString() + ' meses'}
disabled={!selectedCreditLine}
onChangeSlider={(_, value) => setPaymentMonths(value as number)}
onChangeField={(evt) => setPaymentMonths(Number(evt.target.value))}
],
value: Number(paymentMonths),
onChange: (_, value) => setPaymentMonths(value as number),
}}
valueField={paymentMonths.toString()}
suffix={`m${paymentMonths > 1 ? 'e' : 'ê'}s${paymentMonths > 1 ? 'es' : ''}`}
disabled={!selectedSimulCategory}
onChangeField={(evt) => setPaymentMonths(Number(extractNumbers(evt.target.value)))}
onBlurField={(evt) => {
const paymentMonthsValue = Number(extractNumbers(evt.target.value))
if (selectedCreditLine)
if (selectedSimulCategory)
setPaymentMonths(
paymentMonthsValue <= selectedCreditLine.paymentMonths.max ? paymentMonthsValue : selectedCreditLine.paymentMonths.max
paymentMonthsValue <= selectedSimulCategory.maxInstallment ? paymentMonthsValue : selectedSimulCategory.maxInstallment
)
}}
/>
<SliderField
className={classes.sliderField}
title="Quantos meses de carência?"
min={selectedCreditLine?.graceMonths.min || 0}
max={selectedCreditLine?.graceMonths.max || 12}
marks={
selectedCreditLine
title="Quantos dias de carência?"
sliderProps={{
min: 0,
max: selectedSimulCategory?.maxGraceMonths || 12,
marks: selectedSimulCategory
? [
{ value: selectedCreditLine.graceMonths.min, label: `${selectedCreditLine.graceMonths.min} meses` },
{ value: selectedCreditLine.graceMonths.max, label: `${selectedCreditLine.graceMonths.max} meses` },
{ value: 0, label: `${0} dias` },
{ value: selectedSimulCategory?.maxGraceMonths, label: `${selectedSimulCategory?.maxGraceMonths} dias` },
]
: [
{ value: 0, label: '0 meses' },
{ value: 0, label: '0 dias' },
{ value: 12, label: '' },
]
}
valueSlider={Number(graceMonths)}
valueField={graceMonths.toString() + ' meses'}
disabled={!selectedCreditLine}
onChangeSlider={(_, value) => setGraceMonths(value as number)}
onChangeField={(evt) => setGraceMonths(Number(evt.target.value))}
],
value: Number(graceMonths),
onChange: (_, value) => setGraceMonths(value as number),
}}
valueField={graceMonths.toString()}
suffix={`dia${graceMonths > 1 ? 's' : ''}`}
disabled={!selectedSimulCategory}
onChangeField={(evt) => setGraceMonths(Number(extractNumbers(evt.target.value)))}
onBlurField={(evt) => {
const graceMonthsValue = Number(extractNumbers(evt.target.value))
if (selectedCreditLine)
setGraceMonths(graceMonthsValue <= selectedCreditLine.graceMonths.max ? graceMonthsValue : selectedCreditLine.graceMonths.max)
if (selectedSimulCategory)
setGraceMonths(graceMonthsValue <= selectedSimulCategory.maxGraceMonths ? graceMonthsValue : selectedSimulCategory.maxGraceMonths)
}}
/>
<div className={classes.btnContainer}>
<Button disabled={!selectedCreditLine} variant="contained" color="secondary" onClick={handleSimulate}>
<Button disabled={!selectedSimulCategory} variant="contained" color="secondary" onClick={handleSimulate}>
Simular
</Button>
</div>
......@@ -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>
interface Props extends ExtendedProps {
title: string
min: SliderProps['min']
max: SliderProps['max']
marks?: SliderProps['marks']
valueSlider: number
valueField: string
step?: SliderProps['step']
onChangeSlider: (event: React.ChangeEvent<Record<string, unknown>>, value: number | number[]) => void
sliderProps?: Pick<SliderProps, 'min' | 'max' | 'marks' | 'step'> & {
onChange: (event: React.ChangeEvent<Record<string, unknown>>, value: number | number[]) => void
value: number
}
disabled: boolean
onChangeField: TextFieldProps['onChange']
onBlurField: TextFieldProps['onBlur']
disabled: boolean
className?: string
suffix?: string
}
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 (
<div className={className}>
<Typography gutterBottom>{title}</Typography>
<Slider
{sliderProps && <Slider {...sliderProps} disabled={disabled} step={step} />}
<TextField
style={{ width: '18%' }}
disabled={disabled}
min={min}
max={max}
value={valueSlider}
onChange={onChangeSlider}
// valueLabelDisplay="auto"
step={step}
marks={marks}
value={valueField}
onChange={onChangeField}
onBlur={onBlurField}
InputProps={{ endAdornment: suffix && <Typography>{suffix}</Typography> }}
/>
<TextField style={{ width: '12%' }} disabled={disabled} value={valueField} onChange={onChangeField} onBlur={onBlurField} />
</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 { 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 { creditLinesData } from '../../../../../__mocks__/creditLines'
import { connected, ConnectedProps } from './connect'
import GridLine from './GridLine'
import InstallmentsDialog from './InstallmentsDialog'
import styles from './styles'
type ExtendedProps = WithStyles<typeof styles>
type ExtendedProps = WithStyles<typeof styles> & ConnectedProps
interface Props extends ExtendedProps {
id: string
onChangeCanGoForward: (vl: boolean) => void
}
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(() => {
onChangeCanGoForward(true)
}, [])
onChangeCanGoForward(!!selectedSP)
}, [selectedSP])
return (
<div className={classes.contentContainer}>
<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}>
<GridLine label="Número de parcelas" value={creditLine?.paymentMonths} />
<GridLine label="Valor das parcelas" value={formatCurrency(creditLine?.amount.toFixed(2))} />
<GridLine label="Taxa de juros" value={`${creditLine?.interestRate} %`} />
<GridLine label="Custo efetivo total (CET)" value={`${creditLine?.effectiveTotalCost} %`} />
<GridLine label="Taxa abertura crédito (TAC)" value={formatCurrency(creditLine?.openingCreditRate.toFixed(2))} />
<GridLine label="Número de parcelas" value={selectedSP?.maxAmountInstallment || ''} />
{/* <GridLine label="Valor das parcelas" value={formatCurrency(selectedSP?.amount.toFixed(2))} /> */}
<GridLine label="Taxa de juros" value={selectedSP?.fee ? `${selectedSP?.fee} %` : ''} />
<GridLine label="Custo efetivo total (CET)" value={selectedSP?.IOF !== undefined ? `${selectedSP?.IOF} %` : ''} />
<GridLine label="Taxa abertura crédito (TAC)" value={formatCurrency(selectedSP?.TAC?.toFixed(2))} />
</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>
)
}
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'
export default (theme: Theme) =>
createStyles({
...sharedStyles(theme),
gridContainer: {},
gridContainer: { marginBottom: theme.spacing(2) },
gridItem: { whiteSpace: 'nowrap' },
selectField: { marginBottom: theme.spacing(2) },
})
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 { Button, Step, StepLabel, Stepper } from '@material-ui/core'
......@@ -12,16 +12,15 @@ import {
import CreditLinesInfo from './CreditLinesInfo'
import styles from './styles'
import UserForm from './UserForm'
import VerificationCode from './VerificationCode'
type ExtendedProps = WithStyles<typeof styles> & RouteComponentProps
interface Props extends ExtendedProps {}
const Simulation = (props: Props) => {
const { classes, history } = props
const { classes, ...routerProps } = props
const { history } = routerProps
// const theme = useTheme()
const { state: lState } = useLocation<{ id: string }>()
const [activeStep, setActiveStep] = useState(0)
const [canGoForward, setCanGoForward] = useState(true)
......@@ -32,11 +31,9 @@ const Simulation = (props: Props) => {
const renderStep = (step: number) => {
switch (step) {
case 0:
return <CreditLinesInfo id={lState.id} onChangeCanGoForward={handleChangeCanGoForward} />
return <CreditLinesInfo onChangeCanGoForward={handleChangeCanGoForward} />
case 1:
return <UserForm onChangeCanGoForward={handleChangeCanGoForward} />
case 2:
return <VerificationCode onChangeCanGoForward={handleChangeCanGoForward} />
}
}
......@@ -49,7 +46,7 @@ const Simulation = (props: Props) => {
setActiveStep((prevActiveStep) => prevActiveStep - 1)
}
const steps = useMemo(() => 3, [])
const steps = useMemo(() => 2, [])
return (
<div className={classes.pageContent}>
......@@ -66,9 +63,6 @@ const Simulation = (props: Props) => {
<Step>
<StepLabel>Dados pessoais</StepLabel>
</Step>
<Step>
<StepLabel>Código de verificação</StepLabel>
</Step>
</Stepper>
<Button size="large" onClick={handleNext} disabled={!canGoForward} className={classes.stepperBtn}>
{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'
export default (theme: Theme) =>
createStyles({
...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 { formatPhone } from '@agiliza/utils/formatters'
import {
getErrorProps,
invalidState,
useErrorValidator,
Validators,
validState
} from '@agiliza/utils/hooks/errorValidation'
import { isValidEmail, isValidPhone } from '@agiliza/utils/validators'
import { TextField, TextFieldProps } from '@material-ui/core'
import ButtonWithProgress from '@agiliza/components/atoms/ButtonWithProgress'
import LoginTemplate from '@agiliza/components/templates/Login'
import { formatCPF } from '@agiliza/utils/formatters'
import { extractNumbers } from '@agiliza/utils/method'
import { Button, TextFieldProps, Typography } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
import { connected, ConnectedProps } from './connect'
import FirstAccessDialog from './FirstAccessDialog'
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 {
name: string
phoneNumber: string
email: string
username: string
password: string
}
const validators: Validators<Partial<State>> = {
phoneNumber: (vl) => (isValidPhone(vl) ? validState(vl) : invalidState(vl, '')),
email: (vl) => (isValidEmail(vl) ? validState(vl) : invalidState(vl, '')),
}
// const validators: Validators<Partial<State>> = {
// phoneNumber: (vl) => (isValidPhone(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 {
onChangeCanGoForward: (vl: boolean) => void
}
const UserForm = (props: Props) => {
const { classes, onChangeCanGoForward } = props
const [state, setState] = useState<State>({ email: '', phoneNumber: '', name: '' })
const { name, phoneNumber, email } = state
const { classes, fetching, onChangeCanGoForward, customer } = props
const [state, setState] = useState<State>({ username: '', password: '' })
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(() => {
onChangeCanGoForward(isValidState)
onChangeCanGoForward(false)
}, [])
useEffect(() => {
onChangeCanGoForward(isValidState)
}, [isValidState])
// useEffect(() => {
// onChangeCanGoForward(isValidState)
// }, [isValidState])
const handleChange =
(key: keyof State): TextFieldProps['onChange'] =>
(evt) => {
setState({ ...state, [key]: evt.target.value })
actions.validate({ [key]: evt.target.value })
// actions.validate({ [key]: evt.target.value })
}
return (
<div className={classes.contentContainer}>
<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
label="Número de celular"
value={formatPhone(phoneNumber)}
......@@ -68,10 +87,11 @@ const UserForm = (props: Props) => {
inputProps={{ maxLength: 15 }}
{...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>
<FirstAccessDialog open={open} onClose={() => setOpen(false)} />
</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'
export default (theme: Theme) =>
createStyles({
...sharedStyles(theme),
content: { ...sharedStyles(theme).content, width: '15%' },
gridContainer: {},
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 { Redirect, Route, RouteComponentProps, Switch } from 'react-router'
import NotFound from '@agiliza/components/templates/NotFound'
import { CircularProgress } from '@material-ui/core'
import { withStyles, WithStyles } from '@material-ui/core/styles'
......@@ -25,14 +24,15 @@ const CreditLinesRouter = (props: Props) => {
<div className={classes.pageContent}>
<Suspense fallback={<CircularProgress className={classes.circularProgress} />}>
<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
exact
path={match.path + CREDIT_LINES_PATHS.simulation}
render={(rProps) => {
const { location } = rProps as RouteComponentProps<any, any, { id: string }>
if (location.state?.id) return <Simulation {...rProps} />
else return <NotFound {...rProps} />
return <Simulation {...rProps} />
// const { location } = rProps as RouteComponentProps<any, any, { id: string }>
// if (location.state?.id) return <Simulation {...rProps} />
// else return <NotFound {...rProps} />
}}
/>
<Redirect from={match.path} to={match.path + CREDIT_LINES_PATHS.creditLinesList} />
......
......@@ -6,18 +6,12 @@ import { bindActionCreators, Dispatch } from 'redux'
import AuthRoute from '@agiliza/components/atoms/AuthRoute'
import CircularProgress from '@agiliza/components/molecules/CircularProgress'
import { ConfigService } from '@agiliza/curio/SessionManager'
import { StoreState } from '@agiliza/redux'
import { actions as sessionActions } from '@agiliza/redux/session'
import {
getServiceInfo,
isAuthenticated,
isAuthenticating,
isInitializing
} from '@agiliza/redux/session/selectors'
import { isAuthenticated, isAuthenticating, isInitializing } from '@agiliza/redux/session/selectors'
import { actions as drawerActions } from '@agiliza/redux/ui/drawer'
import { isDrawerOpen } from '@agiliza/redux/ui/drawer/selectors'
import { getError } from '@agiliza/redux/ui/error/selectors'
import { actions as loginActions } from '@agiliza/redux/ui/login'
const Login = lazy(() => import('./Login'))
const Main = lazy(() => import('./Main'))
......@@ -28,15 +22,14 @@ interface Props {
error: string
authenticated: boolean
authenticating: boolean
initialize: typeof sessionActions.initialize
serviceInfo?: ConfigService
connect: typeof loginActions.connect
}
const Views = (props: Props) => {
const { authenticating, serviceInfo, initialize } = props
const { authenticating } = props
useEffect(() => {
initialize()
props.connect()
}, [])
if (authenticating) return <CircularProgress root />
......@@ -45,7 +38,7 @@ const Views = (props: Props) => {
<Router history={history}>
<Suspense fallback={<CircularProgress />}>
<Switch>
<Route path="/login" render={(renderProps) => <Login {...renderProps} service={serviceInfo} />} />
<Route path="/login" render={(renderProps) => <Login {...renderProps} />} />
<AuthRoute path="/" component={Main} />
</Switch>
</Suspense>
......@@ -57,7 +50,6 @@ const mapStateToProps = (state: StoreState) => ({
error: getError(state.ui.error),
authenticated: isAuthenticated(state.session),
authenticating: isAuthenticating(state.session) || isInitializing(state.session),
serviceInfo: getServiceInfo(state.session),
drawerOpen: isDrawerOpen(state.ui.drawer),
})
......@@ -65,7 +57,7 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
{
toggleDrawer: drawerActions.toggleDrawer,
initialize: sessionActions.initialize,
connect: loginActions.connect,
},
dispatch
)
......
......@@ -57,5 +57,5 @@
"typeRoots": ["node_modules/@types", "src/typings"]
},
"include": ["src"],
"exclude": ["node_modules", "dist", "public"]
"exclude": ["node_modules", "dist"]
}
......@@ -113,6 +113,7 @@ const makeCommonPlugins = (env) => [
new webpack.EnvironmentPlugin({
PUBLIC_PATH: '/',
NODE_ENV: env.production ? 'production' : 'development',
PRODUCTION: env.production,
APP_TITLE: 'Agiliza',
}),
new HtmlWebpackPlugin({
......@@ -188,5 +189,8 @@ module.exports = (env) => {
new CssMinimizerPlugin(),
],
},
experiments: {
topLevelAwait: true,
},
}
}
......@@ -1264,6 +1264,11 @@
prop-types "^15.7.2"
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":
version "2.1.4"
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