feat: compliance and t&c photos displayed on carousel

This commit is contained in:
José Oliveira 2021-08-03 14:27:40 +01:00
parent 4f7960a59c
commit 4d7a252710
7 changed files with 143 additions and 37 deletions

View file

@ -519,7 +519,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
*/ */
function getCustomerById (id) { function getCustomerById (id) {
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',') const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
const sql = `select id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_override, const sql = `select id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override,
phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration, 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, 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, sanctions_override, total_txs, total_spent, created as last_active, fiat as last_tx_fiat,

View file

@ -6057,6 +6057,11 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"@types/source-list-map": { "@types/source-list-map": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@ -7192,6 +7197,31 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"dev": true "dev": true
}, },
"auto-bind": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-2.1.1.tgz",
"integrity": "sha512-NUwV1i9D3vxxY1KnfZgSZ716d6ovY7o8LfOwLhGIPFBowIb6Ln6DBW64+jCqPzUznel2hRSkQnYQqvh7/ldw8A==",
"requires": {
"@types/react": "^16.8.12"
},
"dependencies": {
"@types/react": {
"version": "16.14.11",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.11.tgz",
"integrity": "sha512-Don0MtsZZ3fjwTJ2BsoqkyOy7e176KplEAKOpr/4XDdzinlyJBn9yfsKn5mcSgn4kh1B22+3tBnzBC1z63ybtQ==",
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"csstype": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
"integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
}
}
},
"autoprefixer": { "autoprefixer": {
"version": "9.8.6", "version": "9.8.6",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz",
@ -21860,6 +21890,15 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
}, },
"react-material-ui-carousel": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/react-material-ui-carousel/-/react-material-ui-carousel-2.2.7.tgz",
"integrity": "sha512-aO42C4oupmIxmJwYaTWrlWaXvVVspKcpEu/5efZ9slteATEsqqPtNAeVaE40Vimw2hZeIh2e8vpRwjq7fSsLxw==",
"requires": {
"auto-bind": "^2.1.1",
"react-swipeable": "^6.1.0"
}
},
"react-number-format": { "react-number-format": {
"version": "4.4.4", "version": "4.4.4",
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.4.4.tgz", "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.4.4.tgz",
@ -23328,6 +23367,11 @@
"throttle-debounce": "^2.1.0" "throttle-debounce": "^2.1.0"
} }
}, },
"react-swipeable": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-6.1.2.tgz",
"integrity": "sha512-vfZtOZNivwd/aI+ZZH1Grx0eQBdbV1UI3pB9p65jbW5guHHdSIPpKsND6XmaiZXP5REOOc9Ckfr36ChswPqwsA=="
},
"react-syntax-highlighter": { "react-syntax-highlighter": {
"version": "12.2.1", "version": "12.2.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",

View file

@ -35,6 +35,7 @@
"react": "^16.12.0", "react": "^16.12.0",
"react-copy-to-clipboard": "^5.0.2", "react-copy-to-clipboard": "^5.0.2",
"react-dom": "^16.10.2", "react-dom": "^16.10.2",
"react-material-ui-carousel": "^2.2.7",
"react-number-format": "^4.4.1", "react-number-format": "^4.4.1",
"react-otp-input": "^2.3.0", "react-otp-input": "^2.3.0",
"react-router-dom": "5.1.2", "react-router-dom": "5.1.2",

View file

@ -14,7 +14,7 @@ const useStyles = makeStyles({
justifyContent: 'end' justifyContent: 'end'
}, },
title: { title: {
margin: [[0, spacer * 2, spacer * 2, spacer * 2]] margin: [[0, spacer * 2, spacer * 2, spacer * 2 + 4]]
} }
}) })

View file

@ -49,8 +49,15 @@ const CustomerDetails = memo(
return ( return (
<Box display="flex"> <Box display="flex">
<PhotosCard <PhotosCard
frontCameraPath={R.path(['frontCameraPath'])(customer)} frontCameraData={R.pick(['frontCameraPath', 'frontCameraAt'])(
txData={txData} customer
)}
txPhotosData={
txData &&
R.map(R.pick(['id', 'txCustomerPhotoPath', 'txCustomerPhotoAt']))(
txData
)
}
/> />
<Box display="flex" flexDirection="column"> <Box display="flex" flexDirection="column">
<div className={classes.name}> <div className={classes.name}>

View file

@ -3,6 +3,7 @@ import Paper from '@material-ui/core/Card'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import * as R from 'ramda' import * as R from 'ramda'
import React, { memo, useState } from 'react' import React, { memo, useState } from 'react'
import Carousel from 'react-material-ui-carousel'
import { InformativeDialog } from 'src/components/InformativeDialog' import { InformativeDialog } from 'src/components/InformativeDialog'
import { Info2, Label1 } from 'src/components/typography' import { Info2, Label1 } from 'src/components/typography'
@ -20,40 +21,67 @@ const Label = ({ children }) => {
return <Label1 className={classes.label}>{children}</Label1> return <Label1 className={classes.label}>{children}</Label1>
} }
const PhotosCard = memo(({ frontCameraPath, txData }) => { const PhotosCard = memo(({ frontCameraData, txPhotosData }) => {
const classes = useStyles() const classes = useStyles()
const [photosDialog, setPhotosDialog] = useState(false) const [photosDialog, setPhotosDialog] = useState(false)
const txsWithCustomerPhoto = R.filter( const mapKeys = pair => {
tx => !R.isNil(tx.txCustomerPhotoAt) && !R.isNil(tx.txCustomerPhotoPath) const key = R.head(pair)
)(txData) const value = R.last(pair)
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
return ['path', value]
}
if (key === 'txCustomerPhotoAt' || key === 'frontCameraAt') {
return ['date', value]
}
return pair
}
const photoDir = frontCameraPath const addPhotoDir = R.map(it => {
? 'front-camera-photo' const hasFrontCameraData = R.has('id')(it)
: 'operator-data/customersphotos' return hasFrontCameraData
? { ...it, photoDir: 'operator-data/customersphotos' }
: { ...it, photoDir: 'front-camera-photo' }
})
const photo = const standardizeKeys = R.map(
frontCameraPath ?? R.head(txsWithCustomerPhoto)?.txCustomerPhotoPath R.compose(R.fromPairs, R.map(mapKeys), R.toPairs)
)
const filterByPhotoAvaiable = R.filter(
tx => !R.isNil(tx.date) && !R.isNil(tx.path)
)
const photosData = filterByPhotoAvaiable(
addPhotoDir(standardizeKeys(R.append(frontCameraData, txPhotosData)))
)
const singlePhoto = R.head(photosData)
const isPhotoRollAvailable = () => {
return !singlePhoto
}
return ( return (
<> <>
<Paper className={classes.photo} elevation={0}> <Paper className={classes.photo} elevation={0}>
<ButtonBase <ButtonBase
disabled={isPhotoRollAvailable()}
className={classes.button} className={classes.button}
onClick={() => { onClick={() => {
setPhotosDialog(true) setPhotosDialog(true)
}}> }}>
{photo ? ( {singlePhoto ? (
<div className={classes.container}> <div className={classes.container}>
<img <img
className={classes.img} className={classes.img}
src={`${URI}/${photoDir}/${photo}`} src={`${URI}/${singlePhoto.photoDir}/${singlePhoto.path}`}
alt="" alt=""
/> />
<circle className={classes.circle}> <circle className={classes.circle}>
<div> <div>
<Info2>{txsWithCustomerPhoto.length}</Info2> <Info2>{photosData.length}</Info2>
</div> </div>
</circle> </circle>
</div> </div>
@ -65,7 +93,7 @@ const PhotosCard = memo(({ frontCameraPath, txData }) => {
<InformativeDialog <InformativeDialog
open={photosDialog} open={photosDialog}
title={`Photo roll`} title={`Photo roll`}
data={<PhotosCarousel txData={txData}></PhotosCarousel>} data={<PhotosCarousel photosData={photosData} />}
onDissmised={() => { onDissmised={() => {
setPhotosDialog(false) setPhotosDialog(false)
}} }}
@ -74,38 +102,62 @@ const PhotosCard = memo(({ frontCameraPath, txData }) => {
) )
}) })
export const PhotosCarousel = memo(({ txData }) => { export const PhotosCarousel = memo(({ photosData }) => {
const classes = useStyles() const classes = useStyles()
const [currentIndex, setCurrentIndex] = useState(0)
const isFaceCustomerPhoto = !R.has('id')(photosData[currentIndex])
const slidePhoto = index => setCurrentIndex(index)
return ( return (
<> <>
<div className={classes.carousel}> <Carousel
<img navButtonsProps={{
className={classes.carouselImg} style: {
src={`${URI}/operator-data/customersphotos/${ backgroundColor: 'transparent',
R.head(txData)?.txCustomerPhotoPath borderRadius: 0,
}`} fontSize: 100
alt="" }
/> }}
</div> className={classes.slideButtons}
<div className={classes.firstRow}> autoPlay={false}
<div> indicators={false}
navButtonsAlwaysVisible={true}
next={activeIndex => slidePhoto(activeIndex)}
prev={activeIndex => slidePhoto(activeIndex)}>
{photosData.map((item, i) => (
<div> <div>
<Label>Session ID</Label> <div className={classes.imgWrapper}>
<CopyToClipboard>{txData && R.head(txData)?.id}</CopyToClipboard> <img
className={classes.imgInner}
src={`${URI}/${item?.photoDir}/${item?.path}`}
alt=""
/>
</div>
</div> </div>
))}
</Carousel>
{!isFaceCustomerPhoto && (
<div className={classes.firstRow}>
<Label>Session ID</Label>
<CopyToClipboard>
{photosData && photosData[currentIndex]?.id}
</CopyToClipboard>
</div> </div>
</div> )}
<div className={classes.secondRow}> <div className={classes.secondRow}>
<div> <div>
<div> <div>
<Label>Date</Label> <Label>Date</Label>
<div>{txData && R.head(txData)?.txCustomerPhotoAt}</div> <div>{photosData && photosData[currentIndex]?.date}</div>
</div> </div>
</div> </div>
<div> <div>
<Label>Taken by</Label> <Label>Taken by</Label>
<div>{'Acceptance of T&C'}</div> <div>
{!isFaceCustomerPhoto ? 'Acceptance of T&C' : 'Compliance scan'}
</div>
</div> </div>
</div> </div>
</> </>

View file

@ -47,7 +47,9 @@ export default {
margin: [[0, 0, 6, 0]] margin: [[0, 0, 6, 0]]
}, },
firstRow: { firstRow: {
padding: [[8]] padding: [[8]],
display: 'flex',
flexDirection: 'column'
}, },
secondRow: { secondRow: {
extend: p, extend: p,
@ -64,14 +66,14 @@ export default {
} }
} }
}, },
carousel: { imgWrapper: {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
display: 'flex', display: 'flex',
width: 550, width: 550,
height: 550 height: 550
}, },
carouselImg: { imgInner: {
objectFit: 'cover', objectFit: 'cover',
objectPosition: 'center', objectPosition: 'center',
width: 550, width: 550,