diff --git a/src/App.tsx b/src/App.tsx index 095152d01..be6b5c2b9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,10 +8,7 @@ import gqlClient from 'config/apolloclient'; import { SessionContext, SideDrawerContext } from 'context/session'; import ErrorHandler from 'containers/ErrorHandler/ErrorHandler'; import { Loading } from 'components/UI/Layout/Loading/Loading'; -import { - getAuthSession, - checkAuthStatusService, -} from 'services/AuthService'; +import { getAuthSession, checkAuthStatusService } from 'services/AuthService'; import { UnauthenticatedRoute } from 'routes/UnauthenticatedRoute/UnauthenticatedRoute'; import { AuthenticatedRoute } from 'routes/AuthenticatedRoute/AuthenticatedRoute'; import { Logout } from 'containers/Auth/Logout/Logout'; diff --git a/src/containers/Chat/ChatMessages/AddAttachment/AddAttachment.test.tsx b/src/containers/Chat/ChatMessages/AddAttachment/AddAttachment.test.tsx index cb48d9cd2..e8a2281d9 100644 --- a/src/containers/Chat/ChatMessages/AddAttachment/AddAttachment.test.tsx +++ b/src/containers/Chat/ChatMessages/AddAttachment/AddAttachment.test.tsx @@ -38,12 +38,12 @@ const addAttachment = (attachmentType = '', attachmentURL = '') => { }; vi.mock('common/notification', async (importOriginal) => { - const mod = await importOriginal() + const mod = await importOriginal(); return { ...mod, - setNotification: vi.fn((...args) => { return args[1] }), - } -}) + setNotification: vi.fn((...args) => args[1]), + }; +}); beforeEach(() => { mockedAxios.get.mockImplementation(() => @@ -134,9 +134,11 @@ test('should get error notification if uploading media fails', async () => { attachmentType: 'IMAGE', uploadPermission: true, }; - render( - - ) + render( + + + + ); const file = { name: 'photo.png' }; @@ -145,7 +147,7 @@ test('should get error notification if uploading media fails', async () => { await waitFor(() => { expect(setNotification).toHaveReturnedWith('warning'); }); -}) +}); test('successful media submission', async () => { const responseData = { data: { is_valid: true } }; @@ -156,15 +158,17 @@ test('successful media submission', async () => { // let url validation complete await waitFor(() => { - expect(screen.queryByText('Please wait for the attachment URL verification')).not.toBeInTheDocument(); + expect( + screen.queryByText('Please wait for the attachment URL verification') + ).not.toBeInTheDocument(); }); await waitFor(() => { fireEvent.click(screen.getByTestId('ok-button')); - }) + }); await waitFor(() => { expect(setAttachmentType).toHaveBeenCalledWith('IMAGE'); - expect(setAttachmentURL).toHaveBeenCalledWith('https://glific.com') + expect(setAttachmentURL).toHaveBeenCalledWith('https://glific.com'); expect(setAttachment).toHaveBeenCalledWith(false); - }) -}) + }); +}); diff --git a/src/containers/Chat/ChatMessages/ChatMessages.module.css b/src/containers/Chat/ChatMessages/ChatMessages.module.css index b340321ac..2736808a1 100644 --- a/src/containers/Chat/ChatMessages/ChatMessages.module.css +++ b/src/containers/Chat/ChatMessages/ChatMessages.module.css @@ -73,11 +73,11 @@ margin-right: 13px !important; } -.Paper>ul>li:hover { +.Paper > ul > li:hover { background-color: #edf6f2 !important; } -.Paper>ul>li { +.Paper > ul > li { color: #073f24; } @@ -131,7 +131,7 @@ html { align-items: center; } -.SessionTimer+div { +.SessionTimer + div { text-align: right; } @@ -140,4 +140,4 @@ html { font-size: 12px; font-weight: 400; margin-right: 5px; -} \ No newline at end of file +} diff --git a/src/containers/Chat/ChatMessages/StatusBar/StatusBar.module.css b/src/containers/Chat/ChatMessages/StatusBar/StatusBar.module.css index a27c7cba9..3fa2eb4ee 100644 --- a/src/containers/Chat/ChatMessages/StatusBar/StatusBar.module.css +++ b/src/containers/Chat/ChatMessages/StatusBar/StatusBar.module.css @@ -10,4 +10,4 @@ .StatusTitle { font-weight: bold; -} \ No newline at end of file +} diff --git a/src/containers/Collection/CollectionContact/CollectionContactList/CollectionContactList.module.css b/src/containers/Collection/CollectionContact/CollectionContactList/CollectionContactList.module.css index 9cc0ea4ff..4aee6ed15 100644 --- a/src/containers/Collection/CollectionContact/CollectionContactList/CollectionContactList.module.css +++ b/src/containers/Collection/CollectionContact/CollectionContactList/CollectionContactList.module.css @@ -40,4 +40,4 @@ font-size: 15px; font-weight: 400; line-height: 20px; -} \ No newline at end of file +} diff --git a/src/containers/ContactManagement/AdminContactManagement/AdminContactManagement.test.tsx b/src/containers/ContactManagement/AdminContactManagement/AdminContactManagement.test.tsx index f2dce4e5b..74debaafd 100644 --- a/src/containers/ContactManagement/AdminContactManagement/AdminContactManagement.test.tsx +++ b/src/containers/ContactManagement/AdminContactManagement/AdminContactManagement.test.tsx @@ -25,12 +25,12 @@ const contactManagement = ( // let's mock setNotification function that's called upon successful upload vi.mock('common/notification', async (importOriginal) => { - const mod = await importOriginal() + const mod = await importOriginal(); return { ...mod, setNotification: vi.fn(), - } -}) + }; +}); test('Admin contact management form renders correctly', async () => { render(contactManagement); @@ -48,18 +48,20 @@ test('the page should have a disabled upload button by default', async () => { expect(uploadButton).toHaveAttribute('disabled'); }); -test('Files other than .csv should raise a warning message upon upload', async() => { +test('Files other than .csv should raise a warning message upon upload', async () => { render(contactManagement); - const nonCSVFile = new File(['This is not a CSV File'], 'test.pdf', {type: 'application/pdf'}); + const nonCSVFile = new File(['This is not a CSV File'], 'test.pdf', { type: 'application/pdf' }); await waitFor(() => { const fileInput = screen.getByTestId('uploadFile'); userEvent.upload(fileInput, nonCSVFile); - }) + }); await waitFor(() => { - expect(screen.getByText(/Please make sure the file format matches the sample/)).toBeInTheDocument(); - }) -}) + expect( + screen.getByText(/Please make sure the file format matches the sample/) + ).toBeInTheDocument(); + }); +}); test('Success Notification should be called upon successful CSV upload', async () => { render(contactManagement); @@ -68,21 +70,21 @@ test('Success Notification should be called upon successful CSV upload', async ( const csvContent = `name,phone,collection John Doe,919876543210,"Optin collection,Optout Collection" Virat Kohli,919876543220,Cricket`; - const file = new File([csvContent], 'test.csv', {type: 'text/csv'}); + const file = new File([csvContent], 'test.csv', { type: 'text/csv' }); await waitFor(() => { const fileInput = screen.getByTestId('uploadFile'); userEvent.upload(fileInput, file); - }) + }); await waitFor(() => { // the filename should be visible instead of Select .csv after upload expect(screen.getByText('test.csv')).toBeInTheDocument(); - }) + }); const uploadBtn = screen.getByTestId('uploadButton'); - userEvent.click(uploadBtn) + userEvent.click(uploadBtn); await waitFor(() => { expect(setNotification).toHaveBeenCalled(); - }) -}) + }); +}); diff --git a/src/containers/ContactManagement/UploadContactsDialog/UploadContactsDialog.test.tsx b/src/containers/ContactManagement/UploadContactsDialog/UploadContactsDialog.test.tsx index b32dddd8b..8043f9696 100644 --- a/src/containers/ContactManagement/UploadContactsDialog/UploadContactsDialog.test.tsx +++ b/src/containers/ContactManagement/UploadContactsDialog/UploadContactsDialog.test.tsx @@ -37,18 +37,18 @@ test('Upload contact dialog renders correctly', async () => { }); }); -test('Files other than .csv should raise a warning message upon upload', async() => { +test('Files other than .csv should raise a warning message upon upload', async () => { render(dialogBox); - const nonCSVFile = new File(['This is not a CSV File'], 'test.pdf', {type: 'application/pdf'}); + const nonCSVFile = new File(['This is not a CSV File'], 'test.pdf', { type: 'application/pdf' }); await waitFor(() => { const fileInput = screen.getByTestId('uploadFile'); userEvent.upload(fileInput, nonCSVFile); - }) + }); await waitFor(() => { expect(screen.getByTestId('invalidCsvFormat')).toBeInTheDocument(); - }) -}) + }); +}); test('Should be able to upload valid CSV', async () => { render(dialogBox); @@ -57,14 +57,14 @@ test('Should be able to upload valid CSV', async () => { const csvContent = `name,phone,collection John Doe,919876543210,"Optin collection,Optout Collection" Virat Kohli,919876543220,Cricket`; - const file = new File([csvContent], 'test.csv', {type: 'text/csv'}); + const file = new File([csvContent], 'test.csv', { type: 'text/csv' }); await waitFor(() => { const fileInput = screen.getByTestId('uploadFile'); userEvent.upload(fileInput, file); - }) + }); await waitFor(() => { // the filename should be visible instead of Select .csv after upload expect(screen.getByText('test.csv')).toBeInTheDocument(); - }) -}) + }); +}); diff --git a/src/containers/SettingList/OrganizationFlows/OrganizationFlows.tsx b/src/containers/SettingList/OrganizationFlows/OrganizationFlows.tsx index 73e4cfb31..274bd9145 100644 --- a/src/containers/SettingList/OrganizationFlows/OrganizationFlows.tsx +++ b/src/containers/SettingList/OrganizationFlows/OrganizationFlows.tsx @@ -8,6 +8,7 @@ import { Checkbox } from 'components/UI/Form/Checkbox/Checkbox'; import { TimePicker } from 'components/UI/Form/TimePicker/TimePicker'; import { Loading } from 'components/UI/Layout/Loading/Loading'; import { AutoComplete } from 'components/UI/Form/AutoComplete/AutoComplete'; +import { Input } from 'components/UI/Form/Input/Input'; import { FormLayout } from 'containers/Form/FormLayout'; import { GET_FLOWS } from 'graphql/queries/Flow'; import { GET_ORGANIZATION, USER_LANGUAGES } from 'graphql/queries/Organization'; @@ -49,6 +50,10 @@ export const OrganizationFlows = () => { const [organizationId, setOrganizationId] = useState(null); const [newcontactFlowId, setNewcontactFlowId] = useState(null); const [newcontactFlowEnabled, setNewcontactFlowEnabled] = useState(false); + const [regxFlowId, setRegxFlowId] = useState(null); + const [regxFlowExpr, setRegxFlowExpr] = useState(''); + const [regxFlowOpt, setRegxFlowOpt] = useState(''); + const [regxFlowEnabled, setRegxFlowEnabled] = useState(false); const [optinFlowId, setOptinFlowId] = useState(null); const [optinFlowEnabled, setOptinFlowEnabled] = useState(false); const [allDayCheck, setAllDayCheck] = useState(false); @@ -62,10 +67,14 @@ export const OrganizationFlows = () => { enabledDays, defaultFlowId, flowId, - optinFlowId, + newcontactFlowId, newcontactFlowEnabled, + regxFlowId, + regxFlowExpr, + regxFlowOpt, + regxFlowEnabled, + optinFlowId, optinFlowEnabled, - newcontactFlowId, allDayCheck, }; @@ -97,6 +106,7 @@ export const OrganizationFlows = () => { const setStates = ({ outOfOffice: outOfOfficeValue, newcontactFlowId: newcontactFlowIdValue, + regxFlow: regxFlowValue, optinFlowId: optinFlowIdValue, }: any) => { setHours(outOfOfficeValue.enabled); @@ -115,6 +125,12 @@ export const OrganizationFlows = () => { setNewcontactFlowEnabled(true); setNewcontactFlowId(getFlow(newcontactFlowIdValue)); } + if (regxFlowValue) { + setRegxFlowEnabled(true); + setRegxFlowId(getFlow(regxFlowValue.flowId)); + setRegxFlowExpr(regxFlowValue.regx); + setRegxFlowOpt(regxFlowValue.regxOpt); + } if (optinFlowIdValue) { setOptinFlowEnabled(true); setOptinFlowId(getFlow(optinFlowIdValue)); @@ -203,6 +219,19 @@ export const OrganizationFlows = () => { is: (val: string) => val, then: (schema) => schema.nullable().required(t('Optin flow is required.')), }), + regxFlowId: Yup.object() + .nullable() + .when('regxFlowEnabled', { + is: (val: string) => val, + then: (schema) => schema.nullable().required(t('Regular expression flow is required.')), + }), + regxFlowExpr: Yup.string() + .nullable() + .when('regxFlowEnabled', { + is: (val: string) => val, + then: (schema) => schema.nullable().required(t('Regular expression is required.')), + }), + regxFlowOpt: Yup.string().nullable(), }; const FormSchema = Yup.object().shape(validation); @@ -316,6 +345,45 @@ export const OrganizationFlows = () => { disabled: !optinFlowEnabled, label: t('Select flow'), }, + + { + component: Checkbox, + name: 'regxFlowEnabled', + className: styles.Checkbox, + title: ( + {t('Regular expression flow')} + ), + handleChange: setRegxFlowEnabled, + }, + { + component: AutoComplete, + name: 'regxFlowId', + options: flow.flows, + optionLabel: 'name', + multiple: false, + disabled: !regxFlowEnabled, + label: t('Select flow'), + helperText: t( + 'The selected flow will trigger when end-users aren’t in any flow, their message doesn’t match any keyword, and their message matches the regular expression defined below.' + ), + }, + { + component: Input, + name: 'regxFlowExpr', + type: 'text', + label: t('Regular expression'), + disabled: !regxFlowEnabled, + helperText: t( + 'A pattern that the regular expression engine attempts to match in input text.' + ), + }, + { + component: Input, + name: 'regxFlowOpt', + type: 'text', + label: t('Regular expression modifiers'), + disabled: !regxFlowEnabled, + }, ]; const assignDays = (enabledDay: any) => { @@ -344,6 +412,7 @@ export const OrganizationFlows = () => { // set active Language Ids let newContactFlowId = null; let optinFlowId = null; + let regxFlow = null; if (newcontactFlowEnabled) { newContactFlowId = payload.newcontactFlowId.id; @@ -352,6 +421,14 @@ export const OrganizationFlows = () => { if (optinFlowEnabled) { optinFlowId = payload.optinFlowId.id; } + + if (regxFlowEnabled) { + regxFlow = { + flowId: payload.regxFlowId.id, + regx: payload.regxFlowExpr, + regxOpt: payload.regxFlowOpt, + }; + } object = { outOfOffice: { defaultFlowId: payload.defaultFlowId ? payload.defaultFlowId.id : null, @@ -361,6 +438,7 @@ export const OrganizationFlows = () => { flowId: payload.flowId ? payload.flowId.id : null, startTime: dayjs(payload.startTime).format(EXTENDED_TIME_FORMAT), }, + regxFlow: regxFlow, newcontactFlowId: newContactFlowId, optinFlowId, }; diff --git a/src/graphql/mutations/Tags.ts b/src/graphql/mutations/Tags.ts index 2d44d74ea..13bb00c03 100644 --- a/src/graphql/mutations/Tags.ts +++ b/src/graphql/mutations/Tags.ts @@ -17,7 +17,7 @@ export const CREATE_LABEL = gql` export const UPDATE_TAG = gql` mutation UpdateTag($id: ID!, $input: TagInput!) { - updateTag(id: $id,input: $input) { + updateTag(id: $id, input: $input) { tag { label } diff --git a/src/graphql/queries/Organization.ts b/src/graphql/queries/Organization.ts index b707adfbc..f7bb384f6 100644 --- a/src/graphql/queries/Organization.ts +++ b/src/graphql/queries/Organization.ts @@ -33,6 +33,11 @@ export const GET_ORGANIZATION = gql` criticalBalanceThreshold sendWarningMail } + regxFlow { + flowId + regx + regxOpt + } signaturePhrase newcontactFlowId optinFlowId diff --git a/src/i18n/en/en.json b/src/i18n/en/en.json index 8faf21ad9..71b990813 100644 --- a/src/i18n/en/en.json +++ b/src/i18n/en/en.json @@ -309,6 +309,13 @@ "Please select days": "Please select days", "End time cannot be 12 AM": "End time cannot be 12 AM", "Not a valid time": "Not a valid time", + "Regular expression flow": "Regular expression flow", + "Regular expression": "Regular expression", + "Regular expression modifiers": "Regular expression modifiers", + "Regular expression flow is required.": "Regural expression flow is required.", + "Regular expression is required.": "Regular expression is required.", + "The selected flow will trigger when end-users aren’t in any flow, their message doesn’t match any keyword, and their message matches the regular expression defined below.": "The selected flow will trigger when end-users aren’t in any flow, their message doesn’t match any keyword, and their message matches the regular expression defined below.", + "A pattern that the regular expression engine attempts to match in input text.": "A pattern that the regular expression engine attempts to match in input text.", "New contact flow is required.": "New contact flow is required.", "Optin flow is required.": "Optin flow is required.", "Default flow": "Default flow", diff --git a/src/mocks/Attachment.tsx b/src/mocks/Attachment.tsx index dcd052092..79e740fe9 100644 --- a/src/mocks/Attachment.tsx +++ b/src/mocks/Attachment.tsx @@ -64,7 +64,7 @@ export const uploadMediaMock = { export const uploadMediaErrorMock = { request: { query: UPLOAD_MEDIA, - variables: { media: { name: 'photo.png' }, extension: 'png' } + variables: { media: { name: 'photo.png' }, extension: 'png' }, }, - error: new Error('An error occurred') -} + error: new Error('An error occurred'), +}; diff --git a/src/mocks/ReportToGupshup.tsx b/src/mocks/ReportToGupshup.tsx index 69f00f0b9..afeb049af 100644 --- a/src/mocks/ReportToGupshup.tsx +++ b/src/mocks/ReportToGupshup.tsx @@ -1,8 +1,8 @@ export const responseData = { - data: { - reportToGupshup: { - errors: null, - message: 'Successfully sent mail to Gupshup Support', - }, + data: { + reportToGupshup: { + errors: null, + message: 'Successfully sent mail to Gupshup Support', }, + }, };