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) {
|
async function updateCustomer (id, data) {
|
||||||
const formattedData = _.pick(
|
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))
|
_.mapKeys(_.snakeCase, data))
|
||||||
|
|
||||||
const sql = Pgp.helpers.update(formattedData, _.keys(formattedData), 'customers') +
|
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
|
* @returns {array} Array of customers with it's transactions aggregations
|
||||||
*/
|
*/
|
||||||
function getCustomersList () {
|
function getCustomersList () {
|
||||||
const sql = `select id, name, authorized_override, front_camera_path, phone, sms_override,
|
const sql = `select id, name, authorized_override, front_camera_path, front_camera_override,
|
||||||
id_card_data, id_card_data_override, id_card_data_expiration, id_card_photo_path,
|
phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||||
id_card_photo_override, total_txs, total_spent, created as last_active,
|
id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||||
fiat as last_tx_fiat, fiat_code as last_tx_fiat_code, tx_class as last_tx_class
|
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 (
|
from (
|
||||||
select c.id, c.name, c.authorized_override, c.front_camera_path, c.phone, c.sms_override,
|
select c.id, c.name, c.authorized_override, c.front_camera_path, c.front_camera_override,
|
||||||
c.id_card_data, c.id_card_data_override, c.id_card_data_expiration, c.id_card_photo_path,
|
c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
|
||||||
c.id_card_photo_override, t.tx_class, t.fiat, t.fiat_code, t.created,
|
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,
|
row_number() over (partition by c.id order by t.created desc) as rn,
|
||||||
sum(case when t.id is not null then 1 else 0 end) over (partition by c.id) as total_txs,
|
sum(case when 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
|
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
|
* @returns {array} Array of customers with it's transactions aggregations
|
||||||
*/
|
*/
|
||||||
function getCustomerById (id) {
|
function getCustomerById (id) {
|
||||||
const sql = `select id, name, authorized_override, front_camera_path, phone, sms_override,
|
const sql = `select id, name, authorized_override, front_camera_path, front_camera_override,
|
||||||
id_card_data, id_card_data_override, id_card_data_expiration, id_card_photo_path,
|
phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||||
id_card_photo_override, total_txs, total_spent, created as last_active,
|
id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||||
fiat as last_tx_fiat, fiat_code as last_tx_fiat_code, tx_class as last_tx_class
|
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 (
|
from (
|
||||||
select c.id, c.name, c.authorized_override, c.front_camera_path, c.phone, c.sms_override,
|
select c.id, c.name, c.authorized_override, c.front_camera_path, c.front_camera_override,
|
||||||
c.id_card_data, c.id_card_data_override, c.id_card_data_expiration, c.id_card_photo_path,
|
c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
|
||||||
c.id_card_photo_override, t.tx_class, t.fiat, t.fiat_code, t.created,
|
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,
|
row_number() over (partition by c.id order by t.created desc) as rn,
|
||||||
sum(case when t.id is not null then 1 else 0 end) over (partition by c.id) as total_txs,
|
sum(case when 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
|
sum(t.fiat) over (partition by c.id) as total_spent
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ const typeDefs = gql`
|
||||||
name: String
|
name: String
|
||||||
authorizedOverride: String
|
authorizedOverride: String
|
||||||
frontCameraPath: String
|
frontCameraPath: String
|
||||||
|
frontCameraOverride: String
|
||||||
phone: String
|
phone: String
|
||||||
smsOverride: String
|
smsOverride: String
|
||||||
idCardData: JSONObject
|
idCardData: JSONObject
|
||||||
|
|
@ -75,6 +76,11 @@ const typeDefs = gql`
|
||||||
idCardDataExpiration: Date
|
idCardDataExpiration: Date
|
||||||
idCardPhotoPath: String
|
idCardPhotoPath: String
|
||||||
idCardPhotoOverride: String
|
idCardPhotoOverride: String
|
||||||
|
usSsn: String
|
||||||
|
usSsnOverride: String
|
||||||
|
sanctions: Boolean
|
||||||
|
sanctionsAt: Date
|
||||||
|
sanctionsOverride: String
|
||||||
totalTxs: Int
|
totalTxs: Int
|
||||||
totalSpent: String
|
totalSpent: String
|
||||||
lastActive: Date
|
lastActive: Date
|
||||||
|
|
@ -88,6 +94,7 @@ const typeDefs = gql`
|
||||||
name: String
|
name: String
|
||||||
authorizedOverride: String
|
authorizedOverride: String
|
||||||
frontCameraPath: String
|
frontCameraPath: String
|
||||||
|
frontCameraOverride: String
|
||||||
phone: String
|
phone: String
|
||||||
smsOverride: String
|
smsOverride: String
|
||||||
idCardData: JSONObject
|
idCardData: JSONObject
|
||||||
|
|
@ -95,6 +102,11 @@ const typeDefs = gql`
|
||||||
idCardDataExpiration: Date
|
idCardDataExpiration: Date
|
||||||
idCardPhotoPath: String
|
idCardPhotoPath: String
|
||||||
idCardPhotoOverride: String
|
idCardPhotoOverride: String
|
||||||
|
usSsn: String
|
||||||
|
usSsnOverride: String
|
||||||
|
sanctions: Boolean
|
||||||
|
sanctionsAt: Date
|
||||||
|
sanctionsOverride: String
|
||||||
totalTxs: Int
|
totalTxs: Int
|
||||||
totalSpent: String
|
totalSpent: String
|
||||||
lastActive: Date
|
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,
|
secondaryColorDarker as spring4,
|
||||||
inputFontWeight,
|
inputFontWeight,
|
||||||
spring3,
|
spring3,
|
||||||
|
zircon,
|
||||||
|
primaryColor,
|
||||||
smallestFontSize,
|
smallestFontSize,
|
||||||
inputFontFamily,
|
inputFontFamily,
|
||||||
spacer,
|
spacer,
|
||||||
|
|
@ -18,13 +20,15 @@ import {
|
||||||
const colors = {
|
const colors = {
|
||||||
error: tomato,
|
error: tomato,
|
||||||
warning: pumpkin,
|
warning: pumpkin,
|
||||||
success: spring4
|
success: spring4,
|
||||||
|
neutral: primaryColor
|
||||||
}
|
}
|
||||||
|
|
||||||
const backgroundColors = {
|
const backgroundColors = {
|
||||||
error: mistyRose,
|
error: mistyRose,
|
||||||
warning: linen,
|
warning: linen,
|
||||||
success: spring3
|
success: spring3,
|
||||||
|
neutral: zircon
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
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 Link from './Link'
|
||||||
import SimpleButton from './SimpleButton'
|
import SimpleButton from './SimpleButton'
|
||||||
import SupportLinkButton from './SupportLinkButton'
|
import SupportLinkButton from './SupportLinkButton'
|
||||||
|
import SubpageButton from './SubpageButton'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -18,4 +19,5 @@ export {
|
||||||
IDButton,
|
IDButton,
|
||||||
AddButton,
|
AddButton,
|
||||||
SupportLinkButton
|
SupportLinkButton
|
||||||
|
SubpageButton
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { makeStyles, Breadcrumbs, Box } from '@material-ui/core'
|
||||||
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
|
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { memo } from 'react'
|
import React, { memo, useState } from 'react'
|
||||||
import { useHistory, useParams } from 'react-router-dom'
|
import { useHistory, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { ActionButton } from 'src/components/buttons'
|
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 AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg'
|
||||||
import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/block/white.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 { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/zodiac.svg'
|
||||||
|
import { fromNamespace, namespaces } from 'src/utils/config'
|
||||||
|
|
||||||
import styles from './CustomerProfile.styles'
|
import styles from './CustomerProfile.styles'
|
||||||
import {
|
import {
|
||||||
CustomerDetails,
|
CustomerDetails,
|
||||||
IdDataCard,
|
TransactionsList,
|
||||||
PhoneCard,
|
ComplianceDetails
|
||||||
IdCardPhotoCard,
|
|
||||||
TransactionsList
|
|
||||||
} from './components'
|
} from './components'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const GET_CUSTOMER = gql`
|
const GET_CUSTOMER = gql`
|
||||||
query customer($customerId: ID!) {
|
query customer($customerId: ID!) {
|
||||||
|
config
|
||||||
customer(customerId: $customerId) {
|
customer(customerId: $customerId) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
authorizedOverride
|
authorizedOverride
|
||||||
frontCameraPath
|
frontCameraPath
|
||||||
|
frontCameraOverride
|
||||||
phone
|
phone
|
||||||
smsOverride
|
smsOverride
|
||||||
idCardData
|
idCardData
|
||||||
|
|
@ -42,6 +43,11 @@ const GET_CUSTOMER = gql`
|
||||||
idCardDataExpiration
|
idCardDataExpiration
|
||||||
idCardPhotoPath
|
idCardPhotoPath
|
||||||
idCardPhotoOverride
|
idCardPhotoOverride
|
||||||
|
usSsn
|
||||||
|
usSsnOverride
|
||||||
|
sanctions
|
||||||
|
sanctionsAt
|
||||||
|
sanctionsOverride
|
||||||
totalTxs
|
totalTxs
|
||||||
totalSpent
|
totalSpent
|
||||||
lastActive
|
lastActive
|
||||||
|
|
@ -70,6 +76,7 @@ const SET_CUSTOMER = gql`
|
||||||
name
|
name
|
||||||
authorizedOverride
|
authorizedOverride
|
||||||
frontCameraPath
|
frontCameraPath
|
||||||
|
frontCameraOverride
|
||||||
phone
|
phone
|
||||||
smsOverride
|
smsOverride
|
||||||
idCardData
|
idCardData
|
||||||
|
|
@ -77,6 +84,11 @@ const SET_CUSTOMER = gql`
|
||||||
idCardDataExpiration
|
idCardDataExpiration
|
||||||
idCardPhotoPath
|
idCardPhotoPath
|
||||||
idCardPhotoOverride
|
idCardPhotoOverride
|
||||||
|
usSsn
|
||||||
|
usSsnOverride
|
||||||
|
sanctions
|
||||||
|
sanctionsAt
|
||||||
|
sanctionsOverride
|
||||||
totalTxs
|
totalTxs
|
||||||
totalSpent
|
totalSpent
|
||||||
lastActive
|
lastActive
|
||||||
|
|
@ -90,6 +102,7 @@ const SET_CUSTOMER = gql`
|
||||||
const CustomerProfile = memo(() => {
|
const CustomerProfile = memo(() => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
|
const [showCompliance, setShowCompliance] = useState(false)
|
||||||
const { id: customerId } = useParams()
|
const { id: customerId } = useParams()
|
||||||
|
|
||||||
const { data: customerResponse, refetch: getCustomer, loading } = useQuery(
|
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 customerData = R.path(['customer'])(customerResponse) ?? []
|
||||||
const rawTransactions = R.path(['transactions'])(customerData) ?? []
|
const rawTransactions = R.path(['transactions'])(customerData) ?? []
|
||||||
const sortedTransactions = R.sort(R.descend(R.prop('cryptoAtoms')))(
|
const sortedTransactions = R.sort(R.descend(R.prop('cryptoAtoms')))(
|
||||||
|
|
@ -137,8 +152,15 @@ const CustomerProfile = memo(() => {
|
||||||
</Label2>
|
</Label2>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
<div>
|
<div>
|
||||||
<Box display="flex" justifyContent="space-between">
|
<Box
|
||||||
<CustomerDetails customer={customerData} />
|
className={classes.customerDetails}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between">
|
||||||
|
<CustomerDetails
|
||||||
|
customer={customerData}
|
||||||
|
locale={locale}
|
||||||
|
setShowCompliance={() => setShowCompliance(!showCompliance)}
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Label1 className={classes.actionLabel}>Actions</Label1>
|
<Label1 className={classes.actionLabel}>Actions</Label1>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|
@ -156,22 +178,21 @@ const CustomerProfile = memo(() => {
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
<Box display="flex">
|
|
||||||
<IdDataCard
|
|
||||||
customerData={customerData}
|
|
||||||
updateCustomer={updateCustomer}
|
|
||||||
/>
|
|
||||||
<PhoneCard
|
|
||||||
customerData={customerData}
|
|
||||||
updateCustomer={updateCustomer}
|
|
||||||
/>
|
|
||||||
<IdCardPhotoCard
|
|
||||||
customerData={customerData}
|
|
||||||
updateCustomer={updateCustomer}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</div>
|
</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: {
|
actionLabel: {
|
||||||
color: comet,
|
color: comet,
|
||||||
margin: [[4, 0]]
|
margin: [[4, 0]]
|
||||||
|
},
|
||||||
|
customerDetails: {
|
||||||
|
marginBottom: 18
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,13 @@ import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { fromNamespace, namespaces } from 'src/utils/config'
|
||||||
|
|
||||||
import CustomersList from './CustomersList'
|
import CustomersList from './CustomersList'
|
||||||
|
|
||||||
const GET_CUSTOMERS = gql`
|
const GET_CUSTOMERS = gql`
|
||||||
{
|
{
|
||||||
|
config
|
||||||
customers {
|
customers {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
|
@ -18,6 +21,7 @@ const GET_CUSTOMERS = gql`
|
||||||
lastTxFiat
|
lastTxFiat
|
||||||
lastTxFiatCode
|
lastTxFiatCode
|
||||||
lastTxClass
|
lastTxClass
|
||||||
|
authorizedOverride
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -29,6 +33,8 @@ const Customers = () => {
|
||||||
const handleCustomerClicked = customer =>
|
const handleCustomerClicked = customer =>
|
||||||
history.push(`/compliance/customer/${customer.id}`)
|
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'))])(
|
const customersData = R.sortWith([R.descend(R.prop('lastActive'))])(
|
||||||
R.path(['customers'])(customersResponse) ?? []
|
R.path(['customers'])(customersResponse) ?? []
|
||||||
)
|
)
|
||||||
|
|
@ -36,6 +42,7 @@ const Customers = () => {
|
||||||
return (
|
return (
|
||||||
<CustomersList
|
<CustomersList
|
||||||
data={customersData}
|
data={customersData}
|
||||||
|
locale={locale}
|
||||||
onClick={handleCustomerClicked}
|
onClick={handleCustomerClicked}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import moment from 'moment'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import { MainStatus } from 'src/components/Status'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import DataTable from 'src/components/tables/DataTable'
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
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 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 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 = [
|
const elements = [
|
||||||
{
|
|
||||||
header: 'Phone',
|
|
||||||
width: 186,
|
|
||||||
view: it => parsePhoneNumberFromString(it.phone).formatInternational()
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
width: 277,
|
width: 241,
|
||||||
view: R.path(['name'])
|
view: R.path(['name'])
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: 'Phone',
|
||||||
|
width: 172,
|
||||||
|
view: it =>
|
||||||
|
it.phone && locale.country
|
||||||
|
? parsePhoneNumberFromString(
|
||||||
|
it.phone,
|
||||||
|
locale.country
|
||||||
|
).formatInternational()
|
||||||
|
: ''
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: 'Total TXs',
|
header: 'Total TXs',
|
||||||
width: 154,
|
width: 126,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it => `${Number.parseInt(it.totalTxs)}`
|
view: it => `${Number.parseInt(it.totalTxs)}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Total spent',
|
header: 'Total spent',
|
||||||
width: 188,
|
width: 152,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it =>
|
view: it =>
|
||||||
`${Number.parseFloat(it.totalSpent)} ${it.lastTxFiatCode ?? ''}`
|
`${Number.parseFloat(it.totalSpent)} ${it.lastTxFiatCode ?? ''}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Last active',
|
header: 'Last active',
|
||||||
width: 197,
|
width: 133,
|
||||||
view: it =>
|
view: it =>
|
||||||
ifNotNull(it.lastActive, moment.utc(it.lastActive).format('YYYY-MM-D'))
|
ifNotNull(it.lastActive, moment.utc(it.lastActive).format('YYYY-MM-D'))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Last transaction',
|
header: 'Last transaction',
|
||||||
width: 198,
|
width: 161,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it => {
|
view: it => {
|
||||||
const hasLastTx = !R.isNil(it.lastTxFiatCode)
|
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 typographyStyles from 'src/components/typography/styles'
|
||||||
import baseStyles from 'src/pages/Logs.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 { label1 } = typographyStyles
|
||||||
const { titleWrapper, titleAndButtonsContainer } = baseStyles
|
const { titleWrapper, titleAndButtonsContainer } = baseStyles
|
||||||
|
|
@ -80,5 +80,26 @@ export default {
|
||||||
},
|
},
|
||||||
customerName: {
|
customerName: {
|
||||||
marginBottom: 32
|
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 { makeStyles, Box } from '@material-ui/core'
|
||||||
import moment from 'moment'
|
import { parsePhoneNumberFromString } from 'libphonenumber-js'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
|
||||||
|
import { SubpageButton } from 'src/components/buttons'
|
||||||
import { H2, Label1, P } from 'src/components/typography'
|
import { H2, Label1, P } from 'src/components/typography'
|
||||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
import { ReactComponent as IdIcon } from 'src/styling/icons/ID/card/zodiac.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as LawIconInverse } from 'src/styling/icons/circle buttons/law/white.svg'
|
||||||
import { comet } from 'src/styling/variables'
|
import { ReactComponent as LawIcon } from 'src/styling/icons/circle buttons/law/zodiac.svg'
|
||||||
import { ifNotNull } from 'src/utils/nullCheck'
|
|
||||||
|
import mainStyles from '../CustomersList.styles'
|
||||||
|
|
||||||
import FrontCameraPhoto from './FrontCameraPhoto'
|
import FrontCameraPhoto from './FrontCameraPhoto'
|
||||||
|
|
||||||
const styles = {
|
const useStyles = makeStyles(mainStyles)
|
||||||
icon: {
|
|
||||||
marginRight: 11
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
marginTop: 6
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
height: 16
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
marginBottom: 4,
|
|
||||||
color: comet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const CustomerDetails = memo(({ customer, locale, setShowCompliance }) => {
|
||||||
|
|
||||||
const CustomerDetails = memo(({ customer }) => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const LastTxIcon = customer.lastTxClass === 'cashOut' ? TxOutIcon : TxInIcon
|
|
||||||
|
|
||||||
const elements = [
|
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,
|
size: 127,
|
||||||
value: ifNotNull(
|
value: R.path(['usSsn'])(customer) ?? ''
|
||||||
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}`}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -79,9 +49,17 @@ const CustomerDetails = memo(({ customer }) => {
|
||||||
/>
|
/>
|
||||||
<Box display="flex" flexDirection="column">
|
<Box display="flex" flexDirection="column">
|
||||||
<div className={classes.name}>
|
<div className={classes.name}>
|
||||||
|
<IdIcon className={classes.idIcon} />
|
||||||
<H2 noMargin>
|
<H2 noMargin>
|
||||||
{R.path(['name'])(customer) ?? R.path(['phone'])(customer)}
|
{R.path(['name'])(customer) ?? R.path(['phone'])(customer)}
|
||||||
</H2>
|
</H2>
|
||||||
|
<SubpageButton
|
||||||
|
className={classes.subpageButton}
|
||||||
|
Icon={LawIcon}
|
||||||
|
InverseIcon={LawIconInverse}
|
||||||
|
toggle={setShowCompliance}>
|
||||||
|
Compliance details
|
||||||
|
</SubpageButton>
|
||||||
</div>
|
</div>
|
||||||
<Box display="flex" mt="auto">
|
<Box display="flex" mt="auto">
|
||||||
{elements.map(({ size, header }, idx) => (
|
{elements.map(({ size, header }, idx) => (
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { comet } from 'src/styling/variables'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
field: {
|
field: {
|
||||||
width: 144,
|
|
||||||
height: 46
|
height: 46
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
|
|
@ -23,11 +22,13 @@ const useStyles = makeStyles({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const Field = memo(({ label, display, className }) => {
|
const Field = memo(({ label, display, size, className }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames(classes.field, className)}>
|
<div
|
||||||
|
className={classnames(classes.field, className)}
|
||||||
|
style={{ width: size }}>
|
||||||
<Label1 className={classes.label}>{label}</Label1>
|
<Label1 className={classes.label}>{label}</Label1>
|
||||||
<Info3 className={classes.value}>{display}</Info3>
|
<Info3 className={classes.value}>{display}</Info3>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ const IdCardPhotoCard = memo(({ customerData, updateCustomer }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PropertyCard
|
<PropertyCard
|
||||||
className={classes.idCardPhotoCard}
|
// className={classes.idCardPhotoCard}
|
||||||
title={'ID card photo'}
|
title={'ID photo'}
|
||||||
state={R.path(['idCardPhotoOverride'])(customerData)}
|
state={R.path(['idCardPhotoOverride'])(customerData)}
|
||||||
authorize={() =>
|
authorize={() =>
|
||||||
updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED })
|
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 moment from 'moment'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
|
@ -12,65 +12,64 @@ import { ifNotNull } from 'src/utils/nullCheck'
|
||||||
|
|
||||||
import Field from './Field'
|
import Field from './Field'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
idDataCard: {
|
|
||||||
width: 550,
|
|
||||||
height: 240
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
marginBottom: 7
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const IdDataCard = memo(({ customerData, updateCustomer }) => {
|
const IdDataCard = memo(({ customerData, updateCustomer }) => {
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const idData = R.path(['idCardData'])(customerData)
|
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 rawExpirationDate = R.path(['expirationDate'])(idData)
|
||||||
const expirationDate = ifNotNull(
|
|
||||||
rawExpirationDate,
|
|
||||||
moment.utc(rawExpirationDate).format('YYYY-MM-D')
|
|
||||||
)
|
|
||||||
|
|
||||||
const rawDob = R.path(['dateOfBirth'])(idData)
|
const rawDob = R.path(['dateOfBirth'])(idData)
|
||||||
const age = ifNotNull(
|
|
||||||
rawDob,
|
const elements = [
|
||||||
moment.utc().diff(moment.utc(rawDob).format('YYYY-MM-D'), 'years')
|
{
|
||||||
)
|
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 (
|
return (
|
||||||
<PropertyCard
|
<PropertyCard
|
||||||
className={classes.idDataCard}
|
|
||||||
title={'ID data'}
|
title={'ID data'}
|
||||||
state={R.path(['idCardDataOverride'])(customerData)}
|
state={R.path(['idCardDataOverride'])(customerData)}
|
||||||
authorize={() =>
|
authorize={() =>
|
||||||
updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED })
|
updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED })
|
||||||
}
|
}
|
||||||
reject={() => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED })}>
|
reject={() => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED })}>
|
||||||
<div>
|
<Box display="flex" alignItems="center">
|
||||||
<Box
|
{elements.map(({ header, display, size }, idx) => (
|
||||||
display="flex"
|
<Field key={idx} label={header} display={display} size={size} />
|
||||||
alignItems="center"
|
))}
|
||||||
justifyContent="space-between"
|
</Box>
|
||||||
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>
|
|
||||||
</PropertyCard>
|
</PropertyCard>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
|
||||||
import { parsePhoneNumberFromString } from 'libphonenumber-js'
|
import { parsePhoneNumberFromString } from 'libphonenumber-js'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
|
@ -11,35 +10,27 @@ import {
|
||||||
|
|
||||||
import Field from './Field'
|
import Field from './Field'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const PhoneCard = memo(
|
||||||
phoneCard: {
|
({ className, customerData, updateCustomer, locale }) => (
|
||||||
width: 300,
|
|
||||||
height: 240
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const PhoneCard = memo(({ customerData, updateCustomer }) => {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PropertyCard
|
<PropertyCard
|
||||||
className={classes.phoneCard}
|
className={className}
|
||||||
title={'Phone'}
|
title={'Phone nº'}
|
||||||
state={R.path(['smsOverride'])(customerData)}
|
state={R.path(['smsOverride'])(customerData)}
|
||||||
authorize={() => updateCustomer({ smsOverride: OVERRIDE_AUTHORIZED })}
|
authorize={() => updateCustomer({ smsOverride: OVERRIDE_AUTHORIZED })}
|
||||||
reject={() => updateCustomer({ smsOverride: OVERRIDE_REJECTED })}>
|
reject={() => updateCustomer({ smsOverride: OVERRIDE_REJECTED })}>
|
||||||
<Field
|
<Field
|
||||||
label={'Phone'}
|
label={'Phone'}
|
||||||
display={
|
display={
|
||||||
R.path(['phone'])(customerData)
|
customerData.phone && locale.country
|
||||||
? parsePhoneNumberFromString(
|
? parsePhoneNumberFromString(
|
||||||
R.path(['phone'])(customerData)
|
customerData.phone,
|
||||||
|
locale.country
|
||||||
).formatInternational()
|
).formatInternational()
|
||||||
: []
|
: ''
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</PropertyCard>
|
</PropertyCard>
|
||||||
)
|
)
|
||||||
})
|
)
|
||||||
|
|
||||||
export default PhoneCard
|
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 BigNumber from 'bignumber.js'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import DataTable from 'src/components/tables/DataTable'
|
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 TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
import { toUnit } from 'src/utils/coin'
|
import { toUnit } from 'src/utils/coin'
|
||||||
|
import { ifNotNull } from 'src/utils/nullCheck'
|
||||||
|
|
||||||
import CopyToClipboard from '../../Transactions/CopyToClipboard'
|
import CopyToClipboard from '../../Transactions/CopyToClipboard'
|
||||||
import mainStyles from '../CustomersList.styles'
|
import mainStyles from '../CustomersList.styles'
|
||||||
|
|
||||||
const useStyles = makeStyles(mainStyles)
|
const useStyles = makeStyles(mainStyles)
|
||||||
|
|
||||||
const TransactionsList = ({ data, loading }) => {
|
const TransactionsList = ({ customer, data, loading }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const LastTxIcon = customer.lastTxClass === 'cashOut' ? TxOutIcon : TxInIcon
|
||||||
const hasData = !(R.isEmpty(data) || R.isNil(data))
|
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',
|
header: 'Direction',
|
||||||
width: 207,
|
width: 207,
|
||||||
|
|
@ -79,6 +120,31 @@ const TransactionsList = ({ data, loading }) => {
|
||||||
|
|
||||||
return (
|
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.titleWrapper}>
|
||||||
<div className={classes.titleAndButtonsContainer}>
|
<div className={classes.titleAndButtonsContainer}>
|
||||||
<H4>
|
<H4>
|
||||||
|
|
@ -90,7 +156,7 @@ const TransactionsList = ({ data, loading }) => {
|
||||||
</H4>
|
</H4>
|
||||||
</div>
|
</div>
|
||||||
</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 CustomerDetails from './CustomerDetails'
|
||||||
import IdCardPhotoCard from './IdCardPhotoCard'
|
|
||||||
import IdDataCard from './IdDataCard'
|
import IdDataCard from './IdDataCard'
|
||||||
import PhoneCard from './PhoneCard'
|
|
||||||
import TransactionsList from './TransactionsList'
|
import TransactionsList from './TransactionsList'
|
||||||
|
|
||||||
export {
|
export { CustomerDetails, IdDataCard, TransactionsList, ComplianceDetails }
|
||||||
CustomerDetails,
|
|
||||||
IdDataCard,
|
|
||||||
IdCardPhotoCard,
|
|
||||||
PhoneCard,
|
|
||||||
TransactionsList
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { makeStyles } from '@material-ui/core/styles'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
|
||||||
|
import { MainStatus } from 'src/components/Status'
|
||||||
import { ActionButton } from 'src/components/buttons'
|
import { ActionButton } from 'src/components/buttons'
|
||||||
import { H3 } from 'src/components/typography'
|
import { H3 } from 'src/components/typography'
|
||||||
import { ReactComponent as AuthorizeReversedIcon } from 'src/styling/icons/button/authorize/white.svg'
|
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 }) => {
|
({ className, title, state, authorize, reject, children }) => {
|
||||||
const classes = useStyles()
|
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 = {
|
const label1ClassNames = {
|
||||||
[classes.label1]: true,
|
[classes.label1]: true,
|
||||||
[classes.label1Pending]: state === OVERRIDE_PENDING,
|
[classes.label1Pending]: state === OVERRIDE_PENDING,
|
||||||
|
|
@ -58,29 +52,27 @@ const PropertyCard = memo(
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)
|
)
|
||||||
|
|
||||||
const authorizedAsString =
|
const authorized =
|
||||||
state === OVERRIDE_PENDING
|
state === OVERRIDE_PENDING
|
||||||
? 'Pending'
|
? { label: 'Pending', type: 'neutral' }
|
||||||
: state === OVERRIDE_REJECTED
|
: state === OVERRIDE_REJECTED
|
||||||
? 'Rejected'
|
? { label: 'Rejected', type: 'error' }
|
||||||
: 'Accepted'
|
: { label: 'Accepted', type: 'success' }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
className={classnames(propertyCardClassNames, className)}
|
className={classnames(classes.propertyCard, className)}
|
||||||
elevation={0}>
|
elevation={0}>
|
||||||
<div className={classes.rowSpaceBetween}>
|
<H3 className={classes.propertyCardTopRow}>{title}</H3>
|
||||||
<H3>{title}</H3>
|
<div className={classes.propertyCardBottomRow}>
|
||||||
<div className={classnames(label1ClassNames)}>
|
<div className={classnames(label1ClassNames)}>
|
||||||
{authorizedAsString}
|
<MainStatus statuses={[authorized]} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<Paper className={classes.cardProperties} elevation={0}>
|
|
||||||
{children}
|
{children}
|
||||||
</Paper>
|
<div className={classes.buttonsWrapper}>
|
||||||
<div className={classes.buttonsWrapper}>
|
{authorize && state !== OVERRIDE_AUTHORIZED && AuthorizeButton()}
|
||||||
{state !== OVERRIDE_AUTHORIZED && AuthorizeButton()}
|
{reject && state !== OVERRIDE_REJECTED && RejectButton()}
|
||||||
{state !== OVERRIDE_REJECTED && RejectButton()}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,11 @@
|
||||||
import {
|
import { white, tomato, spring4, comet } from 'src/styling/variables'
|
||||||
white,
|
|
||||||
zircon,
|
|
||||||
mistyRose,
|
|
||||||
tomato,
|
|
||||||
spring3,
|
|
||||||
spring4,
|
|
||||||
comet,
|
|
||||||
fontSize5
|
|
||||||
} from 'src/styling/variables'
|
|
||||||
|
|
||||||
const propertyCardStyles = {
|
const propertyCardStyles = {
|
||||||
propertyCard: {
|
|
||||||
margin: [[32, 12, 0, 0]],
|
|
||||||
padding: [[0, 16]],
|
|
||||||
borderRadius: 8
|
|
||||||
},
|
|
||||||
propertyCardPending: {
|
|
||||||
backgroundColor: zircon
|
|
||||||
},
|
|
||||||
propertyCardRejected: {
|
|
||||||
backgroundColor: mistyRose
|
|
||||||
},
|
|
||||||
propertyCardAccepted: {
|
|
||||||
backgroundColor: spring3
|
|
||||||
},
|
|
||||||
label1: {
|
label1: {
|
||||||
fontFamily: 'MuseoSans',
|
display: 'flex',
|
||||||
fontSize: fontSize5,
|
marginBottom: 2,
|
||||||
fontWeight: 500,
|
marginTop: 'auto',
|
||||||
fontStretch: 'normal',
|
width: 85
|
||||||
fontStyle: 'normal',
|
|
||||||
lineHeight: 1.33,
|
|
||||||
letterSpacing: 'normal',
|
|
||||||
color: comet,
|
|
||||||
margin: [[4, 0]]
|
|
||||||
},
|
},
|
||||||
label1Pending: {
|
label1Pending: {
|
||||||
color: comet
|
color: comet
|
||||||
|
|
@ -45,19 +17,32 @@ const propertyCardStyles = {
|
||||||
color: spring4
|
color: spring4
|
||||||
},
|
},
|
||||||
cardActionButton: {
|
cardActionButton: {
|
||||||
|
display: 'flex',
|
||||||
height: 28,
|
height: 28,
|
||||||
|
marginRight: 'auto',
|
||||||
marginLeft: 12
|
marginLeft: 12
|
||||||
},
|
},
|
||||||
cardProperties: {
|
propertyCardTopRow: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
margin: [[0, 10, 5, 0]]
|
||||||
|
},
|
||||||
|
propertyCardBottomRow: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 45
|
||||||
|
},
|
||||||
|
propertyCard: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: 'calc(100% - 104px)',
|
height: 100,
|
||||||
padding: [[20]],
|
padding: [[20]],
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
boxShadow: '0 0 8px 0 rgba(0, 0, 0, 0.04)',
|
boxShadow: '0 0 8px 0 rgba(0, 0, 0, 0.04)',
|
||||||
border: 'solid 0',
|
border: 'solid 0',
|
||||||
backgroundColor: white
|
backgroundColor: white,
|
||||||
|
margin: [[20, 0, 0, 0]]
|
||||||
},
|
},
|
||||||
rowSpaceBetween: {
|
rowSpaceBetween: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
@ -65,11 +50,19 @@ const propertyCardStyles = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between'
|
justifyContent: 'space-between'
|
||||||
},
|
},
|
||||||
|
columnSpaceBetween: {
|
||||||
|
display: 'flex',
|
||||||
|
flexFlow: 'column nowrap',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: 90
|
||||||
|
},
|
||||||
buttonsWrapper: {
|
buttonsWrapper: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
marginTop: 16,
|
marginLeft: 'auto',
|
||||||
marginBottom: 16
|
marginTop: 'auto'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
|
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
<defs>
|
<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>
|
</defs>
|
||||||
<g id="icon/action/close/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="icon/action/close/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<mask id="mask-2" fill="white">
|
<mask id="close_mask-2" fill="white">
|
||||||
<use xlink:href="#path-1"></use>
|
<use xlink:href="#close_path-1"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<use id="Mask" fill="#FFFFFF" xlink:href="#path-1"></use>
|
<use id="close_Mask" fill="#FFFFFF" xlink:href="#close_path-1"></use>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</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": {
|
"got": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz",
|
||||||
"integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==",
|
"integrity": "sha1-BUUP2ECU5rvqVvRRpDqcKJFmOFo=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"decompress-response": "^3.2.0",
|
"decompress-response": "^3.2.0",
|
||||||
"duplexer3": "^0.1.4",
|
"duplexer3": "^0.1.4",
|
||||||
|
|
@ -5801,7 +5801,7 @@
|
||||||
"isurl": {
|
"isurl": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz",
|
||||||
"integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==",
|
"integrity": "sha1-sn9PSfPNqj6kSgpbfzRi5u3DnWc=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-to-string-tag-x": "^1.2.0",
|
"has-to-string-tag-x": "^1.2.0",
|
||||||
"is-object": "^1.0.1"
|
"is-object": "^1.0.1"
|
||||||
|
|
@ -6571,7 +6571,7 @@
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
|
@ -6915,7 +6915,7 @@
|
||||||
"npmlog": {
|
"npmlog": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
"integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"are-we-there-yet": "~1.1.2",
|
"are-we-there-yet": "~1.1.2",
|
||||||
"console-control-strings": "~1.1.0",
|
"console-control-strings": "~1.1.0",
|
||||||
|
|
@ -7216,7 +7216,7 @@
|
||||||
"p-cancelable": {
|
"p-cancelable": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz",
|
||||||
"integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw=="
|
"integrity": "sha1-ueEjgAvOu3rBOkeb4ZW1B7mNMPo="
|
||||||
},
|
},
|
||||||
"p-defer": {
|
"p-defer": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue