diff --git a/lib/customers.js b/lib/customers.js index 1f15dc91..a9e57ef8 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -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 diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index bf099647..ee85541c 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -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 { diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index f8eb7a7f..5850fefc 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -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'] diff --git a/migrations/1641482376890-add-overrides-to-subscriber-info.js b/migrations/1641482376890-add-overrides-to-subscriber-info.js new file mode 100644 index 00000000..192c7e20 --- /dev/null +++ b/migrations/1641482376890-add-overrides-to-subscriber-info.js @@ -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() +} diff --git a/new-lamassu-admin/src/components/buttons/Button.js b/new-lamassu-admin/src/components/buttons/Button.js index 6bd24a71..5945170a 100644 --- a/new-lamassu-admin/src/components/buttons/Button.js +++ b/new-lamassu-admin/src/components/buttons/Button.js @@ -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 (
+ ) } +const RetrieveDataDialog = ({ + setRetrieve, + retrieveAdditionalData, + open, + props +}) => { + const classes = useStyles() + + return ( + +
+ setRetrieve(false)}> + + +
+

{'Retrieve API data from Twilio'}

+ + {`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`} + {` There is a small cost from Twilio for each retrieval. Would you like + to proceed?`} + + + + + +
+ ) +} + export default CustomerData diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js index 373e2f0c..9a4e2871 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js @@ -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 } } diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index 5a0d2314..1d44c02c 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -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`} - - setCustomer({ - variables: { - customerId, - customerInput: { - subscriberInfo: true - } - } - }) - }> - {`Retrieve information`} -
@@ -628,6 +622,7 @@ const CustomerProfile = memo(() => { {isCustomerData && (
{ deleteEditedData={deleteEditedData} updateCustomRequest={setCustomerCustomInfoRequest} authorizeCustomRequest={authorizeCustomRequest} - updateCustomEntry={updateCustomEntry}> + updateCustomEntry={updateCustomEntry} + retrieveAdditionalData={retrieveAdditionalData}>
)} {isNotes && ( diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index 23f3ffec..e83daa23 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -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 ( + <> +
+ {field.label} +

{value}

+
+ + ) +} + 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 }) => (
@@ -183,12 +211,19 @@ const EditableCard = ({ {!hasImage && fields?.map((field, idx) => { return idx >= 0 && idx < 4 ? ( - + !field.editable ? ( + + ) : ( + + ) ) : null })} @@ -196,12 +231,19 @@ const EditableCard = ({ {!hasImage && fields?.map((field, idx) => { return idx >= 4 ? ( - + !field.editable ? ( + + ) : ( + + ) ) : null })} @@ -210,25 +252,34 @@ const EditableCard = ({
{!editing && (
- {// TODO: Remove false condition for next release - false && ( -
+
+ {false && ( deleteEditedData()}> - {`Delete`} + Delete -
- )} + )} + {!hasAdditionalData && ( + retrieveAdditionalData()}> + Retrieve API data + + )} +
setEditing(true)}> - {`Edit`} + Edit
)} diff --git a/new-lamassu-admin/src/pages/Customers/helper.js b/new-lamassu-admin/src/pages/Customers/helper.js index 9c980a33..cba19de8 100644 --- a/new-lamassu-admin/src/pages/Customers/helper.js +++ b/new-lamassu-admin/src/pages/Customers/helper.js @@ -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' }],