diff --git a/lib/customers.js b/lib/customers.js index 7ff7fd94..48bcb7d0 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -99,7 +99,15 @@ function update (id, data, userToken, txId) { */ async function updateCustomer (id, data) { const formattedData = _.pick( - ['authorized_override', 'id_card_photo_override', 'id_card_data_override', 'sms_override', 'us_ssn_override'], + [ + 'authorized_override', + 'id_card_photo_override', + 'id_card_data_override', + 'sms_override', + 'us_ssn_override', + 'sanctions_override', + 'front_camera_override' + ], _.mapKeys(_.snakeCase, data)) const sql = Pgp.helpers.update(formattedData, _.keys(formattedData), 'customers') + @@ -429,14 +437,16 @@ function batch () { * @returns {array} Array of customers with it's transactions aggregations */ function getCustomersList () { - const sql = `select id, name, authorized_override, front_camera_path, phone, sms_override, - id_card_data, id_card_data_override, id_card_data_expiration, id_card_photo_path, - id_card_photo_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 + const sql = `select id, name, authorized_override, front_camera_path, 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, + 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 from ( - select c.id, c.name, c.authorized_override, c.front_camera_path, 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, t.tx_class, t.fiat, t.fiat_code, t.created, + select c.id, c.name, c.authorized_override, c.front_camera_path, 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.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, sum(case when t.id is not null then 1 else 0 end) over (partition by c.id) as total_txs, coalesce(sum(t.fiat) over (partition by c.id), 0) as total_spent @@ -463,14 +473,16 @@ function getCustomersList () { * @returns {array} Array of customers with it's transactions aggregations */ function getCustomerById (id) { - const sql = `select id, name, authorized_override, front_camera_path, phone, sms_override, - id_card_data, id_card_data_override, id_card_data_expiration, id_card_photo_path, - id_card_photo_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 + const sql = `select id, name, authorized_override, front_camera_path, 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, + 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 from ( - select c.id, c.name, c.authorized_override, c.front_camera_path, 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, t.tx_class, t.fiat, t.fiat_code, t.created, + select c.id, c.name, c.authorized_override, c.front_camera_path, 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.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, sum(case when t.id is not null then 1 else 0 end) over (partition by c.id) as total_txs, sum(t.fiat) over (partition by c.id) as total_spent diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index 6b18bf46..fab437be 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -68,6 +68,7 @@ const typeDefs = gql` name: String authorizedOverride: String frontCameraPath: String + frontCameraOverride: String phone: String smsOverride: String idCardData: JSONObject @@ -75,6 +76,11 @@ const typeDefs = gql` idCardDataExpiration: Date idCardPhotoPath: String idCardPhotoOverride: String + usSsn: String + usSsnOverride: String + sanctions: Boolean + sanctionsAt: Date + sanctionsOverride: String totalTxs: Int totalSpent: String lastActive: Date @@ -88,6 +94,7 @@ const typeDefs = gql` name: String authorizedOverride: String frontCameraPath: String + frontCameraOverride: String phone: String smsOverride: String idCardData: JSONObject @@ -95,6 +102,11 @@ const typeDefs = gql` idCardDataExpiration: Date idCardPhotoPath: String idCardPhotoOverride: String + usSsn: String + usSsnOverride: String + sanctions: Boolean + sanctionsAt: Date + sanctionsOverride: String totalTxs: Int totalSpent: String lastActive: Date diff --git a/new-lamassu-admin/src/components/ImagePopper.js b/new-lamassu-admin/src/components/ImagePopper.js new file mode 100644 index 00000000..74d4fc6f --- /dev/null +++ b/new-lamassu-admin/src/components/ImagePopper.js @@ -0,0 +1,52 @@ +import { makeStyles, ClickAwayListener } from '@material-ui/core' +import classnames from 'classnames' +import React, { memo, useState } from 'react' + +import Popper from 'src/components/Popper' +import { FeatureButton } from 'src/components/buttons' +import { ReactComponent as ZoomIconInverse } from 'src/styling/icons/circle buttons/search/white.svg' +import { ReactComponent as ZoomIcon } from 'src/styling/icons/circle buttons/search/zodiac.svg' + +import imagePopperStyles from './ImagePopper.styles' + +const useStyles = makeStyles(imagePopperStyles) + +const ImagePopper = memo(({ className, width, height, src }) => { + const classes = useStyles({ width, height }) + const [popperAnchorEl, setPopperAnchorEl] = useState(null) + + const handleOpenPopper = event => { + setPopperAnchorEl(popperAnchorEl ? null : event.currentTarget) + } + + const handleClosePopper = () => { + setPopperAnchorEl(null) + } + + const popperOpen = Boolean(popperAnchorEl) + + const Image = ({ className }) => ( + + ) + + return ( + +
+ + + +
+ +
+
+
+
+ ) +}) + +export default ImagePopper diff --git a/new-lamassu-admin/src/components/ImagePopper.styles.js b/new-lamassu-admin/src/components/ImagePopper.styles.js new file mode 100644 index 00000000..38e6cd36 --- /dev/null +++ b/new-lamassu-admin/src/components/ImagePopper.styles.js @@ -0,0 +1,20 @@ +export default { + row: { + display: 'flex', + flexDirection: 'row' + }, + unzoomedImg: ({ width, height }) => ({ + objectFit: 'cover', + borderRadius: '8px 0px 0px 8px', + width, + height + }), + button: ({ height }) => ({ + borderRadius: '0px 8px 8px 0px', + height + }), + popoverContent: { + display: 'block', + padding: [[10, 15]] + } +} diff --git a/new-lamassu-admin/src/components/Status.js b/new-lamassu-admin/src/components/Status.js index 8989632d..f1d8ff59 100644 --- a/new-lamassu-admin/src/components/Status.js +++ b/new-lamassu-admin/src/components/Status.js @@ -9,6 +9,8 @@ import { secondaryColorDarker as spring4, inputFontWeight, spring3, + zircon, + primaryColor, smallestFontSize, inputFontFamily, spacer, @@ -18,13 +20,15 @@ import { const colors = { error: tomato, warning: pumpkin, - success: spring4 + success: spring4, + neutral: primaryColor } const backgroundColors = { error: mistyRose, warning: linen, - success: spring3 + success: spring3, + neutral: zircon } const useStyles = makeStyles({ diff --git a/new-lamassu-admin/src/components/buttons/SubpageButton.js b/new-lamassu-admin/src/components/buttons/SubpageButton.js new file mode 100644 index 00000000..c61cd847 --- /dev/null +++ b/new-lamassu-admin/src/components/buttons/SubpageButton.js @@ -0,0 +1,59 @@ +import { makeStyles } from '@material-ui/core/styles' +import classnames from 'classnames' +import React, { memo, useState } from 'react' + +import { ReactComponent as CancelIconInverse } from 'src/styling/icons/button/cancel/white.svg' + +import subpageButtonStyles from './SubpageButton.styles' + +const useStyles = makeStyles(subpageButtonStyles) + +const SubpageButton = memo( + ({ className, Icon, InverseIcon, toggle, children }) => { + const [active, setActive] = useState(false) + + const classes = useStyles() + + const classNames = { + [classes.button]: true, + [classes.normalButton]: !active, + [classes.activeButton]: active + } + + const normalButton = + + const activeButton = ( + <> + + {children} + + + ) + + const innerToggle = () => { + const newActiveState = !active + toggle(newActiveState) + setActive(newActiveState) + } + + return ( + + ) + } +) + +export default SubpageButton diff --git a/new-lamassu-admin/src/components/buttons/SubpageButton.styles.js b/new-lamassu-admin/src/components/buttons/SubpageButton.styles.js new file mode 100644 index 00000000..6217f03e --- /dev/null +++ b/new-lamassu-admin/src/components/buttons/SubpageButton.styles.js @@ -0,0 +1,44 @@ +import baseButtonStyles from 'src/components/buttons/BaseButton.styles' +import { offColor, white } from 'src/styling/variables' + +const { baseButton } = baseButtonStyles + +export default { + button: { + extend: baseButton, + padding: 0, + color: white, + borderRadius: baseButton.height / 2 + }, + normalButton: { + width: baseButton.height + }, + activeButton: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + backgroundColor: offColor, + fontWeight: 'bold', + padding: '0 5px', + '&:hover': { + backgroundColor: offColor + } + }, + buttonIcon: { + '& svg': { + width: 16, + height: 16, + overflow: 'visible', + '& g': { + strokeWidth: 1.8 + } + } + }, + buttonIconActiveLeft: { + marginRight: 12 + }, + buttonIconActiveRight: { + marginRight: 5, + marginLeft: 20 + } +} diff --git a/new-lamassu-admin/src/components/buttons/index.js b/new-lamassu-admin/src/components/buttons/index.js index 5839f7fe..fbcf7ba3 100644 --- a/new-lamassu-admin/src/components/buttons/index.js +++ b/new-lamassu-admin/src/components/buttons/index.js @@ -7,6 +7,7 @@ import IconButton from './IconButton' import Link from './Link' import SimpleButton from './SimpleButton' import SupportLinkButton from './SupportLinkButton' +import SubpageButton from './SubpageButton' export { Button, @@ -18,4 +19,5 @@ export { IDButton, AddButton, SupportLinkButton + SubpageButton } diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index 9572e0bc..c254e660 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -3,7 +3,7 @@ import { makeStyles, Breadcrumbs, Box } from '@material-ui/core' import NavigateNextIcon from '@material-ui/icons/NavigateNext' import gql from 'graphql-tag' import * as R from 'ramda' -import React, { memo } from 'react' +import React, { memo, useState } from 'react' import { useHistory, useParams } from 'react-router-dom' import { ActionButton } from 'src/components/buttons' @@ -16,25 +16,26 @@ import { ReactComponent as AuthorizeReversedIcon } from 'src/styling/icons/butto import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg' import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/block/white.svg' import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/zodiac.svg' +import { fromNamespace, namespaces } from 'src/utils/config' import styles from './CustomerProfile.styles' import { CustomerDetails, - IdDataCard, - PhoneCard, - IdCardPhotoCard, - TransactionsList + TransactionsList, + ComplianceDetails } from './components' const useStyles = makeStyles(styles) const GET_CUSTOMER = gql` query customer($customerId: ID!) { + config customer(customerId: $customerId) { id name authorizedOverride frontCameraPath + frontCameraOverride phone smsOverride idCardData @@ -42,6 +43,11 @@ const GET_CUSTOMER = gql` idCardDataExpiration idCardPhotoPath idCardPhotoOverride + usSsn + usSsnOverride + sanctions + sanctionsAt + sanctionsOverride totalTxs totalSpent lastActive @@ -70,6 +76,7 @@ const SET_CUSTOMER = gql` name authorizedOverride frontCameraPath + frontCameraOverride phone smsOverride idCardData @@ -77,6 +84,11 @@ const SET_CUSTOMER = gql` idCardDataExpiration idCardPhotoPath idCardPhotoOverride + usSsn + usSsnOverride + sanctions + sanctionsAt + sanctionsOverride totalTxs totalSpent lastActive @@ -90,6 +102,7 @@ const SET_CUSTOMER = gql` const CustomerProfile = memo(() => { const classes = useStyles() const history = useHistory() + const [showCompliance, setShowCompliance] = useState(false) const { id: customerId } = useParams() const { data: customerResponse, refetch: getCustomer, loading } = useQuery( @@ -111,6 +124,8 @@ const CustomerProfile = memo(() => { } }) + const configData = R.path(['config'])(customerResponse) ?? [] + const locale = configData && fromNamespace(namespaces.LOCALE, configData) const customerData = R.path(['customer'])(customerResponse) ?? [] const rawTransactions = R.path(['transactions'])(customerData) ?? [] const sortedTransactions = R.sort(R.descend(R.prop('cryptoAtoms')))( @@ -137,8 +152,15 @@ const CustomerProfile = memo(() => {
- - + + setShowCompliance(!showCompliance)} + />
Actions {
- - - - -
- + {!showCompliance && ( + + )} + {showCompliance && ( + + )} ) }) diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js index 4bc96d91..c6522f58 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js @@ -11,5 +11,8 @@ export default { actionLabel: { color: comet, margin: [[4, 0]] + }, + customerDetails: { + marginBottom: 18 } } diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js index d24e59d2..4c72f7c5 100644 --- a/new-lamassu-admin/src/pages/Customers/Customers.js +++ b/new-lamassu-admin/src/pages/Customers/Customers.js @@ -4,10 +4,13 @@ import * as R from 'ramda' import React from 'react' import { useHistory } from 'react-router-dom' +import { fromNamespace, namespaces } from 'src/utils/config' + import CustomersList from './CustomersList' const GET_CUSTOMERS = gql` { + config customers { id name @@ -18,6 +21,7 @@ const GET_CUSTOMERS = gql` lastTxFiat lastTxFiatCode lastTxClass + authorizedOverride } } ` @@ -29,6 +33,8 @@ const Customers = () => { const handleCustomerClicked = customer => history.push(`/compliance/customer/${customer.id}`) + const configData = R.path(['config'])(customersResponse) ?? [] + const locale = configData && fromNamespace(namespaces.LOCALE, configData) const customersData = R.sortWith([R.descend(R.prop('lastActive'))])( R.path(['customers'])(customersResponse) ?? [] ) @@ -36,6 +42,7 @@ const Customers = () => { return ( diff --git a/new-lamassu-admin/src/pages/Customers/CustomersList.js b/new-lamassu-admin/src/pages/Customers/CustomersList.js index cb5956d3..acebff9d 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomersList.js +++ b/new-lamassu-admin/src/pages/Customers/CustomersList.js @@ -4,6 +4,7 @@ import moment from 'moment' import * as R from 'ramda' import React from 'react' +import { MainStatus } from 'src/components/Status' import TitleSection from 'src/components/layout/TitleSection' import DataTable from 'src/components/tables/DataTable' import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' @@ -14,42 +15,58 @@ import styles from './CustomersList.styles' const useStyles = makeStyles(styles) -const CustomersList = ({ data, onClick, loading }) => { +const CUSTOMER_VERIFIED = 'verified' +const CUSTOMER_BLOCKED = 'blocked' + +const CustomersList = ({ data, locale, onClick, loading }) => { const classes = useStyles() + const getAuthorizedStatus = authorizedOverride => + authorizedOverride === CUSTOMER_VERIFIED + ? { label: 'Authorized', type: 'success' } + : authorizedOverride === CUSTOMER_BLOCKED + ? { label: 'Blocked', type: 'error' } + : { label: 'Suspended', type: 'warning' } + const elements = [ - { - header: 'Phone', - width: 186, - view: it => parsePhoneNumberFromString(it.phone).formatInternational() - }, { header: 'Name', - width: 277, + width: 241, view: R.path(['name']) }, + { + header: 'Phone', + width: 172, + view: it => + it.phone && locale.country + ? parsePhoneNumberFromString( + it.phone, + locale.country + ).formatInternational() + : '' + }, { header: 'Total TXs', - width: 154, + width: 126, textAlign: 'right', view: it => `${Number.parseInt(it.totalTxs)}` }, { header: 'Total spent', - width: 188, + width: 152, textAlign: 'right', view: it => `${Number.parseFloat(it.totalSpent)} ${it.lastTxFiatCode ?? ''}` }, { header: 'Last active', - width: 197, + width: 133, view: it => ifNotNull(it.lastActive, moment.utc(it.lastActive).format('YYYY-MM-D')) }, { header: 'Last transaction', - width: 198, + width: 161, textAlign: 'right', view: it => { const hasLastTx = !R.isNil(it.lastTxFiatCode) @@ -63,6 +80,13 @@ const CustomersList = ({ data, onClick, loading }) => { ) } + }, + { + header: 'Status', + width: 188, + view: it => ( + + ) } ] diff --git a/new-lamassu-admin/src/pages/Customers/CustomersList.styles.js b/new-lamassu-admin/src/pages/Customers/CustomersList.styles.js index b4b8aed4..c05c6541 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomersList.styles.js +++ b/new-lamassu-admin/src/pages/Customers/CustomersList.styles.js @@ -1,6 +1,6 @@ import typographyStyles from 'src/components/typography/styles' import baseStyles from 'src/pages/Logs.styles' -import { zircon, primaryColor, fontSize4 } from 'src/styling/variables' +import { zircon, comet, primaryColor, fontSize4 } from 'src/styling/variables' const { label1 } = typographyStyles const { titleWrapper, titleAndButtonsContainer } = baseStyles @@ -80,5 +80,26 @@ export default { }, customerName: { marginBottom: 32 + }, + icon: { + marginRight: 11 + }, + name: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center' + }, + value: { + height: 16 + }, + label: { + marginBottom: 4, + color: comet + }, + idIcon: { + marginRight: 10 + }, + subpageButton: { + marginLeft: 16 } } diff --git a/new-lamassu-admin/src/pages/Customers/components/ComplianceDetails.js b/new-lamassu-admin/src/pages/Customers/components/ComplianceDetails.js new file mode 100644 index 00000000..7137876f --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/ComplianceDetails.js @@ -0,0 +1,141 @@ +import { Box } from '@material-ui/core' +import { makeStyles } from '@material-ui/core/styles' +import { parsePhoneNumberFromString } from 'libphonenumber-js' +import * as R from 'ramda' +import React from 'react' + +import ImagePopper from 'src/components/ImagePopper' +import { H3, Info3 } from 'src/components/typography' +import { + PropertyCard, + OVERRIDE_AUTHORIZED, + OVERRIDE_REJECTED +} from 'src/pages/Customers/components/propertyCard' +import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg' +import { URI } from 'src/utils/apollo' + +import { complianceDetailsStyles } from './ComplianceDetails.styles' +import Field from './Field' + +import { IdDataCard } from './' + +const useStyles = makeStyles(complianceDetailsStyles) + +const imageWidth = 165 +const imageHeight = 45 + +const Photo = ({ show, src }) => { + const classes = useStyles({ width: imageWidth }) + + return ( + <> + {show ? ( + + ) : ( +
+ +
+ )} + + ) +} + +const ComplianceDetails = ({ customer, locale, updateCustomer }) => { + const classes = useStyles({ width: imageWidth }) + + const phone = + customer.phone && locale.country + ? parsePhoneNumberFromString( + customer.phone, + locale.country + ).formatInternational() + : '' + + const sanctions = R.path(['sanctions'])(customer) + const sanctionsAt = R.path(['sanctionsAt'])(customer) + const sanctionsDisplay = !sanctionsAt + ? 'Not checked yet' + : sanctions + ? 'Passed' + : 'Failed' + + return ( +
+

Compliance details

+
+ + + + + updateCustomer({ smsOverride: OVERRIDE_AUTHORIZED }) + } + reject={() => updateCustomer({ smsOverride: OVERRIDE_REJECTED })}> + + + + updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED }) + } + reject={() => + updateCustomer({ idCardPhotoOverride: OVERRIDE_REJECTED }) + }> + + + + updateCustomer({ frontCameraOverride: OVERRIDE_AUTHORIZED }) + } + reject={() => + updateCustomer({ frontCameraOverride: OVERRIDE_REJECTED }) + }> + + + + + + updateCustomer({ usSsnOverride: OVERRIDE_AUTHORIZED }) + } + reject={() => + updateCustomer({ usSsnOverride: OVERRIDE_REJECTED }) + }> + + + + updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }) + } + reject={() => + updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }) + }> + {sanctionsDisplay} + + + +
+
+ ) +} + +export default ComplianceDetails diff --git a/new-lamassu-admin/src/pages/Customers/components/ComplianceDetails.styles.js b/new-lamassu-admin/src/pages/Customers/components/ComplianceDetails.styles.js new file mode 100644 index 00000000..1bb1d48f --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/ComplianceDetails.styles.js @@ -0,0 +1,25 @@ +const complianceDetailsStyles = { + complianceDetailsGrid: { + display: 'flex', + flexDirection: 'row' + }, + firstColumn: { + display: 'flex', + flexDirection: 'column', + width: '100%', + marginRight: 10 + }, + lastColumn: { + display: 'flex', + flexDirection: 'column', + width: '100%', + marginLeft: 10 + }, + photoWrapper: ({ width }) => ({ + display: 'flex', + justifyContent: 'center', + width + }) +} + +export { complianceDetailsStyles } diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomerDetails.js b/new-lamassu-admin/src/pages/Customers/components/CustomerDetails.js index cbcc95da..1ac68cff 100644 --- a/new-lamassu-admin/src/pages/Customers/components/CustomerDetails.js +++ b/new-lamassu-admin/src/pages/Customers/components/CustomerDetails.js @@ -1,74 +1,44 @@ import { makeStyles, Box } from '@material-ui/core' -import moment from 'moment' +import { parsePhoneNumberFromString } from 'libphonenumber-js' import * as R from 'ramda' import React, { memo } from 'react' +import { SubpageButton } from 'src/components/buttons' import { H2, Label1, P } from 'src/components/typography' -import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' -import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' -import { comet } from 'src/styling/variables' -import { ifNotNull } from 'src/utils/nullCheck' +import { ReactComponent as IdIcon } from 'src/styling/icons/ID/card/zodiac.svg' +import { ReactComponent as LawIconInverse } from 'src/styling/icons/circle buttons/law/white.svg' +import { ReactComponent as LawIcon } from 'src/styling/icons/circle buttons/law/zodiac.svg' + +import mainStyles from '../CustomersList.styles' import FrontCameraPhoto from './FrontCameraPhoto' -const styles = { - icon: { - marginRight: 11 - }, - name: { - marginTop: 6 - }, - value: { - height: 16 - }, - label: { - marginBottom: 4, - color: comet - } -} +const useStyles = makeStyles(mainStyles) -const useStyles = makeStyles(styles) - -const CustomerDetails = memo(({ customer }) => { +const CustomerDetails = memo(({ customer, locale, setShowCompliance }) => { const classes = useStyles() - const LastTxIcon = customer.lastTxClass === 'cashOut' ? TxOutIcon : TxInIcon const elements = [ { - header: 'Transactions', + header: 'Phone number', + size: 172, + value: + customer.phone && locale.country + ? parsePhoneNumberFromString( + customer.phone, + locale.country + ).formatInternational() + : '' + }, + { + header: 'ID number', + size: 172, + value: R.path(['idCardData', 'documentNumber'])(customer) ?? '' + }, + { + header: 'US SSN', size: 127, - value: ifNotNull( - customer.totalTxs, - `${Number.parseInt(customer.totalTxs)}` - ) - }, - { - header: 'Transaction volume', - size: 167, - value: ifNotNull( - customer.totalSpent, - `${Number.parseFloat(customer.totalSpent)} ${customer.lastTxFiatCode}` - ) - }, - { - header: 'Last active', - size: 142, - value: ifNotNull( - customer.lastActive, - moment.utc(customer.lastActive).format('YYYY-MM-D') - ) - }, - { - header: 'Last transaction', - size: 198, - value: ifNotNull( - customer.lastTxFiat, - <> - - {`${Number.parseFloat(customer.lastTxFiat)} - ${customer.lastTxFiatCode}`} - - ) + value: R.path(['usSsn'])(customer) ?? '' } ] @@ -79,9 +49,17 @@ const CustomerDetails = memo(({ customer }) => { />
+

{R.path(['name'])(customer) ?? R.path(['phone'])(customer)}

+ + Compliance details +
{elements.map(({ size, header }, idx) => ( diff --git a/new-lamassu-admin/src/pages/Customers/components/Field.js b/new-lamassu-admin/src/pages/Customers/components/Field.js index a1b904a1..f21a9f0b 100644 --- a/new-lamassu-admin/src/pages/Customers/components/Field.js +++ b/new-lamassu-admin/src/pages/Customers/components/Field.js @@ -7,7 +7,6 @@ import { comet } from 'src/styling/variables' const useStyles = makeStyles({ field: { - width: 144, height: 46 }, label: { @@ -23,11 +22,13 @@ const useStyles = makeStyles({ } }) -const Field = memo(({ label, display, className }) => { +const Field = memo(({ label, display, size, className }) => { const classes = useStyles() return ( -
+
{label} {display}
diff --git a/new-lamassu-admin/src/pages/Customers/components/IdCardPhotoCard.js b/new-lamassu-admin/src/pages/Customers/components/IdCardPhotoCard.js index dfae0408..7b87d71b 100644 --- a/new-lamassu-admin/src/pages/Customers/components/IdCardPhotoCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/IdCardPhotoCard.js @@ -32,8 +32,8 @@ const IdCardPhotoCard = memo(({ customerData, updateCustomer }) => { return ( updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED }) diff --git a/new-lamassu-admin/src/pages/Customers/components/IdDataCard.js b/new-lamassu-admin/src/pages/Customers/components/IdDataCard.js index 2a517ef6..60bd5b91 100644 --- a/new-lamassu-admin/src/pages/Customers/components/IdDataCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/IdDataCard.js @@ -1,4 +1,4 @@ -import { makeStyles, Box } from '@material-ui/core' +import { Box } from '@material-ui/core' import moment from 'moment' import * as R from 'ramda' import React, { memo } from 'react' @@ -12,65 +12,64 @@ import { ifNotNull } from 'src/utils/nullCheck' import Field from './Field' -const useStyles = makeStyles({ - idDataCard: { - width: 550, - height: 240 - }, - column: { - marginBottom: 7 - } -}) - const IdDataCard = memo(({ customerData, updateCustomer }) => { - const classes = useStyles() - const idData = R.path(['idCardData'])(customerData) - - const name = R.path(['firstName'])(idData) ?? '' - const lastName = R.path(['lastName'])(idData) ?? '' - - const gender = R.path(['gender'])(idData) - const idNumber = R.path(['documentNumber'])(idData) - const country = R.path(['country'])(idData) - const rawExpirationDate = R.path(['expirationDate'])(idData) - const expirationDate = ifNotNull( - rawExpirationDate, - moment.utc(rawExpirationDate).format('YYYY-MM-D') - ) - const rawDob = R.path(['dateOfBirth'])(idData) - const age = ifNotNull( - rawDob, - moment.utc().diff(moment.utc(rawDob).format('YYYY-MM-D'), 'years') - ) + + const elements = [ + { + header: 'Name', + display: `${R.path(['firstName'])(idData)} ${R.path(['lastName'])( + idData + )}`, + size: 160 + }, + { + header: 'ID number', + display: R.path(['documentNumber'])(idData), + size: 190 + }, + { + header: 'Age', + display: ifNotNull( + rawDob, + moment.utc().diff(moment.utc(rawDob).format('YYYY-MM-D'), 'years') + ), + size: 70 + }, + { + header: 'Gender', + display: R.path(['gender'])(idData), + size: 100 + }, + { + header: 'Country', + display: R.path(['country'])(idData), + size: 140 + }, + { + header: 'Expiration Date', + display: ifNotNull( + rawExpirationDate, + moment.utc(rawExpirationDate).format('YYYY-MM-D') + ) + } + ] return ( updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED }) } reject={() => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED })}> -
- - - - - - - - - - -
+ + {elements.map(({ header, display, size }, idx) => ( + + ))} +
) }) diff --git a/new-lamassu-admin/src/pages/Customers/components/PhoneCard.js b/new-lamassu-admin/src/pages/Customers/components/PhoneCard.js index c5b15a46..74dcd3f5 100644 --- a/new-lamassu-admin/src/pages/Customers/components/PhoneCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/PhoneCard.js @@ -1,4 +1,3 @@ -import { makeStyles } from '@material-ui/core/styles' import { parsePhoneNumberFromString } from 'libphonenumber-js' import * as R from 'ramda' import React, { memo } from 'react' @@ -11,35 +10,27 @@ import { import Field from './Field' -const useStyles = makeStyles({ - phoneCard: { - width: 300, - height: 240 - } -}) - -const PhoneCard = memo(({ customerData, updateCustomer }) => { - const classes = useStyles() - - return ( +const PhoneCard = memo( + ({ className, customerData, updateCustomer, locale }) => ( updateCustomer({ smsOverride: OVERRIDE_AUTHORIZED })} reject={() => updateCustomer({ smsOverride: OVERRIDE_REJECTED })}> ) -}) +) export default PhoneCard diff --git a/new-lamassu-admin/src/pages/Customers/components/TransactionsList.js b/new-lamassu-admin/src/pages/Customers/components/TransactionsList.js index c7fa3228..69225fad 100644 --- a/new-lamassu-admin/src/pages/Customers/components/TransactionsList.js +++ b/new-lamassu-admin/src/pages/Customers/components/TransactionsList.js @@ -1,25 +1,66 @@ -import { makeStyles } from '@material-ui/core/styles' +import { makeStyles, Box } from '@material-ui/core' import BigNumber from 'bignumber.js' import moment from 'moment' import * as R from 'ramda' import React from 'react' import DataTable from 'src/components/tables/DataTable' -import { H4, Label2 } from 'src/components/typography' +import { H3, H4, Label1, Label2, P } from 'src/components/typography' import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' import { toUnit } from 'src/utils/coin' +import { ifNotNull } from 'src/utils/nullCheck' import CopyToClipboard from '../../Transactions/CopyToClipboard' import mainStyles from '../CustomersList.styles' const useStyles = makeStyles(mainStyles) -const TransactionsList = ({ data, loading }) => { +const TransactionsList = ({ customer, data, loading }) => { const classes = useStyles() + const LastTxIcon = customer.lastTxClass === 'cashOut' ? TxOutIcon : TxInIcon const hasData = !(R.isEmpty(data) || R.isNil(data)) - const elements = [ + const summaryElements = [ + { + header: 'Transactions', + size: 127, + value: ifNotNull( + customer.totalTxs, + `${Number.parseInt(customer.totalTxs)}` + ) + }, + { + header: 'Transaction volume', + size: 167, + value: ifNotNull( + customer.totalSpent, + `${Number.parseFloat(customer.totalSpent)} ${customer.lastTxFiatCode}` + ) + }, + { + header: 'Last active', + size: 142, + value: ifNotNull( + customer.lastActive, + moment.utc(customer.lastActive).format('YYYY-MM-D') + ) + }, + { + header: 'Last transaction', + size: 198, + value: ifNotNull( + customer.lastTxFiat, + <> + + {`${Number.parseFloat(customer.lastTxFiat)} + ${customer.lastTxFiatCode}`} + + ) + } + ] + + const tableElements = [ { header: 'Direction', width: 207, @@ -79,6 +120,31 @@ const TransactionsList = ({ data, loading }) => { return ( <> +

Transactions

+ + + {summaryElements.map(({ size, header }, idx) => ( + + {header} + + ))} + + + {summaryElements.map(({ size, value }, idx) => ( +

+ {value} +

+ ))} +
+

@@ -90,7 +156,7 @@ const TransactionsList = ({ data, loading }) => {

- {hasData && } + {hasData && } ) } diff --git a/new-lamassu-admin/src/pages/Customers/components/index.js b/new-lamassu-admin/src/pages/Customers/components/index.js index 940c031a..9b0c8945 100644 --- a/new-lamassu-admin/src/pages/Customers/components/index.js +++ b/new-lamassu-admin/src/pages/Customers/components/index.js @@ -1,13 +1,6 @@ +import ComplianceDetails from './ComplianceDetails' import CustomerDetails from './CustomerDetails' -import IdCardPhotoCard from './IdCardPhotoCard' import IdDataCard from './IdDataCard' -import PhoneCard from './PhoneCard' import TransactionsList from './TransactionsList' -export { - CustomerDetails, - IdDataCard, - IdCardPhotoCard, - PhoneCard, - TransactionsList -} +export { CustomerDetails, IdDataCard, TransactionsList, ComplianceDetails } diff --git a/new-lamassu-admin/src/pages/Customers/components/propertyCard/PropertyCard.js b/new-lamassu-admin/src/pages/Customers/components/propertyCard/PropertyCard.js index 353d2d72..115462f3 100644 --- a/new-lamassu-admin/src/pages/Customers/components/propertyCard/PropertyCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/propertyCard/PropertyCard.js @@ -3,6 +3,7 @@ import { makeStyles } from '@material-ui/core/styles' import classnames from 'classnames' import React, { memo } from 'react' +import { MainStatus } from 'src/components/Status' import { ActionButton } from 'src/components/buttons' import { H3 } from 'src/components/typography' import { ReactComponent as AuthorizeReversedIcon } from 'src/styling/icons/button/authorize/white.svg' @@ -22,13 +23,6 @@ const PropertyCard = memo( ({ className, title, state, authorize, reject, children }) => { const classes = useStyles() - const propertyCardClassNames = { - [classes.propertyCard]: true, - [classes.propertyCardPending]: state === OVERRIDE_PENDING, - [classes.propertyCardRejected]: state === OVERRIDE_REJECTED, - [classes.propertyCardAccepted]: state === OVERRIDE_AUTHORIZED - } - const label1ClassNames = { [classes.label1]: true, [classes.label1Pending]: state === OVERRIDE_PENDING, @@ -58,29 +52,27 @@ const PropertyCard = memo( ) - const authorizedAsString = + const authorized = state === OVERRIDE_PENDING - ? 'Pending' + ? { label: 'Pending', type: 'neutral' } : state === OVERRIDE_REJECTED - ? 'Rejected' - : 'Accepted' + ? { label: 'Rejected', type: 'error' } + : { label: 'Accepted', type: 'success' } return ( -
-

{title}

+

{title}

+
- {authorizedAsString} +
-
- {children} - -
- {state !== OVERRIDE_AUTHORIZED && AuthorizeButton()} - {state !== OVERRIDE_REJECTED && RejectButton()} +
+ {authorize && state !== OVERRIDE_AUTHORIZED && AuthorizeButton()} + {reject && state !== OVERRIDE_REJECTED && RejectButton()} +
) diff --git a/new-lamassu-admin/src/pages/Customers/components/propertyCard/PropertyCard.styles.js b/new-lamassu-admin/src/pages/Customers/components/propertyCard/PropertyCard.styles.js index 9cd8ca65..ff8b334f 100644 --- a/new-lamassu-admin/src/pages/Customers/components/propertyCard/PropertyCard.styles.js +++ b/new-lamassu-admin/src/pages/Customers/components/propertyCard/PropertyCard.styles.js @@ -1,39 +1,11 @@ -import { - white, - zircon, - mistyRose, - tomato, - spring3, - spring4, - comet, - fontSize5 -} from 'src/styling/variables' +import { white, tomato, spring4, comet } from 'src/styling/variables' const propertyCardStyles = { - propertyCard: { - margin: [[32, 12, 0, 0]], - padding: [[0, 16]], - borderRadius: 8 - }, - propertyCardPending: { - backgroundColor: zircon - }, - propertyCardRejected: { - backgroundColor: mistyRose - }, - propertyCardAccepted: { - backgroundColor: spring3 - }, label1: { - fontFamily: 'MuseoSans', - fontSize: fontSize5, - fontWeight: 500, - fontStretch: 'normal', - fontStyle: 'normal', - lineHeight: 1.33, - letterSpacing: 'normal', - color: comet, - margin: [[4, 0]] + display: 'flex', + marginBottom: 2, + marginTop: 'auto', + width: 85 }, label1Pending: { color: comet @@ -45,19 +17,32 @@ const propertyCardStyles = { color: spring4 }, cardActionButton: { + display: 'flex', height: 28, + marginRight: 'auto', marginLeft: 12 }, - cardProperties: { + propertyCardTopRow: { display: 'flex', + margin: [[0, 10, 5, 0]] + }, + propertyCardBottomRow: { + display: 'flex', + flexDirection: 'row', + height: 45 + }, + propertyCard: { + display: 'flex', + flexDirection: 'column', borderRadius: 8, width: '100%', - height: 'calc(100% - 104px)', + height: 100, padding: [[20]], boxSizing: 'border-box', boxShadow: '0 0 8px 0 rgba(0, 0, 0, 0.04)', border: 'solid 0', - backgroundColor: white + backgroundColor: white, + margin: [[20, 0, 0, 0]] }, rowSpaceBetween: { display: 'flex', @@ -65,11 +50,19 @@ const propertyCardStyles = { alignItems: 'center', justifyContent: 'space-between' }, + columnSpaceBetween: { + display: 'flex', + flexFlow: 'column nowrap', + alignItems: 'center', + justifyContent: 'space-between', + width: 90 + }, buttonsWrapper: { display: 'flex', + flexDirection: 'row', justifyContent: 'flex-end', - marginTop: 16, - marginBottom: 16 + marginLeft: 'auto', + marginTop: 'auto' } } diff --git a/new-lamassu-admin/src/styling/icons/action/close/white.svg b/new-lamassu-admin/src/styling/icons/action/close/white.svg index 54486cf5..c0ef73c9 100644 --- a/new-lamassu-admin/src/styling/icons/action/close/white.svg +++ b/new-lamassu-admin/src/styling/icons/action/close/white.svg @@ -3,12 +3,12 @@ Created with Sketch. - + - - + + - + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/circle buttons/law/white.svg b/new-lamassu-admin/src/styling/icons/circle buttons/law/white.svg new file mode 100644 index 00000000..3de17537 --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/circle buttons/law/white.svg @@ -0,0 +1,12 @@ + + + icon/sf-small/law/white + + + + + + + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/circle buttons/law/zodiac.svg b/new-lamassu-admin/src/styling/icons/circle buttons/law/zodiac.svg new file mode 100644 index 00000000..83f80f3a --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/circle buttons/law/zodiac.svg @@ -0,0 +1,12 @@ + + + icon/sf-small/law/zodiac + + + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ac6705c7..e65ab217 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4985,7 +4985,7 @@ "got": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "integrity": "sha1-BUUP2ECU5rvqVvRRpDqcKJFmOFo=", "requires": { "decompress-response": "^3.2.0", "duplexer3": "^0.1.4", @@ -5801,7 +5801,7 @@ "isurl": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "integrity": "sha1-sn9PSfPNqj6kSgpbfzRi5u3DnWc=", "requires": { "has-to-string-tag-x": "^1.2.0", "is-object": "^1.0.1" @@ -6571,7 +6571,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "^1.1.7" } @@ -6915,7 +6915,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -7216,7 +7216,7 @@ "p-cancelable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + "integrity": "sha1-ueEjgAvOu3rBOkeb4ZW1B7mNMPo=" }, "p-defer": { "version": "1.0.0",