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
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={({ code, discount }, { resetForm }) => {
handleAddCoupon(code, discount)
resetForm()
}}>
<Form id="coupon-form" className={classes.form}>
<H3 className={classes.modalLabel1}>Coupon code name</H3> <H3 className={classes.modalLabel1}>Coupon code name</H3>
<TextInput <Field
error={invalidCode} name="code"
name="coupon-code"
autoFocus autoFocus
id="coupon-code"
type="text"
size="lg" size="lg"
autoComplete="off"
width={338} width={338}
onChange={handleCodeChange}
value={codeField}
inputProps={{ style: { textTransform: 'uppercase' } }} inputProps={{ style: { textTransform: 'uppercase' } }}
component={TextInput}
/> />
<div className={classes.modalLabel2Wrapper}> <div className={classes.modalLabel2Wrapper}>
<H3 className={classes.modalLabel2}>Define discount rate</H3> <H3 className={classes.modalLabel2}>Define discount rate</H3>
<Tooltip width={304}> <Tooltip width={304}>
<P> <P>
The discount rate inserted will be applied to the commissions of The discount rate inserted will be applied to the
all transactions performed with this respective coupon code. commissions of all transactions performed with this
respective coupon code.
</P> </P>
<P> <P>
(It should be a number between 0 (zero) and 100 (one hundred)). (It should be a number between 0 (zero) and 100 (one
hundred)).
</P> </P>
</Tooltip> </Tooltip>
</div> </div>
<div className={classes.discountInput}> <div className={classes.discountInput}>
<NumberInput <Field
error={invalidDiscount} name="discount"
name="coupon-discount"
id="coupon-discount"
size="lg" size="lg"
autoComplete="off"
width={50} width={50}
onChange={handleDiscountChange}
value={discountField}
decimalScale={0} decimalScale={0}
className={classes.discountInputField} className={classes.discountInputField}
component={NumberInput}
/> />
<TL1 inline className={classes.inputLabel}> <TL1 inline className={classes.inputLabel}>
% %
</TL1> </TL1>
</div> </div>
<span className={classes.error}>{errorMsg}</span>
<div className={classes.footer}> <div className={classes.footer}>
<Button onClick={handleAddCoupon}>Add coupon</Button> <Button type="submit" form="coupon-form">
Add coupon
</Button>
</div> </div>
</Form>
</Formik>
</Modal> </Modal>
)} )}
</> </>