feat: change custom SMS instances to SMS notices and all its interfaces

This commit is contained in:
Sérgio Salgado 2022-02-04 19:39:35 +00:00
parent 04f4891291
commit 0d5f5167ef
12 changed files with 153 additions and 149 deletions

View file

@ -20,6 +20,7 @@ const MANUAL = 'manual'
const CASH_OUT_DISPENSE_READY = 'cash_out_dispense_ready' const CASH_OUT_DISPENSE_READY = 'cash_out_dispense_ready'
const CONFIRMATION_CODE = 'sms_code' const CONFIRMATION_CODE = 'sms_code'
const RECEIPT = 'sms_receipt'
const WALLET_SCORE_THRESHOLD = 9 const WALLET_SCORE_THRESHOLD = 9
@ -37,5 +38,6 @@ module.exports = {
CONFIRMATION_CODE, CONFIRMATION_CODE,
CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES, CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES,
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES, CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
WALLET_SCORE_THRESHOLD WALLET_SCORE_THRESHOLD,
RECEIPT
} }

View file

@ -1,41 +0,0 @@
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
}

View file

@ -1,13 +1,13 @@
const customSms = require('../../../custom-sms') const customSms = require('../../../sms-notices')
const resolvers = { const resolvers = {
Query: { Query: {
customMessages: () => customSms.getCustomMessages() SMSNotices: () => customSms.getSMSNotices()
}, },
Mutation: { Mutation: {
createCustomMessage: (...[, { event, message }]) => customSms.createCustomMessage(event, message), editSMSNotice: (...[, { id, event, message }]) => customSms.editSMSNotice(id, event, message),
editCustomMessage: (...[, { id, event, message }]) => customSms.editCustomMessage(id, event, message), enableSMSNotice: (...[, { id }]) => customSms.enableSMSNotice(id),
deleteCustomMessage: (...[, { id }]) => customSms.deleteCustomMessage(id) disableSMSNotice: (...[, { id }]) => customSms.disableSMSNotice(id)
} }
} }

View file

@ -1,25 +1,29 @@
const { gql } = require('apollo-server-express') const { gql } = require('apollo-server-express')
const typeDef = gql` const typeDef = gql`
type CustomMessage { type SMSNotice {
id: ID! id: ID!
event: CustomMessageEvent! event: SMSNoticeEvent!
message: String! message: String!
messageName: String!
enabled: Boolean!
allowToggle: Boolean!
} }
enum CustomMessageEvent { enum SMSNoticeEvent {
smsCode smsCode
cashOutDispenseReady cashOutDispenseReady
smsReceipt
} }
type Query { type Query {
customMessages: [CustomMessage] @auth SMSNotices: [SMSNotice] @auth
} }
type Mutation { type Mutation {
createCustomMessage(event: CustomMessageEvent!, message: String!): CustomMessage @auth editSMSNotice(id: ID!, event: SMSNoticeEvent!, message: String!): SMSNotice @auth
editCustomMessage(id: ID!, event: CustomMessageEvent!, message: String!): CustomMessage @auth enableSMSNotice(id: ID!): SMSNotice @auth
deleteCustomMessage(id: ID!): CustomMessage @auth disableSMSNotice(id: ID!): SMSNotice @auth
} }
` `

56
lib/sms-notices.js Normal file
View file

@ -0,0 +1,56 @@
const _ = require('lodash/fp')
const uuid = require('uuid')
const db = require('./db')
const getSMSNotices = () => {
const sql = `SELECT * FROM sms_notices ORDER BY created`
return db.any(sql).then(res => _.map(
it => ({
id: it.id,
event: _.camelCase(it.event),
message: it.message,
messageName: it.message_name,
enabled: it.enabled,
allowToggle: it.allow_toggle
}), res))
}
const createSMSNotice = (event, messageName, message, enabled, allowToggle) => {
const sql = `INSERT INTO sms_notices (id, event, message_name, message${enabled ? `, enabled`: ``}${allowToggle ? `, allowToggle`: ``}) VALUES ($1, $2, $3, $4${enabled ? `, $5`: ``}${allowToggle ? `, $6`: ``})`
return db.none(sql, [uuid.v4(), _.snakeCase(event), messageName, message, enabled, allowToggle])
}
const editSMSNotice = (id, event, message) => {
const sql = `UPDATE sms_notices SET event=$2, message=$3 WHERE id=$1`
return db.none(sql, [id, _.snakeCase(event), message])
}
const deleteSMSNotice = id => {
const sql = `DELETE FROM sms_notices WHERE id=$1`
return db.none(sql, [id])
}
const getSMSNotice = event => {
const sql = `SELECT * FROM sms_notices WHERE event=$1 LIMIT 1`
return db.oneOrNone(sql, [event])
}
const enableSMSNotice = id => {
const sql = `UPDATE sms_notices SET enabled = true WHERE id=$1 LIMIT 1`
return db.oneOrNone(sql, [id])
}
const disableSMSNotice = id => {
const sql = `UPDATE sms_notices SET enabled = false WHERE id=$1 LIMIT 1`
return db.oneOrNone(sql, [id])
}
module.exports = {
getSMSNotices,
createSMSNotice,
editSMSNotice,
deleteSMSNotice,
getSMSNotice,
enableSMSNotice,
disableSMSNotice
}

View file

@ -3,7 +3,7 @@ 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 _ = require('lodash/fp')
const customSms = require('./custom-sms') const customSms = require('./sms-notices')
const getDefaultMessageContent = content => ({ const getDefaultMessageContent = content => ({
smsCode: `Your cryptomat code: ${content.code}`, smsCode: `Your cryptomat code: ${content.code}`,

View file

@ -0,0 +1,21 @@
var db = require('./db')
var smsNotices = require('../lib/sms-notices')
exports.up = function (next) {
var sql = [
`ALTER TABLE custom_messages RENAME TO sms_notices`,
`ALTER TYPE custom_message_event RENAME TO sms_notice_event`,
`ALTER TYPE sms_notice_event ADD VALUE 'sms_receipt'`,
`ALTER TABLE sms_notices ADD COLUMN message_name TEXT UNIQUE NOT NULL`,
`ALTER TABLE sms_notices ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT true`
`ALTER TABLE sms_notices ADD COLUMN allow_toggle BOOLEAN NOT NULL DEFAULT true`
]
db.multi(sql, next)
.then(() => smsNotices.createSMSNotice('sms_code', 'SMS confirmation code', 'Your cryptomat code: #code', true, false))
.then(() => smsNotices.createSMSNotice('cash_out_dispense_ready', 'Cash is ready', 'Your cash is waiting! Go to the Cryptomat and press Redeem within 24 hours. [#timestamp]', true, false))
}
exports.down = function (next) {
next()
}

View file

@ -1,15 +1,15 @@
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import { Form, Formik, Field } from 'formik' 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 * as Yup from 'yup'
import ErrorMessage from 'src/components/ErrorMessage' import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal' import Modal from 'src/components/Modal'
import { Button } from 'src/components/buttons' import { Button } from 'src/components/buttons'
import { Autocomplete, TextInput } from 'src/components/inputs/formik' import { TextInput } from 'src/components/inputs/formik'
import styles from './CustomSMS.styles' import styles from './SMSNotices.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
@ -58,18 +58,9 @@ const prefill = {
} }
} }
const CustomSMSModal = ({ const CustomSMSModal = ({ showModal, onClose, sms, creationError, submit }) => {
showModal,
onClose,
sms,
eventOptions,
creationError,
submit
}) => {
const classes = useStyles() const classes = useStyles()
const [selectedEvent, setSelectedEvent] = useState(sms?.event)
const initialValues = { const initialValues = {
event: !R.isNil(sms) ? sms.event : '', event: !R.isNil(sms) ? sms.event : '',
message: !R.isNil(sms) ? sms.message : '' message: !R.isNil(sms) ? sms.message : ''
@ -78,7 +69,7 @@ const CustomSMSModal = ({
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
event: Yup.string().required('An event is required!'), event: Yup.string().required('An event is required!'),
message: message:
prefill[selectedEvent]?.validator ?? prefill[sms?.event]?.validator ??
Yup.string() Yup.string()
.required('The message content is required!') .required('The message content is required!')
.trim() .trim()
@ -106,7 +97,7 @@ const CustomSMSModal = ({
<> <>
{showModal && ( {showModal && (
<Modal <Modal
title={!R.isNil(sms) ? `Edit custom SMS` : `Add custom SMS`} title={`Edit SMS notice`}
closeOnBackdropClick={true} closeOnBackdropClick={true}
width={600} width={600}
height={500} height={500}
@ -121,17 +112,7 @@ const CustomSMSModal = ({
handleSubmit(values, errors, touched) handleSubmit(values, errors, touched)
}> }>
{({ values, errors, touched }) => ( {({ values, errors, touched }) => (
<Form id="custom-sms" className={classes.form}> <Form id="sms-notice" className={classes.form}>
<Field
name="event"
label="Event"
fullWidth
onChange={setSelectedEvent(values.event)}
options={eventOptions}
labelProp="display"
valueProp="code"
component={Autocomplete}
/>
<Field <Field
name="message" name="message"
label="Message content" label="Message content"
@ -148,9 +129,9 @@ const CustomSMSModal = ({
)} )}
<Button <Button
type="submit" type="submit"
form="custom-sms" form="sms-notice"
className={classes.submit}> className={classes.submit}>
{!R.isNil(sms) ? `Confirm` : `Create SMS`} Confirm
</Button> </Button>
</div> </div>
</Form> </Form>

View file

@ -1,65 +1,59 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles, Box } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { DeleteDialog } from 'src/components/DeleteDialog' import { DeleteDialog } from 'src/components/DeleteDialog'
import { Link, IconButton } from 'src/components/buttons' import { IconButton } from 'src/components/buttons'
import { Switch } from 'src/components/inputs'
import DataTable from 'src/components/tables/DataTable' import DataTable from 'src/components/tables/DataTable'
import { H4 } from 'src/components/typography' 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 { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
import styles from './CustomSMS.styles'
import CustomSMSModal from './CustomSMSModal' import CustomSMSModal from './CustomSMSModal'
import styles from './SMSNotices.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const GET_CUSTOM_MESSAGES = gql` const GET_SMS_NOTICES = gql`
query customMessages { query SMSNotices {
customMessages { SMSNotices {
id id
event event
message message
messageName
enabled
allowToggle
} }
} }
` `
const CREATE_CUSTOM_MESSAGE = gql` const EDIT_SMS_NOTICE = gql`
mutation createCustomMessage($event: CustomMessageEvent!, $message: String!) { mutation editSMSNotice($id: ID!, $event: SMSNoticeEvent!, $message: String!) {
createCustomMessage(event: $event, message: $message) { editSMSNotice(id: $id, event: $event, message: $message) {
id id
} }
} }
` `
const EDIT_CUSTOM_MESSAGE = gql` const ENABLE_SMS_NOTICE = gql`
mutation editCustomMessage( mutation enableSMSNotice($id: ID!) {
$id: ID! enableSMSNotice(id: $id) {
$event: CustomMessageEvent!
$message: String!
) {
editCustomMessage(id: $id, event: $event, message: $message) {
id id
} }
} }
` `
const DELETE_CUSTOM_MESSAGE = gql` const DISABLE_SMS_NOTICE = gql`
mutation deleteCustomMessage($id: ID!) { mutation disableSMSNotice($id: ID!) {
deleteCustomMessage(id: $id) { disableSMSNotice(id: $id) {
id id
} }
} }
` `
const EVENT_OPTIONS = [ const SMSNotices = () => {
{ code: 'smsCode', display: 'On SMS confirmation code' },
{ code: 'cashOutDispenseReady', display: 'Cash out dispense ready' }
]
const CustomSMS = () => {
const classes = useStyles() const classes = useStyles()
const [deleteDialog, setDeleteDialog] = useState(false) const [deleteDialog, setDeleteDialog] = useState(false)
@ -68,22 +62,22 @@ const CustomSMS = () => {
const [errorMsg, setErrorMsg] = useState('') const [errorMsg, setErrorMsg] = useState('')
const { data: messagesData, loading: messagesLoading } = useQuery( const { data: messagesData, loading: messagesLoading } = useQuery(
GET_CUSTOM_MESSAGES GET_SMS_NOTICES
) )
const [createMessage] = useMutation(CREATE_CUSTOM_MESSAGE, { const [editMessage] = useMutation(EDIT_SMS_NOTICE, {
onError: ({ msg }) => setErrorMsg(msg), onError: ({ msg }) => setErrorMsg(msg),
refetchQueries: () => ['customMessages'] refetchQueries: () => ['SMSNotices']
}) })
const [editMessage] = useMutation(EDIT_CUSTOM_MESSAGE, { const [enableMessage] = useMutation(ENABLE_SMS_NOTICE, {
onError: ({ msg }) => setErrorMsg(msg), onError: ({ msg }) => setErrorMsg(msg),
refetchQueries: () => ['customMessages'] refetchQueries: () => ['SMSNotices']
}) })
const [deleteMessage] = useMutation(DELETE_CUSTOM_MESSAGE, { const [disableMessage] = useMutation(DISABLE_SMS_NOTICE, {
onError: ({ msg }) => setErrorMsg(msg), onError: ({ msg }) => setErrorMsg(msg),
refetchQueries: () => ['customMessages'] refetchQueries: () => ['SMSNotices']
}) })
const loading = messagesLoading const loading = messagesLoading
@ -94,19 +88,15 @@ const CustomSMS = () => {
setDeleteDialog(false) setDeleteDialog(false)
} }
const handleOpen = () => { console.log(messagesData)
setErrorMsg('')
setShowModal(true)
}
const elements = [ const elements = [
{ {
header: 'Event', header: 'Message name',
width: 600, width: 500,
size: 'sm', size: 'sm',
textAlign: 'left', textAlign: 'left',
view: it => view: it => R.prop('messageName', it)
R.find(ite => R.propEq('event', ite.code, it), EVENT_OPTIONS).display
}, },
{ {
header: 'Edit', header: 'Edit',
@ -124,18 +114,20 @@ const CustomSMS = () => {
) )
}, },
{ {
header: 'Delete', header: 'Enable',
width: 100, width: 100,
size: 'sm', size: 'sm',
textAlign: 'center', textAlign: 'center',
view: it => ( view: it => (
<IconButton <Switch
disabled={!it.allowToggle}
onClick={() => { onClick={() => {
setSelectedSMS(it) it.enabled
setDeleteDialog(true) ? disableMessage({ variables: { id: it.id } })
}}> : enableMessage({ variables: { id: it.id } })
<DeleteIcon /> }}
</IconButton> checked={it.enabled}
/>
) )
} }
] ]
@ -143,21 +135,15 @@ const CustomSMS = () => {
return ( return (
<> <>
<div className={classes.header}> <div className={classes.header}>
<H4>Custom SMS message</H4> <H4>SMS Notices</H4>
<Box display="flex" justifyContent="flex-end">
<Link color="primary" onClick={() => handleOpen()}>
Add custom SMS
</Link>
</Box>
</div> </div>
{showModal && ( {showModal && (
<CustomSMSModal <CustomSMSModal
showModal={showModal} showModal={showModal}
onClose={handleClose} onClose={handleClose}
eventOptions={EVENT_OPTIONS}
sms={selectedSMS} sms={selectedSMS}
creationError={errorMsg} creationError={errorMsg}
submit={selectedSMS ? editMessage : createMessage} submit={editMessage}
/> />
)} )}
<DeleteDialog <DeleteDialog
@ -167,22 +153,17 @@ const CustomSMS = () => {
}} }}
onConfirmed={() => { onConfirmed={() => {
handleClose() handleClose()
deleteMessage({
variables: {
id: selectedSMS.id
}
})
}} }}
errorMessage={errorMsg} errorMessage={errorMsg}
/> />
<DataTable <DataTable
emptyText="No custom SMS so far" emptyText="No SMS notices so far"
elements={elements} elements={elements}
loading={loading} loading={loading}
data={R.path(['customMessages'])(messagesData)} data={R.path(['SMSNotices'])(messagesData)}
/> />
</> </>
) )
} }
export default CustomSMS export default SMSNotices

View file

@ -16,8 +16,8 @@ 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 CustomSMS from 'src/pages/OperatorInfo/SMSNotices/SMSNotices'
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'
import Services from 'src/pages/Services/Services' import Services from 'src/pages/Services/Services'

View file

@ -19,8 +19,8 @@ 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 CustomSMS from 'src/pages/OperatorInfo/SMSNotices/SMSNotices'
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'
import Services from 'src/pages/Services/Services' import Services from 'src/pages/Services/Services'