fix: coupon form, styling and server handling
This commit is contained in:
parent
5045821593
commit
9d4c4041dc
7 changed files with 138 additions and 118 deletions
|
|
@ -8,10 +8,13 @@ const E = require('../error')
|
|||
|
||||
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))
|
||||
|
||||
const massageUpdates = _.flow(_.omit(['cryptoAtoms', 'direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse']),
|
||||
const massageUpdates = _.flow(_.omit(massageUpdateFields),
|
||||
convertBigNumFields, _.mapKeys(_.snakeCase))
|
||||
|
||||
module.exports = {toObj, upsert, insert, update, massage, isClearToSend}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ const CRYPTO_CURRENCIES = [
|
|||
configFile: 'bitcoin.conf',
|
||||
daemon: 'bitcoind',
|
||||
defaultPort: 8332,
|
||||
unitScale: 8
|
||||
unitScale: 8,
|
||||
displayScale: 5
|
||||
},
|
||||
{
|
||||
cryptoCode: 'ETH',
|
||||
|
|
@ -21,7 +22,8 @@ const CRYPTO_CURRENCIES = [
|
|||
configFile: 'geth.conf',
|
||||
daemon: 'geth',
|
||||
defaultPort: 8545,
|
||||
unitScale: 18
|
||||
unitScale: 18,
|
||||
displayScale: 15
|
||||
},
|
||||
{
|
||||
cryptoCode: 'LTC',
|
||||
|
|
@ -30,7 +32,8 @@ const CRYPTO_CURRENCIES = [
|
|||
configFile: 'litecoin.conf',
|
||||
daemon: 'litecoind',
|
||||
defaultPort: 9332,
|
||||
unitScale: 8
|
||||
unitScale: 8,
|
||||
displayScale: 5
|
||||
},
|
||||
{
|
||||
cryptoCode: 'DASH',
|
||||
|
|
@ -39,7 +42,8 @@ const CRYPTO_CURRENCIES = [
|
|||
configFile: 'dash.conf',
|
||||
daemon: 'dashd',
|
||||
defaultPort: 9998,
|
||||
unitScale: 8
|
||||
unitScale: 8,
|
||||
displayScale: 5
|
||||
},
|
||||
{
|
||||
cryptoCode: 'ZEC',
|
||||
|
|
@ -48,7 +52,8 @@ const CRYPTO_CURRENCIES = [
|
|||
configFile: 'zcash.conf',
|
||||
daemon: 'zcashd',
|
||||
defaultPort: 8232,
|
||||
unitScale: 8
|
||||
unitScale: 8,
|
||||
displayScale: 5
|
||||
},
|
||||
{
|
||||
cryptoCode: 'BCH',
|
||||
|
|
@ -57,7 +62,8 @@ const CRYPTO_CURRENCIES = [
|
|||
configFile: 'bitcoincash.conf',
|
||||
daemon: 'bitcoincashd',
|
||||
defaultPort: 8335,
|
||||
unitScale: 8
|
||||
unitScale: 8,
|
||||
displayScale: 5
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ function truncateCrypto (cryptoAtoms, cryptoCode) {
|
|||
const DECIMAL_PLACES = 3
|
||||
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)
|
||||
|
||||
return BN(cryptoAtoms).truncated().div(scaleFactor)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function createCoupon (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`
|
||||
return db.none(sql, [couponId])
|
||||
}
|
||||
|
|
@ -26,4 +26,4 @@ function getNumberOfAvailableCoupons () {
|
|||
return db.one(sql).then(res => res.count)
|
||||
}
|
||||
|
||||
module.exports = { getAvailableCoupons, getCoupon, createCoupon, softDeleteCoupon, getNumberOfAvailableCoupons }
|
||||
module.exports = { getAvailableCoupons, getCoupon, createCoupon, deleteCoupon, getNumberOfAvailableCoupons }
|
||||
|
|
|
|||
|
|
@ -47,11 +47,12 @@ const Coupons = () => {
|
|||
const classes = useStyles()
|
||||
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [errorMsg, setErrorMsg] = useState(null)
|
||||
const toggleModal = () => setShowModal(!showModal)
|
||||
|
||||
const { data: couponResponse, loading } = useQuery(GET_COUPONS)
|
||||
|
||||
const [softDeleteCoupon] = useMutation(DELETE_COUPON, {
|
||||
const [deleteCoupon] = useMutation(DELETE_COUPON, {
|
||||
refetchQueries: () => ['coupons']
|
||||
})
|
||||
|
||||
|
|
@ -59,8 +60,22 @@ const Coupons = () => {
|
|||
refetchQueries: () => ['coupons']
|
||||
})
|
||||
|
||||
const addCoupon = (code, discount) => {
|
||||
createCoupon({ variables: { code: code, discount: discount } })
|
||||
const addCoupon = async (code, 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 = [
|
||||
|
|
@ -90,7 +105,7 @@ const Coupons = () => {
|
|||
view: t => (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
softDeleteCoupon({ variables: { couponId: t.id } })
|
||||
deleteCoupon({ variables: { couponId: t.id } })
|
||||
}}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
|
|
@ -127,7 +142,11 @@ const Coupons = () => {
|
|||
)}
|
||||
<CouponCodesModal
|
||||
showModal={showModal}
|
||||
toggleModal={toggleModal}
|
||||
onClose={() => {
|
||||
setErrorMsg(null)
|
||||
setShowModal(false)
|
||||
}}
|
||||
errorMsg={errorMsg}
|
||||
addCoupon={addCoupon}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { spacer, fontPrimary, primaryColor } from 'src/styling/variables'
|
||||
import {
|
||||
spacer,
|
||||
fontPrimary,
|
||||
primaryColor,
|
||||
errorColor
|
||||
} from 'src/styling/variables'
|
||||
|
||||
const styles = {
|
||||
footer: {
|
||||
margin: [['auto', 0, spacer * 3, 'auto']]
|
||||
},
|
||||
modalTitle: {
|
||||
marginTop: -5,
|
||||
color: primaryColor,
|
||||
fontFamily: fontPrimary
|
||||
},
|
||||
modalLabel1: {
|
||||
marginTop: 20
|
||||
},
|
||||
|
|
@ -20,7 +20,6 @@ const styles = {
|
|||
},
|
||||
discountInput: {
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start'
|
||||
},
|
||||
|
|
@ -33,6 +32,14 @@ const styles = {
|
|||
},
|
||||
tableWidth: {
|
||||
width: 620
|
||||
},
|
||||
error: {
|
||||
color: errorColor
|
||||
},
|
||||
form: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,121 +1,106 @@
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { Form, Formik, Field } from 'formik'
|
||||
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 { Tooltip } from 'src/components/Tooltip'
|
||||
import { Button } from 'src/components/buttons'
|
||||
import { TextInput, NumberInput } from 'src/components/inputs/base'
|
||||
import { H1, H3, TL1, P } from 'src/components/typography'
|
||||
import { TextInput, NumberInput } from 'src/components/inputs/formik'
|
||||
import { H3, TL1, P } from 'src/components/typography'
|
||||
|
||||
import styles from './CouponCodes.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 [codeField, setCodeField] = useState('')
|
||||
const [discountField, setDiscountField] = useState('')
|
||||
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
|
||||
const handleAddCoupon = (code, discount) => {
|
||||
addCoupon(R.toUpper(code), parseInt(discount))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal && (
|
||||
<Modal
|
||||
title="Add coupon code discount"
|
||||
closeOnBackdropClick={true}
|
||||
width={600}
|
||||
height={500}
|
||||
handleClose={handleClose}
|
||||
handleClose={onClose}
|
||||
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>
|
||||
<TextInput
|
||||
error={invalidCode}
|
||||
name="coupon-code"
|
||||
<Field
|
||||
name="code"
|
||||
autoFocus
|
||||
id="coupon-code"
|
||||
type="text"
|
||||
size="lg"
|
||||
autoComplete="off"
|
||||
width={338}
|
||||
onChange={handleCodeChange}
|
||||
value={codeField}
|
||||
inputProps={{ style: { textTransform: 'uppercase' } }}
|
||||
component={TextInput}
|
||||
/>
|
||||
<div className={classes.modalLabel2Wrapper}>
|
||||
<H3 className={classes.modalLabel2}>Define discount rate</H3>
|
||||
<Tooltip width={304}>
|
||||
<P>
|
||||
The discount rate inserted will be applied to the commissions of
|
||||
all transactions performed with this respective coupon code.
|
||||
The discount rate inserted will be applied to the
|
||||
commissions of all transactions performed with this
|
||||
respective coupon code.
|
||||
</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>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={classes.discountInput}>
|
||||
<NumberInput
|
||||
error={invalidDiscount}
|
||||
name="coupon-discount"
|
||||
id="coupon-discount"
|
||||
<Field
|
||||
name="discount"
|
||||
size="lg"
|
||||
autoComplete="off"
|
||||
width={50}
|
||||
onChange={handleDiscountChange}
|
||||
value={discountField}
|
||||
decimalScale={0}
|
||||
className={classes.discountInputField}
|
||||
component={NumberInput}
|
||||
/>
|
||||
<TL1 inline className={classes.inputLabel}>
|
||||
%
|
||||
</TL1>
|
||||
</div>
|
||||
<span className={classes.error}>{errorMsg}</span>
|
||||
<div className={classes.footer}>
|
||||
<Button onClick={handleAddCoupon}>Add coupon</Button>
|
||||
<Button type="submit" form="coupon-form">
|
||||
Add coupon
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue