Merge pull request #1041 from josepfo/feat/manual-entry-populate-existing-requirement
feat: enable custom entries and custom information requirements
This commit is contained in:
commit
abcce7ff06
11 changed files with 525 additions and 192 deletions
|
|
@ -993,6 +993,7 @@ function addCustomField (customerId, label, value) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
.then(res => !_.isNil(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCustomField (customerId, fieldId, newValue) {
|
function saveCustomField (customerId, fieldId, newValue) {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const resolvers = {
|
||||||
return customers.updateCustomer(customerId, customerInput, token)
|
return customers.updateCustomer(customerId, customerInput, token)
|
||||||
},
|
},
|
||||||
addCustomField: (...[, { customerId, label, value }]) => customers.addCustomField(customerId, label, value),
|
addCustomField: (...[, { customerId, label, value }]) => customers.addCustomField(customerId, label, value),
|
||||||
saveCustomField: (...[, { customerId, fieldId, newValue }]) => customers.saveCustomField(customerId, fieldId, newValue),
|
saveCustomField: (...[, { customerId, fieldId, value }]) => customers.saveCustomField(customerId, fieldId, value),
|
||||||
removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId),
|
removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId),
|
||||||
editCustomer: async (root, { customerId, customerEdit }, context) => {
|
editCustomer: async (root, { customerId, customerEdit }, context) => {
|
||||||
const token = authentication.getToken(context)
|
const token = authentication.getToken(context)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
const { gql } = require('apollo-server-express')
|
const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
const typeDef = gql`
|
const typeDef = gql`
|
||||||
type CustomerCustomField {
|
|
||||||
id: ID
|
|
||||||
label: String
|
|
||||||
value: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Customer {
|
type Customer {
|
||||||
id: ID!
|
id: ID!
|
||||||
authorizedOverride: String
|
authorizedOverride: String
|
||||||
|
|
@ -86,6 +80,12 @@ const typeDef = gql`
|
||||||
content: String
|
content: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CustomerCustomField {
|
||||||
|
id: ID
|
||||||
|
label: String
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
customers(phone: String, name: String, address: String, id: String): [Customer] @auth
|
customers(phone: String, name: String, address: String, id: String): [Customer] @auth
|
||||||
customer(customerId: ID!): Customer @auth
|
customer(customerId: ID!): Customer @auth
|
||||||
|
|
@ -94,9 +94,9 @@ const typeDef = gql`
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
setCustomer(customerId: ID!, customerInput: CustomerInput): Customer @auth
|
setCustomer(customerId: ID!, customerInput: CustomerInput): Customer @auth
|
||||||
addCustomField(customerId: ID!, label: String!, value: String!): CustomerCustomField @auth
|
addCustomField(customerId: ID!, label: String!, value: String!): Boolean @auth
|
||||||
saveCustomField(customerId: ID!, fieldId: ID!, value: String!): CustomerCustomField @auth
|
saveCustomField(customerId: ID!, fieldId: ID!, value: String!): Boolean @auth
|
||||||
removeCustomField(customerId: ID!, fieldId: ID!): CustomerCustomField @auth
|
removeCustomField(customerId: ID!, fieldId: ID!): Boolean @auth
|
||||||
editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth
|
editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth
|
||||||
deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth
|
deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth
|
||||||
replacePhoto(customerId: ID!, photoType: String, newPhoto: UploadGQL): Customer @auth
|
replacePhoto(customerId: ID!, photoType: String, newPhoto: UploadGQL): Customer @auth
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,12 @@ export const Carousel = memo(({ photosData, slidePhoto }) => {
|
||||||
opacity: 1
|
opacity: 1
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
// navButtonsWrapperProps={{
|
||||||
|
// style: {
|
||||||
|
// background: 'linear-gradient(to right, black 10%, transparent 80%)',
|
||||||
|
// opacity: '0.4'
|
||||||
|
// }
|
||||||
|
// }}
|
||||||
autoPlay={false}
|
autoPlay={false}
|
||||||
indicators={false}
|
indicators={false}
|
||||||
navButtonsAlwaysVisible={true}
|
navButtonsAlwaysVisible={true}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import Grid from '@material-ui/core/Grid'
|
import Grid from '@material-ui/core/Grid'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { parse, format, isValid } from 'date-fns/fp'
|
import { parse, format } from 'date-fns/fp'
|
||||||
import _ from 'lodash/fp'
|
import _ from 'lodash/fp'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import { useState, React } from 'react'
|
import { useState, React } from 'react'
|
||||||
|
|
@ -26,6 +26,11 @@ import { URI } from 'src/utils/apollo'
|
||||||
|
|
||||||
import styles from './CustomerData.styles.js'
|
import styles from './CustomerData.styles.js'
|
||||||
import { EditableCard } from './components'
|
import { EditableCard } from './components'
|
||||||
|
import {
|
||||||
|
customerDataElements,
|
||||||
|
customerDataSchemas,
|
||||||
|
formatDates
|
||||||
|
} from './helper.js'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
|
@ -63,7 +68,8 @@ const CustomerData = ({
|
||||||
editCustomer,
|
editCustomer,
|
||||||
deleteEditedData,
|
deleteEditedData,
|
||||||
updateCustomRequest,
|
updateCustomRequest,
|
||||||
authorizeCustomRequest
|
authorizeCustomRequest,
|
||||||
|
updateCustomEntry
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [listView, setListView] = useState(false)
|
const [listView, setListView] = useState(false)
|
||||||
|
|
@ -84,8 +90,8 @@ const CustomerData = ({
|
||||||
R.compose(R.toLower, R.path(['customInfoRequest', 'customRequest', 'name']))
|
R.compose(R.toLower, R.path(['customInfoRequest', 'customRequest', 'name']))
|
||||||
)
|
)
|
||||||
|
|
||||||
const customEntries = null // get customer custom entries
|
const customFields = []
|
||||||
const customRequirements = [] // get customer custom requirements
|
const customRequirements = []
|
||||||
const customInfoRequests = sortByName(
|
const customInfoRequests = sortByName(
|
||||||
R.path(['customInfoRequests'])(customer) ?? []
|
R.path(['customInfoRequests'])(customer) ?? []
|
||||||
)
|
)
|
||||||
|
|
@ -94,87 +100,8 @@ const CustomerData = ({
|
||||||
|
|
||||||
const getVisibleCards = _.filter(elem => elem.isAvailable)
|
const getVisibleCards = _.filter(elem => elem.isAvailable)
|
||||||
|
|
||||||
const schemas = {
|
|
||||||
idScan: Yup.object().shape({
|
|
||||||
firstName: Yup.string().required(),
|
|
||||||
lastName: Yup.string().required(),
|
|
||||||
documentNumber: Yup.string().required(),
|
|
||||||
dateOfBirth: Yup.string()
|
|
||||||
.test({
|
|
||||||
test: val => isValid(parse(new Date(), 'yyyy-MM-dd', val))
|
|
||||||
})
|
|
||||||
.required(),
|
|
||||||
gender: Yup.string().required(),
|
|
||||||
country: Yup.string().required(),
|
|
||||||
expirationDate: Yup.string()
|
|
||||||
.test({
|
|
||||||
test: val => isValid(parse(new Date(), 'yyyy-MM-dd', val))
|
|
||||||
})
|
|
||||||
.required()
|
|
||||||
}),
|
|
||||||
usSsn: Yup.object().shape({
|
|
||||||
usSsn: Yup.string().required()
|
|
||||||
}),
|
|
||||||
idCardPhoto: Yup.object().shape({
|
|
||||||
idCardPhoto: Yup.mixed().required()
|
|
||||||
}),
|
|
||||||
frontCamera: Yup.object().shape({
|
|
||||||
frontCamera: Yup.mixed().required()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const idScanElements = [
|
|
||||||
{
|
|
||||||
name: 'firstName',
|
|
||||||
label: 'First name',
|
|
||||||
component: TextInput
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'documentNumber',
|
|
||||||
label: 'ID number',
|
|
||||||
component: TextInput
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dateOfBirth',
|
|
||||||
label: 'Birthdate',
|
|
||||||
component: TextInput
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gender',
|
|
||||||
label: 'Gender',
|
|
||||||
component: TextInput
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lastName',
|
|
||||||
label: 'Last name',
|
|
||||||
component: TextInput
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'expirationDate',
|
|
||||||
label: 'Expiration Date',
|
|
||||||
component: TextInput
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'country',
|
|
||||||
label: 'Country',
|
|
||||||
component: TextInput
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const usSsnElements = [
|
|
||||||
{
|
|
||||||
name: 'usSsn',
|
|
||||||
label: 'US SSN',
|
|
||||||
component: TextInput,
|
|
||||||
size: 190
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const idCardPhotoElements = [{ name: 'idCardPhoto' }]
|
|
||||||
const frontCameraElements = [{ name: 'frontCamera' }]
|
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
idScan: {
|
idCardData: {
|
||||||
firstName: R.path(['firstName'])(idData) ?? '',
|
firstName: R.path(['firstName'])(idData) ?? '',
|
||||||
lastName: R.path(['lastName'])(idData) ?? '',
|
lastName: R.path(['lastName'])(idData) ?? '',
|
||||||
documentNumber: R.path(['documentNumber'])(idData) ?? '',
|
documentNumber: R.path(['documentNumber'])(idData) ?? '',
|
||||||
|
|
@ -202,19 +129,9 @@ const CustomerData = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDates = values => {
|
|
||||||
_.map(
|
|
||||||
elem =>
|
|
||||||
(values[elem] = format('yyyyMMdd')(
|
|
||||||
parse(new Date(), 'yyyy-MM-dd', values[elem])
|
|
||||||
))
|
|
||||||
)(['dateOfBirth', 'expirationDate'])
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
const cards = [
|
const cards = [
|
||||||
{
|
{
|
||||||
fields: idScanElements,
|
fields: customerDataElements.idCardData,
|
||||||
title: 'ID Scan',
|
title: 'ID Scan',
|
||||||
titleIcon: <CardIcon className={classes.cardIcon} />,
|
titleIcon: <CardIcon className={classes.cardIcon} />,
|
||||||
state: R.path(['idCardDataOverride'])(customer),
|
state: R.path(['idCardDataOverride'])(customer),
|
||||||
|
|
@ -226,8 +143,8 @@ const CustomerData = ({
|
||||||
editCustomer({
|
editCustomer({
|
||||||
idCardData: _.merge(idData, formatDates(values))
|
idCardData: _.merge(idData, formatDates(values))
|
||||||
}),
|
}),
|
||||||
validationSchema: schemas.idScan,
|
validationSchema: customerDataSchemas.idCardData,
|
||||||
initialValues: initialValues.idScan,
|
initialValues: initialValues.idCardData,
|
||||||
isAvailable: !_.isNil(idData)
|
isAvailable: !_.isNil(idData)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -257,7 +174,7 @@ const CustomerData = ({
|
||||||
isAvailable: !_.isNil(sanctions)
|
isAvailable: !_.isNil(sanctions)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: frontCameraElements,
|
fields: customerDataElements.frontCamera,
|
||||||
title: 'Front facing camera',
|
title: 'Front facing camera',
|
||||||
titleIcon: <EditIcon className={classes.editIcon} />,
|
titleIcon: <EditIcon className={classes.editIcon} />,
|
||||||
state: R.path(['frontCameraOverride'])(customer),
|
state: R.path(['frontCameraOverride'])(customer),
|
||||||
|
|
@ -279,12 +196,12 @@ const CustomerData = ({
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
hasImage: true,
|
hasImage: true,
|
||||||
validationSchema: schemas.frontCamera,
|
validationSchema: customerDataSchemas.frontCamera,
|
||||||
initialValues: initialValues.frontCamera,
|
initialValues: initialValues.frontCamera,
|
||||||
isAvailable: !_.isNil(customer.frontCameraPath)
|
isAvailable: !_.isNil(customer.frontCameraPath)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: idCardPhotoElements,
|
fields: customerDataElements.idCardPhoto,
|
||||||
title: 'ID card image',
|
title: 'ID card image',
|
||||||
titleIcon: <EditIcon className={classes.editIcon} />,
|
titleIcon: <EditIcon className={classes.editIcon} />,
|
||||||
state: R.path(['idCardPhotoOverride'])(customer),
|
state: R.path(['idCardPhotoOverride'])(customer),
|
||||||
|
|
@ -304,20 +221,20 @@ const CustomerData = ({
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
hasImage: true,
|
hasImage: true,
|
||||||
validationSchema: schemas.idCardPhoto,
|
validationSchema: customerDataSchemas.idCardPhoto,
|
||||||
initialValues: initialValues.idCardPhoto,
|
initialValues: initialValues.idCardPhoto,
|
||||||
isAvailable: !_.isNil(customer.idCardPhotoPath)
|
isAvailable: !_.isNil(customer.idCardPhotoPath)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: usSsnElements,
|
fields: customerDataElements.usSsn,
|
||||||
title: 'US SSN',
|
title: 'US SSN',
|
||||||
titleIcon: <CardIcon className={classes.cardIcon} />,
|
titleIcon: <CardIcon className={classes.cardIcon} />,
|
||||||
state: R.path(['usSsnOverride'])(customer),
|
state: R.path(['usSsnOverride'])(customer),
|
||||||
authorize: () => updateCustomer({ usSsnOverride: OVERRIDE_AUTHORIZED }),
|
authorize: () => updateCustomer({ usSsnOverride: OVERRIDE_AUTHORIZED }),
|
||||||
reject: () => updateCustomer({ usSsnOverride: OVERRIDE_REJECTED }),
|
reject: () => updateCustomer({ usSsnOverride: OVERRIDE_REJECTED }),
|
||||||
save: values => editCustomer({ usSsn: values.usSsn }),
|
save: values => editCustomer(values),
|
||||||
deleteEditedData: () => deleteEditedData({ usSsn: null }),
|
deleteEditedData: () => deleteEditedData({ usSsn: null }),
|
||||||
validationSchema: schemas.usSsn,
|
validationSchema: customerDataSchemas.usSsn,
|
||||||
initialValues: initialValues.usSsn,
|
initialValues: initialValues.usSsn,
|
||||||
isAvailable: !_.isNil(customer.usSsn)
|
isAvailable: !_.isNil(customer.usSsn)
|
||||||
}
|
}
|
||||||
|
|
@ -374,6 +291,34 @@ const CustomerData = ({
|
||||||
})
|
})
|
||||||
}, customInfoRequests)
|
}, customInfoRequests)
|
||||||
|
|
||||||
|
R.forEach(it => {
|
||||||
|
customFields.push({
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: it.label,
|
||||||
|
label: it.label,
|
||||||
|
value: it.value ?? '',
|
||||||
|
component: TextInput
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title: it.label,
|
||||||
|
titleIcon: <EditIcon className={classes.editIcon} />,
|
||||||
|
save: values => {
|
||||||
|
updateCustomEntry({
|
||||||
|
fieldId: it.id,
|
||||||
|
value: values[it.label]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteEditedData: () => {},
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
[it.label]: Yup.string()
|
||||||
|
}),
|
||||||
|
initialValues: {
|
||||||
|
[it.label]: it.value ?? ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, R.path(['customFields'])(customer) ?? [])
|
||||||
|
|
||||||
const editableCard = (
|
const editableCard = (
|
||||||
{
|
{
|
||||||
title,
|
title,
|
||||||
|
|
@ -415,19 +360,24 @@ const CustomerData = ({
|
||||||
<div>
|
<div>
|
||||||
<div className={classes.header}>
|
<div className={classes.header}>
|
||||||
<H3 className={classes.title}>{'Customer data'}</H3>
|
<H3 className={classes.title}>{'Customer data'}</H3>
|
||||||
<FeatureButton
|
{// TODO: Remove false condition for next release
|
||||||
active={!listView}
|
false && (
|
||||||
className={classes.viewIcons}
|
<>
|
||||||
Icon={OverviewIcon}
|
<FeatureButton
|
||||||
InverseIcon={OverviewReversedIcon}
|
active={!listView}
|
||||||
onClick={() => setListView(false)}
|
className={classes.viewIcons}
|
||||||
/>
|
Icon={OverviewIcon}
|
||||||
<FeatureButton
|
InverseIcon={OverviewReversedIcon}
|
||||||
active={listView}
|
onClick={() => setListView(false)}
|
||||||
className={classes.viewIcons}
|
/>
|
||||||
Icon={CustomerListViewIcon}
|
<FeatureButton
|
||||||
InverseIcon={CustomerListViewReversedIcon}
|
active={listView}
|
||||||
onClick={() => setListView(true)}></FeatureButton>
|
className={classes.viewIcons}
|
||||||
|
Icon={CustomerListViewIcon}
|
||||||
|
InverseIcon={CustomerListViewReversedIcon}
|
||||||
|
onClick={() => setListView(true)}></FeatureButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{!listView && customer && (
|
{!listView && customer && (
|
||||||
|
|
@ -444,9 +394,21 @@ const CustomerData = ({
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
{customEntries && (
|
{!_.isEmpty(customFields) && (
|
||||||
<div className={classes.wrapper}>
|
<div className={classes.wrapper}>
|
||||||
<span className={classes.separator}>Custom data entry</span>
|
<span className={classes.separator}>Custom data entry</span>
|
||||||
|
<Grid container>
|
||||||
|
<Grid container direction="column" item xs={6}>
|
||||||
|
{customFields.map((elem, idx) => {
|
||||||
|
return isEven(idx) ? editableCard(elem, idx) : null
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
<Grid container direction="column" item xs={6}>
|
||||||
|
{customFields.map((elem, idx) => {
|
||||||
|
return !isEven(idx) ? editableCard(elem, idx) : null
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!R.isEmpty(customRequirements) && (
|
{!R.isEmpty(customRequirements) && (
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/bl
|
||||||
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/zodiac.svg'
|
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/zodiac.svg'
|
||||||
import { ReactComponent as DataReversedIcon } from 'src/styling/icons/button/data/white.svg'
|
import { ReactComponent as DataReversedIcon } from 'src/styling/icons/button/data/white.svg'
|
||||||
import { ReactComponent as DataIcon } from 'src/styling/icons/button/data/zodiac.svg'
|
import { ReactComponent as DataIcon } from 'src/styling/icons/button/data/zodiac.svg'
|
||||||
import { ReactComponent as DiscountReversedIcon } from 'src/styling/icons/button/discount/white.svg'
|
// TODO: Enable for next release
|
||||||
import { ReactComponent as Discount } from 'src/styling/icons/button/discount/zodiac.svg'
|
// import { ReactComponent as DiscountReversedIcon } from 'src/styling/icons/button/discount/white.svg'
|
||||||
|
// import { ReactComponent as Discount } from 'src/styling/icons/button/discount/zodiac.svg'
|
||||||
import { fromNamespace, namespaces } from 'src/utils/config'
|
import { fromNamespace, namespaces } from 'src/utils/config'
|
||||||
|
|
||||||
import CustomerData from './CustomerData'
|
import CustomerData from './CustomerData'
|
||||||
|
|
@ -236,6 +237,27 @@ const GET_DATA = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const SET_CUSTOM_ENTRY = gql`
|
||||||
|
mutation addCustomField($customerId: ID!, $label: String!, $value: String!) {
|
||||||
|
addCustomField(customerId: $customerId, label: $label, value: $value)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const EDIT_CUSTOM_ENTRY = gql`
|
||||||
|
mutation saveCustomField($customerId: ID!, $fieldId: ID!, $value: String!) {
|
||||||
|
saveCustomField(customerId: $customerId, fieldId: $fieldId, value: $value)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const GET_ACTIVE_CUSTOM_REQUESTS = gql`
|
||||||
|
query customInfoRequests($onlyEnabled: Boolean) {
|
||||||
|
customInfoRequests(onlyEnabled: $onlyEnabled) {
|
||||||
|
id
|
||||||
|
customRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const CustomerProfile = memo(() => {
|
const CustomerProfile = memo(() => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
|
|
||||||
|
|
@ -255,6 +277,20 @@ const CustomerProfile = memo(() => {
|
||||||
|
|
||||||
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
||||||
|
|
||||||
|
const { data: activeCustomRequests } = useQuery(GET_ACTIVE_CUSTOM_REQUESTS, {
|
||||||
|
variables: {
|
||||||
|
onlyEnabled: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const [setCustomEntry] = useMutation(SET_CUSTOM_ENTRY, {
|
||||||
|
onCompleted: () => getCustomer()
|
||||||
|
})
|
||||||
|
|
||||||
|
const [editCustomEntry] = useMutation(EDIT_CUSTOM_ENTRY, {
|
||||||
|
onCompleted: () => getCustomer()
|
||||||
|
})
|
||||||
|
|
||||||
const [replaceCustomerPhoto] = useMutation(REPLACE_CUSTOMER_PHOTO, {
|
const [replaceCustomerPhoto] = useMutation(REPLACE_CUSTOMER_PHOTO, {
|
||||||
onCompleted: () => getCustomer()
|
onCompleted: () => getCustomer()
|
||||||
})
|
})
|
||||||
|
|
@ -294,6 +330,27 @@ const CustomerProfile = memo(() => {
|
||||||
onCompleted: () => getCustomer()
|
onCompleted: () => getCustomer()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const saveCustomEntry = it => {
|
||||||
|
setCustomEntry({
|
||||||
|
variables: {
|
||||||
|
customerId,
|
||||||
|
label: it.title,
|
||||||
|
value: it.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setWizard(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCustomEntry = it => {
|
||||||
|
editCustomEntry({
|
||||||
|
variables: {
|
||||||
|
customerId,
|
||||||
|
fieldId: it.fieldId,
|
||||||
|
value: it.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const updateCustomer = it =>
|
const updateCustomer = it =>
|
||||||
setCustomer({
|
setCustomer({
|
||||||
variables: {
|
variables: {
|
||||||
|
|
@ -302,7 +359,7 @@ const CustomerProfile = memo(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const replacePhoto = it =>
|
const replacePhoto = it => {
|
||||||
replaceCustomerPhoto({
|
replaceCustomerPhoto({
|
||||||
variables: {
|
variables: {
|
||||||
customerId,
|
customerId,
|
||||||
|
|
@ -310,14 +367,18 @@ const CustomerProfile = memo(() => {
|
||||||
photoType: it.photoType
|
photoType: it.photoType
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
setWizard(null)
|
||||||
|
}
|
||||||
|
|
||||||
const editCustomer = it =>
|
const editCustomer = it => {
|
||||||
editCustomerData({
|
editCustomerData({
|
||||||
variables: {
|
variables: {
|
||||||
customerId,
|
customerId,
|
||||||
customerEdit: it
|
customerEdit: it
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
setWizard(null)
|
||||||
|
}
|
||||||
|
|
||||||
const deleteEditedData = it =>
|
const deleteEditedData = it =>
|
||||||
deleteCustomerEditedData({
|
deleteCustomerEditedData({
|
||||||
|
|
@ -385,6 +446,12 @@ const CustomerProfile = memo(() => {
|
||||||
|
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||||
|
|
||||||
|
const customInfoRequirementOptions =
|
||||||
|
activeCustomRequests?.customInfoRequests?.map(it => ({
|
||||||
|
value: it.id,
|
||||||
|
display: it.customRequest.name
|
||||||
|
})) ?? []
|
||||||
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -428,14 +495,17 @@ const CustomerProfile = memo(() => {
|
||||||
onClick={() => setWizard(true)}>
|
onClick={() => setWizard(true)}>
|
||||||
{`Manual data entry`}
|
{`Manual data entry`}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
{
|
||||||
|
// TODO: Enable for next release
|
||||||
|
/* <ActionButton
|
||||||
className={classes.actionButton}
|
className={classes.actionButton}
|
||||||
color="primary"
|
color="primary"
|
||||||
Icon={Discount}
|
Icon={Discount}
|
||||||
InverseIcon={DiscountReversedIcon}
|
InverseIcon={DiscountReversedIcon}
|
||||||
onClick={() => {}}>
|
onClick={() => {}}>
|
||||||
{`Add individual discount`}
|
{`Add individual discount`}
|
||||||
</ActionButton>
|
</ActionButton> */
|
||||||
|
}
|
||||||
{isSuspended && (
|
{isSuspended && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
className={classes.actionButton}
|
className={classes.actionButton}
|
||||||
|
|
@ -522,7 +592,8 @@ const CustomerProfile = memo(() => {
|
||||||
editCustomer={editCustomer}
|
editCustomer={editCustomer}
|
||||||
deleteEditedData={deleteEditedData}
|
deleteEditedData={deleteEditedData}
|
||||||
updateCustomRequest={setCustomerCustomInfoRequest}
|
updateCustomRequest={setCustomerCustomInfoRequest}
|
||||||
authorizeCustomRequest={authorizeCustomRequest}></CustomerData>
|
authorizeCustomRequest={authorizeCustomRequest}
|
||||||
|
updateCustomEntry={updateCustomEntry}></CustomerData>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isNotes && (
|
{isNotes && (
|
||||||
|
|
@ -544,8 +615,11 @@ const CustomerProfile = memo(() => {
|
||||||
{wizard && (
|
{wizard && (
|
||||||
<Wizard
|
<Wizard
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
save={() => {}}
|
save={saveCustomEntry}
|
||||||
|
addPhoto={replacePhoto}
|
||||||
|
addCustomerData={editCustomer}
|
||||||
onClose={() => setWizard(null)}
|
onClose={() => setWizard(null)}
|
||||||
|
customInfoRequirementOptions={customInfoRequirementOptions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,14 @@ import Stepper from 'src/components/Stepper'
|
||||||
import { Button } from 'src/components/buttons'
|
import { Button } from 'src/components/buttons'
|
||||||
import { comet } from 'src/styling/variables'
|
import { comet } from 'src/styling/variables'
|
||||||
|
|
||||||
import { entryType, customElements } from './helper'
|
import {
|
||||||
|
entryType,
|
||||||
|
customElements,
|
||||||
|
requirementElements,
|
||||||
|
formatDates,
|
||||||
|
REQUIREMENT,
|
||||||
|
ID_CARD_DATA
|
||||||
|
} from './helper'
|
||||||
|
|
||||||
const LAST_STEP = 2
|
const LAST_STEP = 2
|
||||||
|
|
||||||
|
|
@ -41,23 +48,40 @@ const styles = {
|
||||||
margin: [[0, 4, 0, 2]],
|
margin: [[0, 4, 0, 2]],
|
||||||
borderBottom: `1px solid ${comet}`,
|
borderBottom: `1px solid ${comet}`,
|
||||||
display: 'inline-block'
|
display: 'inline-block'
|
||||||
|
},
|
||||||
|
dropdownField: {
|
||||||
|
marginTop: 16,
|
||||||
|
minWidth: 155
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const getStep = (step, selectedValues) => {
|
const getStep = (step, selectedValues) => {
|
||||||
|
const elements =
|
||||||
|
selectedValues?.entryType === REQUIREMENT &&
|
||||||
|
!R.isNil(selectedValues?.requirement)
|
||||||
|
? requirementElements[selectedValues?.requirement]
|
||||||
|
: customElements[selectedValues?.dataType]
|
||||||
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 1:
|
case 1:
|
||||||
return entryType
|
return entryType
|
||||||
case 2:
|
case 2:
|
||||||
return customElements[selectedValues?.dataType]
|
return elements
|
||||||
default:
|
default:
|
||||||
return Fragment
|
return Fragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wizard = ({ onClose, save, error }) => {
|
const Wizard = ({
|
||||||
|
onClose,
|
||||||
|
save,
|
||||||
|
error,
|
||||||
|
customInfoRequirementOptions,
|
||||||
|
addCustomerData,
|
||||||
|
addPhoto
|
||||||
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [selectedValues, setSelectedValues] = useState(null)
|
const [selectedValues, setSelectedValues] = useState(null)
|
||||||
|
|
@ -66,6 +90,10 @@ const Wizard = ({ onClose, save, error }) => {
|
||||||
step: 1
|
step: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isIdCardData = values => values?.requirement === ID_CARD_DATA
|
||||||
|
const formatCustomerData = (it, newConfig) =>
|
||||||
|
isIdCardData(newConfig) ? { [newConfig.requirement]: formatDates(it) } : it
|
||||||
|
|
||||||
const isLastStep = step === LAST_STEP
|
const isLastStep = step === LAST_STEP
|
||||||
const stepOptions = getStep(step, selectedValues)
|
const stepOptions = getStep(step, selectedValues)
|
||||||
|
|
||||||
|
|
@ -74,7 +102,23 @@ const Wizard = ({ onClose, save, error }) => {
|
||||||
setSelectedValues(newConfig)
|
setSelectedValues(newConfig)
|
||||||
|
|
||||||
if (isLastStep) {
|
if (isLastStep) {
|
||||||
return save(newConfig)
|
switch (stepOptions.saveType) {
|
||||||
|
case 'customerData':
|
||||||
|
return addCustomerData(formatCustomerData(it, newConfig))
|
||||||
|
case 'customerDataUpload':
|
||||||
|
return addPhoto({
|
||||||
|
newPhoto: R.head(R.values(it)),
|
||||||
|
photoType: R.head(R.keys(it))
|
||||||
|
})
|
||||||
|
case 'customEntry':
|
||||||
|
return save(newConfig)
|
||||||
|
case 'customInfoRequirement':
|
||||||
|
return
|
||||||
|
// case 'customerEntryUpload':
|
||||||
|
// break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
|
|
@ -106,6 +150,7 @@ const Wizard = ({ onClose, save, error }) => {
|
||||||
<Form className={classes.form}>
|
<Form className={classes.form}>
|
||||||
<stepOptions.Component
|
<stepOptions.Component
|
||||||
selectedValues={selectedValues}
|
selectedValues={selectedValues}
|
||||||
|
customInfoRequirementOptions={customInfoRequirementOptions}
|
||||||
{...stepOptions.props}
|
{...stepOptions.props}
|
||||||
/>
|
/>
|
||||||
<div className={classes.submit}>
|
<div className={classes.submit}>
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ const EditableCard = ({
|
||||||
<H3 className={classes.cardTitle}>{title}</H3>
|
<H3 className={classes.cardTitle}>{title}</H3>
|
||||||
<Tooltip width={304}></Tooltip>
|
<Tooltip width={304}></Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{state && (
|
{state && authorize && (
|
||||||
<div className={classnames(label1ClassNames)}>
|
<div className={classnames(label1ClassNames)}>
|
||||||
<MainStatus statuses={[authorized]} />
|
<MainStatus statuses={[authorized]} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -207,17 +207,19 @@ const EditableCard = ({
|
||||||
<div className={classes.edit}>
|
<div className={classes.edit}>
|
||||||
{!editing && (
|
{!editing && (
|
||||||
<div className={classes.editButton}>
|
<div className={classes.editButton}>
|
||||||
<div className={classes.deleteButton}>
|
{// TODO: Remove false condition for next release
|
||||||
<ActionButton
|
false && (
|
||||||
color="primary"
|
<div className={classes.deleteButton}>
|
||||||
type="button"
|
<ActionButton
|
||||||
Icon={DeleteIcon}
|
color="primary"
|
||||||
InverseIcon={DeleteReversedIcon}
|
type="button"
|
||||||
onClick={() => deleteEditedData()}>
|
Icon={DeleteIcon}
|
||||||
{`Delete`}
|
InverseIcon={DeleteReversedIcon}
|
||||||
</ActionButton>
|
onClick={() => deleteEditedData()}>
|
||||||
</div>
|
{`Delete`}
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="primary"
|
color="primary"
|
||||||
Icon={EditIcon}
|
Icon={EditIcon}
|
||||||
|
|
@ -279,7 +281,7 @@ const EditableCard = ({
|
||||||
Cancel
|
Cancel
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
{authorized.label !== 'Accepted' && (
|
{authorize && authorized.label !== 'Accepted' && (
|
||||||
<div className={classes.button}>
|
<div className={classes.button}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="spring"
|
color="spring"
|
||||||
|
|
@ -291,7 +293,7 @@ const EditableCard = ({
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{authorized.label !== 'Rejected' && (
|
{authorize && authorized.label !== 'Rejected' && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="tomato"
|
color="tomato"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { offColor, subheaderColor } from 'src/styling/variables'
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
box: {
|
box: {
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
marginTop: 40,
|
|
||||||
width: 450,
|
width: 450,
|
||||||
height: 120,
|
height: 120,
|
||||||
borderStyle: 'dashed',
|
borderStyle: 'dashed',
|
||||||
|
|
@ -32,6 +31,7 @@ const useStyles = makeStyles({
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
},
|
},
|
||||||
board: {
|
board: {
|
||||||
|
marginTop: 40,
|
||||||
width: 450,
|
width: 450,
|
||||||
height: 120
|
height: 120
|
||||||
},
|
},
|
||||||
|
|
@ -48,12 +48,15 @@ const Upload = ({ type }) => {
|
||||||
const { setFieldValue } = useFormikContext()
|
const { setFieldValue } = useFormikContext()
|
||||||
|
|
||||||
const IMAGE = 'image'
|
const IMAGE = 'image'
|
||||||
const isImage = type === IMAGE
|
const ID_CARD_PHOTO = 'idCardPhoto'
|
||||||
|
const FRONT_CAMERA = 'frontCamera'
|
||||||
|
|
||||||
|
const isImage =
|
||||||
|
type === IMAGE || type === FRONT_CAMERA || type === ID_CARD_PHOTO
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
acceptedData => {
|
acceptedData => {
|
||||||
// TODO: attach the uploaded data to the form as well
|
setFieldValue(type, R.head(acceptedData))
|
||||||
setFieldValue(type, R.head(acceptedData).name)
|
|
||||||
|
|
||||||
setData({
|
setData({
|
||||||
preview: isImage
|
preview: isImage
|
||||||
|
|
@ -84,12 +87,12 @@ const Upload = ({ type }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!R.isEmpty(data) && type === IMAGE && (
|
{!R.isEmpty(data) && isImage && (
|
||||||
<div key={data.name}>
|
<div key={data.name}>
|
||||||
<img src={data.preview} className={classes.box} alt=""></img>
|
<img src={data.preview} className={classes.box} alt=""></img>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!R.isEmpty(data) && type !== IMAGE && (
|
{!R.isEmpty(data) && !isImage && (
|
||||||
<div className={classes.box}>
|
<div className={classes.box}>
|
||||||
<H3 className={classes.uploadContent}>{data.preview}</H3>
|
<H3 className={classes.uploadContent}>{data.preview}</H3>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
import { makeStyles, Box } from '@material-ui/core'
|
import { makeStyles, Box } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import { parse, isValid, format } from 'date-fns/fp'
|
||||||
import { Field, useFormikContext } from 'formik'
|
import { Field, useFormikContext } from 'formik'
|
||||||
import { parsePhoneNumberFromString } from 'libphonenumber-js'
|
import { parsePhoneNumberFromString } from 'libphonenumber-js'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import { RadioGroup, TextInput } from 'src/components/inputs/formik'
|
import {
|
||||||
|
RadioGroup,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
import { H4 } from 'src/components/typography'
|
import { H4 } from 'src/components/typography'
|
||||||
import { errorColor } from 'src/styling/variables'
|
import { errorColor } from 'src/styling/variables'
|
||||||
import { MANUAL } from 'src/utils/constants'
|
import { MANUAL } from 'src/utils/constants'
|
||||||
|
|
@ -35,10 +40,21 @@ const useStyles = makeStyles({
|
||||||
specialGrid: {
|
specialGrid: {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: [[182, 162, 141]]
|
gridTemplateColumns: [[182, 162, 141]]
|
||||||
|
},
|
||||||
|
picker: {
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
'& > *:last-child': {
|
||||||
|
marginBottom: 24
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const CUSTOMER_BLOCKED = 'blocked'
|
const CUSTOMER_BLOCKED = 'blocked'
|
||||||
|
const CUSTOM = 'custom'
|
||||||
|
const REQUIREMENT = 'requirement'
|
||||||
|
const ID_CARD_DATA = 'idCardData'
|
||||||
|
|
||||||
const getAuthorizedStatus = (it, triggers) => {
|
const getAuthorizedStatus = (it, triggers) => {
|
||||||
const fields = [
|
const fields = [
|
||||||
|
|
@ -97,34 +113,46 @@ const getName = it => {
|
||||||
) ?? ''}`.trim()
|
) ?? ''}`.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manual Entry Wizard
|
||||||
|
|
||||||
const entryOptions = [
|
const entryOptions = [
|
||||||
{ display: 'Custom entry', code: 'custom' },
|
{ display: 'Custom entry', code: 'custom' },
|
||||||
{ display: 'Populate existing requirement', code: 'requirement' }
|
{ display: 'Populate existing requirement', code: 'requirement' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const dataOptions = [
|
const dataOptions = [
|
||||||
{ display: 'Text', code: 'text' },
|
{ display: 'Text', code: 'text' }
|
||||||
{ display: 'File', code: 'file' },
|
// TODO: Requires backend modifications to support File and Image
|
||||||
{ display: 'Image', code: 'image' }
|
// { display: 'File', code: 'file' },
|
||||||
|
// { display: 'Image', code: 'image' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const requirementOptions = [
|
const requirementOptions = [
|
||||||
{ display: 'Birthdate', code: 'birthdate' },
|
|
||||||
{ display: 'ID card image', code: 'idCardPhoto' },
|
{ display: 'ID card image', code: 'idCardPhoto' },
|
||||||
{ display: 'ID data', code: 'idCardData' },
|
{ display: 'ID data', code: 'idCardData' },
|
||||||
{ display: 'Customer camera', code: 'facephoto' },
|
{ display: 'US SSN', code: 'usSsn' },
|
||||||
{ display: 'US SSN', code: 'usSsn' }
|
{ display: 'Customer camera', code: 'frontCamera' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const customTextOptions = [
|
const customTextOptions = [
|
||||||
{ display: 'Data entry title', code: 'title' },
|
{ label: 'Data entry title', name: 'title' },
|
||||||
{ display: 'Data entry', code: 'data' }
|
{ label: 'Data entry', name: 'data' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const customUploadOptions = [{ display: 'Data entry title', code: 'title' }]
|
const customUploadOptions = [{ label: 'Data entry title', name: 'title' }]
|
||||||
|
|
||||||
const entryTypeSchema = Yup.object().shape({
|
const entryTypeSchema = Yup.lazy(values => {
|
||||||
entryType: Yup.string().required()
|
if (values.entryType === 'custom') {
|
||||||
|
return Yup.object().shape({
|
||||||
|
entryType: Yup.string().required(),
|
||||||
|
dataType: Yup.string().required()
|
||||||
|
})
|
||||||
|
} else if (values.entryType === 'requirement') {
|
||||||
|
return Yup.object().shape({
|
||||||
|
entryType: Yup.string().required(),
|
||||||
|
requirement: Yup.string().required()
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const customFileSchema = Yup.object().shape({
|
const customFileSchema = Yup.object().shape({
|
||||||
|
|
@ -142,13 +170,18 @@ const customTextSchema = Yup.object().shape({
|
||||||
data: Yup.string().required()
|
data: Yup.string().required()
|
||||||
})
|
})
|
||||||
|
|
||||||
const EntryType = () => {
|
const updateRequirementOptions = it => [
|
||||||
|
{
|
||||||
|
display: 'Custom information requirement',
|
||||||
|
code: 'custom'
|
||||||
|
},
|
||||||
|
...it
|
||||||
|
]
|
||||||
|
|
||||||
|
const EntryType = ({ customInfoRequirementOptions }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { values } = useFormikContext()
|
const { values } = useFormikContext()
|
||||||
|
|
||||||
const CUSTOM = 'custom'
|
|
||||||
const REQUIREMENT = 'requirement'
|
|
||||||
|
|
||||||
const displayCustomOptions = values.entryType === CUSTOM
|
const displayCustomOptions = values.entryType === CUSTOM
|
||||||
const displayRequirementOptions = values.entryType === REQUIREMENT
|
const displayRequirementOptions = values.entryType === REQUIREMENT
|
||||||
|
|
||||||
|
|
@ -188,7 +221,13 @@ const EntryType = () => {
|
||||||
<Field
|
<Field
|
||||||
component={RadioGroup}
|
component={RadioGroup}
|
||||||
name="requirement"
|
name="requirement"
|
||||||
options={requirementOptions}
|
options={
|
||||||
|
requirementOptions
|
||||||
|
// TODO: Enable once custom info requirement manual entry is finished
|
||||||
|
// !R.isEmpty(customInfoRequirementOptions)
|
||||||
|
// ? updateRequirementOptions(requirementOptions)
|
||||||
|
// : requirementOptions
|
||||||
|
}
|
||||||
labelClassName={classes.label}
|
labelClassName={classes.label}
|
||||||
radioClassName={classes.radio}
|
radioClassName={classes.radio}
|
||||||
className={classnames(classes.radioGroup, classes.specialGrid)}
|
className={classnames(classes.radioGroup, classes.specialGrid)}
|
||||||
|
|
@ -199,18 +238,73 @@ const EntryType = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomData = ({ selectedValues }) => {
|
const ManualDataEntry = ({ selectedValues, customInfoRequirementOptions }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const typeOfEntrySelected = selectedValues?.entryType
|
||||||
const dataTypeSelected = selectedValues?.dataType
|
const dataTypeSelected = selectedValues?.dataType
|
||||||
const upload = dataTypeSelected === 'file' || dataTypeSelected === 'image'
|
const requirementSelected = selectedValues?.requirement
|
||||||
|
|
||||||
|
const displayRequirements = typeOfEntrySelected === 'requirement'
|
||||||
|
|
||||||
|
const isCustomInfoRequirement = requirementSelected === CUSTOM
|
||||||
|
|
||||||
|
const updatedRequirementOptions = !R.isEmpty(customInfoRequirementOptions)
|
||||||
|
? updateRequirementOptions(requirementOptions)
|
||||||
|
: requirementOptions
|
||||||
|
|
||||||
|
const requirementName = displayRequirements
|
||||||
|
? R.find(R.propEq('code', requirementSelected))(updatedRequirementOptions)
|
||||||
|
.display
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const title = displayRequirements
|
||||||
|
? `Requirement ${requirementName}`
|
||||||
|
: `Custom ${dataTypeSelected} entry`
|
||||||
|
|
||||||
|
const elements = displayRequirements
|
||||||
|
? requirementElements[requirementSelected]
|
||||||
|
: customElements[dataTypeSelected]
|
||||||
|
|
||||||
|
const upload = displayRequirements
|
||||||
|
? requirementSelected === 'idCardPhoto' ||
|
||||||
|
requirementSelected === 'frontCamera'
|
||||||
|
: dataTypeSelected === 'file' || dataTypeSelected === 'image'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<H4>{`Custom ${dataTypeSelected} entry`}</H4>
|
<H4>{title}</H4>
|
||||||
</Box>
|
</Box>
|
||||||
{customElements[dataTypeSelected].options.map(({ display, code }) => (
|
{isCustomInfoRequirement && (
|
||||||
<Field name={code} label={display} component={TextInput} width={390} />
|
<Autocomplete
|
||||||
))}
|
fullWidth
|
||||||
{upload && <Upload type={dataTypeSelected}></Upload>}
|
label={`Available requests`}
|
||||||
|
className={classes.picker}
|
||||||
|
getOptionSelected={R.eqProps('code')}
|
||||||
|
labelProp={'display'}
|
||||||
|
options={customInfoRequirementOptions}
|
||||||
|
onChange={(evt, it) => {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={classes.field}>
|
||||||
|
{!upload &&
|
||||||
|
!isCustomInfoRequirement &&
|
||||||
|
elements.options.map(({ label, name }) => (
|
||||||
|
<Field
|
||||||
|
name={name}
|
||||||
|
label={label}
|
||||||
|
component={TextInput}
|
||||||
|
width={390}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{upload && (
|
||||||
|
<Upload
|
||||||
|
type={
|
||||||
|
displayRequirements ? requirementSelected : dataTypeSelected
|
||||||
|
}></Upload>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -219,20 +313,23 @@ const customElements = {
|
||||||
text: {
|
text: {
|
||||||
schema: customTextSchema,
|
schema: customTextSchema,
|
||||||
options: customTextOptions,
|
options: customTextOptions,
|
||||||
Component: CustomData,
|
Component: ManualDataEntry,
|
||||||
initialValues: { data: '', title: '' }
|
initialValues: { data: '', title: '' },
|
||||||
|
saveType: 'customEntry'
|
||||||
},
|
},
|
||||||
file: {
|
file: {
|
||||||
schema: customFileSchema,
|
schema: customFileSchema,
|
||||||
options: customUploadOptions,
|
options: customUploadOptions,
|
||||||
Component: CustomData,
|
Component: ManualDataEntry,
|
||||||
initialValues: { file: '', title: '' }
|
initialValues: { file: null, title: '' },
|
||||||
|
saveType: 'customEntryUpload'
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
schema: customImageSchema,
|
schema: customImageSchema,
|
||||||
options: customUploadOptions,
|
options: customUploadOptions,
|
||||||
Component: CustomData,
|
Component: ManualDataEntry,
|
||||||
initialValues: { image: '', title: '' }
|
initialValues: { image: null, title: '' },
|
||||||
|
saveType: 'customEntryUpload'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,6 +340,142 @@ const entryType = {
|
||||||
initialValues: { entryType: '' }
|
initialValues: { entryType: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Customer data
|
||||||
|
|
||||||
|
const customerDataElements = {
|
||||||
|
idCardData: [
|
||||||
|
{
|
||||||
|
name: 'firstName',
|
||||||
|
label: 'First name',
|
||||||
|
component: TextInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'documentNumber',
|
||||||
|
label: 'ID number',
|
||||||
|
component: TextInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dateOfBirth',
|
||||||
|
label: 'Birthdate',
|
||||||
|
component: TextInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gender',
|
||||||
|
label: 'Gender',
|
||||||
|
component: TextInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lastName',
|
||||||
|
label: 'Last name',
|
||||||
|
component: TextInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expirationDate',
|
||||||
|
label: 'Expiration Date',
|
||||||
|
component: TextInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'country',
|
||||||
|
label: 'Country',
|
||||||
|
component: TextInput
|
||||||
|
}
|
||||||
|
],
|
||||||
|
usSsn: [
|
||||||
|
{
|
||||||
|
name: 'usSsn',
|
||||||
|
label: 'US SSN',
|
||||||
|
component: TextInput,
|
||||||
|
size: 190
|
||||||
|
}
|
||||||
|
],
|
||||||
|
idCardPhoto: [{ name: 'idCardPhoto' }],
|
||||||
|
frontCamera: [{ name: 'frontCamera' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerDataSchemas = {
|
||||||
|
idCardData: Yup.object().shape({
|
||||||
|
firstName: Yup.string().required(),
|
||||||
|
lastName: Yup.string().required(),
|
||||||
|
documentNumber: Yup.string().required(),
|
||||||
|
dateOfBirth: Yup.string()
|
||||||
|
.test({
|
||||||
|
test: val => isValid(parse(new Date(), 'yyyy-MM-dd', val))
|
||||||
|
})
|
||||||
|
.required(),
|
||||||
|
gender: Yup.string().required(),
|
||||||
|
country: Yup.string().required(),
|
||||||
|
expirationDate: Yup.string()
|
||||||
|
.test({
|
||||||
|
test: val => isValid(parse(new Date(), 'yyyy-MM-dd', val))
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
}),
|
||||||
|
usSsn: Yup.object().shape({
|
||||||
|
usSsn: Yup.string().required()
|
||||||
|
}),
|
||||||
|
idCardPhoto: Yup.object().shape({
|
||||||
|
idCardPhoto: Yup.mixed().required()
|
||||||
|
}),
|
||||||
|
frontCamera: Yup.object().shape({
|
||||||
|
frontCamera: Yup.mixed().required()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const requirementElements = {
|
||||||
|
idCardData: {
|
||||||
|
schema: customerDataSchemas.idCardData,
|
||||||
|
options: customerDataElements.idCardData,
|
||||||
|
Component: ManualDataEntry,
|
||||||
|
initialValues: {
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
documentNumber: '',
|
||||||
|
dateOfBirth: '',
|
||||||
|
gender: '',
|
||||||
|
country: '',
|
||||||
|
expirationDate: ''
|
||||||
|
},
|
||||||
|
saveType: 'customerData'
|
||||||
|
},
|
||||||
|
usSsn: {
|
||||||
|
schema: customerDataSchemas.usSsn,
|
||||||
|
options: customerDataElements.usSsn,
|
||||||
|
Component: ManualDataEntry,
|
||||||
|
initialValues: { usSsn: '' },
|
||||||
|
saveType: 'customerData'
|
||||||
|
},
|
||||||
|
idCardPhoto: {
|
||||||
|
schema: customerDataSchemas.idCardPhoto,
|
||||||
|
options: customerDataElements.idCardPhoto,
|
||||||
|
Component: ManualDataEntry,
|
||||||
|
initialValues: { idCardPhoto: null },
|
||||||
|
saveType: 'customerDataUpload'
|
||||||
|
},
|
||||||
|
frontCamera: {
|
||||||
|
schema: customerDataSchemas.frontCamera,
|
||||||
|
options: customerDataElements.frontCamera,
|
||||||
|
Component: ManualDataEntry,
|
||||||
|
initialValues: { frontCamera: null },
|
||||||
|
saveType: 'customerDataUpload'
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
// schema: customerDataSchemas.customInfoRequirement,
|
||||||
|
Component: ManualDataEntry,
|
||||||
|
initialValues: { customInfoRequirement: null },
|
||||||
|
saveType: 'customInfoRequirement'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDates = values => {
|
||||||
|
R.map(
|
||||||
|
elem =>
|
||||||
|
(values[elem] = format('yyyyMMdd')(
|
||||||
|
parse(new Date(), 'yyyy-MM-dd', values[elem])
|
||||||
|
))
|
||||||
|
)(['dateOfBirth', 'expirationDate'])
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
const mapKeys = pair => {
|
const mapKeys = pair => {
|
||||||
const [key, value] = pair
|
const [key, value] = pair
|
||||||
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
|
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
|
||||||
|
|
@ -279,5 +512,12 @@ export {
|
||||||
getName,
|
getName,
|
||||||
entryType,
|
entryType,
|
||||||
customElements,
|
customElements,
|
||||||
formatPhotosData
|
requirementElements,
|
||||||
|
formatPhotosData,
|
||||||
|
customerDataElements,
|
||||||
|
customerDataSchemas,
|
||||||
|
formatDates,
|
||||||
|
REQUIREMENT,
|
||||||
|
CUSTOM,
|
||||||
|
ID_CARD_DATA
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -565,7 +565,7 @@ const Requirement = ({ customInfoRequests }) => {
|
||||||
}
|
}
|
||||||
const options = enableCustomRequirement
|
const options = enableCustomRequirement
|
||||||
? [...requirementOptions, customInfoOption]
|
? [...requirementOptions, customInfoOption]
|
||||||
: [...requirementOptions, { ...customInfoOption, disabled: true }]
|
: [...requirementOptions]
|
||||||
const titleClass = {
|
const titleClass = {
|
||||||
[classes.error]:
|
[classes.error]:
|
||||||
(!!errors.requirement && !isSuspend && !isCustom) ||
|
(!!errors.requirement && !isSuspend && !isCustom) ||
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue