feat: add custom SMS screen

feat: custom messages database migration
feat: populate custom SMS screen with database data
feat: custom SMS creation modal
fix: small fixes on styling
This commit is contained in:
Sérgio Salgado 2021-07-29 16:16:31 +01:00
parent 0035684040
commit 3480bbf8f7
11 changed files with 301 additions and 0 deletions

17
lib/custom-sms.js Normal file
View file

@ -0,0 +1,17 @@
const uuid = require('uuid')
const db = require('./db')
const getCustomMessages = () => {
const sql = `SELECT * FROM custom_messages`
return db.any(sql)
}
const createCustomMessage = (event, deviceId, message) => {
const sql = `INSERT INTO custom_message (event, device_id, message) VALUES ($2, $3, $4)`
return db.none(sql, [uuid.v4(), event, deviceId, message])
}
module.exports = {
getCustomMessages,
createCustomMessage
}

View file

@ -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,

View file

@ -0,0 +1,12 @@
const customSms = require('../../../custom-sms')
const resolvers = {
Query: {
customMessages: () => customSms.getCustomMessages()
},
Mutation: {
createCustomMessage: (...[, { event, deviceId, message }]) => customSms.createCustomMessage(event, deviceId, message)
}
}
module.exports = resolvers

View file

@ -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,

View file

@ -0,0 +1,25 @@
const { gql } = require('apollo-server-express')
const typeDef = gql`
type CustomMessage {
id: ID!
event: CustomMessageEvent!
deviceId: String
message: String!
}
enum CustomMessageEvent {
smsCode
cashOutDispenseReady
}
type Query {
customMessages: [CustomMessage] @auth
}
type Mutation {
createCustomMessage(event: CustomMessageEvent!, deviceId: String, message: String!): CustomMessage @auth
}
`
module.exports = typeDef

View file

@ -0,0 +1,20 @@
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 NOT NULL,
device_id TEXT REFERENCES devices(device_id),
message TEXT NOT NULL
)`,
`CREATE UNIQUE INDEX uq_custom_message_per_device ON custom_messages (event, device_id)`
]
db.multi(sql, next)
}
exports.down = function (next) {
next()
}

View file

@ -0,0 +1,127 @@
import { useQuery } 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 { 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 { global } from '../OperatorInfo.styles'
import CustomSMSModal from './CustomSMSModal'
const useStyles = makeStyles(global)
const GET_CUSTOM_MESSAGES = gql`
query customMessages {
customMessages {
id
event
deviceId
message
}
}
`
const GET_MACHINES = gql`
{
machines {
name
deviceId
}
}
`
const CustomSMS = () => {
const classes = useStyles()
const [showModal, setShowModal] = useState(false)
const { data: messagesData, loading: messagesLoading } = useQuery(
GET_CUSTOM_MESSAGES
)
const { data: machinesData, loading: machinesLoading } = useQuery(
GET_MACHINES
)
const loading = messagesLoading && machinesLoading
const machineOptions =
machinesData &&
R.map(
it => ({ code: it.deviceId, display: it.name }),
R.path(['machines'])(machinesData)
)
const elements = [
{
header: 'Message name',
width: 400,
size: 'sm',
textAlign: 'left',
view: it => it.event
},
{
header: 'Edit',
width: 120,
size: 'sm',
textAlign: 'center',
view: it => (
<IconButton
onClick={() => {
console.log('edit')
}}>
<EditIcon />
</IconButton>
)
},
{
header: 'Delete',
width: 120,
size: 'sm',
textAlign: 'center',
view: it => (
<IconButton
onClick={() => {
console.log('delete')
}}>
<DeleteIcon />
</IconButton>
)
}
]
return (
<>
<div className={classes.headerWithLink}>
<H4>Custom SMS message</H4>
<Box
className={classes.tableWidth}
display="flex"
justifyContent="flex-end">
<Link color="primary" onClick={() => setShowModal(true)}>
Add custom SMS
</Link>
</Box>
</div>
{showModal && (
<CustomSMSModal
showModal={showModal}
onClose={() => setShowModal(false)}
machineOptions={machineOptions}
/>
)}
<DataTable
emptyText="No custom SMS so far"
elements={elements}
loading={loading}
data={R.path(['customMessages'])(messagesData)}
/>
</>
)
}
export default CustomSMS

View file

@ -0,0 +1,73 @@
import { Form, Formik, Field } from 'formik'
import React from 'react'
import * as Yup from 'yup'
import Modal from 'src/components/Modal'
import { Autocomplete } from 'src/components/inputs/formik'
const EVENT_OPTIONS = [
{ code: 'sms_code', display: 'On SMS confirmation code' },
{ code: 'cash_out_dispense_ready', display: 'Cash out dispense ready' }
]
const CustomSMSModal = ({
showModal,
onClose,
customMessage,
machineOptions
}) => {
const initialValues = {
event: '',
device: '',
message: ''
}
const validationSchema = {
event: Yup.string().required('An event is required!'),
device: Yup.string(),
message: Yup.string()
.required('The message content is required!')
.trim()
}
return (
<>
{showModal && (
<Modal
title="Add custom SMS"
closeOnBackdropClick={true}
width={600}
height={500}
open={true}
handleClose={onClose}>
<Formik
validateOnBlur={false}
validateOnChange={false}
initialValues={initialValues}
validationSchema={validationSchema}>
<Form id="custom-sms">
<Field
name="event"
fullWidth
options={EVENT_OPTIONS}
labelProp="display"
valueProp="code"
component={Autocomplete}
/>
<Field
name="device"
fullWidth
options={machineOptions}
labelProp="display"
valueProp="code"
component={Autocomplete}
/>
</Form>
</Formik>
</Modal>
)}
</>
)
}
export default CustomSMSModal

View file

@ -10,6 +10,13 @@ const global = {
position: 'relative', position: 'relative',
flex: 'wrap' flex: 'wrap'
}, },
headerWithLink: {
display: 'flex',
position: 'relative',
alignItems: 'center',
justifyContent: 'space-between',
width: 640
},
section: { section: {
marginBottom: 52 marginBottom: 52
}, },

View file

@ -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',

View file

@ -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',