chore: use monorepo organization

This commit is contained in:
Rafael Taranto 2025-05-12 10:52:54 +01:00
parent deaf7d6ecc
commit a687827f7e
1099 changed files with 8184 additions and 11535 deletions

View file

@ -0,0 +1,278 @@
import { useQuery, useMutation, gql } from '@apollo/client'
import Paper from '@mui/material/Paper'
import Switch from '@mui/material/Switch'
import IconButton from '@mui/material/IconButton'
import * as R from 'ramda'
import React, { useState } from 'react'
import { HelpTooltip } from 'src/components/Tooltip'
import DataTable from 'src/components/tables/DataTable'
import { H4, P, Label3 } from 'src/components/typography'
import EditIcon from 'src/styling/icons/action/edit/enabled.svg?react'
import ExpandIconClosed from 'src/styling/icons/action/expand/closed.svg?react'
import ExpandIconOpen from 'src/styling/icons/action/expand/open.svg?react'
import WhiteLogo from 'src/styling/icons/menu/logo-white.svg?react'
import { SupportLinkButton } from 'src/components/buttons'
import { formatDate } from 'src/utils/timezones'
import CustomSMSModal from './SMSNoticesModal'
import SvgIcon from '@mui/material/SvgIcon'
const GET_SMS_NOTICES = gql`
query SMSNotices {
SMSNotices {
id
event
message
messageName
enabled
allowToggle
}
config
}
`
const EDIT_SMS_NOTICE = gql`
mutation editSMSNotice($id: ID!, $event: SMSNoticeEvent!, $message: String!) {
editSMSNotice(id: $id, event: $event, message: $message) {
id
}
}
`
const ENABLE_SMS_NOTICE = gql`
mutation enableSMSNotice($id: ID!) {
enableSMSNotice(id: $id) {
id
}
}
`
const DISABLE_SMS_NOTICE = gql`
mutation disableSMSNotice($id: ID!) {
disableSMSNotice(id: $id) {
id
}
}
`
const multiReplace = (str, obj) => {
var re = new RegExp(Object.keys(obj).join('|'), 'gi')
return str.replace(re, function (matched) {
return obj[matched.toLowerCase()]
})
}
const formatContent = content => {
const fragments = R.split(/\n/)(content)
return R.map((it, idx) => {
if (idx === fragments.length) return <>{it}</>
return (
<>
{it}
<br />
</>
)
}, fragments)
}
const TOOLTIPS = {
smsCode: ``,
cashOutDispenseReady: ``,
smsReceipt:
formatContent(`The contents of this notice will be appended to the end of the SMS receipt sent, and not replace it.\n
To edit the contents of the SMS receipt, please go to the 'Receipt' tab`)
}
const SMSPreview = ({ sms, coords, timezone }) => {
const matches = {
'#code': 123,
'#timestamp': formatDate(new Date(), timezone, 'HH:mm')
}
return (
<div
className="absolute w-88 overflow-visible"
style={{ left: coords.x, bottom: coords.y }}>
<div className="flex flex-row items-end gap-2">
<div className="flex w-9 h-9 rounded-full bg-[#16D6D3] items-center justify-center">
<WhiteLogo width={22} height={22} />
</div>
<Paper className="w-56 p-4 rounded-2xl">
<P noMargin>
{R.isEmpty(sms?.message) ? (
<i>No content available</i>
) : (
formatContent(multiReplace(sms?.message, matches))
)}
</P>
</Paper>
<Label3>{formatDate(new Date(), timezone, 'HH:mm')}</Label3>
</div>
</div>
)
}
const SMSNotices = () => {
const [showModal, setShowModal] = useState(false)
const [selectedSMS, setSelectedSMS] = useState(null)
const [previewOpen, setPreviewOpen] = useState(false)
const [previewCoords, setPreviewCoords] = useState({ x: 0, y: 0 })
const [errorMsg, setErrorMsg] = useState('')
const { data: messagesData, loading: messagesLoading } =
useQuery(GET_SMS_NOTICES)
const timezone = R.path(['config', 'locale_timezone'])(messagesData)
const [editMessage] = useMutation(EDIT_SMS_NOTICE, {
onError: ({ msg }) => setErrorMsg(msg),
refetchQueries: () => ['SMSNotices']
})
const [enableMessage] = useMutation(ENABLE_SMS_NOTICE, {
onError: ({ msg }) => setErrorMsg(msg),
refetchQueries: () => ['SMSNotices']
})
const [disableMessage] = useMutation(DISABLE_SMS_NOTICE, {
onError: ({ msg }) => setErrorMsg(msg),
refetchQueries: () => ['SMSNotices']
})
const loading = messagesLoading
const handleClose = () => {
setShowModal(false)
setSelectedSMS(null)
}
const elements = [
{
header: 'Message name',
width: 500,
size: 'sm',
textAlign: 'left',
view: it =>
!R.isEmpty(TOOLTIPS[it.event]) ? (
<div className="flex flex-row items-center">
{R.prop('messageName', it)}
<HelpTooltip width={250}>
<P>{TOOLTIPS[it.event]}</P>
</HelpTooltip>
</div>
) : (
R.prop('messageName', it)
)
},
{
header: 'Edit',
width: 100,
size: 'sm',
textAlign: 'center',
view: it => (
<IconButton
onClick={() => {
setPreviewOpen(false)
setSelectedSMS(it)
setShowModal(true)
}}>
<SvgIcon>
<EditIcon />
</SvgIcon>
</IconButton>
)
},
{
header: 'Enable',
width: 100,
size: 'sm',
textAlign: 'center',
view: it => (
<Switch
disabled={!it.allowToggle}
onClick={() => {
it.enabled
? disableMessage({ variables: { id: it.id } })
: enableMessage({ variables: { id: it.id } })
}}
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
})
R.equals(selectedSMS, it)
? setPreviewOpen(!previewOpen)
: setPreviewOpen(true)
}}>
<SvgIcon>
{R.equals(selectedSMS, it) && previewOpen ? (
<ExpandIconOpen />
) : (
<ExpandIconClosed />
)}
</SvgIcon>
</IconButton>
)
}
]
return (
<>
<div className="flex relative items-center justify-between w-200">
<H4>SMS notices</H4>
<HelpTooltip width={320}>
<P>
For details on configuring this panel, please read the relevant
knowledgebase article:
</P>
<SupportLinkButton
link="https://support.lamassu.is/hc/en-us/articles/115001205591-SMS-Phone-Verification"
label="Lamassu Support Article"
bottomSpace="1"
/>
</HelpTooltip>
</div>
{showModal && (
<CustomSMSModal
showModal={showModal}
onClose={handleClose}
sms={selectedSMS}
creationError={errorMsg}
submit={editMessage}
/>
)}
{previewOpen && (
<SMSPreview
sms={selectedSMS}
coords={previewCoords}
timezone={timezone}
/>
)}
<DataTable
emptyText="No SMS notices so far"
elements={elements}
loading={loading}
data={R.path(['SMSNotices'])(messagesData)}
/>
</>
)
}
export default SMSNotices

View file

@ -0,0 +1,194 @@
import Chip from '@mui/material/Chip'
import { Form, Formik, Field } from 'formik'
import * as R from 'ramda'
import React from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { Info2 } from 'src/components/typography'
import DefaultIconReverse from 'src/styling/icons/button/retry/white.svg?react'
import DefaultIcon from 'src/styling/icons/button/retry/zodiac.svg?react'
import * as Yup from 'yup'
import { ActionButton, Button } from 'src/components/buttons'
import { TextInput } from 'src/components/inputs/formik'
import { zircon } from 'src/styling/variables'
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',
message: 'The confirmation code is missing from the message!',
exclusive: false,
test: value => value?.match(/#code/g)?.length > 0
})
.test({
name: 'has-single-code',
message: 'There should be a single confirmation code!',
exclusive: false,
test: value => value?.match(/#code/g)?.length === 1
})
},
cashOutDispenseReady: {
validator: Yup.string().required('The message content is required!').trim()
},
smsReceipt: {
validator: Yup.string().trim()
}
}
const CHIPS = {
smsCode: [
{ code: '#code', display: 'Confirmation code', obligatory: true },
{ code: '#timestamp', display: 'Timestamp', obligatory: false }
],
cashOutDispenseReady: [
{ code: '#timestamp', display: 'Timestamp', obligatory: false }
],
smsReceipt: [{ code: '#timestamp', display: 'Timestamp', obligatory: false }]
}
const DEFAULT_MESSAGES = {
smsCode: 'Your cryptomat code: #code',
cashOutDispenseReady:
'Your cash is waiting! Go to the Cryptomat and press Redeem within 24 hours. [#timestamp]',
smsReceipt: ''
}
const SMSNoticesModal = ({
showModal,
onClose,
sms,
creationError,
submit
}) => {
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[sms?.event]?.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={`SMS notice - ${sms?.messageName}`}
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, setFieldValue }) => (
<Form id="sms-notice" className="flex flex-col h-full gap-5">
<ActionButton
color="primary"
Icon={DefaultIcon}
InverseIcon={DefaultIconReverse}
className="w-37"
type="button"
onClick={() =>
setFieldValue('message', DEFAULT_MESSAGES[sms?.event])
}>
Reset to default
</ActionButton>
<Field
name="message"
label="Message content"
fullWidth
multiline={true}
rows={6}
component={TextInput}
/>
{R.length(CHIPS[sms?.event]) > 0 && (
<Info2 noMargin>Values to attach</Info2>
)}
<div className="w-120">
{R.splitEvery(3, CHIPS[sms?.event]).map((it, idx) => (
<div key={idx} className="flex gap-2">
{it.map((ite, idx2) => (
<Chip
key={idx2}
label={ite.display}
size="small"
style={{ backgroundColor: zircon }}
disabled={R.includes(ite.code, values.message)}
className="p-2"
onClick={() => {
setFieldValue(
'message',
values.message.concat(
R.last(values.message) === ' ' ? '' : ' ',
ite.code
)
)
}}
/>
))}
</div>
))}
</div>
<div className="flex flex-row mt-auto mx-0 mb-6">
{getErrorMsg(errors, touched, creationError) && (
<ErrorMessage>
{getErrorMsg(errors, touched, creationError)}
</ErrorMessage>
)}
<Button
type="submit"
form="sms-notice"
className="mt-auto ml-auto mr-0 mb-0">
Confirm
</Button>
</div>
</Form>
)}
</Formik>
</Modal>
)}
</>
)
}
export default SMSNoticesModal