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 }) => {