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:
parent
0035684040
commit
3480bbf8f7
11 changed files with 301 additions and 0 deletions
17
lib/custom-sms.js
Normal file
17
lib/custom-sms.js
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
12
lib/new-admin/graphql/resolvers/sms.resolver.js
Normal file
12
lib/new-admin/graphql/resolvers/sms.resolver.js
Normal 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
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
25
lib/new-admin/graphql/types/sms.type.js
Normal file
25
lib/new-admin/graphql/types/sms.type.js
Normal 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
|
||||||
20
migrations/1627518944902-custom-sms.js
Normal file
20
migrations/1627518944902-custom-sms.js
Normal 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()
|
||||||
|
}
|
||||||
127
new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
Normal file
127
new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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