diff --git a/new-lamassu-admin/package-lock.json b/new-lamassu-admin/package-lock.json
index 569ead0d..bad28fc6 100644
--- a/new-lamassu-admin/package-lock.json
+++ b/new-lamassu-admin/package-lock.json
@@ -7197,6 +7197,11 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"dev": true
},
+ "attr-accept": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+ "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
+ },
"auto-bind": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-2.1.1.tgz",
@@ -7206,9 +7211,9 @@
},
"dependencies": {
"@types/react": {
- "version": "16.14.11",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.11.tgz",
- "integrity": "sha512-Don0MtsZZ3fjwTJ2BsoqkyOy7e176KplEAKOpr/4XDdzinlyJBn9yfsKn5mcSgn4kh1B22+3tBnzBC1z63ybtQ==",
+ "version": "16.14.21",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.21.tgz",
+ "integrity": "sha512-rY4DzPKK/4aohyWiDRHS2fotN5rhBSK6/rz1X37KzNna9HJyqtaGAbq9fVttrEPWF5ywpfIP1ITL8Xi2QZn6Eg==",
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -7216,9 +7221,9 @@
}
},
"csstype": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
- "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
+ "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
}
}
},
@@ -12769,6 +12774,21 @@
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
"integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
},
+ "file-selector": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz",
+ "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==",
+ "requires": {
+ "tslib": "^2.0.3"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ }
+ }
+ },
"file-system-cache": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-1.0.5.tgz",
@@ -21820,6 +21840,16 @@
"prop-types": "^15.6.0"
}
},
+ "react-dropzone": {
+ "version": "11.4.2",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.4.2.tgz",
+ "integrity": "sha512-ocYzYn7Qgp0tFc1gQtUTOaHHSzVTwhWHxxY+r7cj2jJTPfMTZB5GWSJHdIVoxsl+EQENpjJ/6Zvcw0BqKZQ+Eg==",
+ "requires": {
+ "attr-accept": "^2.2.1",
+ "file-selector": "^0.2.2",
+ "prop-types": "^15.7.2"
+ }
+ },
"react-error-overlay": {
"version": "6.0.9",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
diff --git a/new-lamassu-admin/package.json b/new-lamassu-admin/package.json
index 041b402c..24c17fa3 100644
--- a/new-lamassu-admin/package.json
+++ b/new-lamassu-admin/package.json
@@ -36,6 +36,7 @@
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "^16.10.2",
"react-material-ui-carousel": "^2.2.7",
+ "react-dropzone": "^11.4.2",
"react-number-format": "^4.4.1",
"react-otp-input": "^2.3.0",
"react-router-dom": "5.1.2",
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js
index 6293893c..7cd7d501 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerData.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js
@@ -31,23 +31,23 @@ import { getName } from './helper.js'
const useStyles = makeStyles(styles)
-const imageWidth = 165
-const imageHeight = 45
-const popupImageWidth = 360
-const popupImageHeight = 240
+const IMAGE_WIDTH = 165
+const IMAGE_HEIGHT = 45
+const POPUP_IMAGE_WIDTH = 360
+const POPUP_IMAGE_HEIGHT = 240
const Photo = ({ show, src }) => {
- const classes = useStyles({ width: imageWidth })
+ const classes = useStyles({ width: IMAGE_WIDTH })
return (
<>
{show ? (
) : (
@@ -75,6 +75,8 @@ const CustomerData = ({ customer, updateCustomer }) => {
? 'Passed'
: 'Failed'
+ const customEntries = null // get customer custom entries
+
const isEven = elem => elem % 2 === 0
const getVisibleCards = _.filter(
@@ -256,21 +258,25 @@ const CustomerData = ({ customer, updateCustomer }) => {
}
]
- const editableCard = ({
- title,
- authorize,
- reject,
- state,
- titleIcon,
- data,
- save,
- children,
- validationSchema,
- initialValues
- }) => {
+ const editableCard = (
+ {
+ title,
+ authorize,
+ reject,
+ state,
+ titleIcon,
+ data,
+ save,
+ children,
+ validationSchema,
+ initialValues
+ },
+ idx
+ ) => {
return (
{
{visibleCards.map((elem, idx) => {
- return isEven(idx) ? editableCard(elem) : null
+ return isEven(idx) ? editableCard(elem, idx) : null
})}
{visibleCards.map((elem, idx) => {
- return !isEven(idx) ? editableCard(elem) : null
+ return !isEven(idx) ? editableCard(elem, idx) : null
})}
)}
+ {customEntries && (
+
+
{'Custom data entry'}
+
+ )}
)
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js
index 5dfecd1a..5977bbfe 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js
@@ -1,3 +1,5 @@
+import { offColor } from 'src/styling/variables'
+
export default {
header: {
display: 'flex',
@@ -16,5 +18,24 @@ export default {
},
viewIcons: {
marginRight: 12
+ },
+ wrapper: {
+ display: 'flex'
+ },
+ separator: {
+ display: 'flex',
+ flexBasis: '100%',
+ justifyContent: 'center',
+ color: offColor,
+ margin: [[8, 0, 8, 0]],
+ '&::before, &::after': {
+ content: '',
+ flexGrow: 1,
+ background: offColor,
+ height: 1,
+ fontSize: 1,
+ lineHeight: 0,
+ margin: [[0, 8, 0, 8]]
+ }
}
}
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
index 657cbf47..6652b4d2 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
@@ -16,6 +16,8 @@ import { ReactComponent as AuthorizeReversedIcon } from 'src/styling/icons/butto
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg'
import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/block/white.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 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'
import { fromNamespace, namespaces } from 'src/utils/config'
@@ -25,7 +27,8 @@ import styles from './CustomerProfile.styles'
import {
CustomerDetails,
TransactionsList,
- CustomerSidebar
+ CustomerSidebar,
+ Wizard
} from './components'
import { getFormattedPhone, getName } from './helper'
@@ -110,7 +113,10 @@ const SET_CUSTOMER = gql`
const CustomerProfile = memo(() => {
const history = useHistory()
+
const [showCompliance, setShowCompliance] = useState(false)
+ const [wizard, setWizard] = useState(false)
+ const [error] = useState(null)
const [clickedItem, setClickedItem] = useState('overview')
const { id: customerId } = useParams()
@@ -184,6 +190,16 @@ const CustomerProfile = memo(() => {
/>
Actions
+
+
setWizard(true)}>
+ {`Manual data entry`}
+
+
)}
+ {wizard && (
+ {}}
+ onClose={() => setWizard(null)}
+ />
+ )}
>
)
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js
index 69094460..6326b300 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js
@@ -22,10 +22,16 @@ export default {
padding: [[0, props.blocked ? 35 : 48, 0]]
}),
customerDiscount: {
+ display: 'flex',
+ flexDirection: 'row',
+ margin: [[0, 0, 4, 0]],
+ padding: [[0, 23.5, 0]]
+ },
+ customerManualDataEntry: {
display: 'flex',
flexDirection: 'row',
margin: [[8, 0, 4, 0]],
- padding: [[0, 24, 0]]
+ padding: [[0, 40.5, 0]]
},
panels: {
display: 'flex'
diff --git a/new-lamassu-admin/src/pages/Customers/Wizard.js b/new-lamassu-admin/src/pages/Customers/Wizard.js
new file mode 100644
index 00000000..87bebaa8
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Customers/Wizard.js
@@ -0,0 +1,124 @@
+import { makeStyles } from '@material-ui/core'
+import { Form, Formik } from 'formik'
+import * as R from 'ramda'
+import React, { useState, Fragment } from 'react'
+
+import ErrorMessage from 'src/components/ErrorMessage'
+import Modal from 'src/components/Modal'
+import Stepper from 'src/components/Stepper'
+import { Button } from 'src/components/buttons'
+import { comet } from 'src/styling/variables'
+
+import { entryType, customElements } from './helper'
+
+const LAST_STEP = 2
+
+const styles = {
+ stepper: {
+ margin: [[16, 0, 14, 0]]
+ },
+ submit: {
+ display: 'flex',
+ flexDirection: 'row',
+ margin: [['auto', 0, 24]]
+ },
+ button: {
+ marginLeft: 'auto'
+ },
+ form: {
+ height: '100%',
+ display: 'flex',
+ flexDirection: 'column'
+ },
+ infoTitle: {
+ margin: [[18, 0, 20, 0]]
+ },
+ infoCurrentText: {
+ color: comet
+ },
+ blankSpace: {
+ padding: [[0, 30]],
+ margin: [[0, 4, 0, 2]],
+ borderBottom: `1px solid ${comet}`,
+ display: 'inline-block'
+ }
+}
+
+const useStyles = makeStyles(styles)
+
+const getStep = (step, selectedValues) => {
+ switch (step) {
+ case 1:
+ return entryType
+ case 2:
+ return customElements[selectedValues?.dataType]
+ default:
+ return Fragment
+ }
+}
+
+const Wizard = ({ onClose, save, error }) => {
+ const classes = useStyles()
+
+ const [selectedValues, setSelectedValues] = useState(null)
+
+ const [{ step, config }, setState] = useState({
+ step: 1
+ })
+
+ const isLastStep = step === LAST_STEP
+ const stepOptions = getStep(step, selectedValues)
+
+ const onContinue = async it => {
+ const newConfig = R.merge(config, stepOptions.schema.cast(it))
+ setSelectedValues(newConfig)
+
+ if (isLastStep) {
+ return save(newConfig)
+ }
+
+ setState({
+ step: step + 1,
+ config: newConfig
+ })
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+}
+
+export default Wizard
diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.styles.js b/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.styles.js
index f40f871a..2d967f3d 100644
--- a/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.styles.js
+++ b/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.styles.js
@@ -30,10 +30,10 @@ export default {
color: white,
backgroundColor: offDarkColor,
'&:first-child': {
- borderRadius: '5px 5px 0px 0px'
+ borderRadius: [5, 5, 0, 0]
},
'&:last-child': {
- borderRadius: '0px 0px 5px 5px'
+ borderRadius: [0, 0, 5, 5]
}
},
icon: {
diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js
index fc75f458..a97023dc 100644
--- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js
+++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js
@@ -38,7 +38,7 @@ const fieldStyles = {
},
label: {
color: comet,
- margin: [[0, 3]]
+ margin: [[0, 0, 0, 0]]
},
notEditing: {
display: 'flex',
@@ -58,8 +58,10 @@ const fieldStyles = {
}
},
editing: {
- '& > input': {
- padding: 0
+ '& > div': {
+ '& > input': {
+ padding: 0
+ }
}
}
}
@@ -88,9 +90,7 @@ const EditableField = ({ editing, field, size, ...props }) => {
{
+ const classes = useStyles()
+
+ const [data, setData] = useState({})
+
+ const { setFieldValue } = useFormikContext()
+
+ const IMAGE = 'image'
+ const isImage = type === IMAGE
+
+ const onDrop = useCallback(
+ acceptedData => {
+ // TODO: attach the uploaded data to the form as well
+ setFieldValue(type, R.head(acceptedData).name)
+
+ setData({
+ preview: isImage
+ ? URL.createObjectURL(R.head(acceptedData))
+ : R.head(acceptedData).name
+ })
+ },
+ [isImage, type, setFieldValue]
+ )
+
+ const { getRootProps, getInputProps } = useDropzone({ onDrop })
+
+ return (
+ <>
+
+ {R.isEmpty(data) && (
+
+
+
+ {isImage ? (
+
+ ) : (
+
+ )}
+ {`Drag and drop ${
+ isImage ? 'an image' : 'a file'
+ } or click to open the explorer`}
+
+
+ )}
+ {!R.isEmpty(data) && type === IMAGE && (
+
+

+
+ )}
+ {!R.isEmpty(data) && type !== IMAGE && (
+
+
{data.preview}
+
+ )}
+
+ >
+ )
+}
+
+export default Upload
diff --git a/new-lamassu-admin/src/pages/Customers/components/index.js b/new-lamassu-admin/src/pages/Customers/components/index.js
index a8b7b184..5e52dd81 100644
--- a/new-lamassu-admin/src/pages/Customers/components/index.js
+++ b/new-lamassu-admin/src/pages/Customers/components/index.js
@@ -1,9 +1,12 @@
+import Wizard from '../Wizard'
+
import CustomerDetails from './CustomerDetails'
import CustomerSidebar from './CustomerSidebar'
import EditableCard from './EditableCard'
import Field from './Field'
import IdDataCard from './IdDataCard'
import TransactionsList from './TransactionsList'
+import Upload from './Upload'
export {
CustomerDetails,
@@ -11,5 +14,7 @@ export {
TransactionsList,
CustomerSidebar,
Field,
- EditableCard
+ EditableCard,
+ Wizard,
+ Upload
}
diff --git a/new-lamassu-admin/src/pages/Customers/helper.js b/new-lamassu-admin/src/pages/Customers/helper.js
index 849f898e..a5d776cc 100644
--- a/new-lamassu-admin/src/pages/Customers/helper.js
+++ b/new-lamassu-admin/src/pages/Customers/helper.js
@@ -1,5 +1,41 @@
+import { makeStyles, Box } from '@material-ui/core'
+import classnames from 'classnames'
+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 { H4 } from 'src/components/typography'
+import { errorColor } from 'src/styling/variables'
+
+import { Upload } from './components'
+
+const useStyles = makeStyles({
+ radio: {
+ padding: 4,
+ margin: 4
+ },
+ radioGroup: {
+ flexDirection: 'row'
+ },
+ error: {
+ color: errorColor
+ },
+ specialLabel: {
+ height: 40,
+ padding: 0,
+ width: 250
+ },
+ label: {
+ height: 40,
+ padding: 0
+ },
+ specialGrid: {
+ display: 'grid',
+ gridTemplateColumns: [[182, 162, 141]]
+ }
+})
const CUSTOMER_BLOCKED = 'blocked'
@@ -27,4 +63,156 @@ const getName = it => {
) ?? ''}`.trim()
}
-export { getAuthorizedStatus, getFormattedPhone, getName }
+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' }
+]
+
+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' }
+]
+
+const customTextOptions = [
+ { display: 'Data entry title', code: 'title' },
+ { display: 'Data entry', code: 'data' }
+]
+
+const customUploadOptions = [{ display: 'Data entry title', code: 'title' }]
+
+const entryTypeSchema = Yup.object().shape({
+ entryType: Yup.string().required()
+})
+
+const customFileSchema = Yup.object().shape({
+ title: Yup.string().required(),
+ file: Yup.mixed().required()
+})
+
+const customImageSchema = Yup.object().shape({
+ title: Yup.string().required(),
+ image: Yup.mixed().required()
+})
+
+const customTextSchema = Yup.object().shape({
+ title: Yup.string().required(),
+ data: Yup.string().required()
+})
+
+const EntryType = () => {
+ const classes = useStyles()
+ const { values } = useFormikContext()
+
+ const CUSTOM = 'custom'
+ const REQUIREMENT = 'requirement'
+
+ const displayCustomOptions = values.entryType === CUSTOM
+ const displayRequirementOptions = values.entryType === REQUIREMENT
+
+ return (
+ <>
+
+ Type of entry
+
+
+ {displayCustomOptions && (
+
+
+ Type of data
+
+
+
+ )}
+ {displayRequirementOptions && (
+
+
+ Requirements
+
+
+
+ )}
+ >
+ )
+}
+
+const CustomData = ({ selectedValues }) => {
+ const dataTypeSelected = selectedValues?.dataType
+ const upload = dataTypeSelected === 'file' || dataTypeSelected === 'image'
+ return (
+ <>
+
+ {`Custom ${dataTypeSelected} entry`}
+
+ {customElements[dataTypeSelected].options.map(({ display, code }) => (
+
+ ))}
+ {upload && }
+ >
+ )
+}
+
+const customElements = {
+ text: {
+ schema: customTextSchema,
+ options: customTextOptions,
+ Component: CustomData,
+ initialValues: { data: '', title: '' }
+ },
+ file: {
+ schema: customFileSchema,
+ options: customUploadOptions,
+ Component: CustomData,
+ initialValues: { file: '', title: '' }
+ },
+ image: {
+ schema: customImageSchema,
+ options: customUploadOptions,
+ Component: CustomData,
+ initialValues: { image: '', title: '' }
+ }
+}
+
+const entryType = {
+ schema: entryTypeSchema,
+ options: entryOptions,
+ Component: EntryType,
+ initialValues: { entryType: '' }
+}
+
+export {
+ getAuthorizedStatus,
+ getFormattedPhone,
+ getName,
+ entryType,
+ customElements
+}
diff --git a/new-lamassu-admin/src/styling/icons/action/edit/comet.svg b/new-lamassu-admin/src/styling/icons/action/edit/comet.svg
index 8b77414e..10c785d1 100644
--- a/new-lamassu-admin/src/styling/icons/action/edit/comet.svg
+++ b/new-lamassu-admin/src/styling/icons/action/edit/comet.svg
@@ -1,6 +1,5 @@