feat: Add SMS Integration & SMS Message Form.

This commit is contained in:
elforjani13
2021-11-07 13:39:29 +02:00
parent d26ef01afc
commit 834d365a97
28 changed files with 612 additions and 155 deletions

View File

@@ -0,0 +1,20 @@
import React from 'react';
import '../../../style/pages/SMSMessage/SMSMessage.scss';
import { SMSMessageDialogProvider } from './SMSMessageDialogProvider';
import SMSMessageForm from './SMSMessageForm';
export default function SMSMessageDialogContent({
// #ownProps
dialogName,
notificationkey,
}) {
return (
<SMSMessageDialogProvider
dialogName={dialogName}
notificationkey={notificationkey}
>
<SMSMessageForm />
</SMSMessageDialogProvider>
);
}

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { DialogContent } from 'components';
import {
useSettingEditSMSNotification,
useSettingSMSNotification,
} from 'hooks/query';
const SMSMessageDialogContext = React.createContext();
/**
* SMS Message dialog provider.
*/
function SMSMessageDialogProvider({ notificationkey, dialogName, ...props }) {
// Edit SMS message notification mutations.
const { mutateAsync: editSMSNotificationMutate } =
useSettingEditSMSNotification();
const { data: smsNotification, isLoading: isSMSNotificationLoading } =
useSettingSMSNotification(notificationkey);
// provider.
const provider = {
dialogName,
smsNotification,
editSMSNotificationMutate,
};
return (
<DialogContent isLoading={isSMSNotificationLoading}>
<SMSMessageDialogContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useSMSMessageDialogContext = () =>
React.useContext(SMSMessageDialogContext);
export { SMSMessageDialogProvider, useSMSMessageDialogContext };

View File

@@ -0,0 +1,79 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { omit } from 'lodash';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import SMSMessageFormContent from './SMSMessageFormContent';
import { CreateSMSMessageFormSchema } from './SMSMessageForm.schema';
import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, transformToForm } from 'utils';
const defaultInitialValues = {
notification_key: '',
is_notification_enabled: '',
message_text: '',
};
/**
* SMS Message form.
*/
function SMSMessageForm({
// #withDialogActions
closeDialog,
}) {
const { dialogName, smsNotification, editSMSNotificationMutate } =
useSMSMessageDialogContext();
// Initial form values.
const initialValues = {
...defaultInitialValues,
...transformToForm(smsNotification, defaultInitialValues),
notification_key: smsNotification.key,
message_text: smsNotification.sms_message,
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = {
...omit(values, ['is_notification_enabled', 'sms_message']),
notification_key: smsNotification.key,
};
// Handle request response success.
const onSuccess = (response) => {
AppToaster.show({
message: intl.get('sms_message.dialog.success_message'),
intent: Intent.SUCCESS,
});
closeDialog(dialogName);
};
// Handle request response errors.
const onError = ({
response: {
data: { errors },
},
}) => {
setSubmitting(false);
};
editSMSNotificationMutate(form).then(onSuccess).catch(onError);
};
return (
<Formik
validationSchema={CreateSMSMessageFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={SMSMessageFormContent}
/>
);
}
export default compose(withDialogActions)(SMSMessageForm);

View File

@@ -0,0 +1,11 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from 'common/dataTypes';
const Schema = Yup.object().shape({
notification_key: Yup.string().required(),
is_notification_enabled: Yup.boolean(),
message_text: Yup.string().min(3).max(DATATYPES_LENGTH.TEXT),
});
export const CreateSMSMessageFormSchema = Schema;

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { Form } from 'formik';
import SMSMessageFormFields from './SMSMessageFormFields';
import SMSMessageFormFloatingActions from './SMSMessageFormFloatingActions';
/**
* SMS message form content.
*/
export default function SMSMessageFormContent() {
return (
<Form>
<SMSMessageFormFields />
<SMSMessageFormFloatingActions />
</Form>
);
}

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { Classes, FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T, FieldRequiredHint } from 'components';
import { inputIntent } from 'utils';
export default function SMSMessageFormFields() {
return (
<div className={Classes.DIALOG_BODY}>
{/* ----------- Message Text ----------- */}
<FastField name={'message_text'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'notify_via_sms.dialog.message_text'} />}
className={'form-group--message_text'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'message_text'} />}
>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</div>
);
}

View File

@@ -0,0 +1,47 @@
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from 'components';
import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* SMS Message Form floating actions.
*/
function SMSMessageFormFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting } = useFormikContext();
const { dialogName } = useSMSMessageDialogContext();
// Handle close button click.
const handleCancelBtnClick = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
>
{<T id={'save'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(SMSMessageFormFloatingActions);

View File

@@ -0,0 +1,39 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'redux';
const SMSMessageDialogContent = React.lazy(() =>
import('./SMSMessageDialogContent'),
);
/**
* SMS Message dialog.
*/
function SMSMessageDialog({
dialogName,
payload: { notificationkey },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={intl.get('sms_message')}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
className={'dialog--sms-message'}
>
<DialogSuspense>
<SMSMessageDialogContent
dialogName={dialogName}
notificationkey={notificationkey}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(SMSMessageDialog);

View File

@@ -0,0 +1,39 @@
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useSettings, useSettingSMSNotifications } from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
const SMSIntegrationContext = React.createContext();
/**
* SMS Integration provider.
*/
function SMSIntegrationProvider({ ...props }) {
//Fetches Organization Settings.
const { isLoading: isSettingsLoading } = useSettings();
const { data: notifications, isLoading: isSMSNotificationsLoading } =
useSettingSMSNotifications();
// Provider state.
const provider = {
notifications,
isSMSNotificationsLoading,
};
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION,
)}
>
<SMSIntegrationContext.Provider value={provider} {...props} />
</div>
);
}
const useSMSIntegrationContext = () => React.useContext(SMSIntegrationContext);
export { SMSIntegrationProvider, useSMSIntegrationContext };

View File

@@ -0,0 +1,42 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Tabs, Tab } from '@blueprintjs/core';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import SMSMessagesDataTable from './SMSMessagesDataTable';
import '../../../style/pages/Preferences/SMSIntegration.scss';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
function SMSIntegrationTabs({
// #withDashboardActions
changePreferencesPageTitle,
}) {
React.useEffect(() => {
changePreferencesPageTitle(intl.get('sms_integration.label'));
}, [changePreferencesPageTitle]);
return (
<div className={classNames(CLASSES.CARD)}>
<div className={classNames(CLASSES.PREFERENCES_PAGE_TABS)}>
<Tabs animate={true} defaultSelectedTabId={'sms_messages'}>
<Tab
id="overview"
title={intl.get('sms_integration.label.overview')}
/>
<Tab
id="sms_messages"
title={intl.get('sms_integration.label.sms_messages')}
panel={<SMSMessagesDataTable />}
/>
</Tabs>
</div>
</div>
);
}
export default compose(withDashboardActions)(SMSIntegrationTabs);

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { DataTableEditable, DataTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import { useSMSIntegrationTableColumns } from './components';
import { useSMSIntegrationContext } from './SMSIntegrationProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
function SMSMessagesDataTable({
// #withDialogAction
openDialog,
}) {
// Table columns.
const columns = useSMSIntegrationTableColumns();
const { notifications, isSMSNotificationsLoading } =
useSMSIntegrationContext();
const handleEditSMSMessage = ({ key }) => {
openDialog('sms-message-form', { notificationkey: key });
};
return (
<DataTable
columns={columns}
data={notifications}
loading={isSMSNotificationsLoading}
progressBarLoading={isSMSNotificationsLoading}
TableLoadingRenderer={TableSkeletonRows}
noInitialFetch={true}
payload={{
onEditSMSMessage: handleEditSMSMessage,
}}
/>
);
}
export default compose(withDialogActions)(SMSMessagesDataTable);

View File

@@ -0,0 +1,67 @@
import React from 'react';
import intl from 'react-intl-universal';
import { SwitchFieldCell } from 'components/DataTableCells';
import { safeCallback } from 'utils';
/**
* Notification accessor.
*/
export const NotificationAccessor = (row) => {
return (
<span className="notification">
<span className={'notification__label'}>{row.notification_label}</span>
<span className={'notification__desc'}>
{row.notification_description}
</span>
</span>
);
};
export const SMSMessageCell = ({
payload: { onEditSMSMessage },
row: { original },
}) => (
<div>
{original.sms_message}
<span
className="edit-text"
onClick={safeCallback(onEditSMSMessage, original)}
>
{'Edit'}
</span>
</div>
);
export function useSMSIntegrationTableColumns() {
return React.useMemo(() => [
{
Header: intl.get('sms_message.label_Notification'),
accessor: NotificationAccessor,
className: 'notification',
width: '180',
},
{
Header: intl.get('service'),
accessor: 'module_formatted',
className: 'service',
width: '80',
},
{
Header: intl.get('sms_message.label_mesage'),
accessor: 'sms_message',
Cell: SMSMessageCell,
className: 'sms_message',
clickable: true,
width: '180',
},
{
Header: intl.get('sms_message.label_auto'),
accessor: 'is_notification_enabled',
Cell: SwitchFieldCell,
className: 'is_notification_enabled',
disableSortBy: true,
disableResizing: true,
width: '80',
},
]);
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { SMSIntegrationProvider } from './SMSIntegrationProvider';
import SMSIntegrationTabs from './SMSIntegrationTabs';
/**
* SMS SMS Integration
*/
export default function SMSIntegration() {
return (
<SMSIntegrationProvider>
<SMSIntegrationTabs />
</SMSIntegrationProvider>
);
}

View File

@@ -1,54 +0,0 @@
import React from 'react';
import intl from 'react-intl-universal';
import {
DataTableEditable,
DataTable,
DashboardContentTable,
} from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { useSMSMessagesTemplatesTableColumns } from './components';
import { compose } from 'utils';
function SMSMessagesTemplatesDataTable({
// #withDashboardActions
changePreferencesPageTitle,
}) {
// Table columns.
const columns = useSMSMessagesTemplatesTableColumns();
React.useEffect(() => {
changePreferencesPageTitle(
intl.get('sms_message_template.label.sms_messages_template'),
);
}, [changePreferencesPageTitle]);
const DATA = [
{
notification: 'notification',
service: 'service',
message: 'message',
auto: true,
},
{
notification: 'notification',
service: 'service',
message: 'message',
auto: false,
},
];
return (
<DataTableEditable
columns={columns}
data={[]}
// loading={}
// progressBarLoading={}
TableLoadingRenderer={TableSkeletonRows}
noInitialFetch={true}
/>
);
}
export default compose(withDashboardActions)(SMSMessagesTemplatesDataTable);

View File

@@ -1,35 +0,0 @@
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useSettings } from 'hooks/query';
const SMSMessagesTemplatesContext = React.createContext();
/**
* SMS message templates provider.
*/
function SMSMessagesTemplatesProvider({ ...props }) {
//Fetches Organization Settings.
const { isLoading: isSettingsLoading } = useSettings();
// Provider state.
const provider = {};
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<div className={classNames(CLASSES.CARD)}>
<SMSMessagesTemplatesContext.Provider value={provider} {...props} />
</div>
</div>
);
}
const useSMSMessageTemplateContext = () =>
React.useContext(SMSMessagesTemplatesContext);
export { SMSMessagesTemplatesProvider, useSMSMessageTemplateContext };

View File

@@ -1,40 +0,0 @@
import React from 'react';
import intl from 'react-intl-universal';
import {
InputGroupCell,
TextAreaCell,
SwitchFieldCell,
} from 'components/DataTableCells';
export function useSMSMessagesTemplatesTableColumns() {
return React.useMemo(() => [
{
Header: intl.get('sms_message_template.label_Notification'),
accessor: 'notification',
className: 'notification',
width: '150',
},
{
Header: intl.get('service'),
accessor: 'service',
className: 'service',
width: '100',
},
{
Header: intl.get('sms_message_template.label_mesage'),
accessor: 'message',
className: 'message',
width: '180',
},
{
Header: intl.get('sms_message_template.label_auto'),
accessor: 'auto',
Cell: SwitchFieldCell,
className: 'auto',
disableSortBy: true,
disableResizing: true,
width: '80',
},
]);
}

View File

@@ -1,15 +0,0 @@
import React from 'react';
import { SMSMessagesTemplatesProvider } from './SMSMessagesTemplatesProvider';
import SMSMessagesTemplatesDataTable from './SMSMessagesTemplatesDataTable';
/**
* SMS messages templates.
*/
export default function SMSMessagesTemplates() {
return (
<SMSMessagesTemplatesProvider>
<SMSMessagesTemplatesDataTable />
</SMSMessagesTemplatesProvider>
);
}

View File

@@ -78,7 +78,7 @@ export function ActionsMenu({
*/
function StatusAccessor(user) {
return !user.is_invite_accepted ? (
<Tag minimal={true}>
<Tag minimal={true} >
<T id={'inviting'} />
</Tag>
) : user.active ? (