From c93e1028a428befc65c24bddaeb8b77b1adf582b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Mon, 13 Dec 2021 18:20:39 +0000 Subject: [PATCH] feat: add photos list and carousel --- lib/customers.js | 12 +- .../src/pages/Customers/CustomerPhotos.js | 68 +++++++++ .../pages/Customers/CustomerPhotos.styles.js | 35 +++++ .../src/pages/Customers/CustomerProfile.js | 21 ++- .../Customers/components/CustomerDetails.js | 135 ++++++++---------- .../Customers/components/CustomerSidebar.js | 8 ++ .../pages/Customers/components/PhotosCard.js | 80 +---------- .../Customers/components/PhotosCard.styles.js | 43 +----- .../Customers/components/PhotosCarousel.js | 56 ++++++++ .../components/PhotosCarousel.styles.js | 31 ++++ .../src/pages/Customers/components/index.js | 2 + .../src/pages/Customers/helper.js | 33 ++++- .../icons/customer-nav/photos/comet.svg | 10 ++ .../icons/customer-nav/photos/white.svg | 10 ++ 14 files changed, 342 insertions(+), 202 deletions(-) create mode 100644 new-lamassu-admin/src/pages/Customers/CustomerPhotos.js create mode 100644 new-lamassu-admin/src/pages/Customers/CustomerPhotos.styles.js create mode 100644 new-lamassu-admin/src/pages/Customers/components/PhotosCarousel.js create mode 100644 new-lamassu-admin/src/pages/Customers/components/PhotosCarousel.styles.js create mode 100644 new-lamassu-admin/src/styling/icons/customer-nav/photos/comet.svg create mode 100644 new-lamassu-admin/src/styling/icons/customer-nav/photos/white.svg diff --git a/lib/customers.js b/lib/customers.js index 4bdcd9e4..e96155f9 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -683,18 +683,18 @@ function getCustomersList (phone = null, name = null, address = null, id = null) */ function getCustomerById (id) { 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, - 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, + 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_at, id_card_data, id_card_data_override, id_card_data_expiration, + 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, fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes FROM ( SELECT c.id, c.authorized_override, greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended, c.suspended_until > now() AS is_suspended, - c.front_camera_path, c.front_camera_override, - c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration, - c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions, + c.front_camera_path, c.front_camera_override, c.front_camera_at, + 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_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, 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, diff --git a/new-lamassu-admin/src/pages/Customers/CustomerPhotos.js b/new-lamassu-admin/src/pages/Customers/CustomerPhotos.js new file mode 100644 index 00000000..ebf722f6 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/CustomerPhotos.js @@ -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 ( +
+
+

{'Photos & files'}

+
+
+ {R.map( + it => ( + + ), + photosData + )} +
+ } + onDissmised={() => { + setPhotosDialog(false) + }} + /> +
+ ) +} + +export const PhotoCard = ({ date, src, setPhotosDialog }) => { + const classes = useStyles() + + return ( + setPhotosDialog(true)}> + +
+ + + {format('yyyy-MM-dd', new Date(date))} + +
+
+ ) +} + +export default CustomerPhotos diff --git a/new-lamassu-admin/src/pages/Customers/CustomerPhotos.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerPhotos.styles.js new file mode 100644 index 00000000..88095ea3 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/CustomerPhotos.styles.js @@ -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 diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index 626c5f38..dece97bf 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -24,6 +24,7 @@ import { fromNamespace, namespaces } from 'src/utils/config' import CustomerData from './CustomerData' import CustomerNotes from './CustomerNotes' +import CustomerPhotos from './CustomerPhotos' import styles from './CustomerProfile.styles' import { CustomerDetails, @@ -31,7 +32,7 @@ import { CustomerSidebar, Wizard } from './components' -import { getFormattedPhone, getName } from './helper' +import { getFormattedPhone, getName, formatPhotosData } from './helper' const useStyles = makeStyles(styles) @@ -367,6 +368,18 @@ const CustomerProfile = memo(() => { const isCustomerData = clickedItem === 'customerData' const isOverview = clickedItem === 'overview' 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 @@ -488,6 +501,7 @@ const CustomerProfile = memo(() => { justifyContent="space-between"> setShowCompliance(!showCompliance)} /> @@ -524,6 +538,11 @@ const CustomerProfile = memo(() => { timezone={timezone}> )} + {isPhotos && ( +
+ +
+ )} {wizard && ( { - const classes = useStyles() +const CustomerDetails = memo(({ customer, photosData, locale }) => { + const classes = useStyles() - const idNumber = R.path(['idCardData', 'documentNumber'])(customer) - const usSsn = R.path(['usSsn'])(customer) + const idNumber = R.path(['idCardData', 'documentNumber'])(customer) + const usSsn = R.path(['usSsn'])(customer) - const elements = [ - { - header: 'Phone number', - size: 172, - value: getFormattedPhone(customer.phone, locale.country) - } - ] + const elements = [ + { + header: 'Phone number', + size: 172, + value: getFormattedPhone(customer.phone, locale.country) + } + ] - if (idNumber) - elements.push({ - header: 'ID number', - size: 172, - value: idNumber - }) + if (idNumber) + elements.push({ + header: 'ID number', + size: 172, + value: idNumber + }) - if (usSsn) - elements.push({ - header: 'US SSN', - size: 127, - value: usSsn - }) + if (usSsn) + elements.push({ + header: 'US SSN', + size: 127, + value: usSsn + }) - const name = getName(customer) + const name = getName(customer) - return ( - - - -
- -

- {name.length - ? name - : getFormattedPhone( - R.path(['phone'])(customer), - locale.country - )} -

-
- - {elements.map(({ size, header }, idx) => ( - - {header} - - ))} - - - {elements.map(({ size, value }, idx) => ( -

- {value} -

- ))} -
+ return ( + + + +
+ +

+ {name.length + ? name + : getFormattedPhone(R.path(['phone'])(customer), locale.country)} +

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

+ {value} +

+ ))}
- ) - } -) +
+ ) +}) export default CustomerDetails diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.js b/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.js index a6190f13..6bcf3444 100644 --- a/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.js +++ b/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.js @@ -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 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 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' @@ -33,6 +35,12 @@ const CustomerSidebar = ({ isSelected, onClick }) => { display: 'Notes', Icon: NoteIcon, InverseIcon: NoteReversedIcon + }, + { + code: 'photos', + display: 'Photos & files', + Icon: Photos, + InverseIcon: PhotosReversedIcon } ] diff --git a/new-lamassu-admin/src/pages/Customers/components/PhotosCard.js b/new-lamassu-admin/src/pages/Customers/components/PhotosCard.js index 97674ec8..0181fdca 100644 --- a/new-lamassu-admin/src/pages/Customers/components/PhotosCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/PhotosCard.js @@ -4,58 +4,21 @@ 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 { 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 { URI } from 'src/utils/apollo' -import CopyToClipboard from '../../Transactions/CopyToClipboard' - import styles from './PhotosCard.styles' +import PhotosCarousel from './PhotosCarousel' const useStyles = makeStyles(styles) -const Label = ({ children }) => { - const classes = useStyles() - return {children} -} - -const PhotosCard = memo(({ frontCameraData, txPhotosData }) => { +const PhotosCard = memo(({ photosData }) => { const classes = useStyles() 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) 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 ( - <> - - {!isFaceCustomerPhoto && ( -
- - - {photosData && photosData[currentIndex]?.id} - -
- )} -
-
-
- -
{photosData && photosData[currentIndex]?.date}
-
-
-
- -
- {!isFaceCustomerPhoto ? 'Acceptance of T&C' : 'Compliance scan'} -
-
-
- - ) -}) - export default PhotosCard diff --git a/new-lamassu-admin/src/pages/Customers/components/PhotosCard.styles.js b/new-lamassu-admin/src/pages/Customers/components/PhotosCard.styles.js index 002c5bfb..5d974078 100644 --- a/new-lamassu-admin/src/pages/Customers/components/PhotosCard.styles.js +++ b/new-lamassu-admin/src/pages/Customers/components/PhotosCard.styles.js @@ -1,7 +1,4 @@ -import typographyStyles from 'src/components/typography/styles' -import { zircon, backgroundColor, offColor } from 'src/styling/variables' - -const { p } = typographyStyles +import { zircon, backgroundColor } from 'src/styling/variables' export default { photo: { @@ -41,43 +38,5 @@ export default { alignItems: 'center', justifyContent: 'center', 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 } } diff --git a/new-lamassu-admin/src/pages/Customers/components/PhotosCarousel.js b/new-lamassu-admin/src/pages/Customers/components/PhotosCarousel.js new file mode 100644 index 00000000..e0d6f2e0 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/PhotosCarousel.js @@ -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 {children} + } + + const isFaceCustomerPhoto = !R.has('id')(photosData[currentIndex]) + + const slidePhoto = index => setCurrentIndex(index) + + return ( + <> + + {!isFaceCustomerPhoto && ( +
+ + + {photosData && photosData[currentIndex]?.id} + +
+ )} +
+
+
+ +
{photosData && photosData[currentIndex]?.date}
+
+
+
+ +
+ {!isFaceCustomerPhoto ? 'Acceptance of T&C' : 'Compliance scan'} +
+
+
+ + ) +}) + +export default PhotosCarousel diff --git a/new-lamassu-admin/src/pages/Customers/components/PhotosCarousel.styles.js b/new-lamassu-admin/src/pages/Customers/components/PhotosCarousel.styles.js new file mode 100644 index 00000000..5c568cd9 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/PhotosCarousel.styles.js @@ -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 + } + } + } +} diff --git a/new-lamassu-admin/src/pages/Customers/components/index.js b/new-lamassu-admin/src/pages/Customers/components/index.js index 5e52dd81..7e3c3e19 100644 --- a/new-lamassu-admin/src/pages/Customers/components/index.js +++ b/new-lamassu-admin/src/pages/Customers/components/index.js @@ -5,10 +5,12 @@ import CustomerSidebar from './CustomerSidebar' import EditableCard from './EditableCard' import Field from './Field' import IdDataCard from './IdDataCard' +import PhotosCarousel from './PhotosCarousel' import TransactionsList from './TransactionsList' import Upload from './Upload' export { + PhotosCarousel, CustomerDetails, IdDataCard, TransactionsList, diff --git a/new-lamassu-admin/src/pages/Customers/helper.js b/new-lamassu-admin/src/pages/Customers/helper.js index a5d776cc..719ae628 100644 --- a/new-lamassu-admin/src/pages/Customers/helper.js +++ b/new-lamassu-admin/src/pages/Customers/helper.js @@ -209,10 +209,41 @@ const 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 { getAuthorizedStatus, getFormattedPhone, getName, entryType, - customElements + customElements, + formatPhotosData } diff --git a/new-lamassu-admin/src/styling/icons/customer-nav/photos/comet.svg b/new-lamassu-admin/src/styling/icons/customer-nav/photos/comet.svg new file mode 100644 index 00000000..95ee5f2c --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/customer-nav/photos/comet.svg @@ -0,0 +1,10 @@ + + + icon/customer-nav/photos/comet + + + + + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/customer-nav/photos/white.svg b/new-lamassu-admin/src/styling/icons/customer-nav/photos/white.svg new file mode 100644 index 00000000..e27fef8f --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/customer-nav/photos/white.svg @@ -0,0 +1,10 @@ + + + icon/customer-nav/photos/white + + + + + + + \ No newline at end of file