commit
e57bccf750
14 changed files with 565 additions and 24 deletions
|
|
@ -16,6 +16,9 @@ const USER_SESSIONS_CLEAR_INTERVAL = 1 * T.hour
|
||||||
const AUTOMATIC = 'automatic'
|
const AUTOMATIC = 'automatic'
|
||||||
const MANUAL = 'manual'
|
const MANUAL = 'manual'
|
||||||
|
|
||||||
|
const CASH_OUT_DISPENSE_READY = 'cash_out_dispense_ready'
|
||||||
|
const CONFIRMATION_CODE = 'sms_code'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
anonymousCustomer,
|
anonymousCustomer,
|
||||||
cassetteMaxCapacity,
|
cassetteMaxCapacity,
|
||||||
|
|
@ -25,5 +28,7 @@ module.exports = {
|
||||||
AUTOMATIC,
|
AUTOMATIC,
|
||||||
MANUAL,
|
MANUAL,
|
||||||
USER_SESSIONS_TABLE_NAME,
|
USER_SESSIONS_TABLE_NAME,
|
||||||
USER_SESSIONS_CLEAR_INTERVAL
|
USER_SESSIONS_CLEAR_INTERVAL,
|
||||||
|
CASH_OUT_DISPENSE_READY,
|
||||||
|
CONFIRMATION_CODE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
lib/custom-sms.js
Normal file
41
lib/custom-sms.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const uuid = require('uuid')
|
||||||
|
const db = require('./db')
|
||||||
|
|
||||||
|
const getCustomMessages = () => {
|
||||||
|
const sql = `SELECT * FROM custom_messages ORDER BY created`
|
||||||
|
return db.any(sql).then(res => _.map(
|
||||||
|
it => ({
|
||||||
|
id: it.id,
|
||||||
|
event: _.camelCase(it.event),
|
||||||
|
message: it.message
|
||||||
|
}), res))
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCustomMessage = (event, message) => {
|
||||||
|
const sql = `INSERT INTO custom_messages (id, event, message) VALUES ($1, $2, $3)`
|
||||||
|
return db.none(sql, [uuid.v4(), _.snakeCase(event), message])
|
||||||
|
}
|
||||||
|
|
||||||
|
const editCustomMessage = (id, event, message) => {
|
||||||
|
const sql = `UPDATE custom_messages SET event=$2, message=$3 WHERE id=$1`
|
||||||
|
return db.none(sql, [id, _.snakeCase(event), message])
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCustomMessage = id => {
|
||||||
|
const sql = `DELETE FROM custom_messages WHERE id=$1`
|
||||||
|
return db.none(sql, [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCustomMessage = event => {
|
||||||
|
const sql = `SELECT * FROM custom_messages WHERE event=$1 LIMIT 1`
|
||||||
|
return db.oneOrNone(sql, [event])
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getCustomMessages,
|
||||||
|
createCustomMessage,
|
||||||
|
editCustomMessage,
|
||||||
|
deleteCustomMessage,
|
||||||
|
getCustomMessage
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ const pairing = require('./pairing.resolver')
|
||||||
const rates = require('./rates.resolver')
|
const rates = require('./rates.resolver')
|
||||||
const scalar = require('./scalar.resolver')
|
const scalar = require('./scalar.resolver')
|
||||||
const settings = require('./settings.resolver')
|
const settings = require('./settings.resolver')
|
||||||
|
const sms = require('./sms.resolver')
|
||||||
const status = require('./status.resolver')
|
const status = require('./status.resolver')
|
||||||
const transaction = require('./transaction.resolver')
|
const transaction = require('./transaction.resolver')
|
||||||
const user = require('./users.resolver')
|
const user = require('./users.resolver')
|
||||||
|
|
@ -36,6 +37,7 @@ const resolvers = [
|
||||||
rates,
|
rates,
|
||||||
scalar,
|
scalar,
|
||||||
settings,
|
settings,
|
||||||
|
sms,
|
||||||
status,
|
status,
|
||||||
transaction,
|
transaction,
|
||||||
user,
|
user,
|
||||||
|
|
|
||||||
14
lib/new-admin/graphql/resolvers/sms.resolver.js
Normal file
14
lib/new-admin/graphql/resolvers/sms.resolver.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
const customSms = require('../../../custom-sms')
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
Query: {
|
||||||
|
customMessages: () => customSms.getCustomMessages()
|
||||||
|
},
|
||||||
|
Mutation: {
|
||||||
|
createCustomMessage: (...[, { event, message }]) => customSms.createCustomMessage(event, message),
|
||||||
|
editCustomMessage: (...[, { id, event, message }]) => customSms.editCustomMessage(id, event, message),
|
||||||
|
deleteCustomMessage: (...[, { id }]) => customSms.deleteCustomMessage(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolvers
|
||||||
|
|
@ -15,6 +15,7 @@ const pairing = require('./pairing.type')
|
||||||
const rates = require('./rates.type')
|
const rates = require('./rates.type')
|
||||||
const scalar = require('./scalar.type')
|
const scalar = require('./scalar.type')
|
||||||
const settings = require('./settings.type')
|
const settings = require('./settings.type')
|
||||||
|
const sms = require('./sms.type')
|
||||||
const status = require('./status.type')
|
const status = require('./status.type')
|
||||||
const transaction = require('./transaction.type')
|
const transaction = require('./transaction.type')
|
||||||
const user = require('./users.type')
|
const user = require('./users.type')
|
||||||
|
|
@ -36,6 +37,7 @@ const types = [
|
||||||
rates,
|
rates,
|
||||||
scalar,
|
scalar,
|
||||||
settings,
|
settings,
|
||||||
|
sms,
|
||||||
status,
|
status,
|
||||||
transaction,
|
transaction,
|
||||||
user,
|
user,
|
||||||
|
|
|
||||||
26
lib/new-admin/graphql/types/sms.type.js
Normal file
26
lib/new-admin/graphql/types/sms.type.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
|
const typeDef = gql`
|
||||||
|
type CustomMessage {
|
||||||
|
id: ID!
|
||||||
|
event: CustomMessageEvent!
|
||||||
|
message: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CustomMessageEvent {
|
||||||
|
smsCode
|
||||||
|
cashOutDispenseReady
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
customMessages: [CustomMessage] @auth
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
createCustomMessage(event: CustomMessageEvent!, message: String!): CustomMessage @auth
|
||||||
|
editCustomMessage(id: ID!, event: CustomMessageEvent!, message: String!): CustomMessage @auth
|
||||||
|
deleteCustomMessage(id: ID!): CustomMessage @auth
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
module.exports = typeDef
|
||||||
|
|
@ -23,7 +23,7 @@ const customers = require('./customers')
|
||||||
const commissionMath = require('./commission-math')
|
const commissionMath = require('./commission-math')
|
||||||
const loyalty = require('./loyalty')
|
const loyalty = require('./loyalty')
|
||||||
|
|
||||||
const { cassetteMaxCapacity } = require('./constants')
|
const { cassetteMaxCapacity, CASH_OUT_DISPENSE_READY, CONFIRMATION_CODE } = require('./constants')
|
||||||
|
|
||||||
const notifier = require('./notifier')
|
const notifier = require('./notifier')
|
||||||
|
|
||||||
|
|
@ -366,19 +366,19 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
const phone = tx.phone
|
const phone = tx.phone
|
||||||
const timestamp = dateFormat(new Date(), 'UTC:HH:MM Z')
|
const timestamp = dateFormat(new Date(), 'UTC:HH:MM Z')
|
||||||
const rec = {
|
return sms.getSms(CASH_OUT_DISPENSE_READY, phone, { timestamp })
|
||||||
sms: {
|
.then(smsObj => {
|
||||||
toNumber: phone,
|
const rec = {
|
||||||
body: `Your cash is waiting! Go to the Cryptomat and press Redeem within 24 hours. [${timestamp}]`
|
sms: smsObj
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return sms.sendMessage(settings, rec)
|
||||||
|
.then(() => {
|
||||||
|
const sql = 'UPDATE cash_out_txs SET notified=$1 WHERE id=$2'
|
||||||
|
const values = [true, tx.id]
|
||||||
|
|
||||||
return sms.sendMessage(settings, rec)
|
return db.none(sql, values)
|
||||||
.then(() => {
|
})
|
||||||
const sql = 'update cash_out_txs set notified=$1 where id=$2'
|
|
||||||
const values = [true, tx.id]
|
|
||||||
|
|
||||||
return db.none(sql, values)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -754,15 +754,15 @@ function plugins (settings, deviceId) {
|
||||||
? '123'
|
? '123'
|
||||||
: randomCode()
|
: randomCode()
|
||||||
|
|
||||||
const rec = {
|
return sms.getSms(CONFIRMATION_CODE, phone, { code })
|
||||||
sms: {
|
.then(smsObj => {
|
||||||
toNumber: phone,
|
const rec = {
|
||||||
body: 'Your cryptomat code: ' + code
|
sms: smsObj
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return sms.sendMessage(settings, rec)
|
||||||
return sms.sendMessage(settings, rec)
|
.then(() => code)
|
||||||
.then(() => code)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function sweepHdRow (row) {
|
function sweepHdRow (row) {
|
||||||
|
|
|
||||||
36
lib/sms.js
36
lib/sms.js
|
|
@ -1,6 +1,34 @@
|
||||||
const ph = require('./plugin-helper')
|
const ph = require('./plugin-helper')
|
||||||
const argv = require('minimist')(process.argv.slice(2))
|
const argv = require('minimist')(process.argv.slice(2))
|
||||||
const { utils: coinUtils } = require('lamassu-coins')
|
const { utils: coinUtils } = require('lamassu-coins')
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
const customSms = require('./custom-sms')
|
||||||
|
|
||||||
|
const getDefaultMessageContent = content => ({
|
||||||
|
smsCode: `Your cryptomat code: ${content.code}`,
|
||||||
|
cashOutDispenseReady: `Your cash is waiting! Go to the Cryptomat and press Redeem within 24 hours. [${content.timestamp}]`
|
||||||
|
})
|
||||||
|
|
||||||
|
function getSms (event, phone, content) {
|
||||||
|
return customSms.getCustomMessage(event)
|
||||||
|
.then(msg => {
|
||||||
|
if (!_.isNil(msg)) {
|
||||||
|
var accMsg = msg.message
|
||||||
|
const contentKeys = _.keys(content)
|
||||||
|
const messageContent = _.reduce((acc, it) => _.replace(`#${it}`, content[it], acc), accMsg, contentKeys)
|
||||||
|
return {
|
||||||
|
toNumber: phone,
|
||||||
|
body: messageContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toNumber: phone,
|
||||||
|
body: getDefaultMessageContent(content)[_.camelCase(event)]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getPlugin (settings) {
|
function getPlugin (settings) {
|
||||||
const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio'
|
const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio'
|
||||||
|
|
@ -91,4 +119,10 @@ function formatSmsReceipt (data, options) {
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { sendMessage, formatSmsReceipt, getLookup, toCryptoUnits }
|
module.exports = {
|
||||||
|
getSms,
|
||||||
|
sendMessage,
|
||||||
|
getLookup,
|
||||||
|
formatSmsReceipt,
|
||||||
|
toCryptoUnits
|
||||||
|
}
|
||||||
|
|
|
||||||
19
migrations/1627518944902-custom-sms.js
Normal file
19
migrations/1627518944902-custom-sms.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
var sql = [
|
||||||
|
`CREATE TYPE custom_message_event AS ENUM('sms_code', 'cash_out_dispense_ready')`,
|
||||||
|
`CREATE TABLE custom_messages (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
event custom_message_event UNIQUE NOT NULL,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
created TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
)`
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
188
new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
Normal file
188
new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
import { useQuery, useMutation } from '@apollo/react-hooks'
|
||||||
|
import { makeStyles, Box } from '@material-ui/core'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
import { DeleteDialog } from 'src/components/DeleteDialog'
|
||||||
|
import { Link, IconButton } from 'src/components/buttons'
|
||||||
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
|
import { H4 } from 'src/components/typography'
|
||||||
|
import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
|
||||||
|
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
||||||
|
|
||||||
|
import styles from './CustomSMS.styles'
|
||||||
|
import CustomSMSModal from './CustomSMSModal'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const GET_CUSTOM_MESSAGES = gql`
|
||||||
|
query customMessages {
|
||||||
|
customMessages {
|
||||||
|
id
|
||||||
|
event
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const CREATE_CUSTOM_MESSAGE = gql`
|
||||||
|
mutation createCustomMessage($event: CustomMessageEvent!, $message: String!) {
|
||||||
|
createCustomMessage(event: $event, message: $message) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const EDIT_CUSTOM_MESSAGE = gql`
|
||||||
|
mutation editCustomMessage(
|
||||||
|
$id: ID!
|
||||||
|
$event: CustomMessageEvent!
|
||||||
|
$message: String!
|
||||||
|
) {
|
||||||
|
editCustomMessage(id: $id, event: $event, message: $message) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const DELETE_CUSTOM_MESSAGE = gql`
|
||||||
|
mutation deleteCustomMessage($id: ID!) {
|
||||||
|
deleteCustomMessage(id: $id) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const EVENT_OPTIONS = [
|
||||||
|
{ code: 'smsCode', display: 'On SMS confirmation code' },
|
||||||
|
{ code: 'cashOutDispenseReady', display: 'Cash out dispense ready' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const CustomSMS = () => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const [deleteDialog, setDeleteDialog] = useState(false)
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
const [selectedSMS, setSelectedSMS] = useState(null)
|
||||||
|
const [errorMsg, setErrorMsg] = useState('')
|
||||||
|
|
||||||
|
const { data: messagesData, loading: messagesLoading } = useQuery(
|
||||||
|
GET_CUSTOM_MESSAGES
|
||||||
|
)
|
||||||
|
|
||||||
|
const [createMessage] = useMutation(CREATE_CUSTOM_MESSAGE, {
|
||||||
|
onError: ({ msg }) => setErrorMsg(msg),
|
||||||
|
refetchQueries: () => ['customMessages']
|
||||||
|
})
|
||||||
|
|
||||||
|
const [editMessage] = useMutation(EDIT_CUSTOM_MESSAGE, {
|
||||||
|
onError: ({ msg }) => setErrorMsg(msg),
|
||||||
|
refetchQueries: () => ['customMessages']
|
||||||
|
})
|
||||||
|
|
||||||
|
const [deleteMessage] = useMutation(DELETE_CUSTOM_MESSAGE, {
|
||||||
|
onError: ({ msg }) => setErrorMsg(msg),
|
||||||
|
refetchQueries: () => ['customMessages']
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = messagesLoading
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setSelectedSMS(null)
|
||||||
|
setShowModal(false)
|
||||||
|
setDeleteDialog(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOpen = () => {
|
||||||
|
setErrorMsg('')
|
||||||
|
setShowModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = [
|
||||||
|
{
|
||||||
|
header: 'Event',
|
||||||
|
width: 600,
|
||||||
|
size: 'sm',
|
||||||
|
textAlign: 'left',
|
||||||
|
view: it =>
|
||||||
|
R.find(ite => R.propEq('event', ite.code, it), EVENT_OPTIONS).display
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Edit',
|
||||||
|
width: 100,
|
||||||
|
size: 'sm',
|
||||||
|
textAlign: 'center',
|
||||||
|
view: it => (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedSMS(it)
|
||||||
|
setShowModal(true)
|
||||||
|
}}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Delete',
|
||||||
|
width: 100,
|
||||||
|
size: 'sm',
|
||||||
|
textAlign: 'center',
|
||||||
|
view: it => (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedSMS(it)
|
||||||
|
setDeleteDialog(true)
|
||||||
|
}}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classes.header}>
|
||||||
|
<H4>Custom SMS message</H4>
|
||||||
|
<Box display="flex" justifyContent="flex-end">
|
||||||
|
<Link color="primary" onClick={() => handleOpen()}>
|
||||||
|
Add custom SMS
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
{showModal && (
|
||||||
|
<CustomSMSModal
|
||||||
|
showModal={showModal}
|
||||||
|
onClose={handleClose}
|
||||||
|
eventOptions={EVENT_OPTIONS}
|
||||||
|
sms={selectedSMS}
|
||||||
|
creationError={errorMsg}
|
||||||
|
submit={selectedSMS ? editMessage : createMessage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<DeleteDialog
|
||||||
|
open={deleteDialog}
|
||||||
|
onDismissed={() => {
|
||||||
|
handleClose()
|
||||||
|
}}
|
||||||
|
onConfirmed={() => {
|
||||||
|
handleClose()
|
||||||
|
deleteMessage({
|
||||||
|
variables: {
|
||||||
|
id: selectedSMS.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
errorMessage={errorMsg}
|
||||||
|
/>
|
||||||
|
<DataTable
|
||||||
|
emptyText="No custom SMS so far"
|
||||||
|
elements={elements}
|
||||||
|
loading={loading}
|
||||||
|
data={R.path(['customMessages'])(messagesData)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSMS
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { spacer } from 'src/styling/variables'
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
header: {
|
||||||
|
display: 'flex',
|
||||||
|
position: 'relative',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: 800
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
'& > *': {
|
||||||
|
marginTop: 20
|
||||||
|
},
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
margin: [['auto', 0, spacer * 3, 0]]
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
margin: [['auto', 0, 0, 'auto']]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styles
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { makeStyles } from '@material-ui/core'
|
||||||
|
import { Form, Formik, Field } from 'formik'
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
import ErrorMessage from 'src/components/ErrorMessage'
|
||||||
|
import Modal from 'src/components/Modal'
|
||||||
|
import { Button } from 'src/components/buttons'
|
||||||
|
import { Autocomplete, TextInput } from 'src/components/inputs/formik'
|
||||||
|
|
||||||
|
import styles from './CustomSMS.styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const getErrorMsg = (formikErrors, formikTouched, mutationError) => {
|
||||||
|
if (!formikErrors || !formikTouched) return null
|
||||||
|
if (mutationError) return 'Internal server error'
|
||||||
|
if (formikErrors.event && formikTouched.event) return formikErrors.event
|
||||||
|
if (formikErrors.message && formikTouched.message) return formikErrors.message
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefill = {
|
||||||
|
smsCode: {
|
||||||
|
validator: Yup.string()
|
||||||
|
.required('The message content is required!')
|
||||||
|
.trim()
|
||||||
|
.test({
|
||||||
|
name: 'has-code-tag',
|
||||||
|
message: 'A #code tag is missing from the message!',
|
||||||
|
exclusive: false,
|
||||||
|
test: value => value?.match(/#code/g || [])?.length > 0
|
||||||
|
})
|
||||||
|
.test({
|
||||||
|
name: 'has-single-code-tag',
|
||||||
|
message: 'There should be a single #code tag!',
|
||||||
|
exclusive: false,
|
||||||
|
test: value => value?.match(/#code/g || [])?.length === 1
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cashOutDispenseReady: {
|
||||||
|
validator: Yup.string()
|
||||||
|
.required('The message content is required!')
|
||||||
|
.trim()
|
||||||
|
.test({
|
||||||
|
name: 'has-timestamp-tag',
|
||||||
|
message: 'A #timestamp tag is missing from the message!',
|
||||||
|
exclusive: false,
|
||||||
|
test: value => value?.match(/#timestamp/g || [])?.length > 0
|
||||||
|
})
|
||||||
|
.test({
|
||||||
|
name: 'has-single-timestamp-tag',
|
||||||
|
message: 'There should be a single #timestamp tag!',
|
||||||
|
exclusive: false,
|
||||||
|
test: value => value?.match(/#timestamp/g || [])?.length === 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomSMSModal = ({
|
||||||
|
showModal,
|
||||||
|
onClose,
|
||||||
|
sms,
|
||||||
|
eventOptions,
|
||||||
|
creationError,
|
||||||
|
submit
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const [selectedEvent, setSelectedEvent] = useState(sms?.event)
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
event: !R.isNil(sms) ? sms.event : '',
|
||||||
|
message: !R.isNil(sms) ? sms.message : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationSchema = Yup.object().shape({
|
||||||
|
event: Yup.string().required('An event is required!'),
|
||||||
|
message:
|
||||||
|
prefill[selectedEvent]?.validator ??
|
||||||
|
Yup.string()
|
||||||
|
.required('The message content is required!')
|
||||||
|
.trim()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = values => {
|
||||||
|
sms
|
||||||
|
? submit({
|
||||||
|
variables: {
|
||||||
|
id: sms.id,
|
||||||
|
event: values.event,
|
||||||
|
message: values.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: submit({
|
||||||
|
variables: {
|
||||||
|
event: values.event,
|
||||||
|
message: values.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showModal && (
|
||||||
|
<Modal
|
||||||
|
title={!R.isNil(sms) ? `Edit custom SMS` : `Add custom SMS`}
|
||||||
|
closeOnBackdropClick={true}
|
||||||
|
width={600}
|
||||||
|
height={500}
|
||||||
|
open={true}
|
||||||
|
handleClose={onClose}>
|
||||||
|
<Formik
|
||||||
|
validateOnBlur={false}
|
||||||
|
validateOnChange={false}
|
||||||
|
initialValues={initialValues}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
onSubmit={(values, errors, touched) =>
|
||||||
|
handleSubmit(values, errors, touched)
|
||||||
|
}>
|
||||||
|
{({ values, errors, touched }) => (
|
||||||
|
<Form id="custom-sms" className={classes.form}>
|
||||||
|
<Field
|
||||||
|
name="event"
|
||||||
|
label="Event"
|
||||||
|
fullWidth
|
||||||
|
onChange={setSelectedEvent(values.event)}
|
||||||
|
options={eventOptions}
|
||||||
|
labelProp="display"
|
||||||
|
valueProp="code"
|
||||||
|
component={Autocomplete}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
name="message"
|
||||||
|
label="Message content"
|
||||||
|
fullWidth
|
||||||
|
multiline={true}
|
||||||
|
rows={6}
|
||||||
|
component={TextInput}
|
||||||
|
/>
|
||||||
|
<div className={classes.footer}>
|
||||||
|
{getErrorMsg(errors, touched, creationError) && (
|
||||||
|
<ErrorMessage>
|
||||||
|
{getErrorMsg(errors, touched, creationError)}
|
||||||
|
</ErrorMessage>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="custom-sms"
|
||||||
|
className={classes.submit}>
|
||||||
|
{!R.isNil(sms) ? `Confirm` : `Create SMS`}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSMSModal
|
||||||
|
|
@ -16,6 +16,7 @@ import MachineStatus from 'src/pages/Maintenance/MachineStatus'
|
||||||
import Notifications from 'src/pages/Notifications/Notifications'
|
import Notifications from 'src/pages/Notifications/Notifications'
|
||||||
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
||||||
import ContactInfo from 'src/pages/OperatorInfo/ContactInfo'
|
import ContactInfo from 'src/pages/OperatorInfo/ContactInfo'
|
||||||
|
import CustomSMS from 'src/pages/OperatorInfo/CustomSMS/CustomSMS'
|
||||||
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
||||||
import TermsConditions from 'src/pages/OperatorInfo/TermsConditions'
|
import TermsConditions from 'src/pages/OperatorInfo/TermsConditions'
|
||||||
import ServerLogs from 'src/pages/ServerLogs'
|
import ServerLogs from 'src/pages/ServerLogs'
|
||||||
|
|
@ -172,6 +173,13 @@ const getLamassuRoutes = () => [
|
||||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||||
component: ReceiptPrinting
|
component: ReceiptPrinting
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'custom-sms',
|
||||||
|
label: 'Custom SMS',
|
||||||
|
route: '/settings/operator-info/custom-sms',
|
||||||
|
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||||
|
component: CustomSMS
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'coin-atm-radar',
|
key: 'coin-atm-radar',
|
||||||
label: 'Coin ATM Radar',
|
label: 'Coin ATM Radar',
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import MachineStatus from 'src/pages/Maintenance/MachineStatus'
|
||||||
import Notifications from 'src/pages/Notifications/Notifications'
|
import Notifications from 'src/pages/Notifications/Notifications'
|
||||||
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
||||||
import ContactInfo from 'src/pages/OperatorInfo/ContactInfo'
|
import ContactInfo from 'src/pages/OperatorInfo/ContactInfo'
|
||||||
|
import CustomSMS from 'src/pages/OperatorInfo/CustomSMS/CustomSMS'
|
||||||
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
||||||
import TermsConditions from 'src/pages/OperatorInfo/TermsConditions'
|
import TermsConditions from 'src/pages/OperatorInfo/TermsConditions'
|
||||||
import ServerLogs from 'src/pages/ServerLogs'
|
import ServerLogs from 'src/pages/ServerLogs'
|
||||||
|
|
@ -167,6 +168,13 @@ const getPazuzRoutes = () => [
|
||||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||||
component: ReceiptPrinting
|
component: ReceiptPrinting
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'custom-sms',
|
||||||
|
label: 'Custom SMS',
|
||||||
|
route: '/settings/operator-info/custom-sms',
|
||||||
|
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||||
|
component: CustomSMS
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'coin-atm-radar',
|
key: 'coin-atm-radar',
|
||||||
label: 'Coin ATM Radar',
|
label: 'Coin ATM Radar',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue