chore: use monorepo organization
This commit is contained in:
parent
deaf7d6ecc
commit
a687827f7e
1099 changed files with 8184 additions and 11535 deletions
159
packages/admin-ui/src/pages/Commissions/Commissions.jsx
Normal file
159
packages/admin-ui/src/pages/Commissions/Commissions.jsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import { useQuery, useMutation, gql } from '@apollo/client'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
import { HelpTooltip } from 'src/components/Tooltip'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
import ReverseListingViewIcon from 'src/styling/icons/circle buttons/listing-view/white.svg?react'
|
||||
import ListingViewIcon from 'src/styling/icons/circle buttons/listing-view/zodiac.svg?react'
|
||||
import OverrideLabelIcon from 'src/styling/icons/status/spring2.svg?react'
|
||||
|
||||
import { SupportLinkButton } from 'src/components/buttons'
|
||||
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
import { P } from '../../components/typography'
|
||||
|
||||
import CommissionsDetails from './components/CommissionsDetails'
|
||||
import CommissionsList from './components/CommissionsList'
|
||||
|
||||
const GET_DATA = gql`
|
||||
query getData {
|
||||
config
|
||||
cryptoCurrencies {
|
||||
code
|
||||
display
|
||||
}
|
||||
machines {
|
||||
name
|
||||
deviceId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const SAVE_CONFIG = gql`
|
||||
mutation Save($config: JSONObject) {
|
||||
saveConfig(config: $config)
|
||||
}
|
||||
`
|
||||
const removeCoinFromOverride = crypto => override =>
|
||||
R.mergeRight(override, {
|
||||
cryptoCurrencies: R.without([crypto], override.cryptoCurrencies)
|
||||
})
|
||||
|
||||
const Commissions = ({ name: SCREEN_KEY }) => {
|
||||
const [showMachines, setShowMachines] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
const { data, loading } = useQuery(GET_DATA)
|
||||
const [saveConfig] = useMutation(SAVE_CONFIG, {
|
||||
refetchQueries: () => ['getData'],
|
||||
onError: error => setError(error)
|
||||
})
|
||||
|
||||
const config = data?.config && fromNamespace(SCREEN_KEY)(data.config)
|
||||
const localeConfig =
|
||||
data?.config && fromNamespace(namespaces.LOCALE)(data.config)
|
||||
|
||||
const currency = R.prop('fiatCurrency')(localeConfig)
|
||||
const overrides = R.prop('overrides')(config)
|
||||
|
||||
const save = it => {
|
||||
const config = toNamespace(SCREEN_KEY)(it.commissions[0])
|
||||
return saveConfig({ variables: { config } })
|
||||
}
|
||||
|
||||
const saveOverrides = it => {
|
||||
const config = toNamespace(SCREEN_KEY)(it)
|
||||
setError(null)
|
||||
return saveConfig({ variables: { config } })
|
||||
}
|
||||
|
||||
const saveOverridesFromList = it => (_, override) => {
|
||||
const cryptoOverridden = R.path(['cryptoCurrencies', 0], override)
|
||||
|
||||
const sameMachine = R.eqProps('machine', override)
|
||||
const notSameOverride = it => !R.eqProps('cryptoCurrencies', override, it)
|
||||
|
||||
const filterMachine = R.filter(R.both(sameMachine, notSameOverride))
|
||||
const removeCoin = removeCoinFromOverride(cryptoOverridden)
|
||||
|
||||
const machineOverrides = R.map(removeCoin)(filterMachine(it))
|
||||
|
||||
const overrides = machineOverrides.concat(
|
||||
R.filter(it => !sameMachine(it), it)
|
||||
)
|
||||
|
||||
const config = {
|
||||
commissions_overrides: R.prepend(override, overrides)
|
||||
}
|
||||
|
||||
return saveConfig({ variables: { config } })
|
||||
}
|
||||
|
||||
const labels = showMachines
|
||||
? [
|
||||
{
|
||||
label: 'Override value',
|
||||
icon: <OverrideLabelIcon />
|
||||
}
|
||||
]
|
||||
: []
|
||||
|
||||
return (
|
||||
<>
|
||||
<TitleSection
|
||||
title="Commissions"
|
||||
labels={labels}
|
||||
buttons={[
|
||||
{
|
||||
text: 'List view',
|
||||
icon: ListingViewIcon,
|
||||
inverseIcon: ReverseListingViewIcon,
|
||||
toggle: setShowMachines
|
||||
}
|
||||
]}
|
||||
iconClassName="ml-1"
|
||||
appendix={
|
||||
<HelpTooltip width={320}>
|
||||
<P>
|
||||
For details about commissions, please read the relevant
|
||||
knowledgebase articles:
|
||||
</P>
|
||||
<SupportLinkButton
|
||||
link="https://support.lamassu.is/hc/en-us/articles/115001211752-Fixed-fees-Minimum-transaction"
|
||||
label="Fixed fees & Minimum transaction"
|
||||
bottomSpace="1"
|
||||
/>
|
||||
<SupportLinkButton
|
||||
link="https://support.lamassu.is/hc/en-us/articles/360061558352-Commissions-and-Profit-Calculations"
|
||||
label="Commissions and Profit Calculations"
|
||||
bottomSpace="1"
|
||||
/>
|
||||
</HelpTooltip>
|
||||
}
|
||||
/>
|
||||
|
||||
{!showMachines && !loading && (
|
||||
<CommissionsDetails
|
||||
config={config}
|
||||
locale={localeConfig}
|
||||
currency={currency}
|
||||
data={data}
|
||||
error={error}
|
||||
save={save}
|
||||
saveOverrides={saveOverrides}
|
||||
/>
|
||||
)}
|
||||
{showMachines && !loading && (
|
||||
<CommissionsList
|
||||
config={config}
|
||||
localeConfig={localeConfig}
|
||||
currency={currency}
|
||||
data={data}
|
||||
error={error}
|
||||
saveOverrides={saveOverridesFromList(overrides)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Commissions
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import * as R from 'ramda'
|
||||
import React, { useState, memo } from 'react'
|
||||
import Section from 'src/components/layout/Section'
|
||||
import {
|
||||
mainFields,
|
||||
overrides,
|
||||
getSchema,
|
||||
getOverridesSchema,
|
||||
defaults,
|
||||
overridesDefaults,
|
||||
getOrder
|
||||
} from 'src/pages/Commissions/helper'
|
||||
|
||||
import { Table as EditableTable } from 'src/components/editableTable'
|
||||
|
||||
const CommissionsDetails = memo(
|
||||
({ config, locale, currency, data, error, save, saveOverrides }) => {
|
||||
const [isEditingDefault, setEditingDefault] = useState(false)
|
||||
const [isEditingOverrides, setEditingOverrides] = useState(false)
|
||||
|
||||
const commission = config && !R.isEmpty(config) ? config : defaults
|
||||
const commissionOverrides = commission?.overrides ?? []
|
||||
|
||||
const orderedCommissionsOverrides = R.sortWith([
|
||||
R.ascend(getOrder),
|
||||
R.ascend(R.prop('machine'))
|
||||
])(commissionOverrides)
|
||||
|
||||
const onEditingDefault = (it, editing) => setEditingDefault(editing)
|
||||
const onEditingOverrides = (it, editing) => setEditingOverrides(editing)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<EditableTable
|
||||
error={error?.message}
|
||||
title="Default setup"
|
||||
rowSize="lg"
|
||||
titleLg
|
||||
name="commissions"
|
||||
enableEdit
|
||||
initialValues={commission}
|
||||
save={save}
|
||||
validationSchema={getSchema(locale)}
|
||||
data={R.of(commission)}
|
||||
elements={mainFields(currency)}
|
||||
setEditing={onEditingDefault}
|
||||
forceDisable={isEditingOverrides}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<EditableTable
|
||||
error={error?.message}
|
||||
title="Overrides"
|
||||
titleLg
|
||||
name="overrides"
|
||||
enableDelete
|
||||
enableEdit
|
||||
enableCreate
|
||||
groupBy={getOrder}
|
||||
initialValues={overridesDefaults}
|
||||
save={saveOverrides}
|
||||
validationSchema={getOverridesSchema(
|
||||
orderedCommissionsOverrides,
|
||||
data,
|
||||
locale
|
||||
)}
|
||||
data={orderedCommissionsOverrides}
|
||||
elements={overrides(data, currency, orderedCommissionsOverrides)}
|
||||
setEditing={onEditingOverrides}
|
||||
forceDisable={isEditingDefault}
|
||||
/>
|
||||
</Section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default CommissionsDetails
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import * as R from 'ramda'
|
||||
import React, { memo, useState } from 'react'
|
||||
import {
|
||||
overridesDefaults,
|
||||
getCommissions,
|
||||
getListCommissionsSchema,
|
||||
commissionsList
|
||||
} from 'src/pages/Commissions/helper'
|
||||
|
||||
import { Table as EditableTable } from 'src/components/editableTable'
|
||||
import { Select } from 'src/components/inputs'
|
||||
|
||||
const SHOW_ALL = {
|
||||
code: 'SHOW_ALL',
|
||||
display: 'Show all'
|
||||
}
|
||||
|
||||
const ORDER_OPTIONS = [
|
||||
{
|
||||
code: 'machine',
|
||||
display: 'Machine name'
|
||||
},
|
||||
{
|
||||
code: 'cryptoCurrencies',
|
||||
display: 'Cryptocurrency'
|
||||
},
|
||||
{
|
||||
code: 'cashIn',
|
||||
display: 'Cash-in'
|
||||
},
|
||||
{
|
||||
code: 'cashOut',
|
||||
display: 'Cash-out'
|
||||
},
|
||||
{
|
||||
code: 'fixedFee',
|
||||
display: 'Fixed fee'
|
||||
},
|
||||
{
|
||||
code: 'minimumTx',
|
||||
display: 'Minimum Tx'
|
||||
}
|
||||
]
|
||||
|
||||
const getElement = (code, display) => ({
|
||||
code: code,
|
||||
display: display || code
|
||||
})
|
||||
|
||||
const sortCommissionsBy = prop => {
|
||||
switch (prop) {
|
||||
case ORDER_OPTIONS[0]:
|
||||
return R.sortBy(R.find(R.propEq('code', R.prop('machine'))))
|
||||
case ORDER_OPTIONS[1]:
|
||||
return R.sortBy(R.path(['cryptoCurrencies', 0]))
|
||||
default:
|
||||
return R.sortBy(R.prop(prop.code))
|
||||
}
|
||||
}
|
||||
|
||||
const filterCommissions = (coinFilter, machineFilter) =>
|
||||
R.compose(
|
||||
R.filter(
|
||||
it => (machineFilter === SHOW_ALL) | (machineFilter.code === it.machine)
|
||||
),
|
||||
R.filter(
|
||||
it =>
|
||||
(coinFilter === SHOW_ALL) | (coinFilter.code === it.cryptoCurrencies[0])
|
||||
)
|
||||
)
|
||||
|
||||
const CommissionsList = memo(
|
||||
({ config, localeConfig, currency, data, error, saveOverrides }) => {
|
||||
const [machineFilter, setMachineFilter] = useState(SHOW_ALL)
|
||||
const [coinFilter, setCoinFilter] = useState(SHOW_ALL)
|
||||
const [orderProp, setOrderProp] = useState(ORDER_OPTIONS[0])
|
||||
|
||||
const coins = R.prop('cryptoCurrencies', localeConfig) ?? []
|
||||
|
||||
const getMachineCoins = deviceId => {
|
||||
const override = R.prop('overrides', localeConfig)?.find(
|
||||
R.propEq('machine', deviceId)
|
||||
)
|
||||
|
||||
const machineCoins = override
|
||||
? R.prop('cryptoCurrencies', override)
|
||||
: coins
|
||||
|
||||
return R.xprod([deviceId], machineCoins)
|
||||
}
|
||||
|
||||
const getMachineElement = it =>
|
||||
getElement(R.prop('deviceId', it), R.prop('name', it))
|
||||
|
||||
const cryptoData = R.map(getElement)(coins)
|
||||
|
||||
const machineData = R.sortBy(
|
||||
R.prop('display'),
|
||||
R.map(getMachineElement)(R.prop('machines', data))
|
||||
)
|
||||
|
||||
const machinesCoinsTuples = R.unnest(
|
||||
R.map(getMachineCoins)(machineData.map(R.prop('code')))
|
||||
)
|
||||
|
||||
const commissions = R.map(([deviceId, cryptoCode]) =>
|
||||
getCommissions(cryptoCode, deviceId, config)
|
||||
)(machinesCoinsTuples)
|
||||
|
||||
const tableData = R.compose(
|
||||
sortCommissionsBy(orderProp),
|
||||
filterCommissions(coinFilter, machineFilter)
|
||||
)(commissions)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex mb-6">
|
||||
<Select
|
||||
className="mr-5"
|
||||
onSelectedItemChange={setMachineFilter}
|
||||
label="Machines"
|
||||
default={SHOW_ALL}
|
||||
items={[SHOW_ALL].concat(machineData)}
|
||||
selectedItem={machineFilter}
|
||||
/>
|
||||
<Select
|
||||
className="mr-5"
|
||||
onSelectedItemChange={setCoinFilter}
|
||||
label="Cryptocurrency"
|
||||
default={SHOW_ALL}
|
||||
items={[SHOW_ALL].concat(cryptoData)}
|
||||
selectedItem={coinFilter}
|
||||
/>
|
||||
<Select
|
||||
onSelectedItemChange={setOrderProp}
|
||||
label="Sort by"
|
||||
default={ORDER_OPTIONS[0]}
|
||||
items={ORDER_OPTIONS}
|
||||
selectedItem={orderProp}
|
||||
defaultAsFilter
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 w-full max-h-[70vh] overflow-y-auto">
|
||||
<EditableTable
|
||||
error={error?.message}
|
||||
name="comissionsList"
|
||||
enableEdit
|
||||
save={saveOverrides}
|
||||
initialValues={overridesDefaults}
|
||||
validationSchema={getListCommissionsSchema(localeConfig)}
|
||||
data={tableData}
|
||||
elements={commissionsList(data, currency)}
|
||||
orderedBy={orderProp}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default CommissionsList
|
||||
614
packages/admin-ui/src/pages/Commissions/helper.jsx
Normal file
614
packages/admin-ui/src/pages/Commissions/helper.jsx
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
import TxInIcon from 'src/styling/icons/direction/cash-in.svg?react'
|
||||
import TxOutIcon from 'src/styling/icons/direction/cash-out.svg?react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import { Autocomplete, NumberInput } from 'src/components/inputs/formik'
|
||||
import { bold } from 'src/styling/helpers'
|
||||
import { primaryColor, secondaryColorDark } from 'src/styling/variables'
|
||||
import denominations from 'src/utils/bill-denominations'
|
||||
import { getBillOptions } from 'src/utils/bill-options'
|
||||
import { CURRENCY_MAX } from 'src/utils/constants'
|
||||
|
||||
const ALL_MACHINES = {
|
||||
name: 'All Machines',
|
||||
deviceId: 'ALL_MACHINES'
|
||||
}
|
||||
|
||||
const ALL_COINS = {
|
||||
display: 'All Coins',
|
||||
code: 'ALL_COINS'
|
||||
}
|
||||
|
||||
const cashInAndOutHeaderStyle = { marginLeft: 6, whiteSpace: 'nowrap' }
|
||||
|
||||
const cashInHeader = (
|
||||
<div>
|
||||
<TxInIcon />
|
||||
<span style={cashInAndOutHeaderStyle}>Cash-in</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const cashOutHeader = (
|
||||
<div>
|
||||
<TxOutIcon />
|
||||
<span style={cashInAndOutHeaderStyle}>Cash-out</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
const getView = (data, code, compare) => it => {
|
||||
if (!data) return ''
|
||||
|
||||
// The following boolean should come undefined if it is rendering an unpaired machine
|
||||
const attribute = R.find(R.propEq(compare ?? 'code', it))(data)
|
||||
|
||||
return attribute ? R.prop(code, attribute) : 'Unpaired machine'
|
||||
}
|
||||
|
||||
const displayCodeArray = data => it => {
|
||||
if (!it) return it
|
||||
|
||||
return R.compose(R.join(', '), R.map(getView(data, 'display')))(it)
|
||||
}
|
||||
|
||||
const onCryptoChange = (prev, curr, setValue) => {
|
||||
const hasAllCoins = R.includes(ALL_COINS.code)(curr)
|
||||
const hadAllCoins = R.includes(ALL_COINS.code)(prev)
|
||||
|
||||
if (hasAllCoins && hadAllCoins && R.length(curr) > 1) {
|
||||
return setValue(R.reject(R.equals(ALL_COINS.code))(curr))
|
||||
}
|
||||
|
||||
if (hasAllCoins && !hadAllCoins) {
|
||||
return setValue([ALL_COINS.code])
|
||||
}
|
||||
|
||||
setValue(curr)
|
||||
}
|
||||
|
||||
const getOverridesFields = (getData, currency, auxElements) => {
|
||||
const machineData = [ALL_MACHINES].concat(getData(['machines']))
|
||||
const rawCryptos = getData(['cryptoCurrencies'])
|
||||
const cryptoData = [ALL_COINS].concat(
|
||||
R.map(it => ({ display: it.code, code: it.code }))(rawCryptos ?? [])
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'machine',
|
||||
width: 196,
|
||||
size: 'sm',
|
||||
view: getView(machineData, 'name', 'deviceId'),
|
||||
input: Autocomplete,
|
||||
inputProps: {
|
||||
options: machineData,
|
||||
valueProp: 'deviceId',
|
||||
labelProp: 'name'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cryptoCurrencies',
|
||||
width: 145,
|
||||
size: 'sm',
|
||||
view: displayCodeArray(cryptoData),
|
||||
input: Autocomplete,
|
||||
inputProps: {
|
||||
options: cryptoData,
|
||||
valueProp: 'code',
|
||||
labelProp: 'display',
|
||||
multiple: true,
|
||||
onChange: onCryptoChange,
|
||||
shouldStayOpen: true
|
||||
}
|
||||
},
|
||||
{
|
||||
header: cashInHeader,
|
||||
name: 'cashIn',
|
||||
display: 'Cash-in',
|
||||
width: 123,
|
||||
input: NumberInput,
|
||||
textAlign: 'right',
|
||||
suffix: '%',
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
header: cashOutHeader,
|
||||
name: 'cashOut',
|
||||
display: 'Cash-out',
|
||||
width: 127,
|
||||
input: NumberInput,
|
||||
textAlign: 'right',
|
||||
suffix: '%',
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'fixedFee',
|
||||
display: 'Fixed fee',
|
||||
width: 126,
|
||||
input: NumberInput,
|
||||
doubleHeader: 'Cash-in only',
|
||||
textAlign: 'right',
|
||||
suffix: currency,
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'minimumTx',
|
||||
display: 'Minimum Tx',
|
||||
width: 140,
|
||||
doubleHeader: 'Cash-in only',
|
||||
textAlign: 'center',
|
||||
editingAlign: 'right',
|
||||
input: NumberInput,
|
||||
suffix: currency,
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cashOutFixedFee',
|
||||
display: 'Fixed fee',
|
||||
width: 134,
|
||||
doubleHeader: 'Cash-out only',
|
||||
textAlign: 'center',
|
||||
editingAlign: 'right',
|
||||
input: NumberInput,
|
||||
suffix: currency,
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const mainFields = currency => [
|
||||
{
|
||||
header: cashInHeader,
|
||||
name: 'cashIn',
|
||||
display: 'Cash-in',
|
||||
width: 169,
|
||||
size: 'lg',
|
||||
editingAlign: 'right',
|
||||
input: NumberInput,
|
||||
suffix: '%',
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
header: cashOutHeader,
|
||||
name: 'cashOut',
|
||||
display: 'Cash-out',
|
||||
width: 169,
|
||||
size: 'lg',
|
||||
editingAlign: 'right',
|
||||
input: NumberInput,
|
||||
suffix: '%',
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'fixedFee',
|
||||
display: 'Fixed fee',
|
||||
width: 169,
|
||||
size: 'lg',
|
||||
doubleHeader: 'Cash-in only',
|
||||
textAlign: 'center',
|
||||
editingAlign: 'right',
|
||||
input: NumberInput,
|
||||
suffix: currency,
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'minimumTx',
|
||||
display: 'Minimum Tx',
|
||||
width: 169,
|
||||
size: 'lg',
|
||||
doubleHeader: 'Cash-in only',
|
||||
textAlign: 'center',
|
||||
editingAlign: 'right',
|
||||
input: NumberInput,
|
||||
suffix: currency,
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cashOutFixedFee',
|
||||
display: 'Fixed fee',
|
||||
width: 169,
|
||||
size: 'lg',
|
||||
doubleHeader: 'Cash-out only',
|
||||
textAlign: 'center',
|
||||
editingAlign: 'right',
|
||||
input: NumberInput,
|
||||
suffix: currency,
|
||||
bold: bold,
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const overrides = (auxData, currency, auxElements) => {
|
||||
const getData = R.path(R.__, auxData)
|
||||
|
||||
return getOverridesFields(getData, currency, auxElements)
|
||||
}
|
||||
|
||||
const percentMin = -15
|
||||
const percentMax = 100
|
||||
const getSchema = locale => {
|
||||
const bills = getBillOptions(locale, denominations).map(it => it.code)
|
||||
const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills)
|
||||
|
||||
return Yup.object().shape({
|
||||
cashIn: Yup.number()
|
||||
.label('Cash-in')
|
||||
.min(percentMin)
|
||||
.max(percentMax)
|
||||
.required(),
|
||||
cashOut: Yup.number()
|
||||
.label('Cash-out')
|
||||
.min(percentMin)
|
||||
.max(percentMax)
|
||||
.required(),
|
||||
fixedFee: Yup.number()
|
||||
.label('Cash-in fixed fee')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required(),
|
||||
minimumTx: Yup.number()
|
||||
.label('Minimum Tx')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required(),
|
||||
cashOutFixedFee: Yup.number()
|
||||
.label('Cash-out fixed fee')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required()
|
||||
})
|
||||
}
|
||||
|
||||
const getAlreadyUsed = (id, machine, values) => {
|
||||
const getCrypto = R.prop('cryptoCurrencies')
|
||||
const getMachineId = R.prop('machine')
|
||||
|
||||
const filteredOverrides = R.filter(R.propEq('machine', machine))(values)
|
||||
const originalValue = R.find(R.propEq('id', id))(values)
|
||||
|
||||
const originalCryptos = getCrypto(originalValue)
|
||||
const originalMachineId = getMachineId(originalValue)
|
||||
|
||||
const alreadyUsed = R.compose(
|
||||
R.uniq,
|
||||
R.flatten,
|
||||
R.map(getCrypto)
|
||||
)(filteredOverrides)
|
||||
|
||||
if (machine !== originalMachineId) return alreadyUsed ?? []
|
||||
|
||||
return R.difference(alreadyUsed, originalCryptos)
|
||||
}
|
||||
|
||||
const getOverridesSchema = (values, rawData, locale) => {
|
||||
const getData = R.path(R.__, rawData)
|
||||
const machineData = [ALL_MACHINES].concat(getData(['machines']))
|
||||
const rawCryptos = getData(['cryptoCurrencies'])
|
||||
const cryptoData = [ALL_COINS].concat(
|
||||
R.map(it => ({ display: it.code, code: it.code }))(rawCryptos ?? [])
|
||||
)
|
||||
|
||||
const bills = getBillOptions(locale, denominations).map(it =>
|
||||
parseInt(it.code)
|
||||
)
|
||||
const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills)
|
||||
|
||||
return Yup.object().shape({
|
||||
machine: Yup.string().nullable().label('Machine').required(),
|
||||
cryptoCurrencies: Yup.array()
|
||||
.test({
|
||||
test() {
|
||||
const { id, machine, cryptoCurrencies } = this.parent
|
||||
const alreadyUsed = getAlreadyUsed(id, machine, values)
|
||||
|
||||
const isAllMachines = machine === ALL_MACHINES.deviceId
|
||||
const isAllCoins = R.includes(ALL_COINS.code, cryptoCurrencies)
|
||||
if (isAllMachines && isAllCoins) {
|
||||
return this.createError({
|
||||
message: `All machines and all coins should be configured in the default setup table`
|
||||
})
|
||||
}
|
||||
|
||||
const repeated = R.intersection(alreadyUsed, cryptoCurrencies)
|
||||
if (!R.isEmpty(repeated)) {
|
||||
const codes = displayCodeArray(cryptoData)(repeated)
|
||||
const machineView = getView(
|
||||
machineData,
|
||||
'name',
|
||||
'deviceId'
|
||||
)(machine)
|
||||
|
||||
const message = `${codes} already overridden for machine: ${machineView}`
|
||||
|
||||
return this.createError({ message })
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
.label('Crypto currencies')
|
||||
.required()
|
||||
.min(1),
|
||||
cashIn: Yup.number()
|
||||
.label('Cash-in')
|
||||
.min(percentMin)
|
||||
.max(percentMax)
|
||||
.required(),
|
||||
cashOut: Yup.number()
|
||||
.label('Cash-out')
|
||||
.min(percentMin)
|
||||
.max(percentMax)
|
||||
.required(),
|
||||
fixedFee: Yup.number()
|
||||
.label('Cash-in fixed fee')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required(),
|
||||
minimumTx: Yup.number()
|
||||
.label('Minimum Tx')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required(),
|
||||
cashOutFixedFee: Yup.number()
|
||||
.label('Cash-out fixed fee')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required()
|
||||
})
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
cashIn: '',
|
||||
cashOut: '',
|
||||
fixedFee: '',
|
||||
minimumTx: '',
|
||||
cashOutFixedFee: ''
|
||||
}
|
||||
|
||||
const overridesDefaults = {
|
||||
machine: null,
|
||||
cryptoCurrencies: [],
|
||||
cashIn: '',
|
||||
cashOut: '',
|
||||
fixedFee: '',
|
||||
minimumTx: '',
|
||||
cashOutFixedFee: ''
|
||||
}
|
||||
|
||||
const getOrder = ({ machine, cryptoCurrencies }) => {
|
||||
const isAllMachines = machine === ALL_MACHINES.deviceId
|
||||
const isAllCoins = R.contains(ALL_COINS.code, cryptoCurrencies)
|
||||
|
||||
if (isAllMachines && isAllCoins) return 0
|
||||
if (isAllMachines) return 1
|
||||
if (isAllCoins) return 2
|
||||
|
||||
return 3
|
||||
}
|
||||
|
||||
const createCommissions = (cryptoCode, deviceId, isDefault, config) => {
|
||||
return {
|
||||
minimumTx: config.minimumTx,
|
||||
fixedFee: config.fixedFee,
|
||||
cashOut: config.cashOut,
|
||||
cashIn: config.cashIn,
|
||||
cashOutFixedFee: config.cashOutFixedFee,
|
||||
machine: deviceId,
|
||||
cryptoCurrencies: [cryptoCode],
|
||||
default: isDefault,
|
||||
id: uuidv4()
|
||||
}
|
||||
}
|
||||
|
||||
const getCommissions = (cryptoCode, deviceId, config) => {
|
||||
const overrides = R.prop('overrides', config) ?? []
|
||||
|
||||
if (!overrides && R.isEmpty(overrides)) {
|
||||
return createCommissions(cryptoCode, deviceId, true, config)
|
||||
}
|
||||
|
||||
const specificOverride = R.find(
|
||||
it => it.machine === deviceId && R.includes(cryptoCode)(it.cryptoCurrencies)
|
||||
)(overrides)
|
||||
|
||||
if (specificOverride !== undefined)
|
||||
return createCommissions(cryptoCode, deviceId, false, specificOverride)
|
||||
|
||||
const machineOverride = R.find(
|
||||
it =>
|
||||
it.machine === deviceId && R.includes('ALL_COINS')(it.cryptoCurrencies)
|
||||
)(overrides)
|
||||
|
||||
if (machineOverride !== undefined)
|
||||
return createCommissions(cryptoCode, deviceId, false, machineOverride)
|
||||
|
||||
const coinOverride = R.find(
|
||||
it =>
|
||||
it.machine === 'ALL_MACHINES' &&
|
||||
R.includes(cryptoCode)(it.cryptoCurrencies)
|
||||
)(overrides)
|
||||
|
||||
if (coinOverride !== undefined)
|
||||
return createCommissions(cryptoCode, deviceId, false, coinOverride)
|
||||
|
||||
return createCommissions(cryptoCode, deviceId, true, config)
|
||||
}
|
||||
|
||||
const getListCommissionsSchema = locale => {
|
||||
const bills = getBillOptions(locale, denominations).map(it =>
|
||||
parseInt(it.code)
|
||||
)
|
||||
const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills)
|
||||
|
||||
return Yup.object().shape({
|
||||
machine: Yup.string().label('Machine').required(),
|
||||
cryptoCurrencies: Yup.array().label('Crypto currency').required().min(1),
|
||||
cashIn: Yup.number()
|
||||
.label('Cash-in')
|
||||
.min(percentMin)
|
||||
.max(percentMax)
|
||||
.required(),
|
||||
cashOut: Yup.number()
|
||||
.label('Cash-out')
|
||||
.min(percentMin)
|
||||
.max(percentMax)
|
||||
.required(),
|
||||
fixedFee: Yup.number()
|
||||
.label('Cash-in fixed fee')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required(),
|
||||
minimumTx: Yup.number()
|
||||
.label('Minimum Tx')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required(),
|
||||
cashOutFixedFee: Yup.number()
|
||||
.label('Cash-out fixed fee')
|
||||
.min(0)
|
||||
.max(highestBill)
|
||||
.required()
|
||||
})
|
||||
}
|
||||
|
||||
const getTextStyle = (obj, isEditing) => {
|
||||
return { color: obj.default ? primaryColor : secondaryColorDark }
|
||||
}
|
||||
|
||||
const commissionsList = (auxData, currency, auxElements) => {
|
||||
const getData = R.path(R.__, auxData)
|
||||
|
||||
return getListCommissionsFields(getData, currency, defaults)
|
||||
}
|
||||
|
||||
const getListCommissionsFields = (getData, currency, defaults) => {
|
||||
const machineData = [ALL_MACHINES].concat(getData(['machines']))
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'machine',
|
||||
width: 196,
|
||||
size: 'sm',
|
||||
view: getView(machineData, 'name', 'deviceId'),
|
||||
editable: false
|
||||
},
|
||||
{
|
||||
name: 'cryptoCurrencies',
|
||||
display: 'Crypto Currency',
|
||||
width: 150,
|
||||
view: R.prop(0),
|
||||
size: 'sm',
|
||||
editable: false
|
||||
},
|
||||
{
|
||||
header: cashInHeader,
|
||||
name: 'cashIn',
|
||||
display: 'Cash-in',
|
||||
width: 120,
|
||||
input: NumberInput,
|
||||
textAlign: 'right',
|
||||
suffix: '%',
|
||||
textStyle: obj => getTextStyle(obj),
|
||||
inputProps: {
|
||||
decimalPlaces: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
header: cashOutHeader,
|
||||
name: 'cashOut',
|
||||
display: 'Cash-out',
|
||||
width: 126,
|
||||
input: NumberInput,
|
||||
textAlign: 'right',
|
||||
greenText: true,
|
||||
suffix: '%',
|
||||
textStyle: obj => getTextStyle(obj),
|
||||
inputProps: {
|
||||
decimalPlaces: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'fixedFee',
|
||||
display: 'Fixed fee',
|
||||
width: 140,
|
||||
input: NumberInput,
|
||||
doubleHeader: 'Cash-in only',
|
||||
textAlign: 'right',
|
||||
suffix: currency,
|
||||
textStyle: obj => getTextStyle(obj),
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'minimumTx',
|
||||
display: 'Minimum Tx',
|
||||
width: 140,
|
||||
input: NumberInput,
|
||||
doubleHeader: 'Cash-in only',
|
||||
textAlign: 'right',
|
||||
suffix: currency,
|
||||
textStyle: obj => getTextStyle(obj),
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cashOutFixedFee',
|
||||
display: 'Fixed fee',
|
||||
width: 140,
|
||||
input: NumberInput,
|
||||
doubleHeader: 'Cash-out only',
|
||||
textAlign: 'center',
|
||||
editingAlign: 'right',
|
||||
suffix: currency,
|
||||
textStyle: obj => getTextStyle(obj),
|
||||
inputProps: {
|
||||
decimalPlaces: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export {
|
||||
mainFields,
|
||||
overrides,
|
||||
getSchema,
|
||||
getOverridesSchema,
|
||||
defaults,
|
||||
overridesDefaults,
|
||||
getOrder,
|
||||
getCommissions,
|
||||
getListCommissionsSchema,
|
||||
commissionsList
|
||||
}
|
||||
3
packages/admin-ui/src/pages/Commissions/index.js
Normal file
3
packages/admin-ui/src/pages/Commissions/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Commissions from './Commissions'
|
||||
|
||||
export default Commissions
|
||||
Loading…
Add table
Add a link
Reference in a new issue