From 0981a7d4afe0d0f82c888328b784c929c78070fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Fri, 17 Sep 2021 11:32:43 +0100 Subject: [PATCH 01/18] feat: overview rework and customer data mosaic view --- new-lamassu-admin/src/pages/Customers/CustomerProfile.js | 9 +++++++++ .../pages/Customers/components/CustomerSidebar.styles.js | 4 ++-- .../src/pages/Customers/components/TransactionsList.js | 1 - 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index e197416b..7b92fc30 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -1,6 +1,7 @@ import { useQuery, useMutation } from '@apollo/react-hooks' import { makeStyles, Breadcrumbs, Box } from '@material-ui/core' import NavigateNextIcon from '@material-ui/icons/NavigateNext' +// import classnames from 'classnames' import gql from 'graphql-tag' import * as R from 'ramda' import React, { memo, useState } from 'react' @@ -265,6 +266,14 @@ const CustomerProfile = memo(() => { {`Retrieve information`} +
+ {}}> + {`Add individual discount`} + +
)} 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 2d967f3d..87ef5087 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: [5, 5, 0, 0] + borderRadius: [[5, 5, 0, 0]] }, '&:last-child': { - borderRadius: [0, 0, 5, 5] + borderRadius: [[0, 0, 5, 5]] } }, icon: { diff --git a/new-lamassu-admin/src/pages/Customers/components/TransactionsList.js b/new-lamassu-admin/src/pages/Customers/components/TransactionsList.js index 9bbd35de..8b55208c 100644 --- a/new-lamassu-admin/src/pages/Customers/components/TransactionsList.js +++ b/new-lamassu-admin/src/pages/Customers/components/TransactionsList.js @@ -75,7 +75,6 @@ const TransactionsList = ({ customer, data, loading, locale }) => { view: R.path(['machineName']) }, { - header: 'Direction', width: 125, view: it => ( <> From a8f1c50d07bd617862ed5235fec4b74c443bda48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Tue, 12 Oct 2021 13:54:36 +0100 Subject: [PATCH 02/18] feat: add icons and make customer data cards editable --- .../src/components/buttons/FeatureButton.js | 4 ++-- new-lamassu-admin/src/pages/Customers/CustomerProfile.js | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/new-lamassu-admin/src/components/buttons/FeatureButton.js b/new-lamassu-admin/src/components/buttons/FeatureButton.js index 3fe97cd7..86a06c5e 100644 --- a/new-lamassu-admin/src/components/buttons/FeatureButton.js +++ b/new-lamassu-admin/src/components/buttons/FeatureButton.js @@ -32,8 +32,8 @@ const styles = { const useStyles = makeStyles(styles) const FeatureButton = memo( - ({ className, Icon, InverseIcon, children, ...props }) => { - const classes = useStyles() + ({ className, Icon, InverseIcon, children, active, ...props }) => { + const classes = useStyles({ active }) const classNames = { [classes.featureButton]: true, diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index 7b92fc30..e197416b 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -1,7 +1,6 @@ import { useQuery, useMutation } from '@apollo/react-hooks' import { makeStyles, Breadcrumbs, Box } from '@material-ui/core' import NavigateNextIcon from '@material-ui/icons/NavigateNext' -// import classnames from 'classnames' import gql from 'graphql-tag' import * as R from 'ramda' import React, { memo, useState } from 'react' @@ -266,14 +265,6 @@ const CustomerProfile = memo(() => { {`Retrieve information`} -
- {}}> - {`Add individual discount`} - -
)} From 421543f0c79100ca9845cf1b69ad6d3de98b2f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 18 Oct 2021 13:42:55 +0100 Subject: [PATCH 03/18] feat: add dynamic position to editable cards, reject/authorize buttons --- new-lamassu-admin/src/pages/Customers/components/EditableCard.js | 1 - 1 file changed, 1 deletion(-) diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index a97023dc..c4792419 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -123,7 +123,6 @@ const EditableCard = ({ [classes.label1Rejected]: state === OVERRIDE_REJECTED, [classes.label1Accepted]: state === OVERRIDE_AUTHORIZED } - const authorized = state === OVERRIDE_PENDING ? { label: 'Pending', type: 'neutral' } From 3de7ae2fe9908757ab6f70b658a514a131436d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Wed, 27 Oct 2021 23:54:50 +0100 Subject: [PATCH 04/18] feat: add edit, delete and image input --- lib/customers.js | 153 +++++++++-- .../graphql/resolvers/customer.resolver.js | 9 +- lib/new-admin/graphql/types/customer.type.js | 11 + .../1635159374499-editable-customer-data.js | 33 +++ .../src/components/buttons/ActionButton.js | 4 +- .../components/buttons/ActionButton.styles.js | 43 ++- .../src/pages/Customers/CustomerData.js | 79 ++++-- .../pages/Customers/CustomerData.styles.js | 32 ++- .../src/pages/Customers/CustomerProfile.js | 51 +++- .../Customers/components/EditableCard.js | 252 ++++++++++++------ .../components/EditableCard.styles.js | 14 + new-lamassu-admin/src/styling/variables.js | 3 + 12 files changed, 545 insertions(+), 139 deletions(-) create mode 100644 migrations/1635159374499-editable-customer-data.js diff --git a/lib/customers.js b/lib/customers.js index 2df001c5..4f01ea7b 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -138,6 +138,103 @@ async function updateCustomer (id, data, userToken) { return getCustomerById(id) } +/** + * Update all customer record + * + * @name save + * @function + * + * @param {string} id Customer's id + * @param {object} data Fields to update + * + * @returns {Promise} Newly updated Customer + */ + +function edit (id, data, userToken) { + const defaults = [ + 'front_camera', + 'id_card_data', + 'id_card_photo', + 'us_ssn', + 'subcriber_info', + 'name' + ] + const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, _.omitBy(_.isNil, data))) + if (_.isEmpty(filteredData)) return getCustomerById(id) + if (filteredData.front_camera_data || filteredData.id_card_photo_data) return getCustomerById(id) + const formattedData = enhanceEditedFields(filteredData, userToken) + const defaultDbData = { + customer_id: id, + created: new Date(), + ...formattedData + } + console.log(formattedData, 'FORMATED', defaultDbData, 'DEFAULT DB') + const cs = new Pgp.helpers.ColumnSet(_.keys(defaultDbData), + { table: 'edited_customer_data' }) + const onConflict = ' ON CONFLICT (customer_id) DO UPDATE SET ' + + cs.assignColumns({ from: 'EXCLUDED', skip: ['customer_id', 'created'] }) + const upsert = Pgp.helpers.insert(defaultDbData, cs) + onConflict + db.none(upsert) + .then(res => { + console.log(res) + return getCustomerById(id) + }) +} + +/** + * Add *edited_by and *edited_at fields with acting user's token + * and date of override respectively before saving to db. + * + * @name enhanceEditedFields + * @function + * + * @param {object} fields Fields to be enhanced + * @param {string} userToken Acting user's token + * @returns {object} fields enhanced with *_by and *_at fields + */ + +function enhanceEditedFields (fields, userToken) { + if (!userToken) return fields + _.mapKeys((field) => { + fields[field + '_edited_by'] = userToken + fields[field + '_edited_at'] = 'now()^' + }, fields) + return fields +} + +/** + * Remove the edited data from the db record + * + * @name enhanceOverrideFields + * @function + * + * @param {string} id Customer's id + * @param {object} data Fields to be deleted + * + * @returns {Promise} Newly updated Customer + * + */ + +function deleteEditedData (id, data) { + // NOT IMPLEMENTING THIS FEATURE FOR NOW + // const defaults = [ + // 'front_camera', + // 'id_card_data', + // 'id_card_photo', + // 'us_ssn', + // 'subcriber_info', + // 'name' + // ] + // const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, data)) + // if (_.isEmpty(filteredData)) return getCustomerById(id) + + // const cs = new Pgp.helpers.ColumnSet(_.keys(filteredData), + // { table: 'edited_customer_data' }) + // const update = Pgp.helpers.update(filteredData, cs) + // db.none(update) + return getCustomerById(id) +} + const invalidateCustomerNotifications = (id, data) => { if (data.authorized_override !== 'verified') return Promise.resolve() @@ -510,7 +607,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null) AND ($6 IS NULL OR id_card_data::json->>'address' = $6) AND ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7) limit $3` - return db.any(sql, [passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id ]) + return db.any(sql, [ passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id ]) .then(customers => Promise.all(_.map(customer => { return populateOverrideUsernames(customer) .then(camelize) @@ -518,11 +615,11 @@ function getCustomersList (phone = null, name = null, address = null, id = null) } /** - * Query all customers, ordered by last activity + * Query a specific customer, ordered by last activity * and with aggregate columns based on their * transactions * - * @returns {array} Array of customers with it's transactions aggregations + * @returns {array} A single customer instance with non edited */ function getCustomerById (id) { const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',') @@ -560,6 +657,17 @@ function getCustomerById (id) { .then(camelize) } +/** + * Query the specific customer manually edited data + * + * @param {String} id customer id + * + * @returns {array} A single customer instance with the most recent data + */ +function getManuallyEditedData (id) { + +} + /** * @param {String} id customer id * @param {Object} patch customer update record @@ -768,35 +876,35 @@ function updateFrontCamera (id, patch) { }) } -function addCustomField(customerId, label, value) { +function addCustomField (customerId, label, value) { const sql = `SELECT * FROM custom_field_definitions WHERE label=$1 LIMIT 1` return db.oneOrNone(sql, [label]) .then(res => db.tx(t => { - if (_.isNil(res)) { - const fieldId = uuid.v4() - const q1 = t.none(`INSERT INTO custom_field_definitions (id, label) VALUES ($1, $2)`, [fieldId, label]) - const q2 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, fieldId, value]) - return t.batch([q1, q2]) - } + if (_.isNil(res)) { + const fieldId = uuid.v4() + const q1 = t.none(`INSERT INTO custom_field_definitions (id, label) VALUES ($1, $2)`, [fieldId, label]) + const q2 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, fieldId, value]) + return t.batch([q1, q2]) + } - if (!_.isNil(res) && !res.active) { - const q1 = t.none(`UPDATE custom_field_definitions SET active = true WHERE id=$1`, [res.id]) - const q2 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, res.id, value]) - return t.batch([q1, q2]) - } else if (!_.isNil(res) && res.active) { - const q1 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, res.id, value]) - return t.batch([q1]) - } - }) + if (!_.isNil(res) && !res.active) { + const q1 = t.none(`UPDATE custom_field_definitions SET active = true WHERE id=$1`, [res.id]) + const q2 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, res.id, value]) + return t.batch([q1, q2]) + } else if (!_.isNil(res) && res.active) { + const q1 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, res.id, value]) + return t.batch([q1]) + } + }) ) } -function saveCustomField(customerId, fieldId, newValue) { +function saveCustomField (customerId, fieldId, newValue) { const sql = `UPDATE customer_custom_field_pairs SET value=$1 WHERE customer_id=$2 AND custom_field_id=$3` return db.none(sql, [newValue, customerId, fieldId]) } -function removeCustomField(customerId, fieldId) { +function removeCustomField (customerId, fieldId) { const sql = `SELECT * FROM customer_custom_field_pairs WHERE custom_field_id=$1` return db.any(sql, [fieldId]) .then(res => db.tx(t => { @@ -827,5 +935,8 @@ module.exports = { addCustomField, saveCustomField, removeCustomField, + edit, + getManuallyEditedData, + deleteEditedData, updateTxCustomerPhoto } diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index a26a86fd..b22454bd 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -19,7 +19,14 @@ const resolvers = { }, addCustomField: (...[, { customerId, label, value }]) => customers.addCustomField(customerId, label, value), saveCustomField: (...[, { customerId, fieldId, newValue }]) => customers.saveCustomField(customerId, fieldId, newValue), - removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId) + removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId), + editCustomer: (root, { customerId, customerEdit }, context) => { + const token = !!context.req.cookies.lid && context.req.session.user.id + return customers.edit(customerId, customerEdit, token) + }, + deleteEditedData: (root, { customerId, customerEdit }) => { + return customers.deleteEditedData(customerId, customerEdit) + } } } diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index 134734a7..6d0129db 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -12,6 +12,7 @@ const typeDef = gql` authorizedOverride: String daysSuspended: Int isSuspended: Boolean + frontCamera: String frontCameraPath: String frontCameraAt: Date frontCameraOverride: String @@ -21,6 +22,7 @@ const typeDef = gql` idCardData: JSONObject idCardDataOverride: String idCardDataExpiration: Date + idCardPhoto: String idCardPhotoPath: String idCardPhotoOverride: String usSsn: String @@ -65,6 +67,13 @@ const typeDef = gql` subscriberInfo: Boolean } + input CustomerEdit { + frontCamera: String + idCardData: JSONObject + idCardPhoto: String + usSsn: String + } + type Query { customers(phone: String, name: String, address: String, id: String): [Customer] @auth customer(customerId: ID!): Customer @auth @@ -76,6 +85,8 @@ const typeDef = gql` 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 + editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth + deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth } ` diff --git a/migrations/1635159374499-editable-customer-data.js b/migrations/1635159374499-editable-customer-data.js new file mode 100644 index 00000000..76d6618c --- /dev/null +++ b/migrations/1635159374499-editable-customer-data.js @@ -0,0 +1,33 @@ +const db = require('./db') + +exports.up = function (next) { + var sql = [ + `CREATE TABLE edited_customer_data ( + customer_id uuid PRIMARY KEY REFERENCES customers(id), + id_card_data JSON, + id_card_data_edited_at TIMESTAMPTZ, + id_card_data_edited_by UUID REFERENCES users(id), + front_camera_path TEXT, + front_camera_edited_at TIMESTAMPTZ, + front_camera_edited_by UUID REFERENCES users(id), + id_card_photo_path TEXT, + id_card_photo_edited_at TIMESTAMPTZ, + id_card_photo_edited_by UUID REFERENCES users(id), + subscriber_info JSON, + subscriber_info_edited_at TIMESTAMPTZ, + subscriber_info_edited_by UUID REFERENCES users(id), + name TEXT, + name_info_edited_at TIMESTAMPTZ, + name_info_edited_by UUID REFERENCES users(id), + us_ssn TEXT, + us_ssn_edited_at TIMESTAMPTZ, + us_ssn_edited_by UUID REFERENCES users(id), + created TIMESTAMPTZ NOT NULL DEFAULT now() )` + ] + + db.multi(sql, next) +} + +exports.down = function (next) { + next() +} diff --git a/new-lamassu-admin/src/components/buttons/ActionButton.js b/new-lamassu-admin/src/components/buttons/ActionButton.js index 264adac8..02d7d107 100644 --- a/new-lamassu-admin/src/components/buttons/ActionButton.js +++ b/new-lamassu-admin/src/components/buttons/ActionButton.js @@ -12,7 +12,9 @@ const ActionButton = memo( const classNames = { [classes.actionButton]: true, [classes.primary]: color === 'primary', - [classes.secondary]: color === 'secondary' + [classes.secondary]: color === 'secondary', + [classes.spring]: color === 'spring', + [classes.tomato]: color === 'tomato' } return ( diff --git a/new-lamassu-admin/src/components/buttons/ActionButton.styles.js b/new-lamassu-admin/src/components/buttons/ActionButton.styles.js index a775f811..5d0cde44 100644 --- a/new-lamassu-admin/src/components/buttons/ActionButton.styles.js +++ b/new-lamassu-admin/src/components/buttons/ActionButton.styles.js @@ -5,7 +5,12 @@ import { subheaderColor, subheaderDarkColor, offColor, - offDarkColor + offDarkColor, + secondaryColor, + secondaryColorDark, + secondaryColorDarker, + errorColor, + errorColorDarker } from 'src/styling/variables' const { p } = typographyStyles @@ -68,6 +73,42 @@ export default { display: 'flex' } }, + spring: { + extend: colors(secondaryColor, secondaryColorDark, secondaryColorDarker), + color: white, + '&:active': { + '& $actionButtonIcon': { + display: 'flex' + }, + '& $actionButtonIconActive': { + display: 'none' + } + }, + '& $actionButtonIcon': { + display: 'none' + }, + '& $actionButtonIconActive': { + display: 'flex' + } + }, + tomato: { + extend: colors(errorColor, errorColorDarker, errorColor), + color: white, + '&:active': { + '& $actionButtonIcon': { + display: 'flex' + }, + '& $actionButtonIconActive': { + display: 'none' + } + }, + '& $actionButtonIcon': { + display: 'none' + }, + '& $actionButtonIconActive': { + display: 'flex' + } + }, actionButtonIcon: { display: 'flex', paddingRight: 7, diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index bb034bbd..77934e0c 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -57,7 +57,12 @@ const Photo = ({ show, src }) => { ) } -const CustomerData = ({ customer, updateCustomer }) => { +const CustomerData = ({ + customer, + updateCustomer, + editCustomer, + deleteEditedData +}) => { const classes = useStyles() const [listView, setListView] = useState(false) @@ -75,11 +80,14 @@ const CustomerData = ({ customer, updateCustomer }) => { : 'Failed' const customEntries = null // get customer custom entries + const customRequirements = null // get customer custom requirements const isEven = elem => elem % 2 === 0 const getVisibleCards = _.filter( - elem => !_.isEmpty(elem.data) || !_.isNil(elem.children) + elem => + !_.isEmpty(elem.fields) || + (!_.isNil(elem.children) && !_.isNil(elem.state)) ) const getAvailableFields = _.filter(({ value }) => value !== '') @@ -96,6 +104,12 @@ const CustomerData = ({ customer, updateCustomer }) => { }), usSsn: Yup.object().shape({ usSsn: Yup.string() + }), + idCardPhoto: Yup.object().shape({ + idCardPhoto: Yup.mixed() + }), + frontCamera: Yup.object().shape({ + frontCamera: Yup.mixed() }) } @@ -160,7 +174,7 @@ const CustomerData = ({ customer, updateCustomer }) => { const usSsnElements = [ { - name: 'us ssn', + name: 'usSsn', label: 'US SSN', value: `${customer.usSsn ?? ''}`, component: TextInput, @@ -168,6 +182,9 @@ const CustomerData = ({ customer, updateCustomer }) => { } ] + const idCardPhotoElements = [{ name: 'idCardPhoto' }] + const frontCameraElements = [{ name: 'frontCamera' }] + const initialValues = { idScan: { name: '', @@ -180,19 +197,26 @@ const CustomerData = ({ customer, updateCustomer }) => { }, usSsn: { usSsn: '' + }, + frontCamera: { + frontCamera: null + }, + idCardPhoto: { + idCardPhoto: null } } const cards = [ { - data: getAvailableFields(idScanElements), + fields: getAvailableFields(idScanElements), title: 'ID Scan', titleIcon: , state: R.path(['idCardDataOverride'])(customer), authorize: () => updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED }), - save: values => console.log(values), + deleteEditedData: () => deleteEditedData({ idCardData: null }), + save: values => editCustomer({ idCardData: values }), validationSchema: schemas.idScan, initialValues: initialValues.idScan }, @@ -217,17 +241,18 @@ const CustomerData = ({ customer, updateCustomer }) => { authorize: () => updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }), - save: () => {}, children: {sanctionsDisplay} }, { + fields: getAvailableFields(frontCameraElements), title: 'Front facing camera', titleIcon: , state: R.path(['frontCameraOverride'])(customer), authorize: () => updateCustomer({ frontCameraOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ frontCameraOverride: OVERRIDE_REJECTED }), - save: () => {}, + save: values => editCustomer({ frontCamera: values.frontCamera }), + deleteEditedData: () => deleteEditedData({ frontCamera: null }), children: customer.frontCameraPath ? ( { customer )}`} /> - ) : null + ) : null, + hasImage: true, + validationSchema: schemas.frontCamera, + initialValues: initialValues.frontCamera }, { + fields: getAvailableFields(idCardPhotoElements), title: 'ID card image', titleIcon: , state: R.path(['idCardPhotoOverride'])(customer), authorize: () => updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ idCardPhotoOverride: OVERRIDE_REJECTED }), - save: () => {}, + save: values => editCustomer({ idCardPhoto: values.idCardPhoto }), + deleteEditedData: () => deleteEditedData({ idCardPhoto: null }), children: customer.idCardPhotoPath ? ( - ) : null + ) : null, + hasImage: true, + validationSchema: schemas.idCardPhoto, + initialValues: initialValues.idCardPhoto }, { - data: getAvailableFields(usSsnElements), + fields: getAvailableFields(usSsnElements), title: 'US SSN', titleIcon: , state: R.path(['usSsnOverride'])(customer), authorize: () => updateCustomer({ usSsnOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ usSsnOverride: OVERRIDE_REJECTED }), - save: () => {}, + save: values => editCustomer({ usSsn: values.usSsn }), + deleteEditedData: () => deleteEditedData({ usSsn: null }), validationSchema: schemas.usSsn, initialValues: initialValues.usSsn } @@ -272,11 +306,13 @@ const CustomerData = ({ customer, updateCustomer }) => { reject, state, titleIcon, - data, + fields, save, + deleteEditedData, children, validationSchema, - initialValues + initialValues, + hasImage }, idx ) => { @@ -288,11 +324,13 @@ const CustomerData = ({ customer, updateCustomer }) => { reject={reject} state={state} titleIcon={titleIcon} - data={data} + hasImage={hasImage} + fields={fields} children={children} validationSchema={validationSchema} initialValues={initialValues} - save={save}> + save={save} + deleteEditedData={deleteEditedData}> ) } @@ -317,7 +355,7 @@ const CustomerData = ({ customer, updateCustomer }) => { onClick={() => setListView(true)}>
- {!listView && ( + {!listView && customer && ( {visibleCards.map((elem, idx) => { @@ -333,7 +371,12 @@ const CustomerData = ({ customer, updateCustomer }) => { )} {customEntries && (
-
{'Custom data entry'}
+ Custom data entry +
+ )} + {customRequirements && ( +
+ Custom requirements
)}
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js index 5977bbfe..373e2f0c 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js @@ -20,22 +20,30 @@ export default { marginRight: 12 }, wrapper: { - display: 'flex' + display: 'block', + overflow: 'hidden', + whiteSpace: 'nowrap' }, separator: { - display: 'flex', - flexBasis: '100%', - justifyContent: 'center', color: offColor, - margin: [[8, 0, 8, 0]], - '&::before, &::after': { - content: '', - flexGrow: 1, + margin: [[8, 0, 8, 150]], + position: 'relative', + display: 'inline-block', + '&:before, &:after': { + content: '""', + position: 'absolute', background: offColor, - height: 1, - fontSize: 1, - lineHeight: 0, - margin: [[0, 8, 0, 8]] + top: '50%', + width: 1000, + height: 1 + }, + '&:before': { + right: '100%', + marginRight: 15 + }, + '&:after': { + left: '100%', + marginLeft: 15 } } } diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index e197416b..15e36f4e 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -116,6 +116,29 @@ const SET_CUSTOMER = gql` } } ` +const EDIT_CUSTOMER = gql` + mutation editCustomer($customerId: ID!, $customerEdit: CustomerEdit) { + editCustomer(customerId: $customerId, customerEdit: $customerEdit) { + id + frontCamera + idCardData + idCardPhoto + usSsn + } + } +` + +const DELETE_EDITED_CUSTOMER = gql` + mutation deleteEditedData($customerId: ID!, $customerEdit: CustomerEdit) { + deleteEditedData(customerId: $customerId, customerEdit: $customerEdit) { + id + frontCamera + idCardData + idCardPhoto + usSsn + } + } +` const CustomerProfile = memo(() => { const history = useHistory() @@ -133,6 +156,14 @@ const CustomerProfile = memo(() => { } ) + const [editCustomerData] = useMutation(EDIT_CUSTOMER, { + onCompleted: () => getCustomer() + }) + + const [deleteCustomerEditedData] = useMutation(DELETE_EDITED_CUSTOMER, { + onCompleted: () => getCustomer() + }) + const [setCustomer] = useMutation(SET_CUSTOMER, { onCompleted: () => getCustomer() }) @@ -145,6 +176,22 @@ const CustomerProfile = memo(() => { } }) + const editCustomer = it => + editCustomerData({ + variables: { + customerId, + customerEdit: it + } + }) + + const deleteEditedData = it => + deleteCustomerEditedData({ + variables: { + customerId, + customerEdit: it + } + }) + const onClickSidebarItem = code => setClickedItem(code) const configData = R.path(['config'])(customerResponse) ?? [] @@ -295,7 +342,9 @@ const CustomerProfile = memo(() => {
+ updateCustomer={updateCustomer} + editCustomer={editCustomer} + deleteEditedData={deleteEditedData}>
)} diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index c4792419..4f05559f 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -2,6 +2,7 @@ import { CardContent, Card, Grid } from '@material-ui/core' import { makeStyles } from '@material-ui/core/styles' import classnames from 'classnames' import { Form, Formik, Field as FormikField } from 'formik' +import * as R from 'ramda' import { useState, React } from 'react' import ErrorMessage from 'src/components/ErrorMessage' @@ -15,12 +16,16 @@ import { OVERRIDE_REJECTED, OVERRIDE_PENDING } from 'src/pages/Customers/components/propertyCard' +import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg' +import { ReactComponent as DeleteReversedIcon } from 'src/styling/icons/action/delete/white.svg' import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' import { ReactComponent as EditReversedIcon } from 'src/styling/icons/action/edit/white.svg' -import { ReactComponent as AuthorizeReversedIcon } from 'src/styling/icons/button/authorize/white.svg' -import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg' +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 CancelIcon } from 'src/styling/icons/button/cancel/zodiac.svg' +import { ReactComponent as ReplaceReversedIcon } from 'src/styling/icons/button/replace/white.svg' +import { ReactComponent as ReplaceIcon } from 'src/styling/icons/button/replace/zodiac.svg' import { ReactComponent as SaveReversedIcon } from 'src/styling/icons/circle buttons/save/white.svg' import { ReactComponent as SaveIcon } from 'src/styling/icons/circle buttons/save/zodiac.svg' import { comet } from 'src/styling/variables' @@ -70,7 +75,7 @@ const fieldUseStyles = makeStyles(fieldStyles) const EditableField = ({ editing, field, size, ...props }) => { const classes = fieldUseStyles() - + console.log('FIELDDDDDDDD', field) const classNames = { [classes.field]: true, [classes.notEditing]: !editing @@ -90,8 +95,9 @@ const EditableField = ({ editing, field, size, ...props }) => { { } const EditableCard = ({ - data, + fields, save, authorize, + hasImage, reject, state, title, titleIcon, - children + children, + validationSchema, + initialValues, + deleteEditedData }) => { const classes = useStyles() const [editing, setEditing] = useState(false) + const [input, setInput] = useState(null) const [error, setError] = useState(null) + const triggerInput = () => input.click() + const label1ClassNames = { [classes.label1]: true, [classes.label1Pending]: state === OVERRIDE_PENDING, @@ -130,9 +143,7 @@ const EditableCard = ({ ? { label: 'Rejected', type: 'error' } : { label: 'Accepted', type: 'success' } - const editableField = field => { - return - } + const reader = new FileReader() return (
@@ -142,99 +153,172 @@ const EditableCard = ({ {titleIcon}

{title}

-
- -
+ {state && ( +
+ +
+ )}
+ {children} save(values)} onReset={() => { setEditing(false) setError(false) }}> -
- -
- - - {data?.map((field, idx) => { - return idx >= 0 && idx < 4 ? editableField(field) : null - })} + {({ values, touched, errors, setFieldValue }) => ( + + + {console.log(values, touched, errors, 'FORMIK STATUS')} +
+ + + {!hasImage && + fields?.map((field, idx) => { + return idx >= 0 && idx < 4 ? ( + + ) : null + })} + + + {!hasImage && + fields?.map((field, idx) => { + return idx >= 4 ? ( + + ) : null + })} + - - {data?.map((field, idx) => { - return idx >= 4 ? editableField(field) : null - })} - - -
- {children} -
- {!editing && ( -
- setEditing(true)}> - {`Edit`} - -
- )} - {editing && ( -
- {data && ( -
+
+
+ {!editing && ( +
+
- Save + color="primary" + type="button" + Icon={DeleteIcon} + InverseIcon={DeleteReversedIcon} + onClick={() => deleteEditedData()}> + {`Delete`}
- )} -
+ - Cancel + color="primary" + Icon={EditIcon} + InverseIcon={EditReversedIcon} + onClick={() => setEditing(true)}> + {`Edit`}
- {authorized.label !== 'Accepted' && ( -
- authorize()}> - {'Authorize'} - + )} + {editing && ( +
+
+ {hasImage && ( + triggerInput()}> + { +
+ setInput(fileInput)} + onChange={event => { + // need to store it locally if we want to display it even after saving to db + + const file = R.head(event.target.files) + + reader.onloadend = () => { + // use a regex to remove data url part + setFieldValue( + R.head(fields).name, + reader.result + .replace('data:', '') + .replace(/^.+,/, '') + ) + } + reader.readAsDataURL(file) + event.target.value = null + }} + /> + Replace +
+ } +
+ )}
- )} - {authorized.label !== 'Rejected' && ( - reject()}> - {'Reject'} - - )} - {error && ( - Failed to save changes - )} -
- )} -
- +
+ {fields && ( +
+ + Save + +
+ )} +
+ + Cancel + +
+ {authorized.label !== 'Accepted' && ( +
+ authorize()}> + Authorize + +
+ )} + {authorized.label !== 'Rejected' && ( + reject()}> + Reject + + )} + {error && ( + Failed to save changes + )} +
+
+ )} +
+ + )} diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js index 1c2a8603..f97fea44 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js @@ -20,6 +20,20 @@ export default { display: 'flex', justifyContent: 'right' }, + deleteButton: { + marginRight: 8 + }, + editingWrapper: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between' + }, + replace: { + marginTop: 30 + }, + input: { + display: 'none' + }, button: { marginRight: 8 }, diff --git a/new-lamassu-admin/src/styling/variables.js b/new-lamassu-admin/src/styling/variables.js index 5c27fb7f..c2fb2a9b 100644 --- a/new-lamassu-admin/src/styling/variables.js +++ b/new-lamassu-admin/src/styling/variables.js @@ -25,6 +25,7 @@ const white = '#ffffff' // Error const tomato = '#ff584a' +const tomato1 = '#E45043' const mistyRose = '#ffeceb' const pumpkin = '#ff7311' const linen = '#fbf3ec' @@ -47,6 +48,7 @@ const offColor = comet const offDarkColor = comet2 const placeholderColor = comet const errorColor = tomato +const errorColorDarker = tomato1 const offErrorColor = mistyRose const inputBorderColor = primaryColor @@ -148,6 +150,7 @@ export { linkPrimaryColor, linkSecondaryColor, errorColor, + errorColorDarker, offErrorColor, inputBorderColor, // font sizes From 9baa16c82446ea8af37a86f540cf35f6bc8b6c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 1 Nov 2021 15:19:55 +0000 Subject: [PATCH 05/18] fix: formik bugs and gql upload --- lib/new-admin/admin-server.js | 2 + .../graphql/resolvers/customer.resolver.js | 2 + lib/new-admin/graphql/types/customer.type.js | 8 ++-- .../1635159374499-editable-customer-data.js | 4 +- new-lamassu-admin/package-lock.json | 13 +++++++ new-lamassu-admin/package.json | 1 + .../src/pages/Customers/CustomerData.js | 39 +++++-------------- .../Customers/components/EditableCard.js | 29 +++++--------- new-lamassu-admin/src/utils/apollo.js | 22 ++++++----- 9 files changed, 56 insertions(+), 64 deletions(-) diff --git a/lib/new-admin/admin-server.js b/lib/new-admin/admin-server.js index b1ccda5d..1f37969f 100644 --- a/lib/new-admin/admin-server.js +++ b/lib/new-admin/admin-server.js @@ -9,6 +9,7 @@ const helmet = require('helmet') const nocache = require('nocache') const cookieParser = require('cookie-parser') const { ApolloServer, AuthenticationError } = require('apollo-server-express') +// const { graphqlUploadExpress } = require('graphql-upload') const _ = require('lodash/fp') const { asyncLocalStorage, defaultStore } = require('../async-storage') @@ -47,6 +48,7 @@ app.use(cleanUserSessions(USER_SESSIONS_CLEAR_INTERVAL)) app.use(computeSchema) app.use(findOperatorId) app.use(session) +// app.use(graphqlUploadExpress()) const apolloServer = new ApolloServer({ typeDefs, diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index b22454bd..4173c128 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -3,6 +3,8 @@ const customers = require('../../../customers') const filters = require('../../filters') const resolvers = { + // Upload: GraphQLUpload, + Customer: { isAnonymous: parent => (parent.customerId === anonymous.uuid) }, diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index 6d0129db..151c1527 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -12,7 +12,7 @@ const typeDef = gql` authorizedOverride: String daysSuspended: Int isSuspended: Boolean - frontCamera: String + frontCamera: Upload frontCameraPath: String frontCameraAt: Date frontCameraOverride: String @@ -22,7 +22,7 @@ const typeDef = gql` idCardData: JSONObject idCardDataOverride: String idCardDataExpiration: Date - idCardPhoto: String + idCardPhoto: Upload idCardPhotoPath: String idCardPhotoOverride: String usSsn: String @@ -68,9 +68,9 @@ const typeDef = gql` } input CustomerEdit { - frontCamera: String + frontCamera: Upload idCardData: JSONObject - idCardPhoto: String + idCardPhoto: Upload usSsn: String } diff --git a/migrations/1635159374499-editable-customer-data.js b/migrations/1635159374499-editable-customer-data.js index 76d6618c..3da59080 100644 --- a/migrations/1635159374499-editable-customer-data.js +++ b/migrations/1635159374499-editable-customer-data.js @@ -17,8 +17,8 @@ exports.up = function (next) { subscriber_info_edited_at TIMESTAMPTZ, subscriber_info_edited_by UUID REFERENCES users(id), name TEXT, - name_info_edited_at TIMESTAMPTZ, - name_info_edited_by UUID REFERENCES users(id), + name_edited_at TIMESTAMPTZ, + name_edited_by UUID REFERENCES users(id), us_ssn TEXT, us_ssn_edited_at TIMESTAMPTZ, us_ssn_edited_by UUID REFERENCES users(id), diff --git a/new-lamassu-admin/package-lock.json b/new-lamassu-admin/package-lock.json index 140b59b1..3c21f1c3 100644 --- a/new-lamassu-admin/package-lock.json +++ b/new-lamassu-admin/package-lock.json @@ -6888,6 +6888,14 @@ "tslib": "^1.9.3" } }, + "apollo-upload-client": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-16.0.0.tgz", + "integrity": "sha512-aLhYucyA0T8aBEQ5g+p13qnR9RUyL8xqb8FSZ7e/Kw2KUOsotLUlFluLobqaE7JSUFwc6sKfXIcwB7y4yEjbZg==", + "requires": { + "extract-files": "^11.0.0" + } + }, "apollo-utilities": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz", @@ -12608,6 +12616,11 @@ } } }, + "extract-files": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz", + "integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==" + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", diff --git a/new-lamassu-admin/package.json b/new-lamassu-admin/package.json index ff436eb3..5c2d5569 100644 --- a/new-lamassu-admin/package.json +++ b/new-lamassu-admin/package.json @@ -14,6 +14,7 @@ "apollo-link": "^1.2.14", "apollo-link-error": "^1.1.13", "apollo-link-http": "^1.5.17", + "apollo-upload-client": "^16.0.0", "axios": "0.21.1", "base-64": "^1.0.0", "bignumber.js": "9.0.0", diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index 77934e0c..ed116708 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -117,57 +117,36 @@ const CustomerData = ({ { name: 'name', label: 'Name', - value: `${getName(customer)}`, component: TextInput }, { name: 'idNumber', label: 'ID number', - value: R.path(['documentNumber'])(idData) ?? '', component: TextInput }, { name: 'birthDate', label: 'Birth Date', - value: - (rawDob && - format('yyyy-MM-dd')(parse(new Date(), 'yyyyMMdd', rawDob))) ?? - '', component: TextInput }, { name: 'age', label: 'Age', - value: - (rawDob && - differenceInYears( - parse(new Date(), 'yyyyMMdd', rawDob), - new Date() - )) ?? - '', component: TextInput }, { name: 'gender', label: 'Gender', - value: R.path(['gender'])(idData) ?? '', component: TextInput }, { name: 'state', label: country === 'Canada' ? 'Province' : 'State', - value: R.path(['state'])(idData) ?? '', component: TextInput }, { name: 'expirationDate', label: 'Expiration Date', - value: - (rawExpirationDate && - format('yyyy-MM-dd')( - parse(new Date(), 'yyyyMMdd', rawExpirationDate) - )) ?? - '', component: TextInput } ] @@ -176,7 +155,6 @@ const CustomerData = ({ { name: 'usSsn', label: 'US SSN', - value: `${customer.usSsn ?? ''}`, component: TextInput, size: 190 } @@ -187,16 +165,17 @@ const CustomerData = ({ const initialValues = { idScan: { - name: '', - idNumber: '', - birthDate: '', - age: '', - gender: '', - state: '', - expirationDate: '' + name: getName(customer) ?? '', + idNumber: R.path(['documentNumber'])(idData) ?? '', + birthDate: (rawDob && format('yyyy-MM-dd', rawDob)) ?? '', + age: (rawDob && differenceInYears(rawDob, new Date())) ?? '', + gender: R.path(['gender'])(idData) ?? '', + state: R.path(['state'])(idData) ?? '', + expirationDate: + (rawExpirationDate && format('yyyy-MM-dd', rawExpirationDate)) ?? '' }, usSsn: { - usSsn: '' + usSsn: customer.usSsn ?? '' }, frontCamera: { frontCamera: null diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index 4f05559f..5b1c45e5 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -73,7 +73,7 @@ const fieldStyles = { const fieldUseStyles = makeStyles(fieldStyles) -const EditableField = ({ editing, field, size, ...props }) => { +const EditableField = ({ editing, field, value, size, ...props }) => { const classes = fieldUseStyles() console.log('FIELDDDDDDDD', field) const classNames = { @@ -86,7 +86,7 @@ const EditableField = ({ editing, field, size, ...props }) => { {!editing && ( <> {field.label} - {field.value} + {value} )} {editing && ( @@ -96,7 +96,6 @@ const EditableField = ({ editing, field, size, ...props }) => { className={classes.editing} id={field.name} name={field.name} - value={field.value} component={field.component} type={field.type} width={size} @@ -143,8 +142,6 @@ const EditableCard = ({ ? { label: 'Rejected', type: 'error' } : { label: 'Accepted', type: 'success' } - const reader = new FileReader() - return (
@@ -166,7 +163,10 @@ const EditableCard = ({ enableReinitialize validationSchema={validationSchema} initialValues={initialValues} - onSubmit={values => save(values)} + onSubmit={values => { + save(values) + setEditing(false) + }} onReset={() => { setEditing(false) setError(false) @@ -183,6 +183,7 @@ const EditableCard = ({ return idx >= 0 && idx < 4 ? ( @@ -195,6 +196,7 @@ const EditableCard = ({ return idx >= 4 ? ( @@ -245,20 +247,9 @@ const EditableCard = ({ ref={fileInput => setInput(fileInput)} onChange={event => { // need to store it locally if we want to display it even after saving to db - const file = R.head(event.target.files) - - reader.onloadend = () => { - // use a regex to remove data url part - setFieldValue( - R.head(fields).name, - reader.result - .replace('data:', '') - .replace(/^.+,/, '') - ) - } - reader.readAsDataURL(file) - event.target.value = null + if (!file) return + setFieldValue(R.head(fields).name, file) }} /> Replace diff --git a/new-lamassu-admin/src/utils/apollo.js b/new-lamassu-admin/src/utils/apollo.js index b04b9b69..9e3278b2 100644 --- a/new-lamassu-admin/src/utils/apollo.js +++ b/new-lamassu-admin/src/utils/apollo.js @@ -3,7 +3,7 @@ import { InMemoryCache } from 'apollo-cache-inmemory' import { ApolloClient } from 'apollo-client' import { ApolloLink } from 'apollo-link' import { onError } from 'apollo-link-error' -import { HttpLink } from 'apollo-link-http' +import { createUploadLink } from 'apollo-upload-client' import React, { useContext } from 'react' import { useHistory, useLocation } from 'react-router-dom' @@ -15,6 +15,16 @@ const URI = const ALT_URI = process.env.NODE_ENV === 'development' ? 'http://localhost:4001' : '' +const uploadLink = createUploadLink({ + credentials: 'include', + uri: `${URI}/graphql` +}) + +const uploadLinkALT = createUploadLink({ + credentials: 'include', + uri: `${ALT_URI}/graphql` +}) + const getClient = (history, location, getUserData, setUserData, setRole) => new ApolloClient({ link: ApolloLink.from([ @@ -48,14 +58,8 @@ const getClient = (history, location, getUserData, setUserData, setRole) => }), ApolloLink.split( operation => operation.getContext().clientName === 'pazuz', - new HttpLink({ - credentials: 'include', - uri: `${ALT_URI}/graphql` - }), - new HttpLink({ - credentials: 'include', - uri: `${URI}/graphql` - }) + uploadLink, + uploadLinkALT ) ]), cache: new InMemoryCache(), From 3e4f4a4962469a50cc3122908d0ff9dffc423769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Wed, 3 Nov 2021 20:56:26 +0000 Subject: [PATCH 06/18] fix: apollo server image upload --- lib/customers.js | 49 ++++++++++++++++++- .../graphql/resolvers/customer.resolver.js | 11 +++-- lib/new-admin/graphql/types/customer.type.js | 5 +- .../src/pages/Customers/CustomerData.js | 13 ++++- .../src/pages/Customers/CustomerProfile.js | 34 ++++++++++++- .../Customers/components/EditableCard.js | 1 + 6 files changed, 103 insertions(+), 10 deletions(-) diff --git a/lib/customers.js b/lib/customers.js index 4f01ea7b..029a2fd1 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -216,7 +216,7 @@ function enhanceEditedFields (fields, userToken) { */ function deleteEditedData (id, data) { - // NOT IMPLEMENTING THIS FEATURE FOR NOW + // NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION // const defaults = [ // 'front_camera', // 'id_card_data', @@ -235,6 +235,53 @@ function deleteEditedData (id, data) { return getCustomerById(id) } +/** + * Replace customer's compliance photos + * + * @name save + * @function + * + * @param {string} id Customer's id + * @param {File} photo New photo data + * @param {string} photoType Photo's compliance type + * + * @returns {object} path New photo path + * + */ +function replacePhoto (id, photo, photoType) { + const baseDir = photoType === 'frontCamera' ? frontCameraBaseDir : idPhotoCardBasedir + const { createReadStream } = photo + const stream = createReadStream() + + // workout the image hash + // i.e. 240e85ff2e4bb931f235985dd0134e459239496d2b5af6c5665168d38ef89b50 + const hash = crypto + .createHash('sha256') + .update(imageData) + .digest('hex') + + // workout the image folder + // i.e. 24/0e/85 + const rpath = _.join(path.sep, _.map(_.wrap(_.join, ''), _.take(3, _.chunk(2, _.split('', hash))))) + + // i.e. ..//idphotocard/24/0e/85 + const dirname = path.join(idPhotoCardBasedir, rpath) + + // create the directory tree if needed + _.attempt(() => makeDir.sync(dirname)) + + // i.e. ..//idphotocard/24/0e/85/240e85ff2e4bb931f235985dd01....jpg + const filename = path.join(dirname, hash + '.jpg') + + // update db record patch + // i.e. { + // "idCardPhotoPath": "24/0e/85/240e85ff2e4bb931f235985dd01....jpg", + // "idCardPhotoAt": "now()" + // } + newPatch.idCardPhotoPath = path.join(rpath, hash + '.jpg') + newPatch.idCardPhotoAt = 'now()' +} + const invalidateCustomerNotifications = (id, data) => { if (data.authorized_override !== 'verified') return Promise.resolve() diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index 4173c128..5bfd42e4 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -3,7 +3,6 @@ const customers = require('../../../customers') const filters = require('../../filters') const resolvers = { - // Upload: GraphQLUpload, Customer: { isAnonymous: parent => (parent.customerId === anonymous.uuid) @@ -22,9 +21,15 @@ const resolvers = { addCustomField: (...[, { customerId, label, value }]) => customers.addCustomField(customerId, label, value), saveCustomField: (...[, { customerId, fieldId, newValue }]) => customers.saveCustomField(customerId, fieldId, newValue), removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId), - editCustomer: (root, { customerId, customerEdit }, context) => { + editCustomer: async (root, { customerId, customerEdit }, context) => { const token = !!context.req.cookies.lid && context.req.session.user.id - return customers.edit(customerId, customerEdit, token) + const editedData = await customerEdit + return customers.edit(customerId, editedData, token) + }, + replacePhoto: async (root, { customerId, photoType, newPhoto }, context) => { + const photo = await newPhoto + return customers.replacePhoto(customerId, photoType, photo) + .then(() => customers.getCustomerById(customerId)) }, deleteEditedData: (root, { customerId, customerEdit }) => { return customers.deleteEditedData(customerId, customerEdit) diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index 151c1527..f6d56d4d 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -12,7 +12,8 @@ const typeDef = gql` authorizedOverride: String daysSuspended: Int isSuspended: Boolean - frontCamera: Upload + newPhoto: Upload + photoType: String frontCameraPath: String frontCameraAt: Date frontCameraOverride: String @@ -68,7 +69,6 @@ const typeDef = gql` } input CustomerEdit { - frontCamera: Upload idCardData: JSONObject idCardPhoto: Upload usSsn: String @@ -87,6 +87,7 @@ const typeDef = gql` removeCustomField(customerId: ID!, fieldId: ID!): CustomerCustomField @auth editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth + replacePhoto(customerId: ID!, photoType: String, newPhoto: Upload): Customer @auth } ` diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index ed116708..9785afa3 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -60,6 +60,7 @@ const Photo = ({ show, src }) => { const CustomerData = ({ customer, updateCustomer, + replacePhoto, editCustomer, deleteEditedData }) => { @@ -230,7 +231,11 @@ const CustomerData = ({ authorize: () => updateCustomer({ frontCameraOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ frontCameraOverride: OVERRIDE_REJECTED }), - save: values => editCustomer({ frontCamera: values.frontCamera }), + save: values => + replacePhoto({ + newPhoto: values.frontCamera, + photoType: 'frontCamera' + }), deleteEditedData: () => deleteEditedData({ frontCamera: null }), children: customer.frontCameraPath ? ( updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ idCardPhotoOverride: OVERRIDE_REJECTED }), - save: values => editCustomer({ idCardPhoto: values.idCardPhoto }), + save: values => + replacePhoto({ + newPhoto: values.idCardPhoto, + photoType: 'idCardPhoto' + }), deleteEditedData: () => deleteEditedData({ idCardPhoto: null }), children: customer.idCardPhotoPath ? ( { } ) + const [replaceCustomerPhoto] = useMutation(REPLACE_CUSTOMER_PHOTO, { + onCompleted: () => getCustomer() + }) + const [editCustomerData] = useMutation(EDIT_CUSTOMER, { onCompleted: () => getCustomer() }) @@ -176,6 +196,15 @@ const CustomerProfile = memo(() => { } }) + const replacePhoto = it => + replaceCustomerPhoto({ + variables: { + customerId, + newPhoto: it.newPhoto, + photoType: it.photoType + } + }) + const editCustomer = it => editCustomerData({ variables: { @@ -343,6 +372,7 @@ const CustomerProfile = memo(() => {
diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index 5b1c45e5..936fd076 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -243,6 +243,7 @@ const EditableCard = ({ setInput(fileInput)} onChange={event => { From 68c635ce38cb7faca416a06fab38fd03feca0201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Tue, 9 Nov 2021 13:55:12 +0000 Subject: [PATCH 07/18] fix: use gql upload lib and remove logs --- lib/customers.js | 94 +++++++++++-------- lib/new-admin/admin-server.js | 5 +- .../graphql/resolvers/customer.resolver.js | 5 +- .../graphql/resolvers/scalar.resolver.js | 5 +- lib/new-admin/graphql/types/customer.type.js | 8 +- lib/new-admin/graphql/types/scalar.type.js | 1 + .../src/pages/Customers/CustomerData.js | 37 ++++---- .../Customers/components/EditableCard.js | 2 - package-lock.json | 24 +++++ package.json | 1 + 10 files changed, 112 insertions(+), 70 deletions(-) diff --git a/lib/customers.js b/lib/customers.js index 029a2fd1..c839b97f 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -161,24 +161,20 @@ function edit (id, data, userToken) { ] const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, _.omitBy(_.isNil, data))) if (_.isEmpty(filteredData)) return getCustomerById(id) - if (filteredData.front_camera_data || filteredData.id_card_photo_data) return getCustomerById(id) - const formattedData = enhanceEditedFields(filteredData, userToken) + const formattedData = enhanceEditedPhotos(enhanceEditedFields(filteredData, userToken)) const defaultDbData = { customer_id: id, created: new Date(), ...formattedData } - console.log(formattedData, 'FORMATED', defaultDbData, 'DEFAULT DB') + const cs = new Pgp.helpers.ColumnSet(_.keys(defaultDbData), { table: 'edited_customer_data' }) const onConflict = ' ON CONFLICT (customer_id) DO UPDATE SET ' + cs.assignColumns({ from: 'EXCLUDED', skip: ['customer_id', 'created'] }) const upsert = Pgp.helpers.insert(defaultDbData, cs) + onConflict - db.none(upsert) - .then(res => { - console.log(res) - return getCustomerById(id) - }) + return db.none(upsert) + .then(getCustomerById(id)) } /** @@ -202,6 +198,25 @@ function enhanceEditedFields (fields, userToken) { return fields } +/** + * Add *_path to edited photos fields + * + * @name enhanceEditedFields + * @function + * + * @param {object} fields Fields to be enhanced + * @returns {object} fields enhanced with *_path + */ + +function enhanceEditedPhotos (fields) { + return _.mapKeys((field) => { + if (_.includes(field, ['front_camera', 'id_card_photo'])) { + return field + '_path' + } + return field + }, fields) +} + /** * Remove the edited data from the db record * @@ -248,38 +263,27 @@ function deleteEditedData (id, data) { * @returns {object} path New photo path * */ -function replacePhoto (id, photo, photoType) { +async function updateEditedPhoto (id, photo, photoType) { + const newPatch = {} const baseDir = photoType === 'frontCamera' ? frontCameraBaseDir : idPhotoCardBasedir - const { createReadStream } = photo + const { createReadStream, filename } = photo const stream = createReadStream() - // workout the image hash - // i.e. 240e85ff2e4bb931f235985dd0134e459239496d2b5af6c5665168d38ef89b50 - const hash = crypto - .createHash('sha256') - .update(imageData) - .digest('hex') + const randomString = uuid.v4().toString() + '/' - // workout the image folder - // i.e. 24/0e/85 - const rpath = _.join(path.sep, _.map(_.wrap(_.join, ''), _.take(3, _.chunk(2, _.split('', hash))))) - - // i.e. ..//idphotocard/24/0e/85 - const dirname = path.join(idPhotoCardBasedir, rpath) + // i.e. ..62ed29c5-f37e-4fb7-95bb-c52d4a3738f7/filename.jpg + const rpath = path.join(randomString, filename) // create the directory tree if needed - _.attempt(() => makeDir.sync(dirname)) + _.attempt(() => makeDir.sync(path.join(baseDir, randomString))) - // i.e. ..//idphotocard/24/0e/85/240e85ff2e4bb931f235985dd01....jpg - const filename = path.join(dirname, hash + '.jpg') + // i.e. ..//idphotocard/62ed29c5-f37e-4fb7-95bb-c52d4a3738f7/filename.jpg + const pathName = path.join(baseDir, rpath) - // update db record patch - // i.e. { - // "idCardPhotoPath": "24/0e/85/240e85ff2e4bb931f235985dd01....jpg", - // "idCardPhotoAt": "now()" - // } - newPatch.idCardPhotoPath = path.join(rpath, hash + '.jpg') - newPatch.idCardPhotoAt = 'now()' + await stream.pipe(fs.createWriteStream(pathName)) + newPatch[photoType] = rpath + + return newPatch } const invalidateCustomerNotifications = (id, data) => { @@ -670,9 +674,9 @@ 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, id_card_data_override, id_card_data_expiration, - id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at, + const sql = `select id, authorized_override, days_suspended, is_suspended, front_camera_at, 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, + id_card_photo_at, id_card_photo_path, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at, sanctions_override, total_txs, total_spent, created 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 from ( @@ -680,8 +684,8 @@ function getCustomerById (id) { 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_at, c.front_camera_override, - c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration, - c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions, + 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.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, t.tx_class, t.fiat, t.fiat_code, t.created, 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, @@ -700,6 +704,10 @@ function getCustomerById (id) { where c.id = $2 ) as cl where rn = 1` return db.oneOrNone(sql, [passableErrorCodes, id]) + .then(customerData => { + return getEditedData(id) + .then(customerEditedData => selectLatestData(customerData, customerEditedData)) + }) .then(populateOverrideUsernames) .then(camelize) } @@ -709,10 +717,16 @@ function getCustomerById (id) { * * @param {String} id customer id * - * @returns {array} A single customer instance with the most recent data + * @returns {array} A single customer instance with the most recent edited data */ -function getManuallyEditedData (id) { +function getEditedData (id) { + const sql = `SELECT * FROM edited_customer_data WHERE customer_id = $1` + return db.oneOrNone(sql, [id]) + .then(_.omitBy(_.isNil)) +} +function selectLatestData (customerData, customerEditedData) { + return customerData } /** @@ -983,7 +997,7 @@ module.exports = { saveCustomField, removeCustomField, edit, - getManuallyEditedData, deleteEditedData, + updateEditedPhoto, updateTxCustomerPhoto } diff --git a/lib/new-admin/admin-server.js b/lib/new-admin/admin-server.js index 1f37969f..53f86ccc 100644 --- a/lib/new-admin/admin-server.js +++ b/lib/new-admin/admin-server.js @@ -9,7 +9,7 @@ const helmet = require('helmet') const nocache = require('nocache') const cookieParser = require('cookie-parser') const { ApolloServer, AuthenticationError } = require('apollo-server-express') -// const { graphqlUploadExpress } = require('graphql-upload') +const { graphqlUploadExpress } = require('graphql-upload') const _ = require('lodash/fp') const { asyncLocalStorage, defaultStore } = require('../async-storage') @@ -48,11 +48,12 @@ app.use(cleanUserSessions(USER_SESSIONS_CLEAR_INTERVAL)) app.use(computeSchema) app.use(findOperatorId) app.use(session) -// app.use(graphqlUploadExpress()) +app.use(graphqlUploadExpress()) const apolloServer = new ApolloServer({ typeDefs, resolvers, + uploads: false, schemaDirectives: { auth: AuthDirective }, diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index 5bfd42e4..2db8b153 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -27,9 +27,10 @@ const resolvers = { return customers.edit(customerId, editedData, token) }, replacePhoto: async (root, { customerId, photoType, newPhoto }, context) => { + const token = !!context.req.cookies.lid && context.req.session.user.id const photo = await newPhoto - return customers.replacePhoto(customerId, photoType, photo) - .then(() => customers.getCustomerById(customerId)) + return customers.updateEditedPhoto(customerId, photo, photoType) + .then(newPatch => customers.edit(customerId, newPatch, token)) }, deleteEditedData: (root, { customerId, customerEdit }) => { return customers.deleteEditedData(customerId, customerEdit) diff --git a/lib/new-admin/graphql/resolvers/scalar.resolver.js b/lib/new-admin/graphql/resolvers/scalar.resolver.js index 57d556ae..54782105 100644 --- a/lib/new-admin/graphql/resolvers/scalar.resolver.js +++ b/lib/new-admin/graphql/resolvers/scalar.resolver.js @@ -1,12 +1,13 @@ const { GraphQLDateTime } = require('graphql-iso-date') const { GraphQLJSON, GraphQLJSONObject } = require('graphql-type-json') - +const { GraphQLUpload } = require('graphql-upload') GraphQLDateTime.name = 'Date' const resolvers = { JSON: GraphQLJSON, JSONObject: GraphQLJSONObject, - Date: GraphQLDateTime + Date: GraphQLDateTime, + UploadGQL: GraphQLUpload } module.exports = resolvers diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index f6d56d4d..bbb90aa0 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -12,7 +12,7 @@ const typeDef = gql` authorizedOverride: String daysSuspended: Int isSuspended: Boolean - newPhoto: Upload + newPhoto: UploadGQL photoType: String frontCameraPath: String frontCameraAt: Date @@ -23,7 +23,7 @@ const typeDef = gql` idCardData: JSONObject idCardDataOverride: String idCardDataExpiration: Date - idCardPhoto: Upload + idCardPhoto: UploadGQL idCardPhotoPath: String idCardPhotoOverride: String usSsn: String @@ -70,7 +70,7 @@ const typeDef = gql` input CustomerEdit { idCardData: JSONObject - idCardPhoto: Upload + idCardPhoto: UploadGQL usSsn: String } @@ -87,7 +87,7 @@ const typeDef = gql` removeCustomField(customerId: ID!, fieldId: ID!): CustomerCustomField @auth editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth - replacePhoto(customerId: ID!, photoType: String, newPhoto: Upload): Customer @auth + replacePhoto(customerId: ID!, photoType: String, newPhoto: UploadGQL): Customer @auth } ` diff --git a/lib/new-admin/graphql/types/scalar.type.js b/lib/new-admin/graphql/types/scalar.type.js index 693adccb..c872c1d1 100644 --- a/lib/new-admin/graphql/types/scalar.type.js +++ b/lib/new-admin/graphql/types/scalar.type.js @@ -4,6 +4,7 @@ const typeDef = gql` scalar JSON scalar JSONObject scalar Date + scalar UploadGQL ` module.exports = typeDef diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index 9785afa3..5acef0ba 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -85,13 +85,7 @@ const CustomerData = ({ const isEven = elem => elem % 2 === 0 - const getVisibleCards = _.filter( - elem => - !_.isEmpty(elem.fields) || - (!_.isNil(elem.children) && !_.isNil(elem.state)) - ) - - const getAvailableFields = _.filter(({ value }) => value !== '') + const getVisibleCards = _.filter(elem => elem.isAvailable) const schemas = { idScan: Yup.object().shape({ @@ -188,7 +182,7 @@ const CustomerData = ({ const cards = [ { - fields: getAvailableFields(idScanElements), + fields: idScanElements, title: 'ID Scan', titleIcon: , state: R.path(['idCardDataOverride'])(customer), @@ -198,21 +192,24 @@ const CustomerData = ({ deleteEditedData: () => deleteEditedData({ idCardData: null }), save: values => editCustomer({ idCardData: values }), validationSchema: schemas.idScan, - initialValues: initialValues.idScan + initialValues: initialValues.idScan, + isAvailable: !_.isNil(idData) }, { title: 'SMS Confirmation', titleIcon: , authorize: () => {}, reject: () => {}, - save: () => {} + save: () => {}, + isAvailable: false }, { title: 'Name', titleIcon: , authorize: () => {}, reject: () => {}, - save: () => {} + save: () => {}, + isAvailable: false }, { title: 'Sanctions check', @@ -221,10 +218,11 @@ const CustomerData = ({ authorize: () => updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }), - children: {sanctionsDisplay} + children: {sanctionsDisplay}, + isAvailable: !_.isNil(sanctions) }, { - fields: getAvailableFields(frontCameraElements), + fields: frontCameraElements, title: 'Front facing camera', titleIcon: , state: R.path(['frontCameraOverride'])(customer), @@ -247,10 +245,11 @@ const CustomerData = ({ ) : null, hasImage: true, validationSchema: schemas.frontCamera, - initialValues: initialValues.frontCamera + initialValues: initialValues.frontCamera, + isAvailable: !_.isNil(customer.frontCameraPath) }, { - fields: getAvailableFields(idCardPhotoElements), + fields: idCardPhotoElements, title: 'ID card image', titleIcon: , state: R.path(['idCardPhotoOverride'])(customer), @@ -271,10 +270,11 @@ const CustomerData = ({ ) : null, hasImage: true, validationSchema: schemas.idCardPhoto, - initialValues: initialValues.idCardPhoto + initialValues: initialValues.idCardPhoto, + isAvailable: !_.isNil(customer.idCardPhotoPath) }, { - fields: getAvailableFields(usSsnElements), + fields: usSsnElements, title: 'US SSN', titleIcon: , state: R.path(['usSsnOverride'])(customer), @@ -283,7 +283,8 @@ const CustomerData = ({ save: values => editCustomer({ usSsn: values.usSsn }), deleteEditedData: () => deleteEditedData({ usSsn: null }), validationSchema: schemas.usSsn, - initialValues: initialValues.usSsn + initialValues: initialValues.usSsn, + isAvailable: !_.isNil(customer.usSsn) } ] diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index 936fd076..d75f5bef 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -75,7 +75,6 @@ const fieldUseStyles = makeStyles(fieldStyles) const EditableField = ({ editing, field, value, size, ...props }) => { const classes = fieldUseStyles() - console.log('FIELDDDDDDDD', field) const classNames = { [classes.field]: true, [classes.notEditing]: !editing @@ -174,7 +173,6 @@ const EditableCard = ({ {({ values, touched, errors, setFieldValue }) => (
- {console.log(values, touched, errors, 'FORMIK STATUS')}
diff --git a/package-lock.json b/package-lock.json index 0e4cd28b..5092830b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11479,6 +11479,30 @@ "resolved": "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz", "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==" }, + "graphql-upload": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/graphql-upload/-/graphql-upload-12.0.0.tgz", + "integrity": "sha512-ovZ3Q7sZ17Bmn8tYl22MfrpNR7nYM/DUszXWgkue7SFIlI9jtqszHAli8id8ZcnGBc9GF0gUTNSskYWW+5aNNQ==", + "requires": { + "busboy": "^0.3.1", + "fs-capacitor": "^6.2.0", + "http-errors": "^1.8.0", + "isobject": "^4.0.0", + "object-path": "^0.11.5" + }, + "dependencies": { + "fs-capacitor": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-6.2.0.tgz", + "integrity": "sha512-nKcE1UduoSKX27NSZlg879LdQc94OtbOsEmKMN2MBNudXREvijRKx2GEBsTMTfws+BrbkJoEuynbGSVRSpauvw==" + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==" + } + } + }, "graphql-ws": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.1.0.tgz", diff --git a/package.json b/package.json index 3f94362e..030bf13b 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "graphql-iso-date": "^3.6.1", "graphql-tools": "^7.0.2", "graphql-type-json": "^0.3.1", + "graphql-upload": "12.0.0", "helmet": "^3.8.1", "inquirer": "^5.2.0", "json2csv": "^5.0.3", From b77b732bcba9e779cdf8a82a4715c90ed222c888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Wed, 10 Nov 2021 19:02:46 +0000 Subject: [PATCH 08/18] fix: latest customer data selection and button margin --- lib/customers.js | 55 +++++++++++++------ .../graphql/resolvers/customer.resolver.js | 4 +- .../1635159374499-editable-customer-data.js | 24 ++++---- .../src/pages/Customers/CustomerProfile.js | 4 +- .../components/EditableCard.styles.js | 5 +- 5 files changed, 58 insertions(+), 34 deletions(-) diff --git a/lib/customers.js b/lib/customers.js index c839b97f..01cf7807 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -156,7 +156,7 @@ function edit (id, data, userToken) { 'id_card_data', 'id_card_photo', 'us_ssn', - 'subcriber_info', + 'subscriber_info', 'name' ] const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, _.omitBy(_.isNil, data))) @@ -192,8 +192,8 @@ function edit (id, data, userToken) { function enhanceEditedFields (fields, userToken) { if (!userToken) return fields _.mapKeys((field) => { - fields[field + '_edited_by'] = userToken - fields[field + '_edited_at'] = 'now()^' + fields[field + '_by'] = userToken + fields[field + '_at'] = 'now()^' }, fields) return fields } @@ -232,21 +232,21 @@ function enhanceEditedPhotos (fields) { function deleteEditedData (id, data) { // NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION - // const defaults = [ - // 'front_camera', - // 'id_card_data', - // 'id_card_photo', - // 'us_ssn', - // 'subcriber_info', - // 'name' - // ] - // const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, data)) - // if (_.isEmpty(filteredData)) return getCustomerById(id) + const defaults = [ + 'front_camera', + 'id_card_data', + 'id_card_photo', + 'us_ssn', + 'subcriber_info', + 'name' + ] + const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, data)) + if (_.isEmpty(filteredData)) return getCustomerById(id) - // const cs = new Pgp.helpers.ColumnSet(_.keys(filteredData), - // { table: 'edited_customer_data' }) - // const update = Pgp.helpers.update(filteredData, cs) - // db.none(update) + const cs = new Pgp.helpers.ColumnSet(_.keys(filteredData), + { table: 'edited_customer_data' }) + const update = Pgp.helpers.update(filteredData, cs) + db.none(update) return getCustomerById(id) } @@ -726,6 +726,27 @@ function getEditedData (id) { } function selectLatestData (customerData, customerEditedData) { + const defaults = [ + 'front_camera', + 'id_card_data', + 'id_card_photo', + 'us_ssn', + 'subscriber_info', + 'name' + ] + _.map(field => { + let fieldName = field + if (_.includes(field, ['front_camera', 'id_card_photo'])) fieldName = fieldName + '_path' + const atField = field + '_at' + const byField = field + '_by' + if (!_.has(fieldName, customerData) || !_.has(fieldName, customerEditedData)) return + if (customerData[atField] < customerEditedData[atField]) { + customerData[fieldName] = customerEditedData[fieldName] + customerData[atField] = customerEditedData[atField] + customerData[byField] = customerEditedData[byField] + } + } + , defaults) return customerData } diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index 2db8b153..9ebf636f 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -29,11 +29,13 @@ const resolvers = { replacePhoto: async (root, { customerId, photoType, newPhoto }, context) => { const token = !!context.req.cookies.lid && context.req.session.user.id const photo = await newPhoto + if (!photo) return customers.getCustomerById(customerId) return customers.updateEditedPhoto(customerId, photo, photoType) .then(newPatch => customers.edit(customerId, newPatch, token)) }, deleteEditedData: (root, { customerId, customerEdit }) => { - return customers.deleteEditedData(customerId, customerEdit) + // NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION + return customers.getCustomerById(customerId) } } } diff --git a/migrations/1635159374499-editable-customer-data.js b/migrations/1635159374499-editable-customer-data.js index 3da59080..c77ba5e8 100644 --- a/migrations/1635159374499-editable-customer-data.js +++ b/migrations/1635159374499-editable-customer-data.js @@ -5,23 +5,23 @@ exports.up = function (next) { `CREATE TABLE edited_customer_data ( customer_id uuid PRIMARY KEY REFERENCES customers(id), id_card_data JSON, - id_card_data_edited_at TIMESTAMPTZ, - id_card_data_edited_by UUID REFERENCES users(id), + id_card_data_at TIMESTAMPTZ, + id_card_data_by UUID REFERENCES users(id), front_camera_path TEXT, - front_camera_edited_at TIMESTAMPTZ, - front_camera_edited_by UUID REFERENCES users(id), + front_camera_at TIMESTAMPTZ, + front_camera_by UUID REFERENCES users(id), id_card_photo_path TEXT, - id_card_photo_edited_at TIMESTAMPTZ, - id_card_photo_edited_by UUID REFERENCES users(id), + id_card_photo_at TIMESTAMPTZ, + id_card_photo_by UUID REFERENCES users(id), subscriber_info JSON, - subscriber_info_edited_at TIMESTAMPTZ, - subscriber_info_edited_by UUID REFERENCES users(id), + subscriber_info_at TIMESTAMPTZ, + subscriber_info_by UUID REFERENCES users(id), name TEXT, - name_edited_at TIMESTAMPTZ, - name_edited_by UUID REFERENCES users(id), + name_at TIMESTAMPTZ, + name_by UUID REFERENCES users(id), us_ssn TEXT, - us_ssn_edited_at TIMESTAMPTZ, - us_ssn_edited_by UUID REFERENCES users(id), + us_ssn_at TIMESTAMPTZ, + us_ssn_by UUID REFERENCES users(id), created TIMESTAMPTZ NOT NULL DEFAULT now() )` ] diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index e8f980cd..41dc2dea 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -148,9 +148,9 @@ const DELETE_EDITED_CUSTOMER = gql` mutation deleteEditedData($customerId: ID!, $customerEdit: CustomerEdit) { deleteEditedData(customerId: $customerId, customerEdit: $customerEdit) { id - frontCamera + frontCameraPath idCardData - idCardPhoto + idCardPhotoPath usSsn } } diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js index f97fea44..db50b8ae 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js @@ -29,13 +29,14 @@ export default { justifyContent: 'space-between' }, replace: { - marginTop: 30 + marginTop: 30, + marginRight: 5 }, input: { display: 'none' }, button: { - marginRight: 8 + marginRight: 5 }, editingButtons: { marginTop: 30, From 3667d8b655bc4539ca93881e229a54deb9a5fb23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 15 Nov 2021 18:16:39 +0000 Subject: [PATCH 09/18] fix: forms keys names and img size --- .../src/pages/Customers/CustomerData.js | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index 5acef0ba..5b7ddf41 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -26,7 +26,6 @@ import { URI } from 'src/utils/apollo' import styles from './CustomerData.styles.js' import { EditableCard } from './components' -import { getName } from './helper.js' const useStyles = makeStyles(styles) @@ -69,7 +68,6 @@ const CustomerData = ({ const idData = R.path(['idCardData'])(customer) const rawExpirationDate = R.path(['expirationDate'])(idData) - const country = R.path(['country'])(idData) const rawDob = R.path(['dateOfBirth'])(idData) const sanctions = R.path(['sanctions'])(customer) @@ -89,44 +87,39 @@ const CustomerData = ({ const schemas = { idScan: Yup.object().shape({ - name: Yup.string(), - idNumber: Yup.string(), - birthDate: Yup.string(), - age: Yup.string(), - gender: Yup.string(), - state: Yup.string(), - expirationDate: Yup.string() + firstName: Yup.string().required(), + lastName: Yup.string().required(), + documentNumber: Yup.string().required(), + dateOfBirth: Yup.string().required(), + gender: Yup.string().required(), + country: Yup.string().required(), + expirationDate: Yup.string().required() }), usSsn: Yup.object().shape({ - usSsn: Yup.string() + usSsn: Yup.string().required() }), idCardPhoto: Yup.object().shape({ - idCardPhoto: Yup.mixed() + idCardPhoto: Yup.mixed().required() }), frontCamera: Yup.object().shape({ - frontCamera: Yup.mixed() + frontCamera: Yup.mixed().required() }) } const idScanElements = [ { - name: 'name', - label: 'Name', + name: 'firstName', + label: 'First name', component: TextInput }, { - name: 'idNumber', + name: 'documentNumber', label: 'ID number', component: TextInput }, { - name: 'birthDate', - label: 'Birth Date', - component: TextInput - }, - { - name: 'age', - label: 'Age', + name: 'dateOfBirth', + label: 'Birthdate', component: TextInput }, { @@ -135,14 +128,19 @@ const CustomerData = ({ component: TextInput }, { - name: 'state', - label: country === 'Canada' ? 'Province' : 'State', + name: 'lastName', + label: 'Last name', component: TextInput }, { name: 'expirationDate', label: 'Expiration Date', component: TextInput + }, + { + name: 'country', + label: 'Country', + component: TextInput } ] @@ -160,12 +158,12 @@ const CustomerData = ({ const initialValues = { idScan: { - name: getName(customer) ?? '', - idNumber: R.path(['documentNumber'])(idData) ?? '', - birthDate: (rawDob && format('yyyy-MM-dd', rawDob)) ?? '', - age: (rawDob && differenceInYears(rawDob, new Date())) ?? '', + firstName: R.path(['firstName'])(idData) ?? '', + lastName: R.path(['lastName'])(idData) ?? '', + documentNumber: R.path(['documentNumber'])(idData) ?? '', + dateOfBirth: (rawDob && format('yyyy-MM-dd', rawDob)) ?? '', gender: R.path(['gender'])(idData) ?? '', - state: R.path(['state'])(idData) ?? '', + country: R.path(['country'])(idData) ?? '', expirationDate: (rawExpirationDate && format('yyyy-MM-dd', rawExpirationDate)) ?? '' }, @@ -184,20 +182,20 @@ const CustomerData = ({ { fields: idScanElements, title: 'ID Scan', - titleIcon: , + titleIcon: , state: R.path(['idCardDataOverride'])(customer), authorize: () => updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED }), deleteEditedData: () => deleteEditedData({ idCardData: null }), - save: values => editCustomer({ idCardData: values }), + save: values => editCustomer({ idCardData: _.merge(idData, values) }), validationSchema: schemas.idScan, initialValues: initialValues.idScan, isAvailable: !_.isNil(idData) }, { title: 'SMS Confirmation', - titleIcon: , + titleIcon: , authorize: () => {}, reject: () => {}, save: () => {}, From 3871c5a699071ca9836d8d2713816bc59228c592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 15 Nov 2021 18:17:29 +0000 Subject: [PATCH 10/18] fix: action buttons colors --- .../src/components/buttons/ActionButton.styles.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/new-lamassu-admin/src/components/buttons/ActionButton.styles.js b/new-lamassu-admin/src/components/buttons/ActionButton.styles.js index 5d0cde44..32582425 100644 --- a/new-lamassu-admin/src/components/buttons/ActionButton.styles.js +++ b/new-lamassu-admin/src/components/buttons/ActionButton.styles.js @@ -10,6 +10,7 @@ import { secondaryColorDark, secondaryColorDarker, errorColor, + errorColorDark, errorColorDarker } from 'src/styling/variables' @@ -74,7 +75,7 @@ export default { } }, spring: { - extend: colors(secondaryColor, secondaryColorDark, secondaryColorDarker), + extend: colors(secondaryColorDark, secondaryColor, secondaryColorDarker), color: white, '&:active': { '& $actionButtonIcon': { @@ -92,7 +93,7 @@ export default { } }, tomato: { - extend: colors(errorColor, errorColorDarker, errorColor), + extend: colors(errorColorDark, errorColor, errorColorDarker), color: white, '&:active': { '& $actionButtonIcon': { From 7b1697fff0ff484b37f212fd57c23997595ca693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 15 Nov 2021 18:18:11 +0000 Subject: [PATCH 11/18] feat: add new tomato color darker variant --- new-lamassu-admin/src/styling/variables.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/new-lamassu-admin/src/styling/variables.js b/new-lamassu-admin/src/styling/variables.js index c2fb2a9b..43ea834b 100644 --- a/new-lamassu-admin/src/styling/variables.js +++ b/new-lamassu-admin/src/styling/variables.js @@ -26,6 +26,7 @@ const white = '#ffffff' // Error const tomato = '#ff584a' const tomato1 = '#E45043' +const tomato2 = '#CE463A' const mistyRose = '#ffeceb' const pumpkin = '#ff7311' const linen = '#fbf3ec' @@ -48,7 +49,8 @@ const offColor = comet const offDarkColor = comet2 const placeholderColor = comet const errorColor = tomato -const errorColorDarker = tomato1 +const errorColorDark = tomato1 +const errorColorDarker = tomato2 const offErrorColor = mistyRose const inputBorderColor = primaryColor @@ -151,6 +153,7 @@ export { linkSecondaryColor, errorColor, errorColorDarker, + errorColorDark, offErrorColor, inputBorderColor, // font sizes From a35dd3e77e57352404cc7d3dc27d3a783d8a8d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 15 Nov 2021 18:18:57 +0000 Subject: [PATCH 12/18] fix: status alignment and font sizes --- .../Customers/components/EditableCard.js | 20 +++++++++++-------- .../components/EditableCard.styles.js | 13 ++++++++---- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index d75f5bef..3587e5c4 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -10,7 +10,7 @@ import PromptWhenDirty from 'src/components/PromptWhenDirty' import { MainStatus } from 'src/components/Status' import { Tooltip } from 'src/components/Tooltip' import { ActionButton } from 'src/components/buttons' -import { Label1, Info3, H3 } from 'src/components/typography' +import { Label1, P, H3 } from 'src/components/typography' import { OVERRIDE_AUTHORIZED, OVERRIDE_REJECTED, @@ -39,7 +39,8 @@ const fieldStyles = { position: 'relative', width: 280, height: 48, - padding: [[0, 4, 4, 0]] + padding: [[0, 4, 4, 0]], + marginTop: 2 }, label: { color: comet, @@ -65,7 +66,8 @@ const fieldStyles = { editing: { '& > div': { '& > input': { - padding: 0 + padding: 0, + fontSize: 14 } } } @@ -85,7 +87,7 @@ const EditableField = ({ editing, field, value, size, ...props }) => { {!editing && ( <> {field.label} - {value} +

{value}

)} {editing && ( @@ -145,10 +147,12 @@ const EditableCard = ({
-
- {titleIcon} -

{title}

- +
+
+ {titleIcon} +

{title}

+ +
{state && (
diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js index db50b8ae..3ef803d2 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js @@ -16,20 +16,26 @@ export default { color: spring4 }, editButton: { - marginTop: 30, + marginTop: 20, display: 'flex', justifyContent: 'right' }, deleteButton: { marginRight: 8 }, + headerWrapper: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + height: 40 + }, editingWrapper: { display: 'flex', flexDirection: 'row', - justifyContent: 'space-between' + justifyContent: 'space-between', + marginTop: 20 }, replace: { - marginTop: 30, marginRight: 5 }, input: { @@ -39,7 +45,6 @@ export default { marginRight: 5 }, editingButtons: { - marginTop: 30, display: 'flex', justifyContent: 'right' }, From e3b13a228bdb270a089970d2dd440288f46d7863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Wed, 17 Nov 2021 14:22:46 +0000 Subject: [PATCH 13/18] fix: button new comet3 color, replace lodash with ramda --- .../components/buttons/ActionButton.styles.js | 5 +- .../src/pages/Customers/CustomerData.js | 127 +++++++++--------- .../Customers/components/EditableCard.js | 9 +- new-lamassu-admin/src/styling/variables.js | 3 + 4 files changed, 74 insertions(+), 70 deletions(-) diff --git a/new-lamassu-admin/src/components/buttons/ActionButton.styles.js b/new-lamassu-admin/src/components/buttons/ActionButton.styles.js index 32582425..f924347a 100644 --- a/new-lamassu-admin/src/components/buttons/ActionButton.styles.js +++ b/new-lamassu-admin/src/components/buttons/ActionButton.styles.js @@ -1,11 +1,11 @@ import typographyStyles from 'src/components/typography/styles' import { white, - fontColor, subheaderColor, subheaderDarkColor, offColor, offDarkColor, + offDarkerColor, secondaryColor, secondaryColorDark, secondaryColorDarker, @@ -56,10 +56,9 @@ export default { } }, secondary: { - extend: colors(offColor, offDarkColor, white), + extend: colors(offColor, offDarkColor, offDarkerColor), color: white, '&:active': { - color: fontColor, '& $actionButtonIcon': { display: 'flex' }, diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index 5b7ddf41..482aff9f 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -26,6 +26,7 @@ import { URI } from 'src/utils/apollo' import styles from './CustomerData.styles.js' import { EditableCard } from './components' +import { getName } from './helper.js' const useStyles = makeStyles(styles) @@ -59,7 +60,6 @@ const Photo = ({ show, src }) => { const CustomerData = ({ customer, updateCustomer, - replacePhoto, editCustomer, deleteEditedData }) => { @@ -68,6 +68,7 @@ const CustomerData = ({ const idData = R.path(['idCardData'])(customer) const rawExpirationDate = R.path(['expirationDate'])(idData) + const country = R.path(['country'])(idData) const rawDob = R.path(['dateOfBirth'])(idData) const sanctions = R.path(['sanctions'])(customer) @@ -83,43 +84,54 @@ const CustomerData = ({ const isEven = elem => elem % 2 === 0 - const getVisibleCards = _.filter(elem => elem.isAvailable) + const getVisibleCards = _.filter( + elem => + !_.isEmpty(elem.fields) || + (!_.isNil(elem.children) && !_.isNil(elem.state)) + ) + + const getAvailableFields = _.filter(({ value }) => value !== '') const schemas = { idScan: Yup.object().shape({ - firstName: Yup.string().required(), - lastName: Yup.string().required(), - documentNumber: Yup.string().required(), - dateOfBirth: Yup.string().required(), - gender: Yup.string().required(), - country: Yup.string().required(), - expirationDate: Yup.string().required() + name: Yup.string(), + idNumber: Yup.string(), + birthDate: Yup.string(), + age: Yup.string(), + gender: Yup.string(), + state: Yup.string(), + expirationDate: Yup.string() }), usSsn: Yup.object().shape({ - usSsn: Yup.string().required() + usSsn: Yup.string() }), idCardPhoto: Yup.object().shape({ - idCardPhoto: Yup.mixed().required() + idCardPhoto: Yup.mixed() }), frontCamera: Yup.object().shape({ - frontCamera: Yup.mixed().required() + frontCamera: Yup.mixed() }) } const idScanElements = [ { - name: 'firstName', - label: 'First name', + name: 'name', + label: 'Name', component: TextInput }, { - name: 'documentNumber', + name: 'idNumber', label: 'ID number', component: TextInput }, { - name: 'dateOfBirth', - label: 'Birthdate', + name: 'birthDate', + label: 'Birth Date', + component: TextInput + }, + { + name: 'age', + label: 'Age', component: TextInput }, { @@ -128,19 +140,14 @@ const CustomerData = ({ component: TextInput }, { - name: 'lastName', - label: 'Last name', + name: 'state', + label: country === 'Canada' ? 'Province' : 'State', component: TextInput }, { name: 'expirationDate', label: 'Expiration Date', component: TextInput - }, - { - name: 'country', - label: 'Country', - component: TextInput } ] @@ -158,14 +165,27 @@ const CustomerData = ({ const initialValues = { idScan: { - firstName: R.path(['firstName'])(idData) ?? '', - lastName: R.path(['lastName'])(idData) ?? '', - documentNumber: R.path(['documentNumber'])(idData) ?? '', - dateOfBirth: (rawDob && format('yyyy-MM-dd', rawDob)) ?? '', + name: getName(customer) ?? '', + idNumber: R.path(['documentNumber'])(idData) ?? '', + birthDate: + (rawDob && + format('yyyy-MM-dd')(parse(new Date(), 'yyyyMMdd', rawDob))) ?? + '', + age: + (rawDob && + differenceInYears( + parse(new Date(), 'yyyyMMdd', rawDob), + new Date() + )) ?? + '', gender: R.path(['gender'])(idData) ?? '', - country: R.path(['country'])(idData) ?? '', + state: R.path(['state'])(idData) ?? '', expirationDate: - (rawExpirationDate && format('yyyy-MM-dd', rawExpirationDate)) ?? '' + (rawExpirationDate && + format('yyyy-MM-dd')( + parse(new Date(), 'yyyyMMdd', rawExpirationDate) + )) ?? + '' }, usSsn: { usSsn: customer.usSsn ?? '' @@ -180,34 +200,31 @@ const CustomerData = ({ const cards = [ { - fields: idScanElements, + fields: getAvailableFields(idScanElements), title: 'ID Scan', - titleIcon: , + titleIcon: , state: R.path(['idCardDataOverride'])(customer), authorize: () => updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED }), deleteEditedData: () => deleteEditedData({ idCardData: null }), - save: values => editCustomer({ idCardData: _.merge(idData, values) }), + save: values => editCustomer({ idCardData: values }), validationSchema: schemas.idScan, - initialValues: initialValues.idScan, - isAvailable: !_.isNil(idData) + initialValues: initialValues.idScan }, { title: 'SMS Confirmation', - titleIcon: , + titleIcon: , authorize: () => {}, reject: () => {}, - save: () => {}, - isAvailable: false + save: () => {} }, { title: 'Name', titleIcon: , authorize: () => {}, reject: () => {}, - save: () => {}, - isAvailable: false + save: () => {} }, { title: 'Sanctions check', @@ -216,22 +233,17 @@ const CustomerData = ({ authorize: () => updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }), - children: {sanctionsDisplay}, - isAvailable: !_.isNil(sanctions) + children: {sanctionsDisplay} }, { - fields: frontCameraElements, + fields: getAvailableFields(frontCameraElements), title: 'Front facing camera', titleIcon: , state: R.path(['frontCameraOverride'])(customer), authorize: () => updateCustomer({ frontCameraOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ frontCameraOverride: OVERRIDE_REJECTED }), - save: values => - replacePhoto({ - newPhoto: values.frontCamera, - photoType: 'frontCamera' - }), + save: values => editCustomer({ frontCamera: values.frontCamera }), deleteEditedData: () => deleteEditedData({ frontCamera: null }), children: customer.frontCameraPath ? ( , state: R.path(['idCardPhotoOverride'])(customer), authorize: () => updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ idCardPhotoOverride: OVERRIDE_REJECTED }), - save: values => - replacePhoto({ - newPhoto: values.idCardPhoto, - photoType: 'idCardPhoto' - }), + save: values => editCustomer({ idCardPhoto: values.idCardPhoto }), deleteEditedData: () => deleteEditedData({ idCardPhoto: null }), children: customer.idCardPhotoPath ? ( , state: R.path(['usSsnOverride'])(customer), @@ -281,8 +287,7 @@ const CustomerData = ({ save: values => editCustomer({ usSsn: values.usSsn }), deleteEditedData: () => deleteEditedData({ usSsn: null }), validationSchema: schemas.usSsn, - initialValues: initialValues.usSsn, - isAvailable: !_.isNil(customer.usSsn) + initialValues: initialValues.usSsn } ] diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index 3587e5c4..0239db8a 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -23,11 +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 CancelIcon } from 'src/styling/icons/button/cancel/zodiac.svg' import { ReactComponent as ReplaceReversedIcon } from 'src/styling/icons/button/replace/white.svg' -import { ReactComponent as ReplaceIcon } from 'src/styling/icons/button/replace/zodiac.svg' import { ReactComponent as SaveReversedIcon } from 'src/styling/icons/circle buttons/save/white.svg' -import { ReactComponent as SaveIcon } from 'src/styling/icons/circle buttons/save/zodiac.svg' import { comet } from 'src/styling/variables' import styles from './EditableCard.styles.js' @@ -237,7 +234,7 @@ const EditableCard = ({ triggerInput()}> { @@ -266,7 +263,7 @@ const EditableCard = ({
Save @@ -276,7 +273,7 @@ const EditableCard = ({
Cancel diff --git a/new-lamassu-admin/src/styling/variables.js b/new-lamassu-admin/src/styling/variables.js index 43ea834b..2cb84f7f 100644 --- a/new-lamassu-admin/src/styling/variables.js +++ b/new-lamassu-admin/src/styling/variables.js @@ -7,6 +7,7 @@ const spring = '#48f694' // Secondary const comet = '#5f668a' const comet2 = '#72799d' +const comet3 = '#525772' const spring2 = '#44e188' const spring3 = '#ecfbef' const spring4 = '#3fd07e' @@ -47,6 +48,7 @@ const disabledColor2 = concrete const fontColor = primaryColor const offColor = comet const offDarkColor = comet2 +const offDarkerColor = comet3 const placeholderColor = comet const errorColor = tomato const errorColorDark = tomato1 @@ -146,6 +148,7 @@ export { placeholderColor, offColor, offDarkColor, + offDarkerColor, fontColor, disabledColor, disabledColor2, From d6ce5370fd6903f650245bde84d61abb94ea221c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Thu, 18 Nov 2021 16:25:59 +0000 Subject: [PATCH 14/18] fix: apollo link arguments order --- new-lamassu-admin/src/utils/apollo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/new-lamassu-admin/src/utils/apollo.js b/new-lamassu-admin/src/utils/apollo.js index 9e3278b2..1921cdcf 100644 --- a/new-lamassu-admin/src/utils/apollo.js +++ b/new-lamassu-admin/src/utils/apollo.js @@ -58,8 +58,8 @@ const getClient = (history, location, getUserData, setUserData, setRole) => }), ApolloLink.split( operation => operation.getContext().clientName === 'pazuz', - uploadLink, - uploadLinkALT + uploadLinkALT, + uploadLink ) ]), cache: new InMemoryCache(), From 77858b55bcd68abdb969255a27a6fd3a3a768a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Thu, 18 Nov 2021 18:47:36 +0000 Subject: [PATCH 15/18] fix: remove useless prop on feature button and add refactor reminder --- lib/new-admin/graphql/resolvers/customer.resolver.js | 3 +++ new-lamassu-admin/src/components/buttons/FeatureButton.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index 9ebf636f..059cbe4f 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -14,6 +14,7 @@ const resolvers = { }, Mutation: { setCustomer: (root, { customerId, customerInput }, context, info) => { + // To be replaced by function that fetchs the token const token = !!context.req.cookies.lamassu_sid && context.req.session.user.id if (customerId === anonymous.uuid) return customers.getCustomerById(customerId) return customers.updateCustomer(customerId, customerInput, token) @@ -22,11 +23,13 @@ const resolvers = { saveCustomField: (...[, { customerId, fieldId, newValue }]) => customers.saveCustomField(customerId, fieldId, newValue), removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId), editCustomer: async (root, { customerId, customerEdit }, context) => { + // To be replaced by function that fetchs the token const token = !!context.req.cookies.lid && context.req.session.user.id const editedData = await customerEdit return customers.edit(customerId, editedData, token) }, replacePhoto: async (root, { customerId, photoType, newPhoto }, context) => { + // To be replaced by function that fetchs the token const token = !!context.req.cookies.lid && context.req.session.user.id const photo = await newPhoto if (!photo) return customers.getCustomerById(customerId) diff --git a/new-lamassu-admin/src/components/buttons/FeatureButton.js b/new-lamassu-admin/src/components/buttons/FeatureButton.js index 86a06c5e..3fe97cd7 100644 --- a/new-lamassu-admin/src/components/buttons/FeatureButton.js +++ b/new-lamassu-admin/src/components/buttons/FeatureButton.js @@ -32,8 +32,8 @@ const styles = { const useStyles = makeStyles(styles) const FeatureButton = memo( - ({ className, Icon, InverseIcon, children, active, ...props }) => { - const classes = useStyles({ active }) + ({ className, Icon, InverseIcon, children, ...props }) => { + const classes = useStyles() const classNames = { [classes.featureButton]: true, From d2bb684b00aa785cecc5d99c0f5dfca336350d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Thu, 25 Nov 2021 16:38:38 +0000 Subject: [PATCH 16/18] fix: ambigous total spent --- lib/customers.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/customers.js b/lib/customers.js index 01cf7807..5ce821f7 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -637,9 +637,8 @@ function getCustomersList (phone = null, name = null, address = null, id = null) c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions, c.sanctions_at, c.sanctions_override, t.tx_class, t.fiat, t.fiat_code, t.created, row_number() OVER (partition by c.id order by t.created desc) AS rn, - coalesce(sum(case when error_code is null or error_code not in ($1^) then t.fiat else 0 end) over (partition by c.id), 0) as total_spent, sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (partition by c.id) AS total_txs, - ccf.custom_fields + coalesce(sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (partition by c.id), 0) AS total_spent, ccf.custom_fields FROM customers c LEFT OUTER JOIN ( SELECT 'cashIn' AS tx_class, id, fiat, fiat_code, created, customer_id, error_code FROM cash_in_txs WHERE send_confirmed = true UNION From b995b549b4b7231235fec5422a98c90d134adeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Thu, 25 Nov 2021 17:37:04 +0000 Subject: [PATCH 17/18] fix: img size and retrieve information button alignment --- new-lamassu-admin/src/pages/Customers/CustomerData.js | 2 +- new-lamassu-admin/src/pages/Customers/CustomerProfile.js | 1 + .../src/pages/Customers/CustomerProfile.styles.js | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index 482aff9f..b9eb837a 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -31,7 +31,7 @@ import { getName } from './helper.js' const useStyles = makeStyles(styles) const IMAGE_WIDTH = 165 -const IMAGE_HEIGHT = 45 +const IMAGE_HEIGHT = 32 const POPUP_IMAGE_WIDTH = 360 const POPUP_IMAGE_HEIGHT = 240 diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index 41dc2dea..6b083eb5 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -324,6 +324,7 @@ const CustomerProfile = memo(() => { Date: Fri, 26 Nov 2021 16:10:25 +0000 Subject: [PATCH 18/18] refactor: improve TODOs readability --- lib/customers.js | 2 +- lib/new-admin/graphql/resolvers/customer.resolver.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/customers.js b/lib/customers.js index 5ce821f7..7586267d 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -231,7 +231,7 @@ function enhanceEditedPhotos (fields) { */ function deleteEditedData (id, data) { - // NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION + // TODO: NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION const defaults = [ 'front_camera', 'id_card_data', diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index 059cbe4f..36a24b0f 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -14,7 +14,7 @@ const resolvers = { }, Mutation: { setCustomer: (root, { customerId, customerInput }, context, info) => { - // To be replaced by function that fetchs the token + // TODO: To be replaced by function that fetchs the token const token = !!context.req.cookies.lamassu_sid && context.req.session.user.id if (customerId === anonymous.uuid) return customers.getCustomerById(customerId) return customers.updateCustomer(customerId, customerInput, token) @@ -23,13 +23,13 @@ const resolvers = { saveCustomField: (...[, { customerId, fieldId, newValue }]) => customers.saveCustomField(customerId, fieldId, newValue), removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId), editCustomer: async (root, { customerId, customerEdit }, context) => { - // To be replaced by function that fetchs the token + // TODO: To be replaced by function that fetchs the token const token = !!context.req.cookies.lid && context.req.session.user.id const editedData = await customerEdit return customers.edit(customerId, editedData, token) }, replacePhoto: async (root, { customerId, photoType, newPhoto }, context) => { - // To be replaced by function that fetchs the token + // TODO: To be replaced by function that fetchs the token const token = !!context.req.cookies.lid && context.req.session.user.id const photo = await newPhoto if (!photo) return customers.getCustomerById(customerId) @@ -37,7 +37,7 @@ const resolvers = { .then(newPatch => customers.edit(customerId, newPatch, token)) }, deleteEditedData: (root, { customerId, customerEdit }) => { - // NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION + // TODO: NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION return customers.getCustomerById(customerId) } }