feat: add SMS preview component

This commit is contained in:
Sérgio Salgado 2022-02-09 15:09:31 +00:00
parent 0d5f5167ef
commit 91e209b6ab
6 changed files with 169 additions and 36 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7 KiB