feat: add photos list and carousel
This commit is contained in:
parent
9553bf8fc9
commit
c93e1028a4
14 changed files with 342 additions and 202 deletions
|
|
@ -683,18 +683,18 @@ 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_at, 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_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||||
sanctions_override, total_txs, total_spent, created AS last_active, fiat AS last_tx_fiat,
|
sanctions_override, total_txs, total_spent, created AS last_active, fiat AS last_tx_fiat,
|
||||||
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes
|
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes
|
||||||
FROM (
|
FROM (
|
||||||
SELECT c.id, c.authorized_override,
|
SELECT c.id, c.authorized_override,
|
||||||
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
||||||
c.suspended_until > now() AS is_suspended,
|
c.suspended_until > now() AS is_suspended,
|
||||||
c.front_camera_path, c.front_camera_override,
|
c.front_camera_path, c.front_camera_override, c.front_camera_at,
|
||||||
c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
|
c.phone, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
|
||||||
c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions,
|
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
|
||||||
c.sanctions_at, c.sanctions_override, c.subscriber_info, t.tx_class, t.fiat, t.fiat_code, t.created, cn.notes,
|
c.sanctions_at, c.sanctions_override, c.subscriber_info, t.tx_class, t.fiat, t.fiat_code, t.created, cn.notes,
|
||||||
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,
|
||||||
|
|
|
||||||
68
new-lamassu-admin/src/pages/Customers/CustomerPhotos.js
Normal file
68
new-lamassu-admin/src/pages/Customers/CustomerPhotos.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { makeStyles, Paper } from '@material-ui/core'
|
||||||
|
import { format } from 'date-fns/fp'
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import { React, useState } from 'react'
|
||||||
|
|
||||||
|
import { InformativeDialog } from 'src/components/InformativeDialog'
|
||||||
|
import { Label2, H3 } from 'src/components/typography'
|
||||||
|
import { ReactComponent as CameraIcon } from 'src/styling/icons/ID/photo/comet.svg'
|
||||||
|
import { URI } from 'src/utils/apollo'
|
||||||
|
|
||||||
|
import styles from './CustomerPhotos.styles'
|
||||||
|
import PhotosCarousel from './components/PhotosCarousel'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const CustomerPhotos = ({ photosData, set }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const [photosDialog, setPhotosDialog] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={classes.header}>
|
||||||
|
<H3 className={classes.title}>{'Photos & files'}</H3>
|
||||||
|
</div>
|
||||||
|
<div className={classes.photosChipList}>
|
||||||
|
{R.map(
|
||||||
|
it => (
|
||||||
|
<PhotoCard
|
||||||
|
date={it.date}
|
||||||
|
src={`${URI}/${it.photoDir}/${it.path}`}
|
||||||
|
setPhotosDialog={setPhotosDialog}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
photosData
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<InformativeDialog
|
||||||
|
open={photosDialog}
|
||||||
|
title={`Photo roll`}
|
||||||
|
data={<PhotosCarousel photosData={photosData} />}
|
||||||
|
onDissmised={() => {
|
||||||
|
setPhotosDialog(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PhotoCard = ({ date, src, setPhotosDialog }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
className={classes.photoCardChip}
|
||||||
|
onClick={() => setPhotosDialog(true)}>
|
||||||
|
<img className={classes.image} src={src} alt="" />
|
||||||
|
<div className={classes.footer}>
|
||||||
|
<CameraIcon />
|
||||||
|
<Label2 className={classes.date}>
|
||||||
|
{format('yyyy-MM-dd', new Date(date))}
|
||||||
|
</Label2>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomerPhotos
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
const styles = {
|
||||||
|
header: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
marginTop: 7,
|
||||||
|
marginRight: 24,
|
||||||
|
marginBottom: 32
|
||||||
|
},
|
||||||
|
photosChipList: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap'
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'center',
|
||||||
|
width: 224,
|
||||||
|
height: 200
|
||||||
|
},
|
||||||
|
photoCardChip: {
|
||||||
|
margin: [[0, 16, 0, 0]]
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
margin: [[8, 0, 0, 8]]
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
margin: [[0, 0, 8, 12]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styles
|
||||||
|
|
@ -24,6 +24,7 @@ import { fromNamespace, namespaces } from 'src/utils/config'
|
||||||
|
|
||||||
import CustomerData from './CustomerData'
|
import CustomerData from './CustomerData'
|
||||||
import CustomerNotes from './CustomerNotes'
|
import CustomerNotes from './CustomerNotes'
|
||||||
|
import CustomerPhotos from './CustomerPhotos'
|
||||||
import styles from './CustomerProfile.styles'
|
import styles from './CustomerProfile.styles'
|
||||||
import {
|
import {
|
||||||
CustomerDetails,
|
CustomerDetails,
|
||||||
|
|
@ -31,7 +32,7 @@ import {
|
||||||
CustomerSidebar,
|
CustomerSidebar,
|
||||||
Wizard
|
Wizard
|
||||||
} from './components'
|
} from './components'
|
||||||
import { getFormattedPhone, getName } from './helper'
|
import { getFormattedPhone, getName, formatPhotosData } from './helper'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
|
@ -367,6 +368,18 @@ const CustomerProfile = memo(() => {
|
||||||
const isCustomerData = clickedItem === 'customerData'
|
const isCustomerData = clickedItem === 'customerData'
|
||||||
const isOverview = clickedItem === 'overview'
|
const isOverview = clickedItem === 'overview'
|
||||||
const isNotes = clickedItem === 'notes'
|
const isNotes = clickedItem === 'notes'
|
||||||
|
const isPhotos = clickedItem === 'photos'
|
||||||
|
|
||||||
|
const frontCameraData = R.pick(['frontCameraPath', 'frontCameraAt'])(
|
||||||
|
customerData
|
||||||
|
)
|
||||||
|
const txPhotosData =
|
||||||
|
sortedTransactions &&
|
||||||
|
R.map(R.pick(['id', 'txCustomerPhotoPath', 'txCustomerPhotoAt']))(
|
||||||
|
sortedTransactions
|
||||||
|
)
|
||||||
|
|
||||||
|
const photosData = formatPhotosData(R.append(frontCameraData, txPhotosData))
|
||||||
|
|
||||||
const loading = customerLoading && configLoading
|
const loading = customerLoading && configLoading
|
||||||
|
|
||||||
|
|
@ -488,6 +501,7 @@ const CustomerProfile = memo(() => {
|
||||||
justifyContent="space-between">
|
justifyContent="space-between">
|
||||||
<CustomerDetails
|
<CustomerDetails
|
||||||
customer={customerData}
|
customer={customerData}
|
||||||
|
photosData={photosData}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
setShowCompliance={() => setShowCompliance(!showCompliance)}
|
setShowCompliance={() => setShowCompliance(!showCompliance)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -524,6 +538,11 @@ const CustomerProfile = memo(() => {
|
||||||
timezone={timezone}></CustomerNotes>
|
timezone={timezone}></CustomerNotes>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isPhotos && (
|
||||||
|
<div>
|
||||||
|
<CustomerPhotos photosData={photosData} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{wizard && (
|
{wizard && (
|
||||||
<Wizard
|
<Wizard
|
||||||
|
|
|
||||||
|
|
@ -12,88 +12,73 @@ import PhotosCard from './PhotosCard'
|
||||||
|
|
||||||
const useStyles = makeStyles(mainStyles)
|
const useStyles = makeStyles(mainStyles)
|
||||||
|
|
||||||
const CustomerDetails = memo(
|
const CustomerDetails = memo(({ customer, photosData, locale }) => {
|
||||||
({ txData, customer, locale, setShowCompliance }) => {
|
const classes = useStyles()
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const idNumber = R.path(['idCardData', 'documentNumber'])(customer)
|
const idNumber = R.path(['idCardData', 'documentNumber'])(customer)
|
||||||
const usSsn = R.path(['usSsn'])(customer)
|
const usSsn = R.path(['usSsn'])(customer)
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
header: 'Phone number',
|
header: 'Phone number',
|
||||||
size: 172,
|
size: 172,
|
||||||
value: getFormattedPhone(customer.phone, locale.country)
|
value: getFormattedPhone(customer.phone, locale.country)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if (idNumber)
|
if (idNumber)
|
||||||
elements.push({
|
elements.push({
|
||||||
header: 'ID number',
|
header: 'ID number',
|
||||||
size: 172,
|
size: 172,
|
||||||
value: idNumber
|
value: idNumber
|
||||||
})
|
})
|
||||||
|
|
||||||
if (usSsn)
|
if (usSsn)
|
||||||
elements.push({
|
elements.push({
|
||||||
header: 'US SSN',
|
header: 'US SSN',
|
||||||
size: 127,
|
size: 127,
|
||||||
value: usSsn
|
value: usSsn
|
||||||
})
|
})
|
||||||
|
|
||||||
const name = getName(customer)
|
const name = getName(customer)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex">
|
<Box display="flex">
|
||||||
<PhotosCard
|
<PhotosCard photosData={photosData} />
|
||||||
frontCameraData={R.pick(['frontCameraPath', 'frontCameraAt'])(
|
<Box display="flex" flexDirection="column">
|
||||||
customer
|
<div className={classes.name}>
|
||||||
)}
|
<IdIcon className={classes.idIcon} />
|
||||||
txPhotosData={
|
<H2 noMargin>
|
||||||
txData &&
|
{name.length
|
||||||
R.map(R.pick(['id', 'txCustomerPhotoPath', 'txCustomerPhotoAt']))(
|
? name
|
||||||
txData
|
: getFormattedPhone(R.path(['phone'])(customer), locale.country)}
|
||||||
)
|
</H2>
|
||||||
}
|
</div>
|
||||||
/>
|
<Box display="flex" mt="auto">
|
||||||
<Box display="flex" flexDirection="column">
|
{elements.map(({ size, header }, idx) => (
|
||||||
<div className={classes.name}>
|
<Label1
|
||||||
<IdIcon className={classes.idIcon} />
|
noMargin
|
||||||
<H2 noMargin>
|
key={idx}
|
||||||
{name.length
|
className={classes.label}
|
||||||
? name
|
style={{ width: size }}>
|
||||||
: getFormattedPhone(
|
{header}
|
||||||
R.path(['phone'])(customer),
|
</Label1>
|
||||||
locale.country
|
))}
|
||||||
)}
|
</Box>
|
||||||
</H2>
|
<Box display="flex">
|
||||||
</div>
|
{elements.map(({ size, value }, idx) => (
|
||||||
<Box display="flex" mt="auto">
|
<P
|
||||||
{elements.map(({ size, header }, idx) => (
|
noMargin
|
||||||
<Label1
|
key={idx}
|
||||||
noMargin
|
className={classes.value}
|
||||||
key={idx}
|
style={{ width: size }}>
|
||||||
className={classes.label}
|
{value}
|
||||||
style={{ width: size }}>
|
</P>
|
||||||
{header}
|
))}
|
||||||
</Label1>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
<Box display="flex">
|
|
||||||
{elements.map(({ size, value }, idx) => (
|
|
||||||
<P
|
|
||||||
noMargin
|
|
||||||
key={idx}
|
|
||||||
className={classes.value}
|
|
||||||
style={{ width: size }}>
|
|
||||||
{value}
|
|
||||||
</P>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
</Box>
|
||||||
}
|
)
|
||||||
)
|
})
|
||||||
|
|
||||||
export default CustomerDetails
|
export default CustomerDetails
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import { ReactComponent as NoteReversedIcon } from 'src/styling/icons/customer-n
|
||||||
import { ReactComponent as NoteIcon } from 'src/styling/icons/customer-nav/note/white.svg'
|
import { ReactComponent as NoteIcon } from 'src/styling/icons/customer-nav/note/white.svg'
|
||||||
import { ReactComponent as OverviewReversedIcon } from 'src/styling/icons/customer-nav/overview/comet.svg'
|
import { ReactComponent as OverviewReversedIcon } from 'src/styling/icons/customer-nav/overview/comet.svg'
|
||||||
import { ReactComponent as OverviewIcon } from 'src/styling/icons/customer-nav/overview/white.svg'
|
import { ReactComponent as OverviewIcon } from 'src/styling/icons/customer-nav/overview/white.svg'
|
||||||
|
import { ReactComponent as PhotosReversedIcon } from 'src/styling/icons/customer-nav/photos/comet.svg'
|
||||||
|
import { ReactComponent as Photos } from 'src/styling/icons/customer-nav/photos/white.svg'
|
||||||
|
|
||||||
import styles from './CustomerSidebar.styles.js'
|
import styles from './CustomerSidebar.styles.js'
|
||||||
|
|
||||||
|
|
@ -33,6 +35,12 @@ const CustomerSidebar = ({ isSelected, onClick }) => {
|
||||||
display: 'Notes',
|
display: 'Notes',
|
||||||
Icon: NoteIcon,
|
Icon: NoteIcon,
|
||||||
InverseIcon: NoteReversedIcon
|
InverseIcon: NoteReversedIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'photos',
|
||||||
|
display: 'Photos & files',
|
||||||
|
Icon: Photos,
|
||||||
|
InverseIcon: PhotosReversedIcon
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,58 +4,21 @@ 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 'src/components/Carousel'
|
|
||||||
import { InformativeDialog } from 'src/components/InformativeDialog'
|
import { InformativeDialog } from 'src/components/InformativeDialog'
|
||||||
import { Info2, Label1 } from 'src/components/typography'
|
import { Info2 } from 'src/components/typography'
|
||||||
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
|
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
|
||||||
import { URI } from 'src/utils/apollo'
|
import { URI } from 'src/utils/apollo'
|
||||||
|
|
||||||
import CopyToClipboard from '../../Transactions/CopyToClipboard'
|
|
||||||
|
|
||||||
import styles from './PhotosCard.styles'
|
import styles from './PhotosCard.styles'
|
||||||
|
import PhotosCarousel from './PhotosCarousel'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const Label = ({ children }) => {
|
const PhotosCard = memo(({ photosData }) => {
|
||||||
const classes = useStyles()
|
|
||||||
return <Label1 className={classes.label}>{children}</Label1>
|
|
||||||
}
|
|
||||||
|
|
||||||
const PhotosCard = memo(({ frontCameraData, txPhotosData }) => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [photosDialog, setPhotosDialog] = useState(false)
|
const [photosDialog, setPhotosDialog] = useState(false)
|
||||||
|
|
||||||
const mapKeys = pair => {
|
|
||||||
const [key, value] = pair
|
|
||||||
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
|
|
||||||
return ['path', value]
|
|
||||||
}
|
|
||||||
if (key === 'txCustomerPhotoAt' || key === 'frontCameraAt') {
|
|
||||||
return ['date', value]
|
|
||||||
}
|
|
||||||
return pair
|
|
||||||
}
|
|
||||||
|
|
||||||
const addPhotoDir = R.map(it => {
|
|
||||||
const hasFrontCameraData = R.has('id')(it)
|
|
||||||
return hasFrontCameraData
|
|
||||||
? { ...it, photoDir: 'operator-data/customersphotos' }
|
|
||||||
: { ...it, photoDir: 'front-camera-photo' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const standardizeKeys = R.map(
|
|
||||||
R.compose(R.fromPairs, R.map(mapKeys), R.toPairs)
|
|
||||||
)
|
|
||||||
|
|
||||||
const filterByPhotoAvailable = R.filter(
|
|
||||||
tx => !R.isNil(tx.date) && !R.isNil(tx.path)
|
|
||||||
)
|
|
||||||
|
|
||||||
const photosData = filterByPhotoAvailable(
|
|
||||||
addPhotoDir(standardizeKeys(R.append(frontCameraData, txPhotosData)))
|
|
||||||
)
|
|
||||||
|
|
||||||
const singlePhoto = R.head(photosData)
|
const singlePhoto = R.head(photosData)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -97,41 +60,4 @@ const PhotosCard = memo(({ frontCameraData, txPhotosData }) => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const PhotosCarousel = memo(({ photosData }) => {
|
|
||||||
const classes = useStyles()
|
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
|
||||||
|
|
||||||
const isFaceCustomerPhoto = !R.has('id')(photosData[currentIndex])
|
|
||||||
|
|
||||||
const slidePhoto = index => setCurrentIndex(index)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Carousel photosData={photosData} slidePhoto={slidePhoto} />
|
|
||||||
{!isFaceCustomerPhoto && (
|
|
||||||
<div className={classes.firstRow}>
|
|
||||||
<Label>Session ID</Label>
|
|
||||||
<CopyToClipboard>
|
|
||||||
{photosData && photosData[currentIndex]?.id}
|
|
||||||
</CopyToClipboard>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={classes.secondRow}>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<Label>Date</Label>
|
|
||||||
<div>{photosData && photosData[currentIndex]?.date}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label>Taken by</Label>
|
|
||||||
<div>
|
|
||||||
{!isFaceCustomerPhoto ? 'Acceptance of T&C' : 'Compliance scan'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default PhotosCard
|
export default PhotosCard
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import typographyStyles from 'src/components/typography/styles'
|
import { zircon, backgroundColor } from 'src/styling/variables'
|
||||||
import { zircon, backgroundColor, offColor } from 'src/styling/variables'
|
|
||||||
|
|
||||||
const { p } = typographyStyles
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
photo: {
|
photo: {
|
||||||
|
|
@ -41,43 +38,5 @@ export default {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
},
|
|
||||||
label: {
|
|
||||||
color: offColor,
|
|
||||||
margin: [[0, 0, 6, 0]]
|
|
||||||
},
|
|
||||||
firstRow: {
|
|
||||||
padding: [[8]],
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column'
|
|
||||||
},
|
|
||||||
secondRow: {
|
|
||||||
extend: p,
|
|
||||||
display: 'flex',
|
|
||||||
padding: [[8]],
|
|
||||||
'& > div': {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
'& > div': {
|
|
||||||
width: 144,
|
|
||||||
height: 37,
|
|
||||||
marginBottom: 15,
|
|
||||||
marginRight: 55
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
imgWrapper: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
display: 'flex',
|
|
||||||
width: 550,
|
|
||||||
height: 550
|
|
||||||
},
|
|
||||||
imgInner: {
|
|
||||||
objectFit: 'cover',
|
|
||||||
objectPosition: 'center',
|
|
||||||
width: 550,
|
|
||||||
height: 550,
|
|
||||||
marginBottom: 40
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import React, { memo, useState } from 'react'
|
||||||
|
|
||||||
|
import { Carousel } from 'src/components/Carousel'
|
||||||
|
import { Label1 } from 'src/components/typography'
|
||||||
|
|
||||||
|
import CopyToClipboard from '../../Transactions/CopyToClipboard'
|
||||||
|
|
||||||
|
import styles from './PhotosCarousel.styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const PhotosCarousel = memo(({ photosData }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0)
|
||||||
|
|
||||||
|
const Label = ({ children }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
return <Label1 className={classes.label}>{children}</Label1>
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFaceCustomerPhoto = !R.has('id')(photosData[currentIndex])
|
||||||
|
|
||||||
|
const slidePhoto = index => setCurrentIndex(index)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Carousel photosData={photosData} slidePhoto={slidePhoto} />
|
||||||
|
{!isFaceCustomerPhoto && (
|
||||||
|
<div className={classes.firstRow}>
|
||||||
|
<Label>Session ID</Label>
|
||||||
|
<CopyToClipboard>
|
||||||
|
{photosData && photosData[currentIndex]?.id}
|
||||||
|
</CopyToClipboard>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={classes.secondRow}>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Label>Date</Label>
|
||||||
|
<div>{photosData && photosData[currentIndex]?.date}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Taken by</Label>
|
||||||
|
<div>
|
||||||
|
{!isFaceCustomerPhoto ? 'Acceptance of T&C' : 'Compliance scan'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default PhotosCarousel
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
|
import { offColor } from 'src/styling/variables'
|
||||||
|
|
||||||
|
const { p } = typographyStyles
|
||||||
|
|
||||||
|
export default {
|
||||||
|
label: {
|
||||||
|
color: offColor,
|
||||||
|
margin: [[0, 0, 6, 0]]
|
||||||
|
},
|
||||||
|
firstRow: {
|
||||||
|
padding: [[8]],
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
secondRow: {
|
||||||
|
extend: p,
|
||||||
|
display: 'flex',
|
||||||
|
padding: [[8]],
|
||||||
|
'& > div': {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
'& > div': {
|
||||||
|
width: 144,
|
||||||
|
height: 37,
|
||||||
|
marginBottom: 15,
|
||||||
|
marginRight: 55
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,10 +5,12 @@ import CustomerSidebar from './CustomerSidebar'
|
||||||
import EditableCard from './EditableCard'
|
import EditableCard from './EditableCard'
|
||||||
import Field from './Field'
|
import Field from './Field'
|
||||||
import IdDataCard from './IdDataCard'
|
import IdDataCard from './IdDataCard'
|
||||||
|
import PhotosCarousel from './PhotosCarousel'
|
||||||
import TransactionsList from './TransactionsList'
|
import TransactionsList from './TransactionsList'
|
||||||
import Upload from './Upload'
|
import Upload from './Upload'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
PhotosCarousel,
|
||||||
CustomerDetails,
|
CustomerDetails,
|
||||||
IdDataCard,
|
IdDataCard,
|
||||||
TransactionsList,
|
TransactionsList,
|
||||||
|
|
|
||||||
|
|
@ -209,10 +209,41 @@ const entryType = {
|
||||||
initialValues: { entryType: '' }
|
initialValues: { entryType: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapKeys = pair => {
|
||||||
|
const [key, value] = pair
|
||||||
|
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
|
||||||
|
return ['path', value]
|
||||||
|
}
|
||||||
|
if (key === 'txCustomerPhotoAt' || key === 'frontCameraAt') {
|
||||||
|
return ['date', value]
|
||||||
|
}
|
||||||
|
return pair
|
||||||
|
}
|
||||||
|
|
||||||
|
const addPhotoDir = R.map(it => {
|
||||||
|
const hasFrontCameraData = R.has('id')(it)
|
||||||
|
return hasFrontCameraData
|
||||||
|
? { ...it, photoDir: 'operator-data/customersphotos' }
|
||||||
|
: { ...it, photoDir: 'front-camera-photo' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const standardizeKeys = R.map(R.compose(R.fromPairs, R.map(mapKeys), R.toPairs))
|
||||||
|
|
||||||
|
const filterByPhotoAvailable = R.filter(
|
||||||
|
tx => !R.isNil(tx.date) && !R.isNil(tx.path)
|
||||||
|
)
|
||||||
|
|
||||||
|
const formatPhotosData = R.compose(
|
||||||
|
filterByPhotoAvailable,
|
||||||
|
addPhotoDir,
|
||||||
|
standardizeKeys
|
||||||
|
)
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getAuthorizedStatus,
|
getAuthorizedStatus,
|
||||||
getFormattedPhone,
|
getFormattedPhone,
|
||||||
getName,
|
getName,
|
||||||
entryType,
|
entryType,
|
||||||
customElements
|
customElements,
|
||||||
|
formatPhotosData
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?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/customer-nav/photos/comet</title>
|
||||||
|
<g id="icon/customer-nav/photos/comet" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<rect id="Rectangle" stroke="#5F668A" stroke-width="2" x="1" y="1" width="18" height="18" rx="1"></rect>
|
||||||
|
<circle id="Oval" stroke="#5F668A" stroke-width="2" cx="15" cy="5" r="1"></circle>
|
||||||
|
<polyline id="Path" stroke="#5F668A" stroke-width="2" stroke-linejoin="round" points="1 19 7 13 13 19"></polyline>
|
||||||
|
<path d="M13.3333333,14 L18,19 L13.3333333,19 L11,16.5 L13.3333333,14 Z" id="Combined-Shape" stroke="#5F668A" stroke-width="2" stroke-linejoin="round"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 850 B |
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?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/customer-nav/photos/white</title>
|
||||||
|
<g id="icon/customer-nav/photos/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<rect id="Rectangle" stroke="#FFFFFF" stroke-width="2" x="1" y="1" width="18" height="18" rx="1"></rect>
|
||||||
|
<circle id="Oval" stroke="#FFFFFF" stroke-width="2" cx="15" cy="5" r="1"></circle>
|
||||||
|
<polyline id="Path" stroke="#FFFFFF" stroke-width="2" stroke-linejoin="round" points="1 19 7 13 13 19"></polyline>
|
||||||
|
<path d="M13.3333333,14 L18,19 L13.3333333,19 L11,16.5 L13.3333333,14 Z" id="Combined-Shape" stroke="#FFFFFF" stroke-width="2" stroke-linejoin="round"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 850 B |
Loading…
Add table
Add a link
Reference in a new issue