fix: coupon form, styling and server handling

This commit is contained in:
Sérgio Salgado 2021-01-07 15:01:49 +00:00 committed by Josh Harvey
parent 5045821593
commit 9d4c4041dc
7 changed files with 138 additions and 118 deletions

View file

@ -8,10 +8,13 @@ const E = require('../error')
const PENDING_INTERVAL_MS = 60 * T.minutes const PENDING_INTERVAL_MS = 60 * T.minutes
const massage = _.flow(_.omit(['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse']), const massageFields = ['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse']
const massageUpdateFields = _.concat(massageFields, 'cryptoAtoms')
const massage = _.flow(_.omit(massageFields),
convertBigNumFields, _.mapKeys(_.snakeCase)) convertBigNumFields, _.mapKeys(_.snakeCase))
const massageUpdates = _.flow(_.omit(['cryptoAtoms', 'direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse']), const massageUpdates = _.flow(_.omit(massageUpdateFields),
convertBigNumFields, _.mapKeys(_.snakeCase)) convertBigNumFields, _.mapKeys(_.snakeCase))
module.exports = {toObj, upsert, insert, update, massage, isClearToSend} module.exports = {toObj, upsert, insert, update, massage, isClearToSend}

View file

@ -12,7 +12,8 @@ const CRYPTO_CURRENCIES = [
configFile: 'bitcoin.conf', configFile: 'bitcoin.conf',
daemon: 'bitcoind', daemon: 'bitcoind',
defaultPort: 8332, defaultPort: 8332,
unitScale: 8 unitScale: 8,
displayScale: 5
}, },
{ {
cryptoCode: 'ETH', cryptoCode: 'ETH',
@ -21,7 +22,8 @@ const CRYPTO_CURRENCIES = [
configFile: 'geth.conf', configFile: 'geth.conf',
daemon: 'geth', daemon: 'geth',
defaultPort: 8545, defaultPort: 8545,
unitScale: 18 unitScale: 18,
displayScale: 15
}, },
{ {
cryptoCode: 'LTC', cryptoCode: 'LTC',
@ -30,7 +32,8 @@ const CRYPTO_CURRENCIES = [
configFile: 'litecoin.conf', configFile: 'litecoin.conf',
daemon: 'litecoind', daemon: 'litecoind',
defaultPort: 9332, defaultPort: 9332,
unitScale: 8 unitScale: 8,
displayScale: 5
}, },
{ {
cryptoCode: 'DASH', cryptoCode: 'DASH',
@ -39,7 +42,8 @@ const CRYPTO_CURRENCIES = [
configFile: 'dash.conf', configFile: 'dash.conf',
daemon: 'dashd', daemon: 'dashd',
defaultPort: 9998, defaultPort: 9998,
unitScale: 8 unitScale: 8,
displayScale: 5
}, },
{ {
cryptoCode: 'ZEC', cryptoCode: 'ZEC',
@ -48,7 +52,8 @@ const CRYPTO_CURRENCIES = [
configFile: 'zcash.conf', configFile: 'zcash.conf',
daemon: 'zcashd', daemon: 'zcashd',
defaultPort: 8232, defaultPort: 8232,
unitScale: 8 unitScale: 8,
displayScale: 5
}, },
{ {
cryptoCode: 'BCH', cryptoCode: 'BCH',
@ -57,7 +62,8 @@ const CRYPTO_CURRENCIES = [
configFile: 'bitcoincash.conf', configFile: 'bitcoincash.conf',
daemon: 'bitcoincashd', daemon: 'bitcoincashd',
defaultPort: 8335, defaultPort: 8335,
unitScale: 8 unitScale: 8,
displayScale: 5
} }
] ]

View file

@ -6,7 +6,7 @@ function truncateCrypto (cryptoAtoms, cryptoCode) {
const DECIMAL_PLACES = 3 const DECIMAL_PLACES = 3
if (cryptoAtoms.eq(0)) return cryptoAtoms if (cryptoAtoms.eq(0)) return cryptoAtoms
const scale = 5 // TODO: change this to coins.displayScale when coins have that attribute const scale = coinUtils.getCryptoCurrency(cryptoCode).displayScale
const scaleFactor = BN(10).pow(scale) const scaleFactor = BN(10).pow(scale)
return BN(cryptoAtoms).truncated().div(scaleFactor) return BN(cryptoAtoms).truncated().div(scaleFactor)

View file

@ -16,7 +16,7 @@ function createCoupon (code, discount) {
return db.one(sql, [uuid.v4(), code, discount]) return db.one(sql, [uuid.v4(), code, discount])
} }
function softDeleteCoupon (couponId) { function deleteCoupon (couponId) {
const sql = `UPDATE coupons SET soft_deleted=true WHERE id=$1` const sql = `UPDATE coupons SET soft_deleted=true WHERE id=$1`
return db.none(sql, [couponId]) return db.none(sql, [couponId])
} }
@ -26,4 +26,4 @@ function getNumberOfAvailableCoupons () {
return db.one(sql).then(res => res.count) return db.one(sql).then(res => res.count)
} }
module.exports = { getAvailableCoupons, getCoupon, createCoupon, softDeleteCoupon, getNumberOfAvailableCoupons } module.exports = { getAvailableCoupons, getCoupon, createCoupon, deleteCoupon, getNumberOfAvailableCoupons }

View file

@ -47,11 +47,12 @@ const Coupons = () => {
const classes = useStyles() const classes = useStyles()
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const [errorMsg, setErrorMsg] = useState(null)
const toggleModal = () => setShowModal(!showModal) const toggleModal = () => setShowModal(!showModal)
const { data: couponResponse, loading } = useQuery(GET_COUPONS) const { data: couponResponse, loading } = useQuery(GET_COUPONS)
const [softDeleteCoupon] = useMutation(DELETE_COUPON, { const [deleteCoupon] = useMutation(DELETE_COUPON, {
refetchQueries: () => ['coupons'] refetchQueries: () => ['coupons']
}) })
@ -59,8 +60,22 @@ const Coupons = () => {
refetchQueries: () => ['coupons'] refetchQueries: () => ['coupons']
}) })
const addCoupon = (code, discount) => { const addCoupon = async (code, discount) => {
createCoupon({ variables: { code: code, discount: discount } }) setErrorMsg(null)
const res = await createCoupon({
variables: { code: code, discount: discount }
})
if (!res.errors) {
return setShowModal(false)
}
const duplicateCouponError = res.errors.some(e => {
return e.message.includes('duplicate')
})
if (duplicateCouponError)
setErrorMsg('There is already a coupon with that code!')
} }
const elements = [ const elements = [
@ -90,7 +105,7 @@ const Coupons = () => {
view: t => ( view: t => (
<IconButton <IconButton
onClick={() => { onClick={() => {
softDeleteCoupon({ variables: { couponId: t.id } }) deleteCoupon({ variables: { couponId: t.id } })
}}> }}>
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
@ -127,7 +142,11 @@ const Coupons = () => {
)} )}
<CouponCodesModal <CouponCodesModal
showModal={showModal} showModal={showModal}
toggleModal={toggleModal} onClose={() => {
setErrorMsg(null)
setShowModal(false)
}}
errorMsg={errorMsg}
addCoupon={addCoupon} addCoupon={addCoupon}
/> />
</> </>

View file

@ -1,14 +1,14 @@
import { spacer, fontPrimary, primaryColor } from 'src/styling/variables' import {
spacer,
fontPrimary,
primaryColor,
errorColor
} from 'src/styling/variables'
const styles = { const styles = {
footer: { footer: {
margin: [['auto', 0, spacer * 3, 'auto']] margin: [['auto', 0, spacer * 3, 'auto']]
}, },
modalTitle: {
marginTop: -5,
color: primaryColor,
fontFamily: fontPrimary
},
modalLabel1: { modalLabel1: {
marginTop: 20 marginTop: 20
}, },
@ -20,7 +20,6 @@ const styles = {
}, },
discountInput: { discountInput: {
display: 'flex', display: 'flex',
height: '100%',
flexDirection: 'row', flexDirection: 'row',
alignItems: 'flex-start' alignItems: 'flex-start'
}, },
@ -33,6 +32,14 @@ const styles = {
}, },
tableWidth: { tableWidth: {
width: 620 width: 620
},
error: {
color: errorColor
},
form: {
display: 'flex',
flexDirection: 'column',
height: '100%'
} }
} }

View file

@ -1,121 +1,106 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { Form, Formik, Field } from 'formik'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React from 'react'
import * as Yup from 'yup'
import Modal from 'src/components/Modal' import Modal from 'src/components/Modal'
import { Tooltip } from 'src/components/Tooltip' import { Tooltip } from 'src/components/Tooltip'
import { Button } from 'src/components/buttons' import { Button } from 'src/components/buttons'
import { TextInput, NumberInput } from 'src/components/inputs/base' import { TextInput, NumberInput } from 'src/components/inputs/formik'
import { H1, H3, TL1, P } from 'src/components/typography' import { H3, TL1, P } from 'src/components/typography'
import styles from './CouponCodes.styles' import styles from './CouponCodes.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const CouponCodesModal = ({ showModal, toggleModal, addCoupon }) => { const initialValues = {
code: '',
discount: ''
}
const validationSchema = Yup.object().shape({
code: Yup.string()
.required()
.trim()
.max(25),
discount: Yup.number()
.required()
.min(0)
.max(100)
})
const CouponCodesModal = ({ showModal, onClose, errorMsg, addCoupon }) => {
const classes = useStyles() const classes = useStyles()
const [codeField, setCodeField] = useState('') const handleAddCoupon = (code, discount) => {
const [discountField, setDiscountField] = useState('') addCoupon(R.toUpper(code), parseInt(discount))
const [invalidCode, setInvalidCode] = useState(false)
const [invalidDiscount, setInvalidDiscount] = useState(false)
const handleCodeChange = event => {
if (event.target.value === '') {
setInvalidCode(false)
}
setCodeField(event.target.value)
}
const handleDiscountChange = event => {
if (event.target.value === '') {
setInvalidDiscount(false)
}
setDiscountField(event.target.value)
}
const handleClose = () => {
setCodeField('')
setDiscountField('')
setInvalidCode(false)
setInvalidDiscount(false)
toggleModal()
}
const handleAddCoupon = () => {
if (codeField.trim() === '') {
setInvalidCode(true)
return
}
if (!validDiscount(discountField)) {
setInvalidDiscount(true)
return
}
if (codeField.trim() !== '' && validDiscount(discountField)) {
addCoupon(R.toUpper(codeField.trim()), parseInt(discountField))
handleClose()
}
}
const validDiscount = discount => {
const parsedDiscount = parseInt(discount)
return parsedDiscount >= 0 && parsedDiscount <= 100
} }
return ( return (
<> <>
{showModal && ( {showModal && (
<Modal <Modal
title="Add coupon code discount"
closeOnBackdropClick={true} closeOnBackdropClick={true}
width={600} width={600}
height={500} height={500}
handleClose={handleClose} handleClose={onClose}
open={true}> open={true}>
<H1 className={classes.modalTitle}>Add coupon code discount</H1> <Formik
<H3 className={classes.modalLabel1}>Coupon code name</H3> initialValues={initialValues}
<TextInput validationSchema={validationSchema}
error={invalidCode} onSubmit={({ code, discount }, { resetForm }) => {
name="coupon-code" handleAddCoupon(code, discount)
autoFocus resetForm()
id="coupon-code" }}>
type="text" <Form id="coupon-form" className={classes.form}>
size="lg" <H3 className={classes.modalLabel1}>Coupon code name</H3>
width={338} <Field
onChange={handleCodeChange} name="code"
value={codeField} autoFocus
inputProps={{ style: { textTransform: 'uppercase' } }} size="lg"
/> autoComplete="off"
<div className={classes.modalLabel2Wrapper}> width={338}
<H3 className={classes.modalLabel2}>Define discount rate</H3> inputProps={{ style: { textTransform: 'uppercase' } }}
<Tooltip width={304}> component={TextInput}
<P> />
The discount rate inserted will be applied to the commissions of <div className={classes.modalLabel2Wrapper}>
all transactions performed with this respective coupon code. <H3 className={classes.modalLabel2}>Define discount rate</H3>
</P> <Tooltip width={304}>
<P> <P>
(It should be a number between 0 (zero) and 100 (one hundred)). The discount rate inserted will be applied to the
</P> commissions of all transactions performed with this
</Tooltip> respective coupon code.
</div> </P>
<div className={classes.discountInput}> <P>
<NumberInput (It should be a number between 0 (zero) and 100 (one
error={invalidDiscount} hundred)).
name="coupon-discount" </P>
id="coupon-discount" </Tooltip>
size="lg" </div>
width={50} <div className={classes.discountInput}>
onChange={handleDiscountChange} <Field
value={discountField} name="discount"
decimalScale={0} size="lg"
className={classes.discountInputField} autoComplete="off"
/> width={50}
<TL1 inline className={classes.inputLabel}> decimalScale={0}
% className={classes.discountInputField}
</TL1> component={NumberInput}
</div> />
<div className={classes.footer}> <TL1 inline className={classes.inputLabel}>
<Button onClick={handleAddCoupon}>Add coupon</Button> %
</div> </TL1>
</div>
<span className={classes.error}>{errorMsg}</span>
<div className={classes.footer}>
<Button type="submit" form="coupon-form">
Add coupon
</Button>
</div>
</Form>
</Formik>
</Modal> </Modal>
)} )}
</> </>