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 MANUAL = 'manual'
|
||||
|
||||
const CASH_OUT_DISPENSE_READY = 'cash_out_dispense_ready'
|
||||
const CONFIRMATION_CODE = 'sms_code'
|
||||
|
||||
module.exports = {
|
||||
anonymousCustomer,
|
||||
cassetteMaxCapacity,
|
||||
|
|
@ -25,5 +28,7 @@ module.exports = {
|
|||
AUTOMATIC,
|
||||
MANUAL,
|
||||
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 scalar = require('./scalar.resolver')
|
||||
const settings = require('./settings.resolver')
|
||||
const sms = require('./sms.resolver')
|
||||
const status = require('./status.resolver')
|
||||
const transaction = require('./transaction.resolver')
|
||||
const user = require('./users.resolver')
|
||||
|
|
@ -36,6 +37,7 @@ const resolvers = [
|
|||
rates,
|
||||
scalar,
|
||||
settings,
|
||||
sms,
|
||||
status,
|
||||
transaction,
|
||||
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 scalar = require('./scalar.type')
|
||||
const settings = require('./settings.type')
|
||||
const sms = require('./sms.type')
|
||||
const status = require('./status.type')
|
||||
const transaction = require('./transaction.type')
|
||||
const user = require('./users.type')
|
||||
|
|
@ -36,6 +37,7 @@ const types = [
|
|||
rates,
|
||||
scalar,
|
||||
settings,
|
||||
sms,
|
||||
status,
|
||||
transaction,
|
||||
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 loyalty = require('./loyalty')
|
||||
|
||||
const { cassetteMaxCapacity } = require('./constants')
|
||||
const { cassetteMaxCapacity, CASH_OUT_DISPENSE_READY, CONFIRMATION_CODE } = require('./constants')
|
||||
|
||||
const notifier = require('./notifier')
|
||||
|
||||
|
|
@ -366,20 +366,20 @@ function plugins (settings, deviceId) {
|
|||
|
||||
const phone = tx.phone
|
||||
const timestamp = dateFormat(new Date(), 'UTC:HH:MM Z')
|
||||
return sms.getSms(CASH_OUT_DISPENSE_READY, phone, { timestamp })
|
||||
.then(smsObj => {
|
||||
const rec = {
|
||||
sms: {
|
||||
toNumber: phone,
|
||||
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 sql = 'UPDATE cash_out_txs SET notified=$1 WHERE id=$2'
|
||||
const values = [true, tx.id]
|
||||
|
||||
return db.none(sql, values)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function notifyOperator (tx, rec) {
|
||||
|
|
@ -754,15 +754,15 @@ function plugins (settings, deviceId) {
|
|||
? '123'
|
||||
: randomCode()
|
||||
|
||||
return sms.getSms(CONFIRMATION_CODE, phone, { code })
|
||||
.then(smsObj => {
|
||||
const rec = {
|
||||
sms: {
|
||||
toNumber: phone,
|
||||
body: 'Your cryptomat code: ' + code
|
||||
}
|
||||
sms: smsObj
|
||||
}
|
||||
|
||||
return sms.sendMessage(settings, rec)
|
||||
.then(() => code)
|
||||
})
|
||||
}
|
||||
|
||||
function sweepHdRow (row) {
|
||||
|
|
|
|||
36
lib/sms.js
36
lib/sms.js
|
|
@ -1,6 +1,34 @@
|
|||
const ph = require('./plugin-helper')
|
||||
const argv = require('minimist')(process.argv.slice(2))
|
||||
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) {
|
||||
const pluginCode = argv.mockSms ? 'mock-sms' : 'twilio'
|
||||
|
|
@ -91,4 +119,10 @@ function formatSmsReceipt (data, options) {
|
|||
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 CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
||||
import ContactInfo from 'src/pages/OperatorInfo/ContactInfo'
|
||||
import CustomSMS from 'src/pages/OperatorInfo/CustomSMS/CustomSMS'
|
||||
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
||||
import TermsConditions from 'src/pages/OperatorInfo/TermsConditions'
|
||||
import ServerLogs from 'src/pages/ServerLogs'
|
||||
|
|
@ -172,6 +173,13 @@ const getLamassuRoutes = () => [
|
|||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||
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',
|
||||
label: 'Coin ATM Radar',
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import MachineStatus from 'src/pages/Maintenance/MachineStatus'
|
|||
import Notifications from 'src/pages/Notifications/Notifications'
|
||||
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
||||
import ContactInfo from 'src/pages/OperatorInfo/ContactInfo'
|
||||
import CustomSMS from 'src/pages/OperatorInfo/CustomSMS/CustomSMS'
|
||||
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
||||
import TermsConditions from 'src/pages/OperatorInfo/TermsConditions'
|
||||
import ServerLogs from 'src/pages/ServerLogs'
|
||||
|
|
@ -167,6 +168,13 @@ const getPazuzRoutes = () => [
|
|||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||
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',
|
||||
label: 'Coin ATM Radar',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue