diff --git a/lib/customers.js b/lib/customers.js index 1c93d261..1b03b9ea 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -993,6 +993,7 @@ function addCustomField (customerId, label, value) { } }) ) + .then(res => !_.isNil(res)) } function saveCustomField (customerId, fieldId, newValue) { diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index 58461fd8..9edbf762 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -21,7 +21,7 @@ const resolvers = { return customers.updateCustomer(customerId, customerInput, token) }, 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), editCustomer: async (root, { customerId, customerEdit }, context) => { const token = authentication.getToken(context) diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index ce8cb3cb..f302c263 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -1,12 +1,6 @@ const { gql } = require('apollo-server-express') const typeDef = gql` - type CustomerCustomField { - id: ID - label: String - value: String - } - type Customer { id: ID! authorizedOverride: String @@ -86,6 +80,12 @@ const typeDef = gql` content: String } + type CustomerCustomField { + id: ID + label: String + value: String + } + type Query { customers(phone: String, name: String, address: String, id: String): [Customer] @auth customer(customerId: ID!): Customer @auth @@ -94,9 +94,9 @@ const typeDef = gql` type Mutation { setCustomer(customerId: ID!, customerInput: CustomerInput): Customer @auth - addCustomField(customerId: ID!, label: String!, value: String!): CustomerCustomField @auth - saveCustomField(customerId: ID!, fieldId: ID!, value: String!): CustomerCustomField @auth - removeCustomField(customerId: ID!, fieldId: ID!): CustomerCustomField @auth + addCustomField(customerId: ID!, label: String!, value: String!): Boolean @auth + saveCustomField(customerId: ID!, fieldId: ID!, value: String!): Boolean @auth + removeCustomField(customerId: ID!, fieldId: ID!): Boolean @auth editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth replacePhoto(customerId: ID!, photoType: String, newPhoto: UploadGQL): Customer @auth diff --git a/new-lamassu-admin/src/components/Carousel.js b/new-lamassu-admin/src/components/Carousel.js index f751cd1f..35f4ef20 100644 --- a/new-lamassu-admin/src/components/Carousel.js +++ b/new-lamassu-admin/src/components/Carousel.js @@ -38,6 +38,12 @@ export const Carousel = memo(({ photosData, slidePhoto }) => { opacity: 1 } }} + // navButtonsWrapperProps={{ + // style: { + // background: 'linear-gradient(to right, black 10%, transparent 80%)', + // opacity: '0.4' + // } + // }} autoPlay={false} indicators={false} navButtonsAlwaysVisible={true} diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index 18a53cac..1a06b233 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -1,6 +1,6 @@ import Grid from '@material-ui/core/Grid' 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 * as R from 'ramda' import { useState, React } from 'react' @@ -26,6 +26,11 @@ import { URI } from 'src/utils/apollo' import styles from './CustomerData.styles.js' import { EditableCard } from './components' +import { + customerDataElements, + customerDataSchemas, + formatDates +} from './helper.js' const useStyles = makeStyles(styles) @@ -63,7 +68,8 @@ const CustomerData = ({ editCustomer, deleteEditedData, updateCustomRequest, - authorizeCustomRequest + authorizeCustomRequest, + updateCustomEntry }) => { const classes = useStyles() const [listView, setListView] = useState(false) @@ -84,8 +90,8 @@ const CustomerData = ({ R.compose(R.toLower, R.path(['customInfoRequest', 'customRequest', 'name'])) ) - const customEntries = null // get customer custom entries - const customRequirements = [] // get customer custom requirements + const customFields = [] + const customRequirements = [] const customInfoRequests = sortByName( R.path(['customInfoRequests'])(customer) ?? [] ) @@ -94,87 +100,8 @@ const CustomerData = ({ 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 = { - idScan: { + idCardData: { firstName: R.path(['firstName'])(idData) ?? '', lastName: R.path(['lastName'])(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 = [ { - fields: idScanElements, + fields: customerDataElements.idCardData, title: 'ID Scan', titleIcon: , state: R.path(['idCardDataOverride'])(customer), @@ -226,8 +143,8 @@ const CustomerData = ({ editCustomer({ idCardData: _.merge(idData, formatDates(values)) }), - validationSchema: schemas.idScan, - initialValues: initialValues.idScan, + validationSchema: customerDataSchemas.idCardData, + initialValues: initialValues.idCardData, isAvailable: !_.isNil(idData) }, { @@ -257,7 +174,7 @@ const CustomerData = ({ isAvailable: !_.isNil(sanctions) }, { - fields: frontCameraElements, + fields: customerDataElements.frontCamera, title: 'Front facing camera', titleIcon: , state: R.path(['frontCameraOverride'])(customer), @@ -279,12 +196,12 @@ const CustomerData = ({ /> ) : null, hasImage: true, - validationSchema: schemas.frontCamera, + validationSchema: customerDataSchemas.frontCamera, initialValues: initialValues.frontCamera, isAvailable: !_.isNil(customer.frontCameraPath) }, { - fields: idCardPhotoElements, + fields: customerDataElements.idCardPhoto, title: 'ID card image', titleIcon: , state: R.path(['idCardPhotoOverride'])(customer), @@ -304,20 +221,20 @@ const CustomerData = ({ /> ) : null, hasImage: true, - validationSchema: schemas.idCardPhoto, + validationSchema: customerDataSchemas.idCardPhoto, initialValues: initialValues.idCardPhoto, isAvailable: !_.isNil(customer.idCardPhotoPath) }, { - fields: usSsnElements, + fields: customerDataElements.usSsn, title: 'US SSN', titleIcon: , state: R.path(['usSsnOverride'])(customer), authorize: () => updateCustomer({ usSsnOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ usSsnOverride: OVERRIDE_REJECTED }), - save: values => editCustomer({ usSsn: values.usSsn }), + save: values => editCustomer(values), deleteEditedData: () => deleteEditedData({ usSsn: null }), - validationSchema: schemas.usSsn, + validationSchema: customerDataSchemas.usSsn, initialValues: initialValues.usSsn, isAvailable: !_.isNil(customer.usSsn) } @@ -374,6 +291,34 @@ const CustomerData = ({ }) }, customInfoRequests) + R.forEach(it => { + customFields.push({ + fields: [ + { + name: it.label, + label: it.label, + value: it.value ?? '', + component: TextInput + } + ], + title: it.label, + titleIcon: , + 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 = ( { title, @@ -415,19 +360,24 @@ const CustomerData = ({

{'Customer data'}

- setListView(false)} - /> - setListView(true)}> + {// TODO: Remove false condition for next release + false && ( + <> + setListView(false)} + /> + setListView(true)}> + + )}
{!listView && customer && ( @@ -444,9 +394,21 @@ const CustomerData = ({ )} - {customEntries && ( + {!_.isEmpty(customFields) && (
Custom data entry + + + {customFields.map((elem, idx) => { + return isEven(idx) ? editableCard(elem, idx) : null + })} + + + {customFields.map((elem, idx) => { + return !isEven(idx) ? editableCard(elem, idx) : null + })} + +
)} {!R.isEmpty(customRequirements) && ( diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index 91b076ee..59d0202c 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -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 DataReversedIcon } from 'src/styling/icons/button/data/white.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' -import { ReactComponent as Discount } from 'src/styling/icons/button/discount/zodiac.svg' +// TODO: Enable for next release +// 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 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 history = useHistory() @@ -255,6 +277,20 @@ const CustomerProfile = memo(() => { 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, { onCompleted: () => getCustomer() }) @@ -294,6 +330,27 @@ const CustomerProfile = memo(() => { 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 => setCustomer({ variables: { @@ -302,7 +359,7 @@ const CustomerProfile = memo(() => { } }) - const replacePhoto = it => + const replacePhoto = it => { replaceCustomerPhoto({ variables: { customerId, @@ -310,14 +367,18 @@ const CustomerProfile = memo(() => { photoType: it.photoType } }) + setWizard(null) + } - const editCustomer = it => + const editCustomer = it => { editCustomerData({ variables: { customerId, customerEdit: it } }) + setWizard(null) + } const deleteEditedData = it => deleteCustomerEditedData({ @@ -385,6 +446,12 @@ const CustomerProfile = memo(() => { const timezone = R.path(['config', 'locale_timezone'], configResponse) + const customInfoRequirementOptions = + activeCustomRequests?.customInfoRequests?.map(it => ({ + value: it.id, + display: it.customRequest.name + })) ?? [] + const classes = useStyles() return ( @@ -428,14 +495,17 @@ const CustomerProfile = memo(() => { onClick={() => setWizard(true)}> {`Manual data entry`} - {}}> {`Add individual discount`} - + */ + } {isSuspended && ( { editCustomer={editCustomer} deleteEditedData={deleteEditedData} updateCustomRequest={setCustomerCustomInfoRequest} - authorizeCustomRequest={authorizeCustomRequest}> + authorizeCustomRequest={authorizeCustomRequest} + updateCustomEntry={updateCustomEntry}>
)} {isNotes && ( @@ -544,8 +615,11 @@ const CustomerProfile = memo(() => { {wizard && ( {}} + save={saveCustomEntry} + addPhoto={replacePhoto} + addCustomerData={editCustomer} onClose={() => setWizard(null)} + customInfoRequirementOptions={customInfoRequirementOptions} /> )}
diff --git a/new-lamassu-admin/src/pages/Customers/Wizard.js b/new-lamassu-admin/src/pages/Customers/Wizard.js index 87bebaa8..6f81b9de 100644 --- a/new-lamassu-admin/src/pages/Customers/Wizard.js +++ b/new-lamassu-admin/src/pages/Customers/Wizard.js @@ -9,7 +9,14 @@ import Stepper from 'src/components/Stepper' import { Button } from 'src/components/buttons' 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 @@ -41,23 +48,40 @@ const styles = { margin: [[0, 4, 0, 2]], borderBottom: `1px solid ${comet}`, display: 'inline-block' + }, + dropdownField: { + marginTop: 16, + minWidth: 155 } } const useStyles = makeStyles(styles) const getStep = (step, selectedValues) => { + const elements = + selectedValues?.entryType === REQUIREMENT && + !R.isNil(selectedValues?.requirement) + ? requirementElements[selectedValues?.requirement] + : customElements[selectedValues?.dataType] + switch (step) { case 1: return entryType case 2: - return customElements[selectedValues?.dataType] + return elements default: return Fragment } } -const Wizard = ({ onClose, save, error }) => { +const Wizard = ({ + onClose, + save, + error, + customInfoRequirementOptions, + addCustomerData, + addPhoto +}) => { const classes = useStyles() const [selectedValues, setSelectedValues] = useState(null) @@ -66,6 +90,10 @@ const Wizard = ({ onClose, save, error }) => { 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 stepOptions = getStep(step, selectedValues) @@ -74,7 +102,23 @@ const Wizard = ({ onClose, save, error }) => { setSelectedValues(newConfig) 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({ @@ -106,6 +150,7 @@ const Wizard = ({ onClose, save, error }) => {
diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index 0239db8a..2a7cba0a 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -150,7 +150,7 @@ const EditableCard = ({

{title}

- {state && ( + {state && authorize && (
@@ -207,17 +207,19 @@ const EditableCard = ({
{!editing && (
-
- deleteEditedData()}> - {`Delete`} - -
- + {// TODO: Remove false condition for next release + false && ( +
+ deleteEditedData()}> + {`Delete`} + +
+ )}
- {authorized.label !== 'Accepted' && ( + {authorize && authorized.label !== 'Accepted' && (
)} - {authorized.label !== 'Rejected' && ( + {authorize && authorized.label !== 'Rejected' && ( { const { setFieldValue } = useFormikContext() 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( acceptedData => { - // TODO: attach the uploaded data to the form as well - setFieldValue(type, R.head(acceptedData).name) + setFieldValue(type, R.head(acceptedData)) setData({ preview: isImage @@ -84,12 +87,12 @@ const Upload = ({ type }) => {
)} - {!R.isEmpty(data) && type === IMAGE && ( + {!R.isEmpty(data) && isImage && (
)} - {!R.isEmpty(data) && type !== IMAGE && ( + {!R.isEmpty(data) && !isImage && (

{data.preview}

diff --git a/new-lamassu-admin/src/pages/Customers/helper.js b/new-lamassu-admin/src/pages/Customers/helper.js index c21f1d7e..83cbe3ea 100644 --- a/new-lamassu-admin/src/pages/Customers/helper.js +++ b/new-lamassu-admin/src/pages/Customers/helper.js @@ -1,11 +1,16 @@ import { makeStyles, Box } from '@material-ui/core' import classnames from 'classnames' +import { parse, isValid, format } from 'date-fns/fp' import { Field, useFormikContext } from 'formik' import { parsePhoneNumberFromString } from 'libphonenumber-js' import * as R from 'ramda' 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 { errorColor } from 'src/styling/variables' import { MANUAL } from 'src/utils/constants' @@ -35,10 +40,21 @@ const useStyles = makeStyles({ specialGrid: { display: 'grid', gridTemplateColumns: [[182, 162, 141]] + }, + picker: { + width: 150 + }, + field: { + '& > *:last-child': { + marginBottom: 24 + } } }) const CUSTOMER_BLOCKED = 'blocked' +const CUSTOM = 'custom' +const REQUIREMENT = 'requirement' +const ID_CARD_DATA = 'idCardData' const getAuthorizedStatus = (it, triggers) => { const fields = [ @@ -97,34 +113,46 @@ const getName = it => { ) ?? ''}`.trim() } +// Manual Entry Wizard + const entryOptions = [ { display: 'Custom entry', code: 'custom' }, { display: 'Populate existing requirement', code: 'requirement' } ] const dataOptions = [ - { display: 'Text', code: 'text' }, - { display: 'File', code: 'file' }, - { display: 'Image', code: 'image' } + { display: 'Text', code: 'text' } + // TODO: Requires backend modifications to support File and Image + // { display: 'File', code: 'file' }, + // { display: 'Image', code: 'image' } ] const requirementOptions = [ - { display: 'Birthdate', code: 'birthdate' }, { display: 'ID card image', code: 'idCardPhoto' }, { 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 = [ - { display: 'Data entry title', code: 'title' }, - { display: 'Data entry', code: 'data' } + { label: 'Data entry title', name: 'title' }, + { 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({ - entryType: Yup.string().required() +const entryTypeSchema = Yup.lazy(values => { + 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({ @@ -142,13 +170,18 @@ const customTextSchema = Yup.object().shape({ data: Yup.string().required() }) -const EntryType = () => { +const updateRequirementOptions = it => [ + { + display: 'Custom information requirement', + code: 'custom' + }, + ...it +] + +const EntryType = ({ customInfoRequirementOptions }) => { const classes = useStyles() const { values } = useFormikContext() - const CUSTOM = 'custom' - const REQUIREMENT = 'requirement' - const displayCustomOptions = values.entryType === CUSTOM const displayRequirementOptions = values.entryType === REQUIREMENT @@ -188,7 +221,13 @@ const EntryType = () => { { ) } -const CustomData = ({ selectedValues }) => { +const ManualDataEntry = ({ selectedValues, customInfoRequirementOptions }) => { + const classes = useStyles() + + const typeOfEntrySelected = selectedValues?.entryType 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 ( <> -

{`Custom ${dataTypeSelected} entry`}

+

{title}

- {customElements[dataTypeSelected].options.map(({ display, code }) => ( - - ))} - {upload && } + {isCustomInfoRequirement && ( + {}} + /> + )} +
+ {!upload && + !isCustomInfoRequirement && + elements.options.map(({ label, name }) => ( + + ))} +
+ {upload && ( + + )} ) } @@ -219,20 +313,23 @@ const customElements = { text: { schema: customTextSchema, options: customTextOptions, - Component: CustomData, - initialValues: { data: '', title: '' } + Component: ManualDataEntry, + initialValues: { data: '', title: '' }, + saveType: 'customEntry' }, file: { schema: customFileSchema, options: customUploadOptions, - Component: CustomData, - initialValues: { file: '', title: '' } + Component: ManualDataEntry, + initialValues: { file: null, title: '' }, + saveType: 'customEntryUpload' }, image: { schema: customImageSchema, options: customUploadOptions, - Component: CustomData, - initialValues: { image: '', title: '' } + Component: ManualDataEntry, + initialValues: { image: null, title: '' }, + saveType: 'customEntryUpload' } } @@ -243,6 +340,142 @@ const 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 [key, value] = pair if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') { @@ -279,5 +512,12 @@ export { getName, entryType, customElements, - formatPhotosData + requirementElements, + formatPhotosData, + customerDataElements, + customerDataSchemas, + formatDates, + REQUIREMENT, + CUSTOM, + ID_CARD_DATA } diff --git a/new-lamassu-admin/src/pages/Triggers/helper.js b/new-lamassu-admin/src/pages/Triggers/helper.js index 6889fc66..6830c99e 100644 --- a/new-lamassu-admin/src/pages/Triggers/helper.js +++ b/new-lamassu-admin/src/pages/Triggers/helper.js @@ -565,7 +565,7 @@ const Requirement = ({ customInfoRequests }) => { } const options = enableCustomRequirement ? [...requirementOptions, customInfoOption] - : [...requirementOptions, { ...customInfoOption, disabled: true }] + : [...requirementOptions] const titleClass = { [classes.error]: (!!errors.requirement && !isSuspend && !isCustom) ||