partial: trigger and transactions

This commit is contained in:
Rafael Taranto 2025-05-07 18:55:21 +01:00
parent 6d6edf578c
commit 42ab9e8fa4
30 changed files with 354 additions and 1116 deletions

View file

@ -1,25 +1,20 @@
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import * as R from 'ramda'
import React, { useState, useEffect } from 'react'
import { CopyToClipboard as ReactCopyToClipboard } from 'react-copy-to-clipboard'
import Popover from 'src/components/Popper'
import Popover from 'src/components/Popper.jsx'
import CopyIcon from 'src/styling/icons/action/copy/copy.svg?react'
import { comet } from 'src/styling/variables'
import { comet } from 'src/styling/variables.js'
import { cpcStyles } from './Transactions.styles'
const useStyles = makeStyles(cpcStyles)
import { Label1, Mono } from './typography/index.jsx'
const CopyToClipboard = ({
className,
buttonClassname,
children,
variant,
wrapperClassname,
removeSpace = true,
...props
removeSpace = true
}) => {
const [anchorEl, setAnchorEl] = useState(null)
@ -27,8 +22,6 @@ const CopyToClipboard = ({
if (anchorEl) setTimeout(() => setAnchorEl(null), 3000)
}, [anchorEl])
const classes = useStyles()
const handleClick = event => {
setAnchorEl(anchorEl ? null : event.currentTarget)
}
@ -41,16 +34,17 @@ const CopyToClipboard = ({
const id = open ? 'simple-popper' : undefined
return (
<div className={classnames(classes.wrapper, wrapperClassname)}>
<div className={classnames('flex items-center', wrapperClassname)}>
{children && (
<>
<div className={classnames(classes.address, className)}>
<Mono noMargin className={className}>
{children}
</div>
<div className={classnames(classes.buttonWrapper, buttonClassname)}>
</Mono>
<div className={buttonClassname}>
<ReactCopyToClipboard
text={removeSpace ? R.replace(/\s/g, '')(children) : children}>
<button
className="border-0 bg-transparent cursor-pointer"
aria-describedby={id}
onClick={event => handleClick(event)}>
<CopyIcon />
@ -64,10 +58,11 @@ const CopyToClipboard = ({
onClose={handleClose}
arrowSize={3}
bgColor={comet}
className="py-1 px-2"
placement="top">
<div className={classes.popoverContent}>
<div>Copied to clipboard!</div>
</div>
<Label1 noMargin className="text-white rounded-sm">
Copied to clipboard!
</Label1>
</Popover>
</>
)}

View file

@ -2,7 +2,7 @@ import * as R from 'ramda'
import React, { useState } from 'react'
import { DeleteDialog } from 'src/components/DeleteDialog'
import DataTable from 'src/components/tables/DataTable'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
import DeleteIcon from 'src/styling/icons/action/delete/enabled.svg?react'
import { IconButton } from 'src/components/buttons'

View file

@ -5,7 +5,7 @@ import { Label1 } from 'src/components/typography'
import { formatDate } from 'src/utils/timezones'
import CopyToClipboard from '../../Transactions/CopyToClipboard'
import CopyToClipboard from '../../../components/CopyToClipboard.jsx'
const PhotosCarousel = memo(({ photosData, timezone }) => {
const [currentIndex, setCurrentIndex] = useState(0)

View file

@ -10,7 +10,7 @@ import TxOutIcon from 'src/styling/icons/direction/cash-out.svg?react'
import { ifNotNull } from 'src/utils/nullCheck'
import { formatDate } from 'src/utils/timezones'
import CopyToClipboard from '../../Transactions/CopyToClipboard'
import CopyToClipboard from '../../../components/CopyToClipboard.jsx'
const TransactionsList = ({ customer, data, loading }) => {
const LastTxIcon = customer.lastTxClass === 'cashOut' ? TxOutIcon : TxInIcon

View file

@ -1,4 +1,4 @@
import { useQuery, gql } from "@apollo/client";
import { useQuery, gql } from '@apollo/client'
import { formatCryptoAddress } from '@lamassu/coins/lightUtils'
import { makeStyles } from '@mui/styles'
import BigNumber from 'bignumber.js'
@ -19,7 +19,7 @@ import {
Label1,
Label3
} from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
import { primaryColor } from 'src/styling/variables'

View file

@ -4,7 +4,7 @@ import React from 'react'
import { Status } from 'src/components/Status'
import MachineActions from 'src/components/machineActions/MachineActions'
import { H3, Label1, P } from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
const Overview = ({ data, onActionSuccess }) => {
return (

View file

@ -1,6 +1,5 @@
import { useQuery, useLazyQuery, gql } from "@apollo/client";
import { useQuery, useLazyQuery, gql } from '@apollo/client'
import { toUnit, formatCryptoAddress } from '@lamassu/coins/lightUtils'
import { makeStyles } from '@mui/styles'
import BigNumber from 'bignumber.js'
import * as R from 'ramda'
import React, { useEffect, useState } from 'react'
@ -8,18 +7,21 @@ import DetailsRow from 'src/pages/Transactions/DetailsCard'
import TxInIcon from 'src/styling/icons/direction/cash-in.svg?react'
import TxOutIcon from 'src/styling/icons/direction/cash-out.svg?react'
import { mainStyles } from 'src/pages/Transactions/Transactions.styles'
import { getStatus } from 'src/pages/Transactions/helper'
import * as Customer from 'src/utils/customer'
import { formatDate } from 'src/utils/timezones'
import DataTable from './DataTable'
const useStyles = makeStyles(mainStyles)
const NUM_LOG_RESULTS = 5
const GET_TRANSACTIONS = gql`
query transactions($limit: Int, $from: DateTimeISO, $until: DateTimeISO, $deviceId: String) {
query transactions(
$limit: Int
$from: DateTimeISO
$until: DateTimeISO
$deviceId: String
) {
transactions(
limit: $limit
from: $from
@ -67,8 +69,6 @@ const GET_DATA = gql`
`
const Transactions = ({ id }) => {
const classes = useStyles()
const [extraHeight, setExtraHeight] = useState(0)
const [clickedId, setClickedId] = useState('')
@ -130,7 +130,7 @@ const Transactions = ({ id }) => {
{
header: 'Address',
view: it => formatCryptoAddress(it.cryptoCode, it.toAddress),
className: classes.overflowTd,
className: 'overflow-hidden whitespace-nowrap text-ellipsis',
size: 'sm',
textAlign: 'left',
width: 140

View file

@ -1,6 +1,5 @@
import { useLazyQuery, useMutation, gql } from '@apollo/client'
import { toUnit, formatCryptoAddress } from '@lamassu/coins/lightUtils'
import { makeStyles } from '@mui/styles'
import BigNumber from 'bignumber.js'
import classNames from 'classnames'
import { add, differenceInYears, format, sub, parse } from 'date-fns/fp'
@ -34,11 +33,9 @@ import {
import { SWEEPABLE_CRYPTOS } from 'src/utils/constants'
import * as Customer from 'src/utils/customer'
import CopyToClipboard from './CopyToClipboard'
import styles from './DetailsCard.styles'
import CopyToClipboard from '../../components/CopyToClipboard.jsx'
import { getStatus, getStatusDetails } from './helper'
const useStyles = makeStyles(styles)
const MINUTES_OFFSET = 3
const TX_SUMMARY = gql`
query txSummaryAndLogs(
@ -100,12 +97,14 @@ const formatAddress = (cryptoCode = '', address = '') =>
formatCryptoAddress(cryptoCode, address).replace(/(.{5})/g, '$1 ')
const Label = ({ children }) => {
const classes = useStyles()
return <Label1 className={classes.label}>{children}</Label1>
return (
<Label1 noMargin className="text-comet mb-1">
{children}
</Label1>
)
}
const DetailsRow = ({ it: tx, timezone }) => {
const classes = useStyles()
const [action, setAction] = useState({ command: null })
const [errorMessage, setErrorMessage] = useState('')
@ -183,9 +182,11 @@ const DetailsRow = ({ it: tx, timezone }) => {
const errorElements = (
<>
<Label>Transaction status</Label>
<span className={classes.bold}>{getStatus(tx)}</span>
<span className="font-bold">{getStatus(tx)}</span>
{getStatusDetails(tx) ? (
<CopyToClipboard removeSpace={false} className={classes.errorCopy}>
<CopyToClipboard
removeSpace={false}
className="font-museo break-normal max-w-45">
{getStatusDetails(tx)}
</CopyToClipboard>
) : (
@ -195,7 +196,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
)
const walletScoreEl = (
<div className={classes.walletScore}>
<div className="flex flex-row items-center">
<svg width={103} height={10}>
{R.range(0, 10).map((it, idx) => (
<circle
@ -218,8 +219,8 @@ const DetailsRow = ({ it: tx, timezone }) => {
<P
noMargin
className={classNames({
[classes.bold]: true,
[classes.error]: hasChainAnalysisError(tx)
'font-bold ml-1': true,
'text-tomato': hasChainAnalysisError(tx)
})}>
{tx.walletScore}
</P>
@ -234,24 +235,23 @@ const DetailsRow = ({ it: tx, timezone }) => {
}
return (
<div data-cy="details" className={classes.wrapper}>
<div className={classes.row}>
<div data-cy="direction" className={classes.direction}>
<div data-cy="details" className="flex flex-col mt-6">
<div className="flex flex-row mb-9">
<div data-cy="direction" className="w-59">
<Label>Direction</Label>
<div>
<span className={classes.txIcon}>
<span className="mr-3">
{!isCashIn ? <TxOutIcon /> : <TxInIcon />}
</span>
<span>{!isCashIn ? 'Cash-out' : 'Cash-in'}</span>
</div>
</div>
<div data-cy="availableIds" className={classes.availableIds}>
<div data-cy="availableIds" className="w-58">
<Label>Available IDs</Label>
<div className="flex">
<div className="flex gap-1">
{tx.customerPhone && (
<IDButton
className={classes.idButton}
name="phone"
Icon={PhoneIdIcon}
InverseIcon={PhoneIdInverseIcon}>
@ -260,13 +260,11 @@ const DetailsRow = ({ it: tx, timezone }) => {
)}
{tx.customerIdCardPhotoPath && !tx.customerIdCardData && (
<IDButton
popoverClassname={classes.clipboardPopover}
className={classes.idButton}
popoverClassname="h-41 w-54"
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<img
className={classes.idCardPhoto}
src={`/id-card-photo/${tx.customerIdCardPhotoPath}`}
alt=""
/>
@ -274,33 +272,32 @@ const DetailsRow = ({ it: tx, timezone }) => {
)}
{tx.customerIdCardData && (
<IDButton
className={classes.idButton}
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<div className={classes.idCardDataCard}>
<div>
<div className="font-museo flex py-3 px-2 gap-16">
<div className="flex flex-col gap-4">
<div>
<Label>Name</Label>
<div>{customer.name}</div>
<P noMargin>{customer.name}</P>
</div>
<div>
<Label>Age</Label>
<div>{customer.age}</div>
<P noMargin>{customer.age}</P>
</div>
<div>
<Label>Country</Label>
<div>{customer.country}</div>
<P noMargin>{customer.country}</P>
</div>
</div>
<div>
<div>
<Label>ID number</Label>
<div>{customer.idCardNumber}</div>
<P noMargin>{customer.idCardNumber}</P>
</div>
<div>
<Label>Expiration date</Label>
<div>{customer.idCardExpirationDate}</div>
<P noMargin>{customer.idCardExpirationDate}</P>
</div>
</div>
</div>
@ -308,7 +305,6 @@ const DetailsRow = ({ it: tx, timezone }) => {
)}
{tx.customerFrontCameraPath && (
<IDButton
className={classes.idButton}
name="cam"
Icon={CamIdIcon}
InverseIcon={CamIdInverseIcon}>
@ -331,17 +327,17 @@ const DetailsRow = ({ it: tx, timezone }) => {
)}
</div>
</div>
<div data-cy="exchangeRate" className={classes.exchangeRate}>
<div data-cy="exchangeRate" className="w-62">
<Label>Exchange rate</Label>
<div>{crypto > 0 ? displayExRate : '-'}</div>
</div>
<div data-cy="commission" className={classes.commission}>
<div data-cy="commission" className="w-54">
<Label>Commission</Label>
<div className={classes.container}>
<div className="flex">
{`${commission} ${tx.fiatCode} (${commissionPercentage} %)`}
{discount && (
<div className={classes.chip}>
<Label1 className={classes.chipLabel}>{discount}</Label1>
<div className="flex items-center py-1 px-2 bg-comet text-white h-6 -mb-6 -mt-1 ml-2 rounded-sm">
<Label1 className="text-white">{discount}</Label1>
</div>
)}
</div>
@ -351,9 +347,9 @@ const DetailsRow = ({ it: tx, timezone }) => {
<div>{`${fixedFee} ${tx.fiatCode}`}</div>
</div>
</div>
<div className={classes.secondRow}>
<div data-cy="address" className={classes.address}>
<div className={classes.addressHeader}>
<div className="flex flex-row justify-between mb-9">
<div data-cy="address" className="w-70">
<div className="flex flex-row justify-between items-center">
<Label>Address</Label>
{!R.isNil(tx.walletScore) && (
<HelpTooltip parentElements={walletScoreEl}>
@ -367,7 +363,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
</CopyToClipboard>
</div>
</div>
<div data-cy="transactionId" className={classes.transactionId}>
<div data-cy="transactionId" className="w-70">
<Label>Transaction ID</Label>
<div>
{tx.txClass === 'cashOut' ? (
@ -378,18 +374,18 @@ const DetailsRow = ({ it: tx, timezone }) => {
</div>
</div>
{tx.txClass === 'cashIn' && (
<div data-cy="networkFee" className={classes.blockFee}>
<div data-cy="networkFee" className="w-35">
<Label>Network Fee</Label>
{cryptoFee}
</div>
)}
<div data-cy="sessionId" className={classes.sessionId}>
<div data-cy="sessionId" className="w-54">
<Label>Session ID</Label>
<CopyToClipboard>{tx.id}</CopyToClipboard>
</div>
</div>
<div className={classes.lastRow}>
<div data-cy="status" className={classes.status}>
<div className="flex flex-row mb-8">
<div data-cy="status" className="62">
{errorElements}
{((tx.txClass === 'cashOut' && getStatus(tx) === 'Pending') ||
(tx.txClass === 'cashIn' && getStatus(tx) === 'Batched')) && (
@ -397,7 +393,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
color="primary"
Icon={CancelIcon}
InverseIcon={CancelInverseIcon}
className={classes.cancelTransaction}
className="w-40"
onClick={() =>
setAction({
command: 'cancelTx'
@ -408,21 +404,19 @@ const DetailsRow = ({ it: tx, timezone }) => {
)}
</div>
{!R.isNil(tx.swept) && R.includes(tx.cryptoCode, SWEEPABLE_CRYPTOS) && (
<div data-cy="swept" className={classes.swept}>
<div data-cy="swept" className="w-63">
<Label>Sweep status</Label>
<span className={classes.bold}>
{tx.swept ? `Swept` : `Unswept`}
</span>
<span className="font-bold">{tx.swept ? `Swept` : `Unswept`}</span>
</div>
)}
<div>
<Label>Other actions</Label>
<div className={classes.otherActionsGroup}>
<div className="flex flex-row">
<ActionButton
color="primary"
Icon={Download}
InverseIcon={DownloadInverseIcon}
className={classes.downloadRawLogs}
className="w-45"
onClick={() => downloadRawLogs(tx, timezone)}>
Download raw logs
</ActionButton>

View file

@ -1,146 +0,0 @@
import typographyStyles from 'src/components/typography/styles'
import { offColor, comet, white, tomato } from 'src/styling/variables'
const { p, label3 } = typographyStyles
export default {
wrapper: {
display: 'flex',
flexDirection: 'column',
marginTop: 24
},
row: {
display: 'flex',
flexDirection: 'row',
marginBottom: 36
},
secondRow: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 36
},
lastRow: {
display: 'flex',
flexDirection: 'row',
marginBottom: 32
},
label: {
color: offColor,
margin: [[0, 0, 6, 0]],
whiteSpace: 'nowrap'
},
txIcon: {
marginRight: 10
},
clipboardPopover: {
height: 164,
width: 215
},
idButton: {
marginRight: 4
},
idCardDataCard: {
extend: p,
display: 'flex',
padding: [[11, 8]],
'& > div': {
display: 'flex',
flexDirection: 'column',
'& > div': {
width: 144,
height: 37,
marginBottom: 15,
'&:last-child': {
marginBottom: 0
}
}
}
},
bold: {
fontWeight: 700
},
direction: {
width: 233
},
availableIds: {
width: 232
},
exchangeRate: {
width: 250
},
commission: {
width: 217
},
address: {
width: 280
},
downloadRawLogs: {
width: 180
},
cancelTransaction: {
width: 160
},
status: {
width: 230,
'& > button': {
marginTop: 20
}
},
transactionId: {
width: 280
},
blockFee: {
width: 140
},
sessionId: {
width: 215
},
container: {
display: 'flex'
},
chip: {
display: 'flex',
alignItems: 'center',
padding: '4px 8px 4px 8px',
backgroundColor: comet,
color: white,
height: 24,
marginBottom: -24,
marginTop: -3,
marginLeft: 7,
borderRadius: 4
},
chipLabel: {
color: white
},
otherActionsGroup: {
display: 'flex',
flexDirection: 'row'
},
addressHeader: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
walletScore: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
'& > p': {
marginLeft: 5
}
},
error: {
color: tomato
},
swept: {
width: 250
},
errorCopy: {
extend: label3,
lineBreak: 'normal',
maxWidth: 180
}
}

View file

@ -1,6 +1,5 @@
import { useQuery, gql } from "@apollo/client";
import { useQuery, gql } from '@apollo/client'
import { toUnit, formatCryptoAddress } from '@lamassu/coins/lightUtils'
import { makeStyles } from '@mui/styles'
import BigNumber from 'bignumber.js'
import * as R from 'ramda'
import React, { useEffect, useState } from 'react'
@ -8,7 +7,6 @@ import { useHistory } from 'react-router-dom'
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
import SearchBox from 'src/components/SearchBox'
import SearchFilter from 'src/components/SearchFilter'
import Title from 'src/components/Title'
import { HelpTooltip } from 'src/components/Tooltip'
import DataTable from 'src/components/tables/DataTable'
import TxInIcon from 'src/styling/icons/direction/cash-in.svg?react'
@ -22,10 +20,8 @@ import * as Customer from 'src/utils/customer'
import { formatDate } from 'src/utils/timezones'
import DetailsRow from './DetailsCard'
import { mainStyles } from './Transactions.styles'
import { getStatus } from './helper'
const useStyles = makeStyles(mainStyles)
import TitleSection from '../../components/layout/TitleSection.jsx'
const NUM_LOG_RESULTS = 1000
@ -138,7 +134,6 @@ const getFiltersObj = filters =>
R.reduce((s, f) => ({ ...s, [f.type]: f.value }), {}, filters)
const Transactions = () => {
const classes = useStyles()
const history = useHistory()
const [filters, setFilters] = useState([])
@ -187,14 +182,18 @@ const Transactions = () => {
width: 202,
size: 'sm',
view: it => (
<div className={classes.flexWrapper}>
<div className={classes.overflowTd}>{Customer.displayName(it)}</div>
<div className="flex items-center justify-between mr-4">
<div className="overflow-hidden whitespace-nowrap text-ellipsis">
{Customer.displayName(it)}
</div>
{!it.isAnonymous && (
<div data-cy="customer-link" onClick={() => redirect(it.customerId)}>
<div
data-cy="customer-link"
onClick={() => redirect(it.customerId)}>
{it.hasError || it.batchError ? (
<CustomerLinkWhiteIcon className={classes.customerLinkIcon} />
<CustomerLinkWhiteIcon />
) : (
<CustomerLinkIcon className={classes.customerLinkIcon} />
<CustomerLinkIcon />
)}
</div>
)}
@ -221,7 +220,7 @@ const Transactions = () => {
{
header: 'Address',
view: it => formatCryptoAddress(it.cryptoCode, it.toAddress),
className: classes.overflowTd,
className: 'overflow-hidden whitespace-nowrap text-ellipsis',
size: 'sm',
width: 140
},
@ -238,7 +237,7 @@ const Transactions = () => {
view: it => {
if (getStatus(it) === 'Pending')
return (
<div className={classes.pendingBox}>
<div className="flex items-center">
{'Pending'}
<HelpTooltip width={285}>
<SupportLinkButton
@ -332,10 +331,15 @@ const Transactions = () => {
return (
<>
<div className={classes.titleWrapper}>
<div className={classes.titleAndButtonsContainer}>
<Title>Transactions</Title>
<div className={classes.buttonsWrapper}>
<TitleSection
title="Transactions"
labels={[
{ icon: <TxInIcon />, label: 'Cash-in' },
{ icon: <TxOutIcon />, label: 'Cash-out' },
{ icon: errorLabel, label: 'Transaction error' }
]}
appendix={
<div className="flex ml-4 gap-4">
<SearchBox
loading={filtersLoading}
filters={filters}
@ -343,9 +347,7 @@ const Transactions = () => {
inputPlaceholder={'Search transactions'}
onChange={onFilterChange}
/>
</div>
{txList && (
<div className={classes.buttonsWrapper}>
<LogsDowloaderPopover
title="Download logs"
name="transactions"
@ -355,24 +357,10 @@ const Transactions = () => {
timezone={timezone}
args={{ timezone }}
/>
</div>
)}
</div>
<div className={classes.headerLabels}>
<div>
<TxInIcon />
<span>Cash-in</span>
</div>
<div>
<TxOutIcon />
<span>Cash-out</span>
</div>
<div>
{errorLabel}
<span>Transaction error</span>
</div>
</div>
</div>
}
/>
{filters.length > 0 && (
<SearchFilter
entries={txList.length}

View file

@ -1,147 +0,0 @@
import typographyStyles from 'src/components/typography/styles'
import baseStyles from 'src/pages/Logs.styles'
import {
offColor,
white,
primaryColor,
zircon,
smallestFontSize,
inputFontFamily,
inputFontWeight,
spacer
} from 'src/styling/variables'
const { label1, mono, p } = typographyStyles
const { titleWrapper, titleAndButtonsContainer, buttonsWrapper } = baseStyles
const cpcStyles = {
wrapper: {
extend: mono,
display: 'flex',
alignItems: 'center'
},
address: {
lineBreak: 'anywhere'
},
buttonWrapper: {
'& button': {
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer'
}
},
popoverContent: {
extend: label1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: white,
borderRadius: 4,
padding: [[5, 9]]
}
}
const detailsRowStyles = {
idCardDataCard: {
extend: p,
display: 'flex',
padding: [[11, 8]],
'& > div': {
display: 'flex',
flexDirection: 'column',
'& > div': {
width: 144,
height: 37,
marginBottom: 15,
'&:last-child': {
marginBottom: 0
}
}
}
}
}
const labelStyles = {
label: {
extend: label1,
color: offColor,
marginBottom: 4
}
}
const mainStyles = {
titleWrapper,
titleAndButtonsContainer,
buttonsWrapper,
pendingBox: {
display: 'flex',
alignItems: 'center',
},
headerLabels: {
display: 'flex',
flexDirection: 'row',
'& div': {
display: 'flex',
alignItems: 'center'
},
'& > div': {
marginLeft: 24
},
'& > div:first-child': {
marginLeft: 0
},
'& span': {
extend: label1,
marginLeft: 6
}
},
overflowTd: {
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis'
},
flexWrapper: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginRight: 16
},
customerLinkIcon: {
marginLeft: 2
},
flexRow: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
},
machineRedirectContainer: {
marginLeft: 10,
paddingLeft: 5,
paddingRight: 5
}
}
const chipStyles = {
root: {
borderRadius: spacer / 2,
marginTop: spacer / 2,
marginRight: spacer / 4,
marginBottom: spacer / 2,
marginLeft: spacer / 4,
height: spacer * 3,
backgroundColor: zircon,
'&:hover, &:focus, &:active': {
backgroundColor: zircon
}
},
label: {
fontSize: smallestFontSize,
fontWeight: inputFontWeight,
fontFamily: inputFontFamily,
paddingRight: spacer / 2,
paddingLeft: spacer / 2,
color: primaryColor
}
}
export { cpcStyles, detailsRowStyles, labelStyles, mainStyles, chipStyles }

View file

@ -30,13 +30,4 @@ const getStatusDetails = it => {
return null
}
const getStatusProperties = status => ({
hasError: status === 'Error' || null,
batchError: status === 'Error' || null,
dispense: status === 'Success' || null,
expired: status === 'Expired' || null,
operatorCompleted: status === 'Cancelled' || null,
sendConfirmed: status === 'Sent' || null
})
export { getStatus, getStatusProperties, getStatusDetails }
export { getStatus, getStatusDetails }

View file

@ -1,6 +1,4 @@
import { useMutation, useQuery, gql } from "@apollo/client";
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import { useMutation, useQuery, gql } from '@apollo/client'
import * as R from 'ramda'
import React, { useState } from 'react'
import { DeleteDialog } from 'src/components/DeleteDialog'
@ -12,10 +10,8 @@ import EditIcon from 'src/styling/icons/action/edit/enabled.svg?react'
import { IconButton, Button, Link } from 'src/components/buttons'
import { fromNamespace, namespaces, toNamespace } from 'src/utils/config'
import styles from './CustomInfoRequests.styles'
import DetailsRow from './DetailsCard'
import Wizard from './Wizard'
const useStyles = makeStyles(styles)
const inputTypeDisplay = {
numerical: 'Numerical',
@ -76,8 +72,6 @@ const CustomInfoRequests = ({
toggleWizard,
data: customRequests
}) => {
const classes = useStyles()
const [toBeDeleted, setToBeDeleted] = useState()
const [toBeEdited, setToBeEdited] = useState()
const [deleteDialog, setDeleteDialog] = useState(false)
@ -178,7 +172,9 @@ const CustomInfoRequests = ({
</>
)
return (!configLoading && (<>
return (
!configLoading && (
<>
{customRequests.length > 0 && (
<DataTable
emptyText="No custom info requests so far"
@ -220,7 +216,7 @@ const CustomInfoRequests = ({
size="large">
<EditIcon />
</IconButton>
);
)
}
},
{
@ -238,7 +234,7 @@ const CustomInfoRequests = ({
size="large">
<DeleteIcon />
</IconButton>
);
)
}
}
]}
@ -249,11 +245,11 @@ const CustomInfoRequests = ({
/>
)}
{!customRequests.length && (
<div className={classes.centerItems}>
<Info1 className={classnames(classes.m0, classes.mb10)}>
<div className="flex flex-col items-center h-1/2 justify-center">
<Info1 className="m-0 mb-3">
It seems you haven't added any custom information requests yet.
</Info1>
<Info3 className={classnames(classes.m0, classes.mb10)}>
<Info3 className="m-0 mb-3">
Please read our{' '}
<a href="https://support.lamassu.is/hc/en-us/sections/115000817232-Compliance">
<Link>Support Article</Link>
@ -289,7 +285,9 @@ const CustomInfoRequests = ({
extraMessage={detailedDeleteMsg}
onConfirmed={() => handleDelete(toBeDeleted)}
/>
</>));
</>
)
)
}
export default CustomInfoRequests

View file

@ -1,19 +0,0 @@
export default {
m0: {
margin: 0
},
mb10: {
marginBottom: 10
},
centerItems: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
height: '50%',
justifyContent: 'center'
},
alignWithTitleSection: {
marginTop: -47,
display: 'flex'
}
}

View file

@ -1,32 +1,8 @@
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import React from 'react'
import { Label1, Info2 } from 'src/components/typography'
const styles = {
flex: {
display: 'flex'
},
column: {
flexDirection: 'column'
},
halfWidth: {
width: '50%',
marginBottom: 15,
marginRight: 50
},
marginTop: {
marginTop: 20
},
marginBottom: {
marginBottom: 20
}
}
const useStyles = makeStyles(styles)
const DetailsCard = ({ it }) => {
const customRequest = it.customRequest
const classes = useStyles()
const getScreen2Data = () => {
const label1Display =
customRequest.input.constraintType === 'spaceSeparation'
@ -36,12 +12,12 @@ const DetailsCard = ({ it }) => {
case 'text':
return (
<>
<div className={classes.halfWidth}>
<div className="w-1/2 mb-4 mr-12">
<Info2>{label1Display}</Info2>
<Label1>{customRequest.input.label1}</Label1>
</div>
{customRequest.input.constraintType === 'spaceSeparation' && (
<div className={classes.halfWidth}>
<div className="w-1/2 mb-4 mr-12">
<Info2>Second word label</Info2>
<Label1>{customRequest.input.label2}</Label1>
</div>
@ -51,11 +27,11 @@ const DetailsCard = ({ it }) => {
default:
return (
<>
<div className={classes.halfWidth}>
<div className="w-1/2 mb-4 mr-12">
<Info2>Screen 2 input title</Info2>
<Label1>{customRequest.screen2.title}</Label1>
</div>
<div className={classes.halfWidth}>
<div className="w-1/2 mb-4 mr-12">
<Info2>Screen 2 input description</Info2>
<Label1>{customRequest.screen2.text}</Label1>
</div>
@ -87,22 +63,19 @@ const DetailsCard = ({ it }) => {
return (
<div>
<div className={classnames(classes.flex, classes.row, classes.marginTop)}>
<div className={classes.halfWidth}>
<div className="flex mt-5">
<div className="w-1/2 mb-4 mr-12">
<Info2>Screen 1 title</Info2>
<Label1>{customRequest.screen1.title}</Label1>
</div>
<div className={classnames(classes.halfWidth, classes.flex)}>
{getScreen2Data()}
<div className="flex w-1/2 mb-4 mr-12">{getScreen2Data()}</div>
</div>
</div>
<div
className={classnames(classes.flex, classes.row, classes.marginBottom)}>
<div className={classes.halfWidth}>
<div className="flex mb-5">
<div className="w-1/2 mb-4 mr-12">
<Info2>Screen 1 text</Info2>
<Label1>{customRequest.screen1.text}</Label1>
</div>
<div className={classes.halfWidth}>{getInputData()}</div>
<div className="w-1/2 mb-4 mr-12">{getInputData()}</div>
</div>
</div>
)

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import { Field, useFormikContext, FieldArray } from 'formik'
import * as R from 'ramda'
@ -10,9 +9,6 @@ import { H4 } from 'src/components/typography'
import AddIconInverse from 'src/styling/icons/button/add/white.svg?react'
import AddIcon from 'src/styling/icons/button/add/zodiac.svg?react'
import styles from './formStyles.styles'
const useStyles = makeStyles(styles)
const nonEmptyStr = obj => obj.text && obj.text.length
const options = [
@ -21,15 +17,14 @@ const options = [
]
const ChoiceList = () => {
const classes = useStyles()
const context = useFormikContext()
const choiceListRef = useRef(null)
const listChoices = R.path(['values', 'listChoices'])(context) ?? []
const choiceListError = R.path(['errors', 'listChoices'])(context) ?? false
const showErrorColor = {
[classes.radioSubtitle]: true,
[classes.error]:
'mb-0': true,
'text-tomato':
!R.path(['values', 'constraintType'])(context) &&
R.path(['errors', 'constraintType'])(context)
}
@ -56,20 +51,20 @@ const ChoiceList = () => {
<Field
component={RadioGroup}
options={options}
className={classes.row}
className="flex-col"
name="constraintType"
/>
<FieldArray name="listChoices">
{({ push }) => {
return (
<div className={classnames(classes.flex, classes.column)}>
<H4 className={classes.subtitle}>Choices</H4>
<div className={classes.choiceList}>
<div className="flex flex-col">
<H4 className="mb-0">Choices</H4>
<div className="flex flex-col max-h-60">
{listChoices.map((choice, idx) => {
return (
<div ref={choiceListRef} key={idx}>
<Field
className={classes.textInput}
className="w-105"
error={hasError(choice)}
component={TextInput}
name={`listChoices[${idx}].text`}
@ -83,7 +78,7 @@ const ChoiceList = () => {
Icon={AddIcon}
color="primary"
InverseIcon={AddIconInverse}
className={classes.button}
className="w-30 h-7 mt-7"
onClick={e => {
e.preventDefault()
return push({ text: '' })

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import { Field, useFormikContext } from 'formik'
import * as R from 'ramda'
@ -7,9 +6,6 @@ import NumberInput from 'src/components/inputs/formik/NumberInput'
import RadioGroup from 'src/components/inputs/formik/RadioGroup'
import { TL1, H4 } from 'src/components/typography'
import styles from './formStyles.styles'
const useStyles = makeStyles(styles)
const options = [
{ display: 'None', code: 'none' },
{ display: 'Date', code: 'date' },
@ -17,7 +13,6 @@ const options = [
]
const NumericalEntry = () => {
const classes = useStyles()
const context = useFormikContext()
const isLength =
@ -25,8 +20,8 @@ const NumericalEntry = () => {
'length'
const showErrorColor = {
[classes.radioSubtitle]: true,
[classes.error]:
'mb-0': true,
'text-tomat':
!R.path(['values', 'constraintType'])(context) &&
R.path(['errors', 'constraintType'])(context)
}
@ -37,13 +32,13 @@ const NumericalEntry = () => {
Numerical entry constraints
</H4>
<Field
className={classes.row}
className="flex-row"
component={RadioGroup}
options={options}
name="constraintType"
/>
{isLength && (
<div className={classnames(classes.flex, classes.numberField)}>
<div className="flex mt-27 max-w-29">
<Field
component={NumberInput}
name={'inputLength'}
@ -51,7 +46,7 @@ const NumericalEntry = () => {
decimalPlaces={0}
allowNegative={false}
/>
<TL1 className={classes.tl1}>digits</TL1>
<TL1 className="ml-2 mt-6">digits</TL1>
</div>
)}
</>

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import { Field, useFormikContext } from 'formik'
import * as R from 'ramda'
@ -7,9 +6,6 @@ import RadioGroup from 'src/components/inputs/formik/RadioGroup'
import TextInput from 'src/components/inputs/formik/TextInput'
import { H4 } from 'src/components/typography'
import styles from './formStyles.styles'
const useStyles = makeStyles(styles)
const options = [
{ display: 'None', code: 'none' },
{ display: 'Email', code: 'email' },
@ -21,11 +17,10 @@ const options = [
]
const TextEntry = () => {
const classes = useStyles()
const context = useFormikContext()
const showErrorColor = {
[classes.radioSubtitle]: true,
[classes.error]:
'mt-0': true,
'text-tomato':
!R.path(['values', 'constraintType'])(context) &&
R.path(['errors', 'constraintType'])(context)
}
@ -34,15 +29,15 @@ const TextEntry = () => {
switch (context.values.constraintType) {
case 'spaceSeparation':
return (
<div className={classes.flex}>
<div className="flex">
<Field
className={classes.label}
className="w-50 mr-2"
component={TextInput}
name={'inputLabel1'}
label={'First word label'}
/>
<Field
className={classes.label}
className="w-50 mr-2"
component={TextInput}
name={'inputLabel2'}
label={'Second word label'}
@ -52,7 +47,7 @@ const TextEntry = () => {
default:
return (
<Field
className={classes.label}
className="w-50 mr-2"
component={TextInput}
name={'inputLabel1'}
label={'Text entry label'}
@ -65,7 +60,7 @@ const TextEntry = () => {
<>
<H4 className={classnames(showErrorColor)}>Text entry constraints</H4>
<Field
className={classes.row}
className="flex-row"
component={RadioGroup}
options={options}
name="constraintType"

View file

@ -1,50 +0,0 @@
import { errorColor, spacer } from 'src/styling/variables'
const styles = {
flex: {
display: 'flex'
},
column: {
flexDirection: 'column'
},
choiceList: {
display: 'flex',
flexDirection: 'column',
maxHeight: 240,
overflowY: 'auto'
},
button: {
width: 120,
height: 28,
marginTop: 28
},
textInput: {
width: 420
},
row: {
flexDirection: 'row'
},
subtitle: {
marginBottom: 0
},
radioSubtitle: {
marginBottom: 0
},
error: {
color: errorColor
},
tl1: {
marginLeft: 8,
marginTop: 25
},
numberField: {
marginTop: 109,
maxWidth: 115
},
label: {
width: 200,
marginRight: spacer
}
}
export default styles

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import { Form, Formik } from 'formik'
import * as R from 'ramda'
import React, { useState } from 'react'
@ -32,27 +31,6 @@ import WizardSplash from './WizardSplash'
const LAST_STEP = 5
const styles = {
stepper: {
margin: [[16, 0, 14, 0]]
},
submit: {
display: 'flex',
flexDirection: 'row',
margin: [['auto', 0, 24]]
},
button: {
marginLeft: 'auto'
},
form: {
height: '100%',
display: 'flex',
flexDirection: 'column'
}
}
const useStyles = makeStyles(styles)
const getStep = (step, existingRequirements) =>
[
{
@ -153,7 +131,6 @@ const Wizard = ({
hasError,
existingRequirements
}) => {
const classes = useStyles()
const isEditing = !R.isNil(toBeEdited)
const [step, setStep] = useState(isEditing ? 1 : 0)
@ -206,7 +183,7 @@ const Wizard = ({
open={true}>
{step > 0 && (
<Stepper
className={classes.stepper}
className="mt-4 mb-4 mx-0"
steps={LAST_STEP}
currentStep={step}
/>
@ -221,15 +198,17 @@ const Wizard = ({
initialValues={initialValues}
validationSchema={stepOptions.validationSchema}>
{({ errors }) => (
<Form className={classes.form} id={'custom-requirement-form'}>
<Form
className="h-full flex flex-col"
id={'custom-requirement-form'}>
<stepOptions.Component />
<div className={classes.submit}>
<div className="flex flex-row mt-auto mx-0 mb-4">
{(hasError || !R.isEmpty(errors)) && (
<ErrorMessage>
{R.head(R.values(errors)) ?? `Failed to save`}
</ErrorMessage>
)}
<Button className={classes.button} type="submit">
<Button className="ml-auto" type="submit">
{isLastStep ? 'Save' : 'Next'}
</Button>
</div>

View file

@ -1,43 +1,15 @@
import { makeStyles } from '@mui/styles'
import React from 'react'
import { H1, P } from 'src/components/typography'
import CustomReqLogo from 'src/styling/icons/compliance/custom-requirement.svg?react'
import { Button } from 'src/components/buttons'
const styles = {
logo: {
maxHeight: 150,
maxWidth: 200
},
title: {
margin: [[24, 0, 32, 0]]
},
text: {
margin: 0
},
button: {
marginTop: 'auto',
marginBottom: 58
},
modalContent: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: [[0, 42]],
flex: 1
}
}
const useStyles = makeStyles(styles)
const WizardSplash = ({ onContinue }) => {
const classes = useStyles()
return (
<div className={classes.modalContent}>
<CustomReqLogo className={classes.logo} />
<H1 className={classes.title}>Custom information request</H1>
<P className={classes.text}>
<div className="flex flex-col items-center py-0 px-10 flex-1">
<CustomReqLogo className="max-h-37 max-w-50" />
<H1 className="mt-6 mb-8 mx-0">Custom information request</H1>
<P className="m-0">
A custom information request allows you to have an extra option to ask
specific information about your customers when adding a trigger that
isn't an option on the default requirements list.
@ -47,7 +19,7 @@ const WizardSplash = ({ onContinue }) => {
triggers. You will still need to add a trigger with the new requirement
to get this information from your customers.
</P>
<Button className={classes.button} onClick={onContinue}>
<Button className="mt-auto mb-14" onClick={onContinue}>
Get started
</Button>
</div>

View file

@ -1,5 +1,4 @@
import { useMutation, gql } from '@apollo/client'
import { makeStyles } from '@mui/styles'
import * as R from 'ramda'
import React, { useState } from 'react'
import { H2 } from 'src/components/typography'
@ -9,12 +8,9 @@ import { Button } from 'src/components/buttons'
import { Table as EditableTable } from 'src/components/editableTable'
import { fromNamespace, namespaces } from 'src/utils/config'
import styles from './Triggers.styles'
import Wizard from './Wizard'
import { Schema, getElements, sortBy, toServer } from './helper'
const useStyles = makeStyles(styles)
const SAVE_CONFIG = gql`
mutation Save($config: JSONObject) {
saveConfig(config: $config)
@ -34,7 +30,6 @@ const TriggerView = ({
const currency = R.path(['fiatCurrency'])(
fromNamespace(namespaces.LOCALE)(config)
)
const classes = useStyles()
const [error, setError] = useState(null)
const [saveConfig] = useMutation(SAVE_CONFIG, {
@ -69,7 +64,7 @@ const TriggerView = ({
error={error?.message}
save={save}
validationSchema={Schema}
elements={getElements(currency, classes, customInfoRequests)}
elements={getElements(currency, customInfoRequests)}
/>
{showWizard && (
<Wizard

View file

@ -1,6 +1,5 @@
import { useQuery, useMutation, gql } from '@apollo/client'
import Switch from '@mui/material/Switch'
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import * as R from 'ramda'
import React, { useState } from 'react'
@ -20,10 +19,8 @@ import { fromNamespace, toNamespace } from 'src/utils/config'
import CustomInfoRequests from './CustomInfoRequests'
import TriggerView from './TriggerView'
import styles from './Triggers.styles'
import AdvancedTriggers from './components/AdvancedTriggers'
import { fromServer } from './helper'
const useStyles = makeStyles(styles)
const SAVE_ACCOUNT = gql`
mutation Save($accounts: JSONObject) {
@ -61,7 +58,6 @@ const GET_CUSTOM_REQUESTS = gql`
`
const Triggers = () => {
const classes = useStyles()
const [wizardType, setWizard] = useState(false)
const { data, loading: configLoading, refetch } = useQuery(GET_CONFIG)
const { data: customInfoReqData, loading: customInfoLoading } =
@ -109,7 +105,7 @@ const Triggers = () => {
}
const titleSectionWidth = {
[classes.tableWidth]: !subMenu === 'customInfoRequests'
'w-230': !subMenu === 'customInfoRequests'
}
const setBlur = shouldBlur => {
@ -178,7 +174,7 @@ const Triggers = () => {
}}
value={rejectAddressReuse}
/>
<Label2 className={classes.switchLabel}>
<Label2 className="m-3 w-6">
{rejectAddressReuse ? 'On' : 'Off'}
</Label2>
<HelpTooltip width={304}>

View file

@ -1,16 +0,0 @@
export default {
switchLabel: {
margin: 6,
width: 24
},
tableRadioGroup: {
flexDirection: 'row',
justifyContent: 'space-between'
},
tableRadioLabel: {
marginRight: 0
},
tableWidth: {
width: 918
}
}

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import { Form, Formik, useFormikContext } from 'formik'
import * as R from 'ramda'
import React, { useState, Fragment, useEffect } from 'react'
@ -8,46 +7,12 @@ import Stepper from 'src/components/Stepper'
import { H5, Info3 } from 'src/components/typography'
import { Button } from 'src/components/buttons'
import { comet } from 'src/styling/variables'
import { singularOrPlural } from 'src/utils/string'
import { type, requirements } from './helper'
const LAST_STEP = 2
const styles = {
stepper: {
margin: [[16, 0, 14, 0]]
},
submit: {
display: 'flex',
flexDirection: 'row',
margin: [['auto', 0, 24]]
},
button: {
marginLeft: 'auto'
},
form: {
height: '100%',
display: 'flex',
flexDirection: 'column'
},
infoTitle: {
margin: [[18, 0, 20, 0]]
},
infoCurrentText: {
color: comet
},
blankSpace: {
padding: [[0, 30]],
margin: [[0, 4, 0, 2]],
borderBottom: `1px solid ${comet}`,
display: 'inline-block'
}
}
const useStyles = makeStyles(styles)
const getStep = (
{ step, config },
currency,
@ -74,21 +39,23 @@ const getStep = (
}
}
const getText = (step, config, currency, classes) => {
const getText = (step, config, currency) => {
switch (step) {
// case 1:
// return `In ${getDirectionText(config)} transactions`
case 1:
return <>If the user {getTypeText(config, currency, classes)}</>
return <>If the user {getTypeText(config, currency)}</>
case 2:
return <>the user will be {getRequirementText(config, classes)}.</>
return <>the user will be {getRequirementText(config)}.</>
default:
return <></>
}
}
const orUnderline = (value, classes) => {
const blankSpaceEl = <span className={classes.blankSpace}></span>
const orUnderline = value => {
const blankSpaceEl = (
<span className="py-0 px-7 my-0 mx-1 border-b-1 border-b-comet inline-block"></span>
)
return R.isEmpty(value) || R.isNil(value) ? blankSpaceEl : value
}
@ -105,34 +72,34 @@ const orUnderline = (value, classes) => {
// }
// }
const getTypeText = (config, currency, classes) => {
const getTypeText = (config, currency) => {
switch (config.triggerType) {
case 'txAmount':
return (
<>
makes a single transaction over{' '}
{orUnderline(config.threshold.threshold, classes)} {currency}
{orUnderline(config.threshold.threshold)} {currency}
</>
)
case 'txVolume':
return (
<>
makes more than {orUnderline(config.threshold.threshold, classes)}{' '}
{currency} worth of transactions within{' '}
{orUnderline(config.threshold.thresholdDays, classes)}{' '}
makes more than {orUnderline(config.threshold.threshold)} {currency}{' '}
worth of transactions within{' '}
{orUnderline(config.threshold.thresholdDays)}{' '}
{singularOrPlural(config.threshold.thresholdDays, 'day', 'days')}
</>
)
case 'txVelocity':
return (
<>
makes more than {orUnderline(config.threshold.threshold, classes)}{' '}
makes more than {orUnderline(config.threshold.threshold)}{' '}
{singularOrPlural(
config.threshold.threshold,
'transaction',
'transactions'
)}{' '}
in {orUnderline(config.threshold.thresholdDays, classes)}{' '}
in {orUnderline(config.threshold.thresholdDays)}{' '}
{singularOrPlural(config.threshold.thresholdDays, 'day', 'days')}
</>
)
@ -140,7 +107,7 @@ const getTypeText = (config, currency, classes) => {
return (
<>
at least one transaction every day for{' '}
{orUnderline(config.threshold.thresholdDays, classes)}{' '}
{orUnderline(config.threshold.thresholdDays)}{' '}
{singularOrPlural(config.threshold.thresholdDays, 'day', 'days')}
</>
)
@ -149,7 +116,7 @@ const getTypeText = (config, currency, classes) => {
}
}
const getRequirementText = (config, classes) => {
const getRequirementText = config => {
switch (config.requirement?.requirement) {
case 'email':
return <>asked to enter code provided through email verification</>
@ -170,8 +137,7 @@ const getRequirementText = (config, classes) => {
case 'suspend':
return (
<>
suspended for{' '}
{orUnderline(config.requirement.suspensionDays, classes)}{' '}
suspended for {orUnderline(config.requirement.suspensionDays)}{' '}
{singularOrPlural(config.requirement.suspensionDays, 'day', 'days')}
</>
)
@ -182,28 +148,24 @@ const getRequirementText = (config, classes) => {
case 'external':
return <>redirected to an external verification process</>
default:
return orUnderline(null, classes)
return orUnderline(null)
}
}
const InfoPanel = ({ step, config = {}, liveValues = {}, currency }) => {
const classes = useStyles()
const oldText = R.range(1, step).map((it, idx) => (
<React.Fragment key={idx}>
{getText(it, config, currency, classes)}
</React.Fragment>
<React.Fragment key={idx}>{getText(it, config, currency)}</React.Fragment>
))
const newText = getText(step, liveValues, currency, classes)
const newText = getText(step, liveValues, currency)
const isLastStep = step === LAST_STEP
return (
<>
<H5 className={classes.infoTitle}>Trigger overview so far</H5>
<H5 className="my-5 mx-0">Trigger overview so far</H5>
<Info3 noMargin>
{oldText}
{step !== 1 && ', '}
<span className={classes.infoCurrentText}>{newText}</span>
<span className="text-comet">{newText}</span>
{!isLastStep && '...'}
</Info3>
</>
@ -229,8 +191,6 @@ const Wizard = ({
emailAuth,
triggers
}) => {
const classes = useStyles()
const [liveValues, setLiveValues] = useState({})
const [{ step, config }, setState] = useState({
step: 1
@ -321,11 +281,7 @@ const Wizard = ({
}
infoPanelHeight={172}
open={true}>
<Stepper
className={classes.stepper}
steps={LAST_STEP}
currentStep={step}
/>
<Stepper className="my-4 mx-0" steps={LAST_STEP} currentStep={step} />
<Formik
validateOnBlur={false}
validateOnChange={true}
@ -334,17 +290,17 @@ const Wizard = ({
initialValues={stepOptions.initialValues}
validationSchema={stepOptions.schema}>
{({ errors, touched, values }) => (
<Form className={classes.form}>
<Form className="h-full flex flex-col">
<GetValues setValues={setLiveValues} />
<stepOptions.Component {...stepOptions.props} />
<div className={classes.submit}>
<div className="flex flex-row mt-auto mx-0 mb-6">
{error && <ErrorMessage>Failed to save</ErrorMessage>}
{createErrorMessage(errors, touched, values) && (
<ErrorMessage>
{createErrorMessage(errors, touched, values)}
</ErrorMessage>
)}
<Button className={classes.button} type="submit">
<Button className="ml-auto" type="submit">
{isLastStep ? 'Finish' : 'Next'}
</Button>
</div>

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import { Field, useFormikContext } from 'formik'
import * as R from 'ramda'
@ -7,95 +6,9 @@ import { H4, Label2, Label1, Info1, Info2 } from 'src/components/typography'
import * as Yup from 'yup'
import { NumberInput, RadioGroup, Dropdown } from 'src/components/inputs/formik'
import { errorColor } from 'src/styling/variables'
import { transformNumber } from 'src/utils/number'
import { onlyFirstToUpper } from 'src/utils/string'
// import TxInIcon from 'src/styling/icons/direction/cash-in.svg?react'
// import TxOutIcon from 'src/styling/icons/direction/cash-out.svg?react'
const useStyles = makeStyles({
radioLabel: {
height: 40,
padding: [[0, 10]]
},
radio: {
padding: 4,
margin: 4
},
radioGroup: {
flexDirection: 'row'
},
error: {
color: errorColor
},
specialLabel: {
height: 40,
padding: 0
},
specialGrid: {
display: 'grid',
gridTemplateColumns: [[182, 162, 181]]
},
directionIcon: {
marginRight: 2
},
directionName: {
marginLeft: 6
},
thresholdWrapper: {
display: 'flex',
flexDirection: 'column'
},
thresholdTitle: {
marginTop: 50
},
thresholdContentWrapper: {
display: 'flex',
flexDirection: 'row'
},
thresholdField: {
marginRight: 6,
width: 75
},
description: {
marginTop: 7
},
space: {
marginLeft: 6,
marginRight: 6
},
lastSpace: {
marginLeft: 6
},
suspensionDays: {
width: 34
},
input: {
marginTop: -2
},
limitedInput: {
width: 50
},
daysInput: {
width: 60
},
dropdownField: {
marginTop: 16,
minWidth: 155
},
externalFields: {
'& > *': {
marginRight: 15
},
'& > *:last-child': {
marginRight: 0
}
}
})
// const direction = Yup.string().required()
const triggerType = Yup.string().required()
const threshold = Yup.object().shape({
threshold: Yup.number()
@ -171,92 +84,6 @@ const Schema = Yup.object()
})
})
// Direction V2 only
// const directionSchema = Yup.object().shape({ direction })
// const directionOptions = [
// {
// display: 'Both',
// code: 'both'
// },
// {
// display: 'Only cash-in',
// code: 'cashIn'
// },
// {
// display: 'Only cash-out',
// code: 'cashOut'
// }
// ]
// const directionOptions2 = [
// {
// display: (
// <>
// <TxInIcon /> in
// </>
// ),
// code: 'cashIn'
// },
// {
// display: (
// <>
// <TxOutIcon /> out
// </>
// ),
// code: 'cashOut'
// },
// {
// display: (
// <>
// <Box display="flex">
// <Box mr={0.25}>
// <TxOutIcon />
// </Box>
// <Box>
// <TxInIcon />
// </Box>
// </Box>
// </>
// ),
// code: 'both'
// }
// ]
// const Direction = () => {
// const classes = useStyles()
// const { errors } = useFormikContext()
// const titleClass = {
// [classes.error]: errors.direction
// }
// return (
// <>
// <Box display="flex" alignItems="center">
// <H4 className={classnames(titleClass)}>
// In which type of transactions will it trigger?
// </H4>
// </Box>
// <Field
// component={RadioGroup}
// name="direction"
// options={directionOptions}
// labelClassName={classes.radioLabel}
// radioClassName={classes.radio}
// className={classes.radioGroup}
// />
// </>
// )
// }
// const txDirection = {
// schema: directionSchema,
// options: directionOptions,
// Component: Direction,
// initialValues: { direction: '' }
// }
// TYPE
const typeSchema = Yup.object()
.shape({
@ -314,18 +141,20 @@ const typeSchema = Yup.object()
const typeOptions = [
{ display: 'Transaction amount', code: 'txAmount' },
{ display: 'Transaction volume', code: 'txVolume' },
{
display: 'Transaction volume',
code: 'txVolume'
},
{ display: 'Transaction velocity', code: 'txVelocity' },
{ display: 'Consecutive days', code: 'consecutiveDays' }
]
const Type = ({ ...props }) => {
const classes = useStyles()
const { errors, touched, values, setTouched, handleChange } =
useFormikContext()
const typeClass = {
[classes.error]: errors.triggerType && touched.triggerType
'text-tomato': errors.triggerType && touched.triggerType
}
const containsType = R.contains(values?.triggerType)
@ -348,7 +177,7 @@ const Type = ({ ...props }) => {
const triggerTypeError = !!(hasDaysError || hasAmountError)
const thresholdClass = {
[classes.error]: triggerTypeError
'text-tomato': triggerTypeError
}
const isRadioGroupActive = () => {
@ -369,9 +198,9 @@ const Type = ({ ...props }) => {
component={RadioGroup}
name="triggerType"
options={typeOptions}
labelClassName={classes.radioLabel}
radioClassName={classes.radio}
className={classes.radioGroup}
labelClassName="h-10 py-0 px-3"
radioClassName="p-1 m-1"
className="flex-row"
onChange={e => {
handleChange(e)
setTouched({
@ -381,73 +210,58 @@ const Type = ({ ...props }) => {
}}
/>
<div className={classes.thresholdWrapper}>
<div className="flex flex-col">
{isRadioGroupActive() && (
<H4 className={classnames(thresholdClass, classes.thresholdTitle)}>
Threshold
</H4>
<H4 className={classnames(thresholdClass, 'mt-12')}>Threshold</H4>
)}
<div className={classes.thresholdContentWrapper}>
<div className="flex flex-row">
{isThresholdCurrencyEnabled && (
<>
<Field
className={classes.thresholdField}
className="mr-2 w-19"
component={NumberInput}
size="lg"
name="threshold.threshold"
error={hasAmountError}
/>
<Info1 className={classnames(classes.description)}>
{props.currency}
</Info1>
<Info1 className="mt-2">{props.currency}</Info1>
</>
)}
{isTransactionAmountEnabled && (
<>
<Field
className={classes.thresholdField}
className="mr-2 w-19"
component={NumberInput}
size="lg"
name="threshold.threshold"
error={hasAmountError}
/>
<Info1 className={classnames(classes.description)}>
transactions
</Info1>
<Info1 className="mt-2">transactions</Info1>
</>
)}
{isThresholdDaysEnabled && (
<>
<Info1
className={classnames(
typeClass,
classes.space,
classes.description
)}>
in
</Info1>
<Info1 className={classnames(typeClass, 'mx-2 mt-2')}>in</Info1>
<Field
className={classes.thresholdField}
className="mr-2 w-19"
component={NumberInput}
size="lg"
name="threshold.thresholdDays"
error={hasDaysError}
/>
<Info1 className={classnames(classes.description)}>days</Info1>
<Info1 className="mt-2">days</Info1>
</>
)}
{isConsecutiveDaysEnabled && (
<>
<Field
className={classes.thresholdField}
className="mr-2 w-19"
component={NumberInput}
size="lg"
name="threshold.thresholdDays"
error={hasDaysError}
/>
<Info1 className={classnames(classes.description)}>
consecutive days
</Info1>
<Info1 className="mt-2">consecutive days</Info1>
</>
)}
</div>
@ -527,16 +341,27 @@ const requirementSchema = Yup.object()
const requirementOptions = [
{ display: 'SMS verification', code: 'sms' },
{ display: 'Email verification', code: 'email' },
{
display: 'Email verification',
code: 'email'
},
{ display: 'ID card image', code: 'idCardPhoto' },
{ display: 'ID data', code: 'idCardData' },
{
display: 'ID data',
code: 'idCardData'
},
{ display: 'Customer camera', code: 'facephoto' },
{ display: 'Sanctions', code: 'sanctions' },
{ display: 'US SSN', code: 'usSsn' },
// { display: 'Super user', code: 'superuser' },
{
display: 'US SSN',
code: 'usSsn'
}, // { display: 'Super user', code: 'superuser' },
{ display: 'Suspend', code: 'suspend' },
{ display: 'Block', code: 'block' },
{ display: 'External verification', code: 'external' }
{
display: 'External verification',
code: 'external'
}
]
const hasRequirementError = (errors, touched, values) =>
@ -563,7 +388,6 @@ const Requirement = ({
complianceServices,
customInfoRequests = []
}) => {
const classes = useStyles()
const { touched, errors, values, handleChange, setTouched } =
useFormikContext()
@ -587,7 +411,10 @@ const Requirement = ({
const availableCustomRequirements = R.filter(
it =>
!R.includes(
{ triggerType: config.triggerType, id: it.id },
{
triggerType: config.triggerType,
id: it.id
},
customRequirementsInUse
),
customInfoRequests
@ -613,7 +440,7 @@ const Requirement = ({
enableCustomRequirement && options.push(customInfoOption)
const titleClass = {
[classes.error]:
'text-tomato':
(!!errors.requirement && !isSuspend && !isCustom && !isExternal) ||
(isSuspend && hasRequirementError(errors, touched, values)) ||
(isCustom && hasCustomRequirementError(errors, touched, values)) ||
@ -629,9 +456,9 @@ const Requirement = ({
component={RadioGroup}
name="requirement.requirement"
options={options}
labelClassName={classes.specialLabel}
radioClassName={classes.radio}
className={classnames(classes.radioGroup, classes.specialGrid)}
labelClassName="h-10 p-0"
radioClassName="p-1 m-1"
className="flex-row grid grid-cols-[182px_162px_181px]"
onChange={e => {
handleChange(e)
setTouched({
@ -641,7 +468,7 @@ const Requirement = ({
/>
{isSuspend && (
<Field
className={classes.thresholdField}
className="mr-2 w-19"
component={NumberInput}
label="Days"
size="lg"
@ -652,7 +479,7 @@ const Requirement = ({
{isCustom && (
<div>
<Field
className={classes.dropdownField}
className="mt-4 min-w-[155px]"
component={Dropdown}
label="Available requests"
name="requirement.customInfoRequestId"
@ -661,9 +488,9 @@ const Requirement = ({
</div>
)}
{isExternal && (
<div className={classes.externalFields}>
<div className="flex flex-col gap-4">
<Field
className={classes.dropdownField}
className="mt-4 w-[155px]"
component={Dropdown}
label="Service"
name="requirement.externalService"
@ -714,29 +541,12 @@ const getView = (data, code, compare) => it => {
return R.compose(R.prop(code), R.find(R.propEq(compare ?? 'code', it)))(data)
}
// const DirectionDisplay = ({ code }) => {
// const classes = useStyles()
// const displayName = getView(directionOptions, 'display')(code)
// const showCashIn = code === 'cashIn' || code === 'both'
// const showCashOut = code === 'cashOut' || code === 'both'
// return (
// <div>
// {showCashOut && <TxOutIcon className={classes.directionIcon} />}
// {showCashIn && <TxInIcon className={classes.directionIcon} />}
// <span className={classes.directionName}>{displayName}</span>
// </div>
// )
// }
const customReqIdMatches = customReqId => it => {
return it.id === customReqId
}
const RequirementInput = ({ customInfoRequests = [] }) => {
const { values } = useFormikContext()
const classes = useStyles()
const requirement = values?.requirement?.requirement
const customRequestId =
R.path(['requirement', 'customInfoRequestId'])(values) ?? ''
@ -753,7 +563,7 @@ const RequirementInput = ({ customInfoRequests = [] }) => {
{isSuspend && (
<Field
bold
className={classes.suspensionDays}
className="w-8"
name="requirement.suspensionDays"
component={NumberInput}
textAlign="center"
@ -771,7 +581,6 @@ const RequirementView = ({
externalService,
customInfoRequests = []
}) => {
const classes = useStyles()
const display =
requirement === 'custom'
? (R.path(['customRequest', 'name'])(
@ -785,7 +594,7 @@ const RequirementView = ({
<div className="flex items-baseline">
{`${display} ${isSuspend ? 'for' : ''}`}
{isSuspend && (
<Info2 className={classes.space} noMargin>
<Info2 className="mx-2" noMargin>
{suspensionDays}
</Info2>
)}
@ -795,12 +604,10 @@ const RequirementView = ({
}
const DisplayThreshold = ({ config, currency, isEdit }) => {
const classes = useStyles()
const inputClasses = {
[classes.input]: true,
[classes.limitedInput]: config?.triggerType === 'txVelocity',
[classes.daysInput]: config?.triggerType === 'consecutiveDays'
'-mt-1': true,
'w-13': config?.triggerType === 'txVelocity',
'w-15': config?.triggerType === 'consecutiveDays'
}
const threshold = config?.threshold?.threshold
@ -834,7 +641,7 @@ const DisplayThreshold = ({ config, currency, isEdit }) => {
return (
<div className="flex items-baseline justify-end">
{Threshold}
<Label2 noMargin className={classes.lastSpace}>
<Label2 noMargin className="ml-2">
{currency}
</Label2>
</div>
@ -843,14 +650,14 @@ const DisplayThreshold = ({ config, currency, isEdit }) => {
return (
<div className="flex items-baseline justify-end">
{Threshold}
<Label2 noMargin className={classes.lastSpace}>
<Label2 noMargin className="ml-2">
{currency}
</Label2>
<Label1 noMargin className={classes.space}>
<Label1 noMargin className="mx-2">
in
</Label1>
{ThresholdDays}
<Label1 noMargin className={classes.lastSpace}>
<Label1 noMargin className="ml-2">
days
</Label1>
</div>
@ -859,11 +666,11 @@ const DisplayThreshold = ({ config, currency, isEdit }) => {
return (
<div className="flex items-baseline justify-end">
{Threshold}
<Label1 className={classes.space} noMargin>
<Label1 className="mx-2" noMargin>
transactions in
</Label1>
{ThresholdDays}
<Label1 className={classes.lastSpace} noMargin>
<Label1 className="ml-2" noMargin>
days
</Label1>
</div>
@ -872,7 +679,7 @@ const DisplayThreshold = ({ config, currency, isEdit }) => {
return (
<div className="flex items-baseline justify-end">
{ThresholdDays}
<Label1 className={classes.lastSpace} noMargin>
<Label1 className="mx-2" noMargin>
days
</Label1>
</div>
@ -892,7 +699,7 @@ const ThresholdView = ({ config, currency }) => {
return <DisplayThreshold config={config} currency={currency} />
}
const getElements = (currency, classes, customInfoRequests) => [
const getElements = (currency, customInfoRequests) => [
{
name: 'triggerType',
size: 'sm',
@ -926,18 +733,6 @@ const getElements = (currency, classes, customInfoRequests) => [
input: () => <ThresholdInput currency={currency} />,
view: (it, config) => <ThresholdView config={config} currency={currency} />
}
// {
// name: 'direction',
// size: 'sm',
// width: 282,
// view: it => <DirectionDisplay code={it} />,
// input: RadioGroup,
// inputProps: {
// labelClassName: classes.tableRadioLabel,
// className: classes.tableRadioGroup,
// options: directionOptions2
// }
// }
]
const triggerOrder = R.map(R.prop('code'))(typeOptions)
@ -987,8 +782,7 @@ const toServer = triggers =>
export {
Schema,
getElements,
// txDirection,
getElements, // txDirection,
type,
requirements,
sortBy,

View file

@ -6,7 +6,7 @@ import React, { useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { H1, H3, Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
import * as Yup from 'yup'
import { Button } from 'src/components/buttons'

View file

@ -1,10 +1,10 @@
import { useMutation, gql } from "@apollo/client";
import { useMutation, gql } from '@apollo/client'
import { makeStyles } from '@mui/styles'
import React, { useEffect, useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
import { urlResolver } from 'src/utils/urlResolver'

View file

@ -1,10 +1,10 @@
import { useMutation, gql } from "@apollo/client";
import { useMutation, gql } from '@apollo/client'
import { makeStyles } from '@mui/styles'
import React, { useEffect, useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
import { urlResolver } from 'src/utils/urlResolver'