Merge pull request #1029 from josepfo/feat/twilio-api-data-ui
Feat: twilio api data UI
This commit is contained in:
commit
201fec33e4
11 changed files with 316 additions and 86 deletions
|
|
@ -121,7 +121,8 @@ async function updateCustomer (id, data, userToken) {
|
|||
'us_ssn_override',
|
||||
'sanctions_override',
|
||||
'front_camera_override',
|
||||
'suspended_until'
|
||||
'suspended_until',
|
||||
'phone_override'
|
||||
],
|
||||
_.mapKeys(_.snakeCase, data))
|
||||
|
||||
|
|
@ -169,6 +170,7 @@ function edit (id, data, userToken) {
|
|||
const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, _.omitBy(_.isNil, data)))
|
||||
if (_.isEmpty(filteredData)) return getCustomerById(id)
|
||||
const formattedData = enhanceEditedPhotos(enhanceEditedFields(filteredData, userToken))
|
||||
|
||||
const defaultDbData = {
|
||||
customer_id: id,
|
||||
created: new Date(),
|
||||
|
|
@ -688,18 +690,18 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
|
|||
function getCustomerById (id) {
|
||||
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
||||
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override,
|
||||
phone, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||
phone, phone_at, phone_override, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||
id_card_photo_path, id_card_photo_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||
sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
|
||||
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes, is_test_customer
|
||||
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, subscriber_info_at, custom_fields, notes, is_test_customer
|
||||
FROM (
|
||||
SELECT c.id, c.authorized_override,
|
||||
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
||||
c.suspended_until > now() AS is_suspended,
|
||||
c.front_camera_path, c.front_camera_override, c.front_camera_at,
|
||||
c.phone, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
|
||||
c.phone, c.phone_at, c.phone_override, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
|
||||
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
|
||||
c.sanctions_at, c.sanctions_override, c.subscriber_info, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
||||
c.sanctions_at, c.sanctions_override, c.subscriber_info, c.subscriber_info_at, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
||||
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
|
||||
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY c.id) AS total_txs,
|
||||
sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (PARTITION BY c.id) AS total_spent, ccf.custom_fields
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const typeDef = gql`
|
|||
lastTxClass: String
|
||||
transactions: [Transaction]
|
||||
subscriberInfo: JSONObject
|
||||
phoneOverride: String
|
||||
customFields: [CustomerCustomField]
|
||||
customInfoRequests: [CustomRequestData]
|
||||
notes: [CustomerNote]
|
||||
|
|
@ -63,12 +64,14 @@ const typeDef = gql`
|
|||
lastTxClass: String
|
||||
suspendedUntil: Date
|
||||
subscriberInfo: Boolean
|
||||
phoneOverride: String
|
||||
}
|
||||
|
||||
input CustomerEdit {
|
||||
idCardData: JSONObject
|
||||
idCardPhoto: UploadGQL
|
||||
usSsn: String
|
||||
subscriberInfo: JSONObject
|
||||
}
|
||||
|
||||
type CustomerNote {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const _ = require('lodash/fp')
|
|||
const BN = require('../../../bn')
|
||||
const E = require('../../../error')
|
||||
const { utils: coinUtils } = require('lamassu-coins')
|
||||
const consoleLogLevel = require('console-log-level')
|
||||
|
||||
const NAME = 'FakeWallet'
|
||||
const BATCHABLE_COINS = ['BTC']
|
||||
|
|
|
|||
17
migrations/1641482376890-add-overrides-to-subscriber-info.js
Normal file
17
migrations/1641482376890-add-overrides-to-subscriber-info.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
var db = require('./db')
|
||||
|
||||
exports.up = function (next) {
|
||||
var sql = [
|
||||
`ALTER TABLE customers
|
||||
ADD COLUMN phone_override VERIFICATION_TYPE NOT NULL DEFAULT 'automatic',
|
||||
ADD COLUMN phone_override_by UUID,
|
||||
ADD COLUMN phone_override_at TIMESTAMPTZ
|
||||
`
|
||||
]
|
||||
|
||||
db.multi(sql, next)
|
||||
}
|
||||
|
||||
exports.down = function (next) {
|
||||
next()
|
||||
}
|
||||
|
|
@ -7,8 +7,15 @@ import styles from './Button.styles'
|
|||
const useStyles = makeStyles(styles)
|
||||
|
||||
const ActionButton = memo(
|
||||
({ size = 'lg', children, className, buttonClassName, ...props }) => {
|
||||
const classes = useStyles({ size })
|
||||
({
|
||||
size = 'lg',
|
||||
children,
|
||||
className,
|
||||
buttonClassName,
|
||||
backgroundColor,
|
||||
...props
|
||||
}) => {
|
||||
const classes = useStyles({ size, backgroundColor })
|
||||
return (
|
||||
<div className={classnames(className, classes.wrapper)}>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import {
|
|||
secondaryColor,
|
||||
secondaryColorDark,
|
||||
secondaryColorDarker,
|
||||
offColor,
|
||||
offDarkColor,
|
||||
offDarkerColor,
|
||||
spacer
|
||||
} from 'src/styling/variables'
|
||||
|
||||
|
|
@ -28,11 +31,11 @@ export default {
|
|||
const shadowSize = height / 12
|
||||
return { height: height + shadowSize / 2 }
|
||||
},
|
||||
button: ({ size }) => {
|
||||
button: ({ size, backgroundColor }) => {
|
||||
const height = pickSize(size)
|
||||
const shadowSize = size === 'xl' ? 3 : height / 12
|
||||
const padding = size === 'xl' ? 20 : height / 2
|
||||
|
||||
const isGrey = backgroundColor === 'grey'
|
||||
return {
|
||||
extend: size === 'xl' ? h1 : h3,
|
||||
border: 'none',
|
||||
|
|
@ -40,7 +43,7 @@ export default {
|
|||
cursor: 'pointer',
|
||||
fontWeight: 900,
|
||||
outline: 0,
|
||||
backgroundColor: secondaryColor,
|
||||
backgroundColor: isGrey ? offDarkColor : secondaryColor,
|
||||
'&:disabled': {
|
||||
backgroundColor: disabledColor,
|
||||
boxShadow: 'none',
|
||||
|
|
@ -56,15 +59,19 @@ export default {
|
|||
height,
|
||||
padding: `0 ${padding}px`,
|
||||
borderRadius: height / 4,
|
||||
boxShadow: `0 ${shadowSize}px ${secondaryColorDark}`,
|
||||
boxShadow: `0 ${shadowSize}px ${isGrey ? offColor : secondaryColorDark}`,
|
||||
'&:hover': {
|
||||
backgroundColor: secondaryColorDark,
|
||||
boxShadow: `0 ${shadowSize}px ${secondaryColorDarker}`
|
||||
backgroundColor: isGrey ? offColor : secondaryColorDark,
|
||||
boxShadow: `0 ${shadowSize}px ${
|
||||
isGrey ? offDarkerColor : secondaryColorDarker
|
||||
}`
|
||||
},
|
||||
'&:active': {
|
||||
marginTop: shadowSize / 2,
|
||||
backgroundColor: secondaryColorDark,
|
||||
boxShadow: `0 ${shadowSize / 2}px ${secondaryColorDarker}`
|
||||
backgroundColor: isGrey ? offDarkColor : secondaryColorDark,
|
||||
boxShadow: `0 ${shadowSize / 2}px ${
|
||||
isGrey ? offDarkerColor : secondaryColorDarker
|
||||
}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { DialogActions, DialogContent, Dialog } from '@material-ui/core'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { parse, format } from 'date-fns/fp'
|
||||
import _ from 'lodash/fp'
|
||||
import * as R from 'ramda'
|
||||
import { useState, React } from 'react'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import ImagePopper from 'src/components/ImagePopper'
|
||||
import { FeatureButton } from 'src/components/buttons'
|
||||
import { FeatureButton, Button, IconButton } from 'src/components/buttons'
|
||||
import { TextInput } from 'src/components/inputs/formik'
|
||||
import { H3, Info3 } from 'src/components/typography'
|
||||
import { H3, Info3, H2 } from 'src/components/typography'
|
||||
import {
|
||||
OVERRIDE_AUTHORIZED,
|
||||
OVERRIDE_REJECTED
|
||||
|
|
@ -17,19 +17,22 @@ import {
|
|||
import { ReactComponent as CardIcon } from 'src/styling/icons/ID/card/comet.svg'
|
||||
import { ReactComponent as PhoneIcon } from 'src/styling/icons/ID/phone/comet.svg'
|
||||
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
|
||||
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
||||
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/comet.svg'
|
||||
import { ReactComponent as CustomerListViewReversedIcon } from 'src/styling/icons/circle buttons/customer-list-view/white.svg'
|
||||
import { ReactComponent as CustomerListViewIcon } from 'src/styling/icons/circle buttons/customer-list-view/zodiac.svg'
|
||||
import { ReactComponent as OverviewReversedIcon } from 'src/styling/icons/circle buttons/overview/white.svg'
|
||||
import { ReactComponent as OverviewIcon } from 'src/styling/icons/circle buttons/overview/zodiac.svg'
|
||||
import { URI } from 'src/utils/apollo'
|
||||
import { onlyFirstToUpper } from 'src/utils/string'
|
||||
|
||||
import styles from './CustomerData.styles.js'
|
||||
import { EditableCard } from './components'
|
||||
import {
|
||||
customerDataElements,
|
||||
customerDataSchemas,
|
||||
formatDates
|
||||
formatDates,
|
||||
getFormattedPhone
|
||||
} from './helper.js'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
|
@ -62,6 +65,7 @@ const Photo = ({ show, src }) => {
|
|||
}
|
||||
|
||||
const CustomerData = ({
|
||||
locale,
|
||||
customer,
|
||||
updateCustomer,
|
||||
replacePhoto,
|
||||
|
|
@ -69,10 +73,12 @@ const CustomerData = ({
|
|||
deleteEditedData,
|
||||
updateCustomRequest,
|
||||
authorizeCustomRequest,
|
||||
updateCustomEntry
|
||||
updateCustomEntry,
|
||||
retrieveAdditionalData
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
const [listView, setListView] = useState(false)
|
||||
const [retrieve, setRetrieve] = useState(false)
|
||||
|
||||
const idData = R.path(['idCardData'])(customer)
|
||||
const rawExpirationDate = R.path(['expirationDate'])(idData)
|
||||
|
|
@ -96,9 +102,12 @@ const CustomerData = ({
|
|||
R.path(['customInfoRequests'])(customer) ?? []
|
||||
)
|
||||
|
||||
const phone = R.path(['phone'])(customer)
|
||||
const smsData = R.path(['subscriberInfo', 'result'])(customer)
|
||||
|
||||
const isEven = elem => elem % 2 === 0
|
||||
|
||||
const getVisibleCards = _.filter(elem => elem.isAvailable)
|
||||
const getVisibleCards = R.filter(elem => elem.isAvailable)
|
||||
|
||||
const initialValues = {
|
||||
idCardData: {
|
||||
|
|
@ -126,9 +135,34 @@ const CustomerData = ({
|
|||
},
|
||||
idCardPhoto: {
|
||||
idCardPhoto: null
|
||||
},
|
||||
smsData: {
|
||||
phoneNumber: getFormattedPhone(phone, locale.country)
|
||||
}
|
||||
}
|
||||
|
||||
const smsDataElements = [
|
||||
{
|
||||
name: 'phoneNumber',
|
||||
label: 'Phone number',
|
||||
component: TextInput,
|
||||
editable: false
|
||||
}
|
||||
]
|
||||
|
||||
const smsDataSchema = {
|
||||
smsData: Yup.lazy(values => {
|
||||
const additionalData = R.omit(['phoneNumber'])(values)
|
||||
const fields = R.keys(additionalData)
|
||||
if (R.length(fields) === 2) {
|
||||
return Yup.object().shape({
|
||||
[R.head(fields)]: Yup.string().required(),
|
||||
[R.last(fields)]: Yup.string().required()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const cards = [
|
||||
{
|
||||
fields: customerDataElements.idCardData,
|
||||
|
|
@ -141,19 +175,31 @@ const CustomerData = ({
|
|||
deleteEditedData: () => deleteEditedData({ idCardData: null }),
|
||||
save: values =>
|
||||
editCustomer({
|
||||
idCardData: _.merge(idData, formatDates(values))
|
||||
idCardData: R.merge(idData, formatDates(values))
|
||||
}),
|
||||
validationSchema: customerDataSchemas.idCardData,
|
||||
initialValues: initialValues.idCardData,
|
||||
isAvailable: !_.isNil(idData)
|
||||
isAvailable: !R.isNil(idData)
|
||||
},
|
||||
{
|
||||
title: 'SMS Confirmation',
|
||||
fields: smsDataElements,
|
||||
title: 'SMS data',
|
||||
titleIcon: <PhoneIcon className={classes.cardIcon} />,
|
||||
authorize: () => {},
|
||||
reject: () => {},
|
||||
save: () => {},
|
||||
isAvailable: false
|
||||
state: R.path(['phoneOverride'])(customer),
|
||||
authorize: () => updateCustomer({ phoneOverride: OVERRIDE_AUTHORIZED }),
|
||||
reject: () => updateCustomer({ phoneOverride: OVERRIDE_REJECTED }),
|
||||
save: values => {
|
||||
editCustomer({
|
||||
subscriberInfo: {
|
||||
result: R.merge(smsData, R.omit(['phoneNumber'])(values))
|
||||
}
|
||||
})
|
||||
},
|
||||
validationSchema: smsDataSchema.smsData,
|
||||
retrieveAdditionalData: () => setRetrieve(true),
|
||||
initialValues: initialValues.smsData,
|
||||
isAvailable: !R.isNil(phone),
|
||||
hasAdditionalData: !R.isNil(smsData) && !R.isEmpty(smsData)
|
||||
},
|
||||
{
|
||||
title: 'Name',
|
||||
|
|
@ -171,7 +217,7 @@ const CustomerData = ({
|
|||
updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }),
|
||||
reject: () => updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }),
|
||||
children: <Info3>{sanctionsDisplay}</Info3>,
|
||||
isAvailable: !_.isNil(sanctions)
|
||||
isAvailable: !R.isNil(sanctions)
|
||||
},
|
||||
{
|
||||
fields: customerDataElements.frontCamera,
|
||||
|
|
@ -198,7 +244,7 @@ const CustomerData = ({
|
|||
hasImage: true,
|
||||
validationSchema: customerDataSchemas.frontCamera,
|
||||
initialValues: initialValues.frontCamera,
|
||||
isAvailable: !_.isNil(customer.frontCameraPath)
|
||||
isAvailable: !R.isNil(customer.frontCameraPath)
|
||||
},
|
||||
{
|
||||
fields: customerDataElements.idCardPhoto,
|
||||
|
|
@ -223,7 +269,7 @@ const CustomerData = ({
|
|||
hasImage: true,
|
||||
validationSchema: customerDataSchemas.idCardPhoto,
|
||||
initialValues: initialValues.idCardPhoto,
|
||||
isAvailable: !_.isNil(customer.idCardPhotoPath)
|
||||
isAvailable: !R.isNil(customer.idCardPhotoPath)
|
||||
},
|
||||
{
|
||||
fields: customerDataElements.usSsn,
|
||||
|
|
@ -236,7 +282,7 @@ const CustomerData = ({
|
|||
deleteEditedData: () => deleteEditedData({ usSsn: null }),
|
||||
validationSchema: customerDataSchemas.usSsn,
|
||||
initialValues: initialValues.usSsn,
|
||||
isAvailable: !_.isNil(customer.usSsn)
|
||||
isAvailable: !R.isNil(customer.usSsn)
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -319,6 +365,16 @@ const CustomerData = ({
|
|||
})
|
||||
}, R.path(['customFields'])(customer) ?? [])
|
||||
|
||||
R.forEach(it => {
|
||||
initialValues.smsData[it] = smsData[it]
|
||||
smsDataElements.push({
|
||||
name: it,
|
||||
label: onlyFirstToUpper(it),
|
||||
component: TextInput,
|
||||
editable: true
|
||||
})
|
||||
}, R.keys(smsData) ?? [])
|
||||
|
||||
const editableCard = (
|
||||
{
|
||||
title,
|
||||
|
|
@ -329,10 +385,12 @@ const CustomerData = ({
|
|||
fields,
|
||||
save,
|
||||
deleteEditedData,
|
||||
retrieveAdditionalData,
|
||||
children,
|
||||
validationSchema,
|
||||
initialValues,
|
||||
hasImage
|
||||
hasImage,
|
||||
hasAdditionalData
|
||||
},
|
||||
idx
|
||||
) => {
|
||||
|
|
@ -345,12 +403,14 @@ const CustomerData = ({
|
|||
state={state}
|
||||
titleIcon={titleIcon}
|
||||
hasImage={hasImage}
|
||||
hasAdditionalData={hasAdditionalData}
|
||||
fields={fields}
|
||||
children={children}
|
||||
validationSchema={validationSchema}
|
||||
initialValues={initialValues}
|
||||
save={save}
|
||||
deleteEditedData={deleteEditedData}></EditableCard>
|
||||
deleteEditedData={deleteEditedData}
|
||||
retrieveAdditionalData={retrieveAdditionalData}></EditableCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -394,7 +454,7 @@ const CustomerData = ({
|
|||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{!_.isEmpty(customFields) && (
|
||||
{!R.isEmpty(customFields) && (
|
||||
<div className={classes.wrapper}>
|
||||
<span className={classes.separator}>Custom data entry</span>
|
||||
<Grid container>
|
||||
|
|
@ -429,8 +489,67 @@ const CustomerData = ({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<RetrieveDataDialog
|
||||
setRetrieve={setRetrieve}
|
||||
retrieveAdditionalData={retrieveAdditionalData}
|
||||
open={retrieve}></RetrieveDataDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const RetrieveDataDialog = ({
|
||||
setRetrieve,
|
||||
retrieveAdditionalData,
|
||||
open,
|
||||
props
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
aria-labelledby="form-dialog-title"
|
||||
PaperProps={{
|
||||
style: {
|
||||
borderRadius: 8,
|
||||
minWidth: 656,
|
||||
bottom: 125,
|
||||
right: 7
|
||||
}
|
||||
}}
|
||||
{...props}>
|
||||
<div className={classes.closeButton}>
|
||||
<IconButton
|
||||
size={16}
|
||||
aria-label="close"
|
||||
onClick={() => setRetrieve(false)}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<H2 className={classes.dialogTitle}>{'Retrieve API data from Twilio'}</H2>
|
||||
<DialogContent className={classes.dialogContent}>
|
||||
<Info3>{`With this action you'll be using Twilio's API to retrieve additional
|
||||
data from this user. This includes name and address, if available.\n`}</Info3>
|
||||
<Info3>{` There is a small cost from Twilio for each retrieval. Would you like
|
||||
to proceed?`}</Info3>
|
||||
</DialogContent>
|
||||
<DialogActions className={classes.dialogActions}>
|
||||
<Button
|
||||
backgroundColor="grey"
|
||||
className={classes.cancelButton}
|
||||
onClick={() => setRetrieve(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
retrieveAdditionalData()
|
||||
setRetrieve(false)
|
||||
}}>
|
||||
Confirm
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomerData
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { offColor } from 'src/styling/variables'
|
||||
import { offColor, spacer } from 'src/styling/variables'
|
||||
|
||||
export default {
|
||||
header: {
|
||||
|
|
@ -45,5 +45,26 @@ export default {
|
|||
left: '100%',
|
||||
marginLeft: 15
|
||||
}
|
||||
},
|
||||
closeButton: {
|
||||
display: 'flex',
|
||||
padding: [[spacer * 2, spacer * 2, 0, spacer * 2]],
|
||||
paddingRight: spacer * 1.5,
|
||||
justifyContent: 'end'
|
||||
},
|
||||
dialogTitle: {
|
||||
margin: [[0, spacer * 2, spacer, spacer * 4 + spacer]]
|
||||
},
|
||||
dialogContent: {
|
||||
width: 615,
|
||||
marginLeft: 16
|
||||
},
|
||||
dialogActions: {
|
||||
padding: spacer * 4,
|
||||
paddingTop: spacer * 2
|
||||
},
|
||||
cancelButton: {
|
||||
marginRight: 8,
|
||||
padding: 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ const GET_CUSTOMER = gql`
|
|||
daysSuspended
|
||||
isSuspended
|
||||
isTestCustomer
|
||||
subscriberInfo
|
||||
phoneOverride
|
||||
customFields {
|
||||
id
|
||||
label
|
||||
|
|
@ -138,6 +140,7 @@ const SET_CUSTOMER = gql`
|
|||
lastTxFiatCode
|
||||
lastTxClass
|
||||
subscriberInfo
|
||||
phoneOverride
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
@ -438,6 +441,16 @@ const CustomerProfile = memo(() => {
|
|||
}
|
||||
})
|
||||
|
||||
const retrieveAdditionalData = () =>
|
||||
setCustomer({
|
||||
variables: {
|
||||
customerId,
|
||||
customerInput: {
|
||||
subscriberInfo: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const onClickSidebarItem = code => setClickedItem(code)
|
||||
|
||||
const configData = R.path(['config'])(customerResponse) ?? []
|
||||
|
|
@ -558,25 +571,6 @@ const CustomerProfile = memo(() => {
|
|||
}>
|
||||
{`${blocked ? 'Authorize' : 'Block'} customer`}
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
color="primary"
|
||||
className={classes.actionButton}
|
||||
Icon={blocked ? AuthorizeIcon : BlockIcon}
|
||||
InverseIcon={
|
||||
blocked ? AuthorizeReversedIcon : BlockReversedIcon
|
||||
}
|
||||
onClick={() =>
|
||||
setCustomer({
|
||||
variables: {
|
||||
customerId,
|
||||
customerInput: {
|
||||
subscriberInfo: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}>
|
||||
{`Retrieve information`}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -628,6 +622,7 @@ const CustomerProfile = memo(() => {
|
|||
{isCustomerData && (
|
||||
<div>
|
||||
<CustomerData
|
||||
locale={locale}
|
||||
customer={customerData}
|
||||
updateCustomer={updateCustomer}
|
||||
replacePhoto={replacePhoto}
|
||||
|
|
@ -635,7 +630,8 @@ const CustomerProfile = memo(() => {
|
|||
deleteEditedData={deleteEditedData}
|
||||
updateCustomRequest={setCustomerCustomInfoRequest}
|
||||
authorizeCustomRequest={authorizeCustomRequest}
|
||||
updateCustomEntry={updateCustomEntry}></CustomerData>
|
||||
updateCustomEntry={updateCustomEntry}
|
||||
retrieveAdditionalData={retrieveAdditionalData}></CustomerData>
|
||||
</div>
|
||||
)}
|
||||
{isNotes && (
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import { ReactComponent as EditReversedIcon } from 'src/styling/icons/action/edi
|
|||
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/white.svg'
|
||||
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/white.svg'
|
||||
import { ReactComponent as CancelReversedIcon } from 'src/styling/icons/button/cancel/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 ReplaceReversedIcon } from 'src/styling/icons/button/replace/white.svg'
|
||||
import { ReactComponent as SaveReversedIcon } from 'src/styling/icons/circle buttons/save/white.svg'
|
||||
import { comet } from 'src/styling/variables'
|
||||
|
|
@ -67,6 +69,13 @@ const fieldStyles = {
|
|||
fontSize: 14
|
||||
}
|
||||
}
|
||||
},
|
||||
readOnlyLabel: {
|
||||
color: comet,
|
||||
margin: [[3, 0, 3, 0]]
|
||||
},
|
||||
readOnlyValue: {
|
||||
margin: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +114,23 @@ const EditableField = ({ editing, field, value, size, ...props }) => {
|
|||
)
|
||||
}
|
||||
|
||||
const ReadOnlyField = ({ field, value, ...props }) => {
|
||||
const classes = fieldUseStyles()
|
||||
const classNames = {
|
||||
[classes.field]: true,
|
||||
[classes.notEditing]: true
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classnames(classNames)}>
|
||||
<Label1 className={classes.readOnlyLabel}>{field.label}</Label1>
|
||||
<P className={classes.readOnlyValue}>{value}</P>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const EditableCard = ({
|
||||
fields,
|
||||
save,
|
||||
|
|
@ -117,7 +143,9 @@ const EditableCard = ({
|
|||
children,
|
||||
validationSchema,
|
||||
initialValues,
|
||||
deleteEditedData
|
||||
deleteEditedData,
|
||||
retrieveAdditionalData,
|
||||
hasAdditionalData = true
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
|
||||
|
|
@ -174,7 +202,7 @@ const EditableCard = ({
|
|||
setEditing(false)
|
||||
setError(false)
|
||||
}}>
|
||||
{({ values, touched, errors, setFieldValue }) => (
|
||||
{({ setFieldValue }) => (
|
||||
<Form>
|
||||
<PromptWhenDirty />
|
||||
<div className={classes.row}>
|
||||
|
|
@ -183,12 +211,19 @@ const EditableCard = ({
|
|||
{!hasImage &&
|
||||
fields?.map((field, idx) => {
|
||||
return idx >= 0 && idx < 4 ? (
|
||||
<EditableField
|
||||
field={field}
|
||||
value={initialValues[field.name]}
|
||||
editing={editing}
|
||||
size={180}
|
||||
/>
|
||||
!field.editable ? (
|
||||
<ReadOnlyField
|
||||
field={field}
|
||||
value={initialValues[field.name]}
|
||||
/>
|
||||
) : (
|
||||
<EditableField
|
||||
field={field}
|
||||
value={initialValues[field.name]}
|
||||
editing={editing}
|
||||
size={180}
|
||||
/>
|
||||
)
|
||||
) : null
|
||||
})}
|
||||
</Grid>
|
||||
|
|
@ -196,12 +231,19 @@ const EditableCard = ({
|
|||
{!hasImage &&
|
||||
fields?.map((field, idx) => {
|
||||
return idx >= 4 ? (
|
||||
<EditableField
|
||||
field={field}
|
||||
value={initialValues[field.name]}
|
||||
editing={editing}
|
||||
size={180}
|
||||
/>
|
||||
!field.editable ? (
|
||||
<ReadOnlyField
|
||||
field={field}
|
||||
value={initialValues[field.name]}
|
||||
/>
|
||||
) : (
|
||||
<EditableField
|
||||
field={field}
|
||||
value={initialValues[field.name]}
|
||||
editing={editing}
|
||||
size={180}
|
||||
/>
|
||||
)
|
||||
) : null
|
||||
})}
|
||||
</Grid>
|
||||
|
|
@ -210,25 +252,34 @@ const EditableCard = ({
|
|||
<div className={classes.edit}>
|
||||
{!editing && (
|
||||
<div className={classes.editButton}>
|
||||
{// TODO: Remove false condition for next release
|
||||
false && (
|
||||
<div className={classes.deleteButton}>
|
||||
<div className={classes.deleteButton}>
|
||||
{false && (
|
||||
<ActionButton
|
||||
color="primary"
|
||||
type="button"
|
||||
Icon={DeleteIcon}
|
||||
InverseIcon={DeleteReversedIcon}
|
||||
onClick={() => deleteEditedData()}>
|
||||
{`Delete`}
|
||||
Delete
|
||||
</ActionButton>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{!hasAdditionalData && (
|
||||
<ActionButton
|
||||
color="primary"
|
||||
type="button"
|
||||
Icon={DataIcon}
|
||||
InverseIcon={DataReversedIcon}
|
||||
onClick={() => retrieveAdditionalData()}>
|
||||
Retrieve API data
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
<ActionButton
|
||||
color="primary"
|
||||
Icon={EditIcon}
|
||||
InverseIcon={EditReversedIcon}
|
||||
onClick={() => setEditing(true)}>
|
||||
{`Edit`}
|
||||
Edit
|
||||
</ActionButton>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -355,37 +355,44 @@ const customerDataElements = {
|
|||
{
|
||||
name: 'firstName',
|
||||
label: 'First name',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
name: 'documentNumber',
|
||||
label: 'ID number',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
name: 'dateOfBirth',
|
||||
label: 'Birthdate',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
name: 'gender',
|
||||
label: 'Gender',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
name: 'lastName',
|
||||
label: 'Last name',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
name: 'expirationDate',
|
||||
label: 'Expiration Date',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
label: 'Country',
|
||||
component: TextInput
|
||||
component: TextInput,
|
||||
editable: true
|
||||
}
|
||||
],
|
||||
usSsn: [
|
||||
|
|
@ -393,7 +400,8 @@ const customerDataElements = {
|
|||
name: 'usSsn',
|
||||
label: 'US SSN',
|
||||
component: TextInput,
|
||||
size: 190
|
||||
size: 190,
|
||||
editable: true
|
||||
}
|
||||
],
|
||||
idCardPhoto: [{ name: 'idCardPhoto' }],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue