feat: add SMS preview component
This commit is contained in:
parent
0d5f5167ef
commit
91e209b6ab
6 changed files with 169 additions and 36 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
import { useQuery, useMutation } from '@apollo/react-hooks'
|
import { useQuery, useMutation } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles, Paper } from '@material-ui/core'
|
||||||
|
import { format } from 'date-fns/fp'
|
||||||
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'
|
||||||
|
|
@ -8,11 +9,12 @@ import { DeleteDialog } from 'src/components/DeleteDialog'
|
||||||
import { IconButton } from 'src/components/buttons'
|
import { IconButton } from 'src/components/buttons'
|
||||||
import { Switch } from 'src/components/inputs'
|
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, P, Label3 } from 'src/components/typography'
|
||||||
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 { ReactComponent as WhiteLogo } from 'src/styling/icons/menu/logo-white.svg'
|
||||||
|
|
||||||
import CustomSMSModal from './CustomSMSModal'
|
|
||||||
import styles from './SMSNotices.styles'
|
import styles from './SMSNotices.styles'
|
||||||
|
import CustomSMSModal from './SMSNoticesModal'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
|
@ -53,12 +55,45 @@ const DISABLE_SMS_NOTICE = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const multiReplace = (str, obj) => {
|
||||||
|
var re = new RegExp(Object.keys(obj).join('|'), 'gi')
|
||||||
|
|
||||||
|
return str.replace(re, function(matched) {
|
||||||
|
return obj[matched.toLowerCase()]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const SMSPreview = ({ sms, coords }) => {
|
||||||
|
const classes = useStyles(coords)
|
||||||
|
|
||||||
|
const matches = {
|
||||||
|
'#code': 123,
|
||||||
|
'#timestamp': format('HH:mm', new Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.smsPreview}>
|
||||||
|
<div className={classes.smsPreviewContainer}>
|
||||||
|
<div className={classes.smsPreviewIcon}>
|
||||||
|
<WhiteLogo width={22} height={22} />
|
||||||
|
</div>
|
||||||
|
<Paper className={classes.smsPreviewContent}>
|
||||||
|
<P noMargin>{multiReplace(sms?.message, matches)}</P>
|
||||||
|
</Paper>
|
||||||
|
<Label3>{format('HH:mm', new Date())}</Label3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const SMSNotices = () => {
|
const SMSNotices = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [deleteDialog, setDeleteDialog] = useState(false)
|
const [deleteDialog, setDeleteDialog] = useState(false)
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [selectedSMS, setSelectedSMS] = useState(null)
|
const [selectedSMS, setSelectedSMS] = useState(null)
|
||||||
|
const [previewOpen, setPreviewOpen] = useState(false)
|
||||||
|
const [previewCoords, setPreviewCoords] = useState({ x: 0, y: 0 })
|
||||||
const [errorMsg, setErrorMsg] = useState('')
|
const [errorMsg, setErrorMsg] = useState('')
|
||||||
|
|
||||||
const { data: messagesData, loading: messagesLoading } = useQuery(
|
const { data: messagesData, loading: messagesLoading } = useQuery(
|
||||||
|
|
@ -88,8 +123,6 @@ const SMSNotices = () => {
|
||||||
setDeleteDialog(false)
|
setDeleteDialog(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(messagesData)
|
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
header: 'Message name',
|
header: 'Message name',
|
||||||
|
|
@ -106,6 +139,7 @@ const SMSNotices = () => {
|
||||||
view: it => (
|
view: it => (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setPreviewOpen(false)
|
||||||
setSelectedSMS(it)
|
setSelectedSMS(it)
|
||||||
setShowModal(true)
|
setShowModal(true)
|
||||||
}}>
|
}}>
|
||||||
|
|
@ -129,13 +163,35 @@ const SMSNotices = () => {
|
||||||
checked={it.enabled}
|
checked={it.enabled}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: '',
|
||||||
|
width: 100,
|
||||||
|
size: 'sm',
|
||||||
|
textAlign: 'center',
|
||||||
|
view: it => (
|
||||||
|
<IconButton
|
||||||
|
onClick={e => {
|
||||||
|
setSelectedSMS(it)
|
||||||
|
setPreviewCoords({
|
||||||
|
x: e.currentTarget.getBoundingClientRect().right + 50,
|
||||||
|
y:
|
||||||
|
window.innerHeight -
|
||||||
|
5 -
|
||||||
|
e.currentTarget.getBoundingClientRect().bottom
|
||||||
|
})
|
||||||
|
setPreviewOpen(true)
|
||||||
|
}}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.header}>
|
<div className={classes.header}>
|
||||||
<H4>SMS Notices</H4>
|
<H4>SMS notices</H4>
|
||||||
</div>
|
</div>
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<CustomSMSModal
|
<CustomSMSModal
|
||||||
|
|
@ -156,6 +212,7 @@ const SMSNotices = () => {
|
||||||
}}
|
}}
|
||||||
errorMessage={errorMsg}
|
errorMessage={errorMsg}
|
||||||
/>
|
/>
|
||||||
|
{previewOpen && <SMSPreview sms={selectedSMS} coords={previewCoords} />}
|
||||||
<DataTable
|
<DataTable
|
||||||
emptyText="No SMS notices so far"
|
emptyText="No SMS notices so far"
|
||||||
elements={elements}
|
elements={elements}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,35 @@ const styles = {
|
||||||
},
|
},
|
||||||
submit: {
|
submit: {
|
||||||
margin: [['auto', 0, 0, 'auto']]
|
margin: [['auto', 0, 0, 'auto']]
|
||||||
|
},
|
||||||
|
smsPreview: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: ({ x }) => x,
|
||||||
|
bottom: ({ y }) => y,
|
||||||
|
width: 350,
|
||||||
|
overflow: 'visible'
|
||||||
|
},
|
||||||
|
smsPreviewContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
'& > *': {
|
||||||
|
marginRight: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
smsPreviewIcon: {
|
||||||
|
display: 'flex',
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 18,
|
||||||
|
backgroundColor: '#16D6D3',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
smsPreviewContent: {
|
||||||
|
width: 225,
|
||||||
|
padding: 15,
|
||||||
|
borderRadius: '15px 15px 15px 0px'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles, Chip } 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 from 'react'
|
import React from 'react'
|
||||||
|
|
@ -27,14 +27,14 @@ const prefill = {
|
||||||
.required('The message content is required!')
|
.required('The message content is required!')
|
||||||
.trim()
|
.trim()
|
||||||
.test({
|
.test({
|
||||||
name: 'has-code-tag',
|
name: 'has-code',
|
||||||
message: 'A #code tag is missing from the message!',
|
message: 'The confirmation code is missing from the message!',
|
||||||
exclusive: false,
|
exclusive: false,
|
||||||
test: value => value?.match(/#code/g || [])?.length > 0
|
test: value => value?.match(/#code/g || [])?.length > 0
|
||||||
})
|
})
|
||||||
.test({
|
.test({
|
||||||
name: 'has-single-code-tag',
|
name: 'has-single-code',
|
||||||
message: 'There should be a single #code tag!',
|
message: 'There should be a single confirmation code!',
|
||||||
exclusive: false,
|
exclusive: false,
|
||||||
test: value => value?.match(/#code/g || [])?.length === 1
|
test: value => value?.match(/#code/g || [])?.length === 1
|
||||||
})
|
})
|
||||||
|
|
@ -43,22 +43,33 @@ const prefill = {
|
||||||
validator: Yup.string()
|
validator: Yup.string()
|
||||||
.required('The message content is required!')
|
.required('The message content is required!')
|
||||||
.trim()
|
.trim()
|
||||||
.test({
|
// .test({
|
||||||
name: 'has-timestamp-tag',
|
// name: 'has-timestamp-tag',
|
||||||
message: 'A #timestamp tag is missing from the message!',
|
// message: 'A #timestamp tag is missing from the message!',
|
||||||
exclusive: false,
|
// exclusive: false,
|
||||||
test: value => value?.match(/#timestamp/g || [])?.length > 0
|
// test: value => value?.match(/#timestamp/g || [])?.length > 0
|
||||||
})
|
// })
|
||||||
.test({
|
// .test({
|
||||||
name: 'has-single-timestamp-tag',
|
// name: 'has-single-timestamp-tag',
|
||||||
message: 'There should be a single #timestamp tag!',
|
// message: 'There should be a single #timestamp tag!',
|
||||||
exclusive: false,
|
// exclusive: false,
|
||||||
test: value => value?.match(/#timestamp/g || [])?.length === 1
|
// test: value => value?.match(/#timestamp/g || [])?.length === 1
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomSMSModal = ({ showModal, onClose, sms, creationError, submit }) => {
|
const chips = {
|
||||||
|
smsCode: [{ code: '#code', display: 'Confirmation code', removable: false }],
|
||||||
|
cashOutDispenseReady: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const SMSNoticesModal = ({
|
||||||
|
showModal,
|
||||||
|
onClose,
|
||||||
|
sms,
|
||||||
|
creationError,
|
||||||
|
submit
|
||||||
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
|
|
@ -111,7 +122,7 @@ const CustomSMSModal = ({ showModal, onClose, sms, creationError, submit }) => {
|
||||||
onSubmit={(values, errors, touched) =>
|
onSubmit={(values, errors, touched) =>
|
||||||
handleSubmit(values, errors, touched)
|
handleSubmit(values, errors, touched)
|
||||||
}>
|
}>
|
||||||
{({ values, errors, touched }) => (
|
{({ values, errors, touched, setFieldValue }) => (
|
||||||
<Form id="sms-notice" className={classes.form}>
|
<Form id="sms-notice" className={classes.form}>
|
||||||
<Field
|
<Field
|
||||||
name="message"
|
name="message"
|
||||||
|
|
@ -121,6 +132,22 @@ const CustomSMSModal = ({ showModal, onClose, sms, creationError, submit }) => {
|
||||||
rows={6}
|
rows={6}
|
||||||
component={TextInput}
|
component={TextInput}
|
||||||
/>
|
/>
|
||||||
|
{R.map(
|
||||||
|
it => (
|
||||||
|
<Chip
|
||||||
|
label={it.display}
|
||||||
|
onClick={() => {
|
||||||
|
R.includes(it.code, values.message)
|
||||||
|
? setFieldValue('message', values.message)
|
||||||
|
: setFieldValue(
|
||||||
|
'message',
|
||||||
|
values.message.concat(' ', it.code, ' ')
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
chips[sms?.event]
|
||||||
|
)}
|
||||||
<div className={classes.footer}>
|
<div className={classes.footer}>
|
||||||
{getErrorMsg(errors, touched, creationError) && (
|
{getErrorMsg(errors, touched, creationError) && (
|
||||||
<ErrorMessage>
|
<ErrorMessage>
|
||||||
|
|
@ -143,4 +170,4 @@ const CustomSMSModal = ({ showModal, onClose, sms, creationError, submit }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomSMSModal
|
export default SMSNoticesModal
|
||||||
|
|
@ -17,7 +17,7 @@ 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 ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
||||||
import CustomSMS from 'src/pages/OperatorInfo/SMSNotices/SMSNotices'
|
import SMSNotices 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'
|
||||||
|
|
@ -174,11 +174,11 @@ const getLamassuRoutes = () => [
|
||||||
component: ReceiptPrinting
|
component: ReceiptPrinting
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'custom-sms',
|
key: 'sms-notices',
|
||||||
label: 'Custom SMS',
|
label: 'SMS notices',
|
||||||
route: '/settings/operator-info/custom-sms',
|
route: '/settings/operator-info/sms-notices',
|
||||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||||
component: CustomSMS
|
component: SMSNotices
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'coin-atm-radar',
|
key: 'coin-atm-radar',
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ 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 ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
||||||
import CustomSMS from 'src/pages/OperatorInfo/SMSNotices/SMSNotices'
|
import SMSNotices 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'
|
||||||
|
|
@ -169,11 +169,11 @@ const getPazuzRoutes = () => [
|
||||||
component: ReceiptPrinting
|
component: ReceiptPrinting
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'custom-sms',
|
key: 'sms-notices',
|
||||||
label: 'Custom SMS',
|
label: 'SMS notices',
|
||||||
route: '/settings/operator-info/custom-sms',
|
route: '/settings/operator-info/sms-notices',
|
||||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||||
component: CustomSMS
|
component: SMSNotices
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'coin-atm-radar',
|
key: 'coin-atm-radar',
|
||||||
|
|
|
||||||
20
new-lamassu-admin/src/styling/icons/menu/logo-white.svg
Normal file
20
new-lamassu-admin/src/styling/icons/menu/logo-white.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7 KiB |
Loading…
Add table
Add a link
Reference in a new issue