![]()
({
parentNav: 'campaigns',
- routes: ['ongoing_campaigns', 'one_off'],
+ routes: ['campaigns_sms_index', 'campaigns_livechat_index'],
menuItems: [
{
icon: 'arrow-swap',
- label: 'ONGOING',
+ label: 'LIVE_CHAT',
key: 'ongoingCampaigns',
hasSubMenu: false,
- toState: frontendURL(`accounts/${accountId}/campaigns/ongoing`),
- toStateName: 'ongoing_campaigns',
+ toState: frontendURL(`accounts/${accountId}/campaigns/live_chat`),
+ toStateName: 'campaigns_livechat_index',
},
{
key: 'oneOffCampaigns',
icon: 'sound-source',
- label: 'ONE_OFF',
+ label: 'SMS',
hasSubMenu: false,
- toState: frontendURL(`accounts/${accountId}/campaigns/one_off`),
- toStateName: 'one_off',
+ toState: frontendURL(`accounts/${accountId}/campaigns/sms`),
+ toStateName: 'campaigns_sms_index',
},
],
});
diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/primaryMenu.js b/app/javascript/dashboard/components/layout/config/sidebarItems/primaryMenu.js
index 67d05c3d6..4bd42e987 100644
--- a/app/javascript/dashboard/components/layout/config/sidebarItems/primaryMenu.js
+++ b/app/javascript/dashboard/components/layout/config/sidebarItems/primaryMenu.js
@@ -47,7 +47,7 @@ const primaryMenuItems = accountId => [
label: 'CAMPAIGNS',
featureFlag: FEATURE_FLAGS.CAMPAIGNS,
toState: frontendURL(`accounts/${accountId}/campaigns`),
- toStateName: 'ongoing_campaigns',
+ toStateName: 'campaigns_ongoing_index',
},
{
icon: 'library',
diff --git a/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue b/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue
index 28d597862..5987a3b76 100644
--- a/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue
+++ b/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue
@@ -65,6 +65,7 @@ const props = defineProps({
modelValue: { type: String, default: '' },
editorId: { type: String, default: '' },
placeholder: { type: String, default: '' },
+ disabled: { type: Boolean, default: false },
isPrivate: { type: Boolean, default: false },
enableSuggestions: { type: Boolean, default: true },
overrideLineBreaks: { type: Boolean, default: false },
@@ -299,6 +300,8 @@ function handleEmptyBodyWithSignature() {
}
function focusEditor(content) {
+ if (props.disabled) return;
+
const unrefContent = unref(content);
if (isBodyEmpty(unrefContent) && sendWithSignature.value) {
// reload state can be called when switching between conversations, or when drafts is loaded
@@ -561,6 +564,7 @@ function onKeydown(event) {
function createEditorView() {
editorView = new EditorView(editor.value, {
state: state,
+ editable: () => !props.disabled,
dispatchTransaction: tx => {
state = state.apply(tx);
editorView.updateState(state);
@@ -570,17 +574,21 @@ function createEditorView() {
},
handleDOMEvents: {
keyup: () => {
- typingIndicator.start();
- updateImgToolbarOnDelete();
+ if (!props.disabled) {
+ typingIndicator.start();
+ updateImgToolbarOnDelete();
+ }
},
- keydown: (view, event) => onKeydown(event),
- focus: () => emit('focus'),
- click: isEditorMouseFocusedOnAnImage,
+ keydown: (view, event) => !props.disabled && onKeydown(event),
+ focus: () => !props.disabled && emit('focus'),
+ click: () => !props.disabled && isEditorMouseFocusedOnAnImage(),
blur: () => {
+ if (props.disabled) return;
typingIndicator.stop();
emit('blur');
},
paste: (_view, event) => {
+ if (props.disabled) return;
const data = event.clipboardData.files;
if (data.length > 0) {
event.preventDefault();
diff --git a/app/javascript/dashboard/helper/inbox.js b/app/javascript/dashboard/helper/inbox.js
index 71c615c4b..6f318218b 100644
--- a/app/javascript/dashboard/helper/inbox.js
+++ b/app/javascript/dashboard/helper/inbox.js
@@ -1,4 +1,28 @@
-import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
+export const INBOX_TYPES = {
+ WEB: 'Channel::WebWidget',
+ FB: 'Channel::FacebookPage',
+ TWITTER: 'Channel::TwitterProfile',
+ TWILIO: 'Channel::TwilioSms',
+ WHATSAPP: 'Channel::Whatsapp',
+ API: 'Channel::Api',
+ EMAIL: 'Channel::Email',
+ TELEGRAM: 'Channel::Telegram',
+ LINE: 'Channel::Line',
+ SMS: 'Channel::Sms',
+};
+
+const INBOX_ICON_MAP = {
+ [INBOX_TYPES.WEB]: 'i-ri-global-fill',
+ [INBOX_TYPES.FB]: 'i-ri-messenger-fill',
+ [INBOX_TYPES.TWITTER]: 'i-ri-twitter-x-fill',
+ [INBOX_TYPES.WHATSAPP]: 'i-ri-whatsapp-fill',
+ [INBOX_TYPES.API]: 'i-ri-cloudy-fill',
+ [INBOX_TYPES.EMAIL]: 'i-ri-mail-fill',
+ [INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-fill',
+ [INBOX_TYPES.LINE]: 'i-ri-line-fill',
+};
+
+const DEFAULT_ICON = 'i-ri-chat-1-fill';
export const getInboxSource = (type, phoneNumber, inbox) => {
switch (type) {
@@ -86,6 +110,17 @@ export const getInboxClassByType = (type, phoneNumber) => {
}
};
+export const getInboxIconByType = (type, phoneNumber) => {
+ // Special case for Twilio (whatsapp and sms)
+ if (type === INBOX_TYPES.TWILIO) {
+ return phoneNumber?.startsWith('whatsapp')
+ ? 'i-ri-whatsapp-fill'
+ : 'i-ri-chat-1-fill';
+ }
+
+ return INBOX_ICON_MAP[type] ?? DEFAULT_ICON;
+};
+
export const getInboxWarningIconClass = (type, reauthorizationRequired) => {
const allowedInboxTypes = [INBOX_TYPES.FB, INBOX_TYPES.EMAIL];
if (allowedInboxTypes.includes(type) && reauthorizationRequired) {
diff --git a/app/javascript/dashboard/helper/specs/inbox.spec.js b/app/javascript/dashboard/helper/specs/inbox.spec.js
index 7a9d71e79..621c131f2 100644
--- a/app/javascript/dashboard/helper/specs/inbox.spec.js
+++ b/app/javascript/dashboard/helper/specs/inbox.spec.js
@@ -1,4 +1,9 @@
-import { getInboxClassByType, getInboxWarningIconClass } from '../inbox';
+import {
+ INBOX_TYPES,
+ getInboxClassByType,
+ getInboxIconByType,
+ getInboxWarningIconClass,
+} from '../inbox';
describe('#Inbox Helpers', () => {
describe('getInboxClassByType', () => {
@@ -35,6 +40,74 @@ describe('#Inbox Helpers', () => {
});
});
+ describe('getInboxIconByType', () => {
+ it('returns correct icon for web widget', () => {
+ expect(getInboxIconByType(INBOX_TYPES.WEB)).toBe('i-ri-global-fill');
+ });
+
+ it('returns correct icon for Facebook', () => {
+ expect(getInboxIconByType(INBOX_TYPES.FB)).toBe('i-ri-messenger-fill');
+ });
+
+ it('returns correct icon for Twitter', () => {
+ expect(getInboxIconByType(INBOX_TYPES.TWITTER)).toBe(
+ 'i-ri-twitter-x-fill'
+ );
+ });
+
+ describe('Twilio cases', () => {
+ it('returns WhatsApp icon for Twilio WhatsApp number', () => {
+ expect(
+ getInboxIconByType(INBOX_TYPES.TWILIO, 'whatsapp:+1234567890')
+ ).toBe('i-ri-whatsapp-fill');
+ });
+
+ it('returns SMS icon for regular Twilio number', () => {
+ expect(getInboxIconByType(INBOX_TYPES.TWILIO, '+1234567890')).toBe(
+ 'i-ri-chat-1-fill'
+ );
+ });
+
+ it('returns SMS icon when phone number is undefined', () => {
+ expect(getInboxIconByType(INBOX_TYPES.TWILIO, undefined)).toBe(
+ 'i-ri-chat-1-fill'
+ );
+ });
+ });
+
+ it('returns correct icon for WhatsApp', () => {
+ expect(getInboxIconByType(INBOX_TYPES.WHATSAPP)).toBe(
+ 'i-ri-whatsapp-fill'
+ );
+ });
+
+ it('returns correct icon for API', () => {
+ expect(getInboxIconByType(INBOX_TYPES.API)).toBe('i-ri-cloudy-fill');
+ });
+
+ it('returns correct icon for Email', () => {
+ expect(getInboxIconByType(INBOX_TYPES.EMAIL)).toBe('i-ri-mail-fill');
+ });
+
+ it('returns correct icon for Telegram', () => {
+ expect(getInboxIconByType(INBOX_TYPES.TELEGRAM)).toBe(
+ 'i-ri-telegram-fill'
+ );
+ });
+
+ it('returns correct icon for Line', () => {
+ expect(getInboxIconByType(INBOX_TYPES.LINE)).toBe('i-ri-line-fill');
+ });
+
+ it('returns default icon for unknown type', () => {
+ expect(getInboxIconByType('UNKNOWN_TYPE')).toBe('i-ri-chat-1-fill');
+ });
+
+ it('returns default icon for undefined type', () => {
+ expect(getInboxIconByType(undefined)).toBe('i-ri-chat-1-fill');
+ });
+ });
+
describe('getInboxWarningIconClass', () => {
it('should return correct class for warning', () => {
expect(getInboxWarningIconClass('Channel::FacebookPage', true)).toEqual(
diff --git a/app/javascript/dashboard/i18n/locale/en/campaign.json b/app/javascript/dashboard/i18n/locale/en/campaign.json
index bbcc463ee..e2418d52e 100644
--- a/app/javascript/dashboard/i18n/locale/en/campaign.json
+++ b/app/javascript/dashboard/i18n/locale/en/campaign.json
@@ -1,126 +1,150 @@
{
"CAMPAIGN": {
- "HEADER": "Campaigns",
- "SIDEBAR_TXT": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations. Click on
Add Campaign to create a new campaign. You can also edit or delete an existing campaign by clicking on the Edit or Delete button.",
- "HEADER_BTN_TXT": {
- "ONE_OFF": "Create a one off campaign",
- "ONGOING": "Create a ongoing campaign"
- },
- "ADD": {
- "TITLE": "Create a campaign",
- "DESC": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations.",
- "CANCEL_BUTTON_TEXT": "Cancel",
- "CREATE_BUTTON_TEXT": "Create",
- "FORM": {
- "TITLE": {
- "LABEL": "Title",
- "PLACEHOLDER": "Please enter the title of campaign",
- "ERROR": "Title is required"
+ "LIVE_CHAT": {
+ "HEADER_TITLE": "Live chat campaigns",
+ "NEW_CAMPAIGN": "Create campaign",
+ "CARD": {
+ "STATUS": {
+ "ENABLED": "Enabled",
+ "DISABLED": "Disabled"
},
- "SCHEDULED_AT": {
- "LABEL": "Scheduled time",
- "PLACEHOLDER": "Please select the time",
- "CONFIRM": "Confirm",
- "ERROR": "Scheduled time is required"
- },
- "AUDIENCE": {
- "LABEL": "Audience",
- "PLACEHOLDER": "Select the customer labels",
- "ERROR": "Audience is required"
- },
- "INBOX": {
- "LABEL": "Select Inbox",
- "PLACEHOLDER": "Select Inbox",
- "ERROR": "Inbox is required"
- },
- "MESSAGE": {
- "LABEL": "Message",
- "PLACEHOLDER": "Please enter the message of campaign",
- "ERROR": "Message is required"
- },
- "SENT_BY": {
- "LABEL": "Sent by",
- "PLACEHOLDER": "Please select the the content of campaign",
- "ERROR": "Sender is required"
- },
- "END_POINT": {
- "LABEL": "URL",
- "PLACEHOLDER": "Please enter the URL",
- "ERROR": "Please enter a valid URL"
- },
- "TIME_ON_PAGE": {
- "LABEL": "Time on page(Seconds)",
- "PLACEHOLDER": "Please enter the time",
- "ERROR": "Time on page is required"
- },
- "ENABLED": "Enable campaign",
- "TRIGGER_ONLY_BUSINESS_HOURS": "Trigger only during business hours",
- "SUBMIT": "Add Campaign"
+ "CAMPAIGN_DETAILS": {
+ "SENT_BY": "Sent by",
+ "BOT": "Bot",
+ "FROM": "from",
+ "URL": "URL:"
+ }
},
- "API": {
- "SUCCESS_MESSAGE": "Campaign created successfully",
- "ERROR_MESSAGE": "There was an error. Please try again."
+ "EMPTY_STATE": {
+ "TITLE": "No live chat campaigns are available",
+ "SUBTITLE": "Connect with your customers using proactive messages. Click 'Create campaign' to get started."
+ },
+ "CREATE": {
+ "TITLE": "Create a live chat campaign",
+ "CANCEL_BUTTON_TEXT": "Cancel",
+ "CREATE_BUTTON_TEXT": "Create",
+ "FORM": {
+ "TITLE": {
+ "LABEL": "Title",
+ "PLACEHOLDER": "Please enter the title of campaign",
+ "ERROR": "Title is required"
+ },
+ "MESSAGE": {
+ "LABEL": "Message",
+ "PLACEHOLDER": "Please enter the message of campaign",
+ "ERROR": "Message is required"
+ },
+ "INBOX": {
+ "LABEL": "Select Inbox",
+ "PLACEHOLDER": "Select Inbox",
+ "ERROR": "Inbox is required"
+ },
+ "SENT_BY": {
+ "LABEL": "Sent by",
+ "PLACEHOLDER": "Please select sender",
+ "ERROR": "Sender is required"
+ },
+ "END_POINT": {
+ "LABEL": "URL",
+ "PLACEHOLDER": "Please enter the URL",
+ "ERROR": "Please enter a valid URL"
+ },
+ "TIME_ON_PAGE": {
+ "LABEL": "Time on page(Seconds)",
+ "PLACEHOLDER": "Please enter the time",
+ "ERROR": "Time on page is required"
+ },
+ "OTHER_PREFERENCES": {
+ "TITLE": "Other preferences",
+ "ENABLED": "Enable campaign",
+ "TRIGGER_ONLY_BUSINESS_HOURS": "Trigger only during business hours"
+ },
+ "BUTTONS": {
+ "CREATE": "Create",
+ "CANCEL": "Cancel"
+ },
+ "API": {
+ "SUCCESS_MESSAGE": "Live chat campaign created successfully",
+ "ERROR_MESSAGE": "There was an error. Please try again."
+ }
+ }
+ },
+ "EDIT": {
+ "TITLE": "Edit live chat campaign",
+ "FORM": {
+ "API": {
+ "SUCCESS_MESSAGE": "Live chat campaign updated successfully",
+ "ERROR_MESSAGE": "There was an error. Please try again."
+ }
+ }
}
},
- "DELETE": {
- "BUTTON_TEXT": "Delete",
- "CONFIRM": {
- "TITLE": "Confirm Deletion",
- "MESSAGE": "Are you sure to delete?",
- "YES": "Yes, Delete ",
- "NO": "No, Keep "
+ "SMS": {
+ "HEADER_TITLE": "SMS campaigns",
+ "NEW_CAMPAIGN": "Create campaign",
+ "EMPTY_STATE": {
+ "TITLE": "No SMS campaigns are available",
+ "SUBTITLE": "Launch an SMS campaign to reach your customers directly. Send offers or make announcements with ease. Click 'Create campaign' to get started."
},
+ "CARD": {
+ "STATUS": {
+ "COMPLETED": "Completed",
+ "SCHEDULED": "Scheduled"
+ },
+ "CAMPAIGN_DETAILS": {
+ "SENT_FROM": "Sent from",
+ "ON": "on"
+ }
+ },
+ "CREATE": {
+ "TITLE": "Create SMS campaign",
+ "CANCEL_BUTTON_TEXT": "Cancel",
+ "CREATE_BUTTON_TEXT": "Create",
+ "FORM": {
+ "TITLE": {
+ "LABEL": "Title",
+ "PLACEHOLDER": "Please enter the title of campaign",
+ "ERROR": "Title is required"
+ },
+ "MESSAGE": {
+ "LABEL": "Message",
+ "PLACEHOLDER": "Please enter the message of campaign",
+ "ERROR": "Message is required"
+ },
+ "INBOX": {
+ "LABEL": "Select Inbox",
+ "PLACEHOLDER": "Select Inbox",
+ "ERROR": "Inbox is required"
+ },
+ "AUDIENCE": {
+ "LABEL": "Audience",
+ "PLACEHOLDER": "Select the customer labels",
+ "ERROR": "Audience is required"
+ },
+ "SCHEDULED_AT": {
+ "LABEL": "Scheduled time",
+ "PLACEHOLDER": "Please select the time",
+ "ERROR": "Scheduled time is required"
+ },
+ "BUTTONS": {
+ "CREATE": "Create",
+ "CANCEL": "Cancel"
+ },
+ "API": {
+ "SUCCESS_MESSAGE": "SMS campaign created successfully",
+ "ERROR_MESSAGE": "There was an error. Please try again."
+ }
+ }
+ }
+ },
+ "CONFIRM_DELETE": {
+ "TITLE": "Are you sure to delete?",
+ "DESCRIPTION": "The delete action is permanent and cannot be reversed.",
+ "CONFIRM": "Delete",
"API": {
"SUCCESS_MESSAGE": "Campaign deleted successfully",
- "ERROR_MESSAGE": "Could not delete the campaign. Please try again later."
+ "ERROR_MESSAGE": "There was an error. Please try again."
}
- },
- "EDIT": {
- "TITLE": "Edit campaign",
- "UPDATE_BUTTON_TEXT": "Update",
- "API": {
- "SUCCESS_MESSAGE": "Campaign updated successfully",
- "ERROR_MESSAGE": "There was an error, please try again"
- }
- },
- "LIST": {
- "LOADING_MESSAGE": "Loading campaigns...",
- "404": "There are no campaigns created for this inbox.",
- "TABLE_HEADER": {
- "TITLE": "Title",
- "MESSAGE": "Message",
- "INBOX": "Inbox",
- "STATUS": "Status",
- "SENDER": "Sender",
- "URL": "URL",
- "SCHEDULED_AT": "Scheduled time",
- "TIME_ON_PAGE": "Time(Seconds)",
- "CREATED_AT": "Created at"
- },
- "BUTTONS": {
- "ADD": "Add",
- "EDIT": "Edit",
- "DELETE": "Delete"
- },
- "STATUS": {
- "ENABLED": "Enabled",
- "DISABLED": "Disabled",
- "COMPLETED": "Completed",
- "ACTIVE": "Active"
- },
- "SENDER": {
- "BOT": "Bot"
- }
- },
- "ONE_OFF": {
- "HEADER": "One off campaigns",
- "404": "There are no one off campaigns created",
- "INBOXES_NOT_FOUND": "Please create an sms inbox and start adding campaigns"
- },
- "ONGOING": {
- "HEADER": "Ongoing campaigns",
- "404": "There are no ongoing campaigns created",
- "INBOXES_NOT_FOUND": "Please create an website inbox and start adding campaigns"
}
}
}
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index d2c0b7c5e..414291dfd 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -267,6 +267,8 @@
"NEW_INBOX": "New inbox",
"REPORTS_CONVERSATION": "Conversations",
"CSAT": "CSAT",
+ "LIVE_CHAT": "Live Chat",
+ "SMS": "SMS",
"CAMPAIGNS": "Campaigns",
"ONGOING": "Ongoing",
"ONE_OFF": "One off",
diff --git a/app/javascript/dashboard/routes/dashboard/campaigns/campaigns.routes.js b/app/javascript/dashboard/routes/dashboard/campaigns/campaigns.routes.js
new file mode 100644
index 000000000..a4f0a94f5
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/campaigns/campaigns.routes.js
@@ -0,0 +1,60 @@
+import { frontendURL } from 'dashboard/helper/URLHelper.js';
+
+import CampaignsPageRouteView from './pages/CampaignsPageRouteView.vue';
+import LiveChatCampaignsPage from './pages/LiveChatCampaignsPage.vue';
+import SMSCampaignsPage from './pages/SMSCampaignsPage.vue';
+
+const campaignsRoutes = {
+ routes: [
+ {
+ path: frontendURL('accounts/:accountId/campaigns'),
+ component: CampaignsPageRouteView,
+ children: [
+ {
+ path: '',
+ redirect: to => {
+ return { name: 'campaigns_ongoing_index', params: to.params };
+ },
+ },
+ {
+ path: 'ongoing',
+ name: 'campaigns_ongoing_index',
+ meta: {
+ permissions: ['administrator'],
+ },
+ redirect: to => {
+ return { name: 'campaigns_livechat_index', params: to.params };
+ },
+ },
+ {
+ path: 'one_off',
+ name: 'campaigns_one_off_index',
+ meta: {
+ permissions: ['administrator'],
+ },
+ redirect: to => {
+ return { name: 'campaigns_sms_index', params: to.params };
+ },
+ },
+ {
+ path: 'live_chat',
+ name: 'campaigns_livechat_index',
+ meta: {
+ permissions: ['administrator'],
+ },
+ component: LiveChatCampaignsPage,
+ },
+ {
+ path: 'sms',
+ name: 'campaigns_sms_index',
+ meta: {
+ permissions: ['administrator'],
+ },
+ component: SMSCampaignsPage,
+ },
+ ],
+ },
+ ],
+};
+
+export default campaignsRoutes;
diff --git a/app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue b/app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue
new file mode 100644
index 000000000..9bbe7b917
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/campaigns/pages/LiveChatCampaignsPage.vue b/app/javascript/dashboard/routes/dashboard/campaigns/pages/LiveChatCampaignsPage.vue
new file mode 100644
index 000000000..93d5d1ec6
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/campaigns/pages/LiveChatCampaignsPage.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/campaigns/pages/SMSCampaignsPage.vue b/app/javascript/dashboard/routes/dashboard/campaigns/pages/SMSCampaignsPage.vue
new file mode 100644
index 000000000..a38a818f0
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/campaigns/pages/SMSCampaignsPage.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
index 5ec8ebf78..c6d60eb53 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
@@ -3,8 +3,7 @@ import { ref } from 'vue';
// constants & helpers
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
-import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
-import { getInboxSource } from 'dashboard/helper/inbox';
+import { getInboxSource, INBOX_TYPES } from 'dashboard/helper/inbox';
// store
import { mapGetters } from 'vuex';
diff --git a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js
index 8bb11b9be..f76c5e4e7 100644
--- a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js
@@ -6,6 +6,7 @@ import { routes as notificationRoutes } from './notifications/routes';
import { routes as inboxRoutes } from './inbox/routes';
import { frontendURL } from '../../helper/URLHelper';
import helpcenterRoutes from './helpcenter/helpcenter.routes';
+import campaignsRoutes from './campaigns/campaigns.routes';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
@@ -35,6 +36,7 @@ export default {
...searchRoutes,
...notificationRoutes,
...helpcenterRoutes.routes,
+ ...campaignsRoutes.routes,
],
},
{
diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/AddCampaign.vue b/app/javascript/dashboard/routes/dashboard/settings/campaigns/AddCampaign.vue
deleted file mode 100644
index 15acbcd04..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/AddCampaign.vue
+++ /dev/null
@@ -1,389 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/Campaign.vue b/app/javascript/dashboard/routes/dashboard/settings/campaigns/Campaign.vue
deleted file mode 100644
index 8bfcbad73..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/Campaign.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/CampaignCard.vue b/app/javascript/dashboard/routes/dashboard/settings/campaigns/CampaignCard.vue
deleted file mode 100644
index 48390c63a..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/CampaignCard.vue
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
-
-
-
-
-
- {{ campaign.title }}
-
-
-
-
-
- {{ $t('CAMPAIGN.LIST.BUTTONS.EDIT') }}
-
-
- {{ $t('CAMPAIGN.LIST.BUTTONS.DELETE') }}
-
-
-
-
-
-
-
-
-
- {{ campaign.trigger_rules.url }}
-
-
- {{ messageStamp(new Date(campaign.scheduled_at), 'LLL d, h:mm a') }}
-
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/CampaignsTable.vue b/app/javascript/dashboard/routes/dashboard/settings/campaigns/CampaignsTable.vue
deleted file mode 100644
index 5ef15a26b..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/CampaignsTable.vue
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
- {{ $t('CAMPAIGN.LIST.LOADING_MESSAGE') }}
-
-
-
-
- $emit('edit', campaign)"
- @delete="campaign => $emit('delete', campaign)"
- />
-
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/EditCampaign.vue b/app/javascript/dashboard/routes/dashboard/settings/campaigns/EditCampaign.vue
deleted file mode 100644
index 6aaec07a8..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/EditCampaign.vue
+++ /dev/null
@@ -1,304 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/campaigns/Index.vue
deleted file mode 100644
index e326dace1..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/Index.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
- {{ buttonText }}
-
-
-
-
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js
deleted file mode 100644
index 22268d11a..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import { frontendURL } from '../../../../helper/URLHelper';
-import SettingsContent from '../Wrapper.vue';
-import Index from './Index.vue';
-
-export default {
- routes: [
- {
- path: frontendURL('accounts/:accountId/campaigns'),
- component: SettingsContent,
- props: {
- headerTitle: 'CAMPAIGN.ONGOING.HEADER',
- icon: 'arrow-swap',
- },
- children: [
- {
- path: '',
- redirect: to => {
- return { name: 'ongoing_campaigns', params: to.params };
- },
- },
- {
- path: 'ongoing',
- name: 'ongoing_campaigns',
- meta: {
- permissions: ['administrator'],
- },
- component: Index,
- },
- ],
- },
- {
- path: frontendURL('accounts/:accountId/campaigns'),
- component: SettingsContent,
- props: {
- headerTitle: 'CAMPAIGN.ONE_OFF.HEADER',
- icon: 'sound-source',
- },
- children: [
- {
- path: 'one_off',
- name: 'one_off',
- meta: {
- permissions: ['administrator'],
- },
- component: Index,
- },
- ],
- },
- ],
-};
diff --git a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js
index 1b9f69d2a..ef39688a5 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js
@@ -11,7 +11,6 @@ import attributes from './attributes/attributes.routes';
import automation from './automation/automation.routes';
import auditlogs from './auditlogs/audit.routes';
import billing from './billing/billing.routes';
-import campaigns from './campaigns/campaigns.routes';
import canned from './canned/canned.routes';
import inbox from './inbox/inbox.routes';
import integrations from './integrations/integrations.routes';
@@ -50,7 +49,6 @@ export default {
...automation.routes,
...auditlogs.routes,
...billing.routes,
- ...campaigns.routes,
...canned.routes,
...inbox.routes,
...integrations.routes,
diff --git a/app/javascript/dashboard/store/modules/campaigns.js b/app/javascript/dashboard/store/modules/campaigns.js
index a2ffc1363..c5a73d1d3 100644
--- a/app/javascript/dashboard/store/modules/campaigns.js
+++ b/app/javascript/dashboard/store/modules/campaigns.js
@@ -17,9 +17,9 @@ export const getters = {
return _state.uiFlags;
},
getCampaigns: _state => campaignType => {
- return _state.records.filter(
- record => record.campaign_type === campaignType
- );
+ return _state.records
+ .filter(record => record.campaign_type === campaignType)
+ .sort((a1, a2) => a1.id - a2.id);
},
getAllCampaigns: _state => {
return _state.records;
diff --git a/app/javascript/dashboard/store/modules/inboxes.js b/app/javascript/dashboard/store/modules/inboxes.js
index 95d5f625a..8e1dbb301 100644
--- a/app/javascript/dashboard/store/modules/inboxes.js
+++ b/app/javascript/dashboard/store/modules/inboxes.js
@@ -1,6 +1,6 @@
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
import * as types from '../mutation-types';
-import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
+import { INBOX_TYPES } from 'dashboard/helper/inbox';
import InboxesAPI from '../../api/inboxes';
import WebChannel from '../../api/channel/webChannel';
import FBChannel from '../../api/channel/fbChannel';
diff --git a/app/javascript/dashboard/store/modules/specs/campaigns/getters.spec.js b/app/javascript/dashboard/store/modules/specs/campaigns/getters.spec.js
index 88ee86a31..52f14c296 100644
--- a/app/javascript/dashboard/store/modules/specs/campaigns/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/campaigns/getters.spec.js
@@ -5,36 +5,8 @@ describe('#getters', () => {
it('get ongoing campaigns', () => {
const state = { records: campaigns };
expect(getters.getCampaigns(state)('ongoing')).toEqual([
- {
- id: 1,
- title: 'Welcome',
- description: null,
- account_id: 1,
- campaign_type: 'ongoing',
- message: 'Hey, What brings you today',
- enabled: true,
- trigger_rules: {
- url: 'https://github.com',
- time_on_page: 10,
- },
- created_at: '2021-05-03T04:53:36.354Z',
- updated_at: '2021-05-03T04:53:36.354Z',
- },
- {
- id: 3,
- title: 'Thanks',
- description: null,
- account_id: 1,
- campaign_type: 'ongoing',
- message: 'Thanks for coming to the show. How may I help you?',
- enabled: false,
- trigger_rules: {
- url: 'https://noshow.com',
- time_on_page: 10,
- },
- created_at: '2021-05-03T10:22:51.025Z',
- updated_at: '2021-05-03T10:22:51.025Z',
- },
+ campaigns[0],
+ campaigns[2],
]);
});
diff --git a/app/javascript/shared/composables/specs/useCampaign.spec.js b/app/javascript/shared/composables/specs/useCampaign.spec.js
deleted file mode 100644
index 6110fcded..000000000
--- a/app/javascript/shared/composables/specs/useCampaign.spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { describe, it, expect, vi } from 'vitest';
-import { useCampaign } from '../useCampaign';
-import { useRoute } from 'vue-router';
-import { CAMPAIGN_TYPES } from '../../constants/campaign';
-
-// Mock the useRoute composable
-vi.mock('vue-router', () => ({
- useRoute: vi.fn(),
-}));
-
-describe('useCampaign', () => {
- it('returns the correct campaign type for ongoing campaigns', () => {
- useRoute.mockReturnValue({ name: 'ongoing_campaigns' });
- const { campaignType } = useCampaign();
- expect(campaignType.value).toBe(CAMPAIGN_TYPES.ONGOING);
- });
-
- it('returns the correct campaign type for one-off campaigns', () => {
- useRoute.mockReturnValue({ name: 'one_off' });
- const { campaignType } = useCampaign();
- expect(campaignType.value).toBe(CAMPAIGN_TYPES.ONE_OFF);
- });
-
- it('isOngoingType returns true for ongoing campaigns', () => {
- useRoute.mockReturnValue({ name: 'ongoing_campaigns' });
- const { isOngoingType } = useCampaign();
- expect(isOngoingType.value).toBe(true);
- });
-
- it('isOngoingType returns false for one-off campaigns', () => {
- useRoute.mockReturnValue({ name: 'one_off' });
- const { isOngoingType } = useCampaign();
- expect(isOngoingType.value).toBe(false);
- });
-
- it('isOneOffType returns true for one-off campaigns', () => {
- useRoute.mockReturnValue({ name: 'one_off' });
- const { isOneOffType } = useCampaign();
- expect(isOneOffType.value).toBe(true);
- });
-
- it('isOneOffType returns false for ongoing campaigns', () => {
- useRoute.mockReturnValue({ name: 'ongoing_campaigns' });
- const { isOneOffType } = useCampaign();
- expect(isOneOffType.value).toBe(false);
- });
-});
diff --git a/app/javascript/shared/composables/useCampaign.js b/app/javascript/shared/composables/useCampaign.js
deleted file mode 100644
index 825a65c0e..000000000
--- a/app/javascript/shared/composables/useCampaign.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { computed } from 'vue';
-import { useRoute } from 'vue-router';
-import { CAMPAIGN_TYPES } from '../constants/campaign';
-
-/**
- * Composable to manage campaign types.
- *
- * @returns {Object} - Computed properties for campaign type and its checks.
- */
-export const useCampaign = () => {
- const route = useRoute();
-
- /**
- * Computed property to determine the current campaign type based on the route name.
- */
- const campaignType = computed(() => {
- const campaignTypeMap = {
- ongoing_campaigns: CAMPAIGN_TYPES.ONGOING,
- one_off: CAMPAIGN_TYPES.ONE_OFF,
- };
- return campaignTypeMap[route.name];
- });
-
- /**
- * Computed property to check if the current campaign type is 'ongoing'.
- */
- const isOngoingType = computed(() => {
- return campaignType.value === CAMPAIGN_TYPES.ONGOING;
- });
-
- /**
- * Computed property to check if the current campaign type is 'one-off'.
- */
- const isOneOffType = computed(() => {
- return campaignType.value === CAMPAIGN_TYPES.ONE_OFF;
- });
-
- return {
- campaignType,
- isOngoingType,
- isOneOffType,
- };
-};
diff --git a/app/javascript/shared/mixins/inboxMixin.js b/app/javascript/shared/mixins/inboxMixin.js
index 3953cfb40..82ee9db9e 100644
--- a/app/javascript/shared/mixins/inboxMixin.js
+++ b/app/javascript/shared/mixins/inboxMixin.js
@@ -1,15 +1,4 @@
-export const INBOX_TYPES = {
- WEB: 'Channel::WebWidget',
- FB: 'Channel::FacebookPage',
- TWITTER: 'Channel::TwitterProfile',
- TWILIO: 'Channel::TwilioSms',
- WHATSAPP: 'Channel::Whatsapp',
- API: 'Channel::Api',
- EMAIL: 'Channel::Email',
- TELEGRAM: 'Channel::Telegram',
- LINE: 'Channel::Line',
- SMS: 'Channel::Sms',
-};
+import { INBOX_TYPES } from 'dashboard/helper/inbox';
export const INBOX_FEATURES = {
REPLY_TO: 'replyTo',