feat: change customer screen transaction list part to the new design
feat: created the compliance details component (no data for now) fix: added missing properties into the gql schema and the compliance details component feat: added another chip type for a neutral situation style: change the property card style for the v1 specs fix: added front facing camera override to schema and components feat: added authorized override (status) column to the customers list table fix: moved name to the front of the phone on the customers list table fix: added sanctions description text on it's card fix: added id icon to the right of the customer name feat: created subpage button component and use it in the customer profile feat: created an image popper component and use it in the customer compliance page fix: added varying sizes to the customer details and id data cards fields refactor: simplify the compliance subpage code
This commit is contained in:
parent
8ff0a7f79b
commit
f53a934092
28 changed files with 744 additions and 260 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
52
new-lamassu-admin/src/components/ImagePopper.js
Normal file
52
new-lamassu-admin/src/components/ImagePopper.js
Normal file
|
|
@ -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 }) => (
|
||||
<img className={classnames(className)} src={src} alt="" />
|
||||
)
|
||||
|
||||
return (
|
||||
<ClickAwayListener onClickAway={handleClosePopper}>
|
||||
<div className={classnames(classes.row, className)}>
|
||||
<Image className={classes.unzoomedImg} />
|
||||
<FeatureButton
|
||||
Icon={ZoomIcon}
|
||||
InverseIcon={ZoomIconInverse}
|
||||
className={classes.button}
|
||||
onClick={handleOpenPopper}
|
||||
/>
|
||||
<Popper open={popperOpen} anchorEl={popperAnchorEl} placement="top">
|
||||
<div className={classes.popoverContent}>
|
||||
<Image />
|
||||
</div>
|
||||
</Popper>
|
||||
</div>
|
||||
</ClickAwayListener>
|
||||
)
|
||||
})
|
||||
|
||||
export default ImagePopper
|
||||
20
new-lamassu-admin/src/components/ImagePopper.styles.js
Normal file
20
new-lamassu-admin/src/components/ImagePopper.styles.js
Normal file
|
|
@ -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]]
|
||||
}
|
||||
}
|
||||
|
|
@ -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({
|
||||
|
|
|
|||
59
new-lamassu-admin/src/components/buttons/SubpageButton.js
Normal file
59
new-lamassu-admin/src/components/buttons/SubpageButton.js
Normal file
|
|
@ -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 = <Icon className={classes.buttonIcon} />
|
||||
|
||||
const activeButton = (
|
||||
<>
|
||||
<InverseIcon
|
||||
className={classnames(
|
||||
classes.buttonIcon,
|
||||
classes.buttonIconActiveLeft
|
||||
)}
|
||||
/>
|
||||
{children}
|
||||
<CancelIconInverse
|
||||
className={classnames(
|
||||
classes.buttonIcon,
|
||||
classes.buttonIconActiveRight
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
const innerToggle = () => {
|
||||
const newActiveState = !active
|
||||
toggle(newActiveState)
|
||||
setActive(newActiveState)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classnames(classNames, className)}
|
||||
onClick={innerToggle}>
|
||||
{active ? activeButton : normalButton}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default SubpageButton
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
|||
</Label2>
|
||||
</Breadcrumbs>
|
||||
<div>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<CustomerDetails customer={customerData} />
|
||||
<Box
|
||||
className={classes.customerDetails}
|
||||
display="flex"
|
||||
justifyContent="space-between">
|
||||
<CustomerDetails
|
||||
customer={customerData}
|
||||
locale={locale}
|
||||
setShowCompliance={() => setShowCompliance(!showCompliance)}
|
||||
/>
|
||||
<div>
|
||||
<Label1 className={classes.actionLabel}>Actions</Label1>
|
||||
<ActionButton
|
||||
|
|
@ -156,22 +178,21 @@ const CustomerProfile = memo(() => {
|
|||
</ActionButton>
|
||||
</div>
|
||||
</Box>
|
||||
<Box display="flex">
|
||||
<IdDataCard
|
||||
customerData={customerData}
|
||||
updateCustomer={updateCustomer}
|
||||
/>
|
||||
<PhoneCard
|
||||
customerData={customerData}
|
||||
updateCustomer={updateCustomer}
|
||||
/>
|
||||
<IdCardPhotoCard
|
||||
customerData={customerData}
|
||||
updateCustomer={updateCustomer}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
<TransactionsList data={sortedTransactions} loading={loading} />
|
||||
{!showCompliance && (
|
||||
<TransactionsList
|
||||
customer={customerData}
|
||||
data={sortedTransactions}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
{showCompliance && (
|
||||
<ComplianceDetails
|
||||
customer={customerData}
|
||||
locale={locale}
|
||||
updateCustomer={updateCustomer}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,5 +11,8 @@ export default {
|
|||
actionLabel: {
|
||||
color: comet,
|
||||
margin: [[4, 0]]
|
||||
},
|
||||
customerDetails: {
|
||||
marginBottom: 18
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<CustomersList
|
||||
data={customersData}
|
||||
locale={locale}
|
||||
onClick={handleCustomerClicked}
|
||||
loading={loading}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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 => (
|
||||
<MainStatus statuses={[getAuthorizedStatus(it.authorizedOverride)]} />
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
<ImagePopper src={src} width={imageWidth} height={imageHeight} />
|
||||
) : (
|
||||
<div className={classes.photoWrapper}>
|
||||
<CrossedCameraIcon />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<H3>Compliance details</H3>
|
||||
<div>
|
||||
<IdDataCard customerData={customer} updateCustomer={updateCustomer} />
|
||||
<Box className={classes.complianceDetailsGrid}>
|
||||
<Box className={classes.firstColumn}>
|
||||
<PropertyCard
|
||||
title={'Phone nº'}
|
||||
state={R.path(['smsOverride'])(customer)}
|
||||
authorize={() =>
|
||||
updateCustomer({ smsOverride: OVERRIDE_AUTHORIZED })
|
||||
}
|
||||
reject={() => updateCustomer({ smsOverride: OVERRIDE_REJECTED })}>
|
||||
<Field label={'Phone'} display={phone} />
|
||||
</PropertyCard>
|
||||
<PropertyCard
|
||||
title={'ID photo'}
|
||||
state={R.path(['idCardPhotoOverride'])(customer)}
|
||||
authorize={() =>
|
||||
updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED })
|
||||
}
|
||||
reject={() =>
|
||||
updateCustomer({ idCardPhotoOverride: OVERRIDE_REJECTED })
|
||||
}>
|
||||
<Photo
|
||||
show={customer.idCardPhotoPath}
|
||||
src={`${URI}/id-card-photo/${R.path(['idCardPhotoPath'])(
|
||||
customer
|
||||
)}`}
|
||||
/>
|
||||
</PropertyCard>
|
||||
<PropertyCard
|
||||
title={'Front facing camera'}
|
||||
state={R.path(['frontCameraOverride'])(customer)}
|
||||
authorize={() =>
|
||||
updateCustomer({ frontCameraOverride: OVERRIDE_AUTHORIZED })
|
||||
}
|
||||
reject={() =>
|
||||
updateCustomer({ frontCameraOverride: OVERRIDE_REJECTED })
|
||||
}>
|
||||
<Photo
|
||||
show={customer.frontCameraPath}
|
||||
src={`${URI}/front-camera-photo/${R.path(['frontCameraPath'])(
|
||||
customer
|
||||
)}`}
|
||||
/>
|
||||
</PropertyCard>
|
||||
</Box>
|
||||
<Box className={classes.lastColumn}>
|
||||
<PropertyCard
|
||||
title={'US SSN'}
|
||||
state={R.path(['usSsnOverride'])(customer)}
|
||||
authorize={() =>
|
||||
updateCustomer({ usSsnOverride: OVERRIDE_AUTHORIZED })
|
||||
}
|
||||
reject={() =>
|
||||
updateCustomer({ usSsnOverride: OVERRIDE_REJECTED })
|
||||
}>
|
||||
<Field label={'US SSN'} display={customer.usSsn} />
|
||||
</PropertyCard>
|
||||
<PropertyCard
|
||||
title={'Sanctions check'}
|
||||
state={R.path(['sanctionsOverride'])(customer)}
|
||||
authorize={() =>
|
||||
updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED })
|
||||
}
|
||||
reject={() =>
|
||||
updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED })
|
||||
}>
|
||||
<Info3>{sanctionsDisplay}</Info3>
|
||||
</PropertyCard>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComplianceDetails
|
||||
|
|
@ -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 }
|
||||
|
|
@ -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,
|
||||
<>
|
||||
<LastTxIcon className={classes.icon} />
|
||||
{`${Number.parseFloat(customer.lastTxFiat)}
|
||||
${customer.lastTxFiatCode}`}
|
||||
</>
|
||||
)
|
||||
value: R.path(['usSsn'])(customer) ?? ''
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -79,9 +49,17 @@ const CustomerDetails = memo(({ customer }) => {
|
|||
/>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<div className={classes.name}>
|
||||
<IdIcon className={classes.idIcon} />
|
||||
<H2 noMargin>
|
||||
{R.path(['name'])(customer) ?? R.path(['phone'])(customer)}
|
||||
</H2>
|
||||
<SubpageButton
|
||||
className={classes.subpageButton}
|
||||
Icon={LawIcon}
|
||||
InverseIcon={LawIconInverse}
|
||||
toggle={setShowCompliance}>
|
||||
Compliance details
|
||||
</SubpageButton>
|
||||
</div>
|
||||
<Box display="flex" mt="auto">
|
||||
{elements.map(({ size, header }, idx) => (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className={classnames(classes.field, className)}>
|
||||
<div
|
||||
className={classnames(classes.field, className)}
|
||||
style={{ width: size }}>
|
||||
<Label1 className={classes.label}>{label}</Label1>
|
||||
<Info3 className={classes.value}>{display}</Info3>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ const IdCardPhotoCard = memo(({ customerData, updateCustomer }) => {
|
|||
|
||||
return (
|
||||
<PropertyCard
|
||||
className={classes.idCardPhotoCard}
|
||||
title={'ID card photo'}
|
||||
// className={classes.idCardPhotoCard}
|
||||
title={'ID photo'}
|
||||
state={R.path(['idCardPhotoOverride'])(customerData)}
|
||||
authorize={() =>
|
||||
updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED })
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<PropertyCard
|
||||
className={classes.idDataCard}
|
||||
title={'ID data'}
|
||||
state={R.path(['idCardDataOverride'])(customerData)}
|
||||
authorize={() =>
|
||||
updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED })
|
||||
}
|
||||
reject={() => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED })}>
|
||||
<div>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
mb={1}>
|
||||
<Field label={'Name'} display={`${name} ${lastName}`} />
|
||||
<Field label={'ID number'} display={idNumber} />
|
||||
<Field label={'Age'} display={age} />
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<Field label={'Gender'} display={gender} />
|
||||
<Field label={'Country'} display={country} />
|
||||
<Field label={'Expiration date'} display={expirationDate} />
|
||||
</Box>
|
||||
</div>
|
||||
<Box display="flex" alignItems="center">
|
||||
{elements.map(({ header, display, size }, idx) => (
|
||||
<Field key={idx} label={header} display={display} size={size} />
|
||||
))}
|
||||
</Box>
|
||||
</PropertyCard>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 }) => (
|
||||
<PropertyCard
|
||||
className={classes.phoneCard}
|
||||
title={'Phone'}
|
||||
className={className}
|
||||
title={'Phone nº'}
|
||||
state={R.path(['smsOverride'])(customerData)}
|
||||
authorize={() => updateCustomer({ smsOverride: OVERRIDE_AUTHORIZED })}
|
||||
reject={() => updateCustomer({ smsOverride: OVERRIDE_REJECTED })}>
|
||||
<Field
|
||||
label={'Phone'}
|
||||
display={
|
||||
R.path(['phone'])(customerData)
|
||||
customerData.phone && locale.country
|
||||
? parsePhoneNumberFromString(
|
||||
R.path(['phone'])(customerData)
|
||||
customerData.phone,
|
||||
locale.country
|
||||
).formatInternational()
|
||||
: []
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</PropertyCard>
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
export default PhoneCard
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
<>
|
||||
<LastTxIcon className={classes.icon} />
|
||||
{`${Number.parseFloat(customer.lastTxFiat)}
|
||||
${customer.lastTxFiatCode}`}
|
||||
</>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
const tableElements = [
|
||||
{
|
||||
header: 'Direction',
|
||||
width: 207,
|
||||
|
|
@ -79,6 +120,31 @@ const TransactionsList = ({ data, loading }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<H3>Transactions</H3>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Box display="flex" mt="auto">
|
||||
{summaryElements.map(({ size, header }, idx) => (
|
||||
<Label1
|
||||
noMargin
|
||||
key={idx}
|
||||
className={classes.label}
|
||||
style={{ width: size }}>
|
||||
{header}
|
||||
</Label1>
|
||||
))}
|
||||
</Box>
|
||||
<Box display="flex">
|
||||
{summaryElements.map(({ size, value }, idx) => (
|
||||
<P
|
||||
noMargin
|
||||
key={idx}
|
||||
className={classes.value}
|
||||
style={{ width: size }}>
|
||||
{value}
|
||||
</P>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
<div className={classes.titleWrapper}>
|
||||
<div className={classes.titleAndButtonsContainer}>
|
||||
<H4>
|
||||
|
|
@ -90,7 +156,7 @@ const TransactionsList = ({ data, loading }) => {
|
|||
</H4>
|
||||
</div>
|
||||
</div>
|
||||
{hasData && <DataTable elements={elements} data={data} />}
|
||||
{hasData && <DataTable elements={tableElements} data={data} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
</ActionButton>
|
||||
)
|
||||
|
||||
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 (
|
||||
<Paper
|
||||
className={classnames(propertyCardClassNames, className)}
|
||||
className={classnames(classes.propertyCard, className)}
|
||||
elevation={0}>
|
||||
<div className={classes.rowSpaceBetween}>
|
||||
<H3>{title}</H3>
|
||||
<H3 className={classes.propertyCardTopRow}>{title}</H3>
|
||||
<div className={classes.propertyCardBottomRow}>
|
||||
<div className={classnames(label1ClassNames)}>
|
||||
{authorizedAsString}
|
||||
<MainStatus statuses={[authorized]} />
|
||||
</div>
|
||||
</div>
|
||||
<Paper className={classes.cardProperties} elevation={0}>
|
||||
{children}
|
||||
</Paper>
|
||||
<div className={classes.buttonsWrapper}>
|
||||
{state !== OVERRIDE_AUTHORIZED && AuthorizeButton()}
|
||||
{state !== OVERRIDE_REJECTED && RejectButton()}
|
||||
<div className={classes.buttonsWrapper}>
|
||||
{authorize && state !== OVERRIDE_AUTHORIZED && AuthorizeButton()}
|
||||
{reject && state !== OVERRIDE_REJECTED && RejectButton()}
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M11.10567,8.99966734 L17.5616855,15.4562245 C18.1453995,16.0371533 18.1457502,16.9810221 17.5632744,17.5635465 C17.2826878,17.8441564 16.9043531,17.9996031 16.509308,17.9996031 C16.1150437,17.9996031 15.7367759,17.8439272 15.4563692,17.5634971 L8.99995005,11.1055617 L2.54567824,17.5603569 C2.26578723,17.8430155 1.88614994,17.9996031 1.48961505,17.9996031 C1.09473832,17.9996031 0.717380733,17.844225 0.436725633,17.5635465 C-0.145575211,16.9811971 -0.145575211,16.0373273 0.436725633,15.4578096 L6.89433001,8.99966707 L0.438314479,2.54310994 C-0.145399464,1.96218116 -0.145750215,1.01831232 0.436725633,0.435787934 C1.01746304,-0.144997872 1.95893893,-0.144997872 2.54250446,0.435787934 L8.99995074,6.89377234 L15.4580075,0.434202817 C16.0398949,-0.144908147 16.9801496,-0.144559181 17.5632744,0.435787934 C18.1455752,1.0181373 18.1455752,1.96200713 17.5632744,2.54152483 L11.10567,8.99966734 Z" id="path-1"></path>
|
||||
<path d="M11.10567,8.99966734 L17.5616855,15.4562245 C18.1453995,16.0371533 18.1457502,16.9810221 17.5632744,17.5635465 C17.2826878,17.8441564 16.9043531,17.9996031 16.509308,17.9996031 C16.1150437,17.9996031 15.7367759,17.8439272 15.4563692,17.5634971 L8.99995005,11.1055617 L2.54567824,17.5603569 C2.26578723,17.8430155 1.88614994,17.9996031 1.48961505,17.9996031 C1.09473832,17.9996031 0.717380733,17.844225 0.436725633,17.5635465 C-0.145575211,16.9811971 -0.145575211,16.0373273 0.436725633,15.4578096 L6.89433001,8.99966707 L0.438314479,2.54310994 C-0.145399464,1.96218116 -0.145750215,1.01831232 0.436725633,0.435787934 C1.01746304,-0.144997872 1.95893893,-0.144997872 2.54250446,0.435787934 L8.99995074,6.89377234 L15.4580075,0.434202817 C16.0398949,-0.144908147 16.9801496,-0.144559181 17.5632744,0.435787934 C18.1455752,1.0181373 18.1455752,1.96200713 17.5632744,2.54152483 L11.10567,8.99966734 Z" id="close_path-1"></path>
|
||||
</defs>
|
||||
<g id="icon/action/close/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
<mask id="close_mask-2" fill="white">
|
||||
<use xlink:href="#close_path-1"></use>
|
||||
</mask>
|
||||
<use id="Mask" fill="#FFFFFF" xlink:href="#path-1"></use>
|
||||
<use id="close_Mask" fill="#FFFFFF" xlink:href="#close_path-1"></use>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>icon/sf-small/law/white</title>
|
||||
<g id="icon/sf-small/law/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="bevel">
|
||||
<line x1="8.4141" y1="7.4648" x2="14.0711" y2="1.8078" id="Stroke-1" stroke="#FFFFFF" stroke-width="2"></line>
|
||||
<line x1="12.6568" y1="0.3936" x2="15.4858" y2="3.2216" id="Stroke-3" stroke="#FFFFFF" stroke-width="2"></line>
|
||||
<line x1="7" y1="6.0498" x2="9.829" y2="8.8788" id="Stroke-6" stroke="#FFFFFF" stroke-width="2"></line>
|
||||
<line x1="11.2427" y1="4.6357" x2="19.2427" y2="12.6357" id="Stroke-7" stroke="#FFFFFF" stroke-width="2"></line>
|
||||
<line x1="3" y1="16.5" x2="10" y2="16.5" id="Stroke-9" stroke="#FFFFFF" stroke-width="2"></line>
|
||||
<line x1="0" y1="19.5" x2="13" y2="19.5" id="Stroke-10" stroke="#FFFFFF" stroke-width="2"></line>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>icon/sf-small/law/zodiac</title>
|
||||
<g id="icon/sf-small/law/zodiac" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="bevel">
|
||||
<line x1="8.4141" y1="7.4648" x2="14.0711" y2="1.8078" id="Stroke-1" stroke="#1B2559" stroke-width="2"></line>
|
||||
<line x1="12.6568" y1="0.3936" x2="15.4858" y2="3.2216" id="Stroke-3" stroke="#1B2559" stroke-width="2"></line>
|
||||
<line x1="7" y1="6.0498" x2="9.829" y2="8.8788" id="Stroke-6" stroke="#1B2559" stroke-width="2"></line>
|
||||
<line x1="11.2427" y1="4.6357" x2="19.2427" y2="12.6357" id="Stroke-7" stroke="#1B2559" stroke-width="2"></line>
|
||||
<line x1="3" y1="16.5" x2="10" y2="16.5" id="Stroke-9" stroke="#1B2559" stroke-width="2"></line>
|
||||
<line x1="0" y1="19.5" x2="13" y2="19.5" id="Stroke-10" stroke="#1B2559" stroke-width="2"></line>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue