Merge branch 'develop' into fix/widget-bubble

This commit is contained in:
Sivin Varghese
2025-04-07 10:24:44 +05:30
committed by GitHub
126 changed files with 3348 additions and 778 deletions
@@ -0,0 +1,14 @@
/* global axios */
import ApiClient from '../ApiClient';
class InstagramChannel extends ApiClient {
constructor() {
super('instagram', { accountScoped: true });
}
generateAuthorization(payload) {
return axios.post(`${this.url}/authorization`, payload);
}
}
export default new InstagramChannel();
@@ -17,6 +17,12 @@ class EnterpriseAccountAPI extends ApiClient {
getLimits() {
return axios.get(`${this.url}limits`);
}
toggleDeletion(action) {
return axios.post(`${this.url}toggle_deletion`, {
action_type: action,
});
}
}
export default new EnterpriseAccountAPI();
@@ -10,6 +10,7 @@ describe('#enterpriseAccountAPI', () => {
expect(accountAPI).toHaveProperty('update');
expect(accountAPI).toHaveProperty('delete');
expect(accountAPI).toHaveProperty('checkout');
expect(accountAPI).toHaveProperty('toggleDeletion');
});
describe('API calls', () => {
@@ -42,5 +43,21 @@ describe('#enterpriseAccountAPI', () => {
'/enterprise/api/v1/subscription'
);
});
it('#toggleDeletion with delete action', () => {
accountAPI.toggleDeletion('delete');
expect(axiosMock.post).toHaveBeenCalledWith(
'/enterprise/api/v1/toggle_deletion',
{ action_type: 'delete' }
);
});
it('#toggleDeletion with undelete action', () => {
accountAPI.toggleDeletion('undelete');
expect(axiosMock.post).toHaveBeenCalledWith(
'/enterprise/api/v1/toggle_deletion',
{ action_type: 'undelete' }
);
});
});
});
@@ -26,6 +26,12 @@ const showAccountSwitcher = computed(
() => userAccounts.value.length > 1 && currentAccount.value.name
);
const sortedCurrentUserAccounts = computed(() => {
return [...(currentUser.value.accounts || [])].sort((a, b) =>
a.name.localeCompare(b.name)
);
});
const onChangeAccount = newId => {
const accountUrl = `/app/accounts/${newId}/dashboard`;
window.location.href = accountUrl;
@@ -70,7 +76,7 @@ const emitNewAccount = () => {
<DropdownBody v-if="showAccountSwitcher" class="min-w-80 z-50">
<DropdownSection :title="t('SIDEBAR_ITEMS.SWITCH_ACCOUNT')">
<DropdownItem
v-for="account in currentUser.accounts"
v-for="account in sortedCurrentUserAccounts"
:id="`account-${account.id}`"
:key="account.id"
class="cursor-pointer"
@@ -10,6 +10,7 @@ import {
ALLOWED_FILE_TYPES,
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
ALLOWED_FILE_TYPES_FOR_LINE,
ALLOWED_FILE_TYPES_FOR_INSTAGRAM,
} from 'shared/constants/messages';
import VideoCallButton from '../VideoCallButton.vue';
import AIAssistanceButton from '../AIAssistanceButton.vue';
@@ -113,6 +114,10 @@ export default {
type: String,
required: true,
},
conversationType: {
type: String,
default: '',
},
},
emits: [
'replaceText',
@@ -187,6 +192,9 @@ export default {
showAudioPlayStopButton() {
return this.showAudioRecorder && this.isRecordingAudio;
},
isInstagramDM() {
return this.conversationType === 'instagram_direct_message';
},
allowedFileTypes() {
if (this.isATwilioWhatsAppChannel) {
return ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP;
@@ -194,6 +202,10 @@ export default {
if (this.isALineChannel) {
return ALLOWED_FILE_TYPES_FOR_LINE;
}
if (this.isAInstagramChannel || this.isInstagramDM) {
return ALLOWED_FILE_TYPES_FOR_INSTAGRAM;
}
return ALLOWED_FILE_TYPES;
},
enableDragAndDrop() {
@@ -226,16 +226,23 @@ export default {
return this.$t('CONVERSATION.CANNOT_REPLY');
},
replyWindowLink() {
if (this.isAWhatsAppChannel) {
if (this.isAFacebookInbox || this.isAInstagramChannel) {
return REPLY_POLICY.FACEBOOK;
}
if (this.isAWhatsAppCloudChannel) {
return REPLY_POLICY.WHATSAPP_CLOUD;
}
if (!this.isAPIInbox) {
return REPLY_POLICY.TWILIO_WHATSAPP;
}
return '';
},
replyWindowLinkText() {
if (this.isAWhatsAppChannel) {
if (
this.isAWhatsAppChannel ||
this.isAFacebookInbox ||
this.isAInstagramChannel
) {
return this.$t('CONVERSATION.24_HOURS_WINDOW');
}
if (!this.isAPIInbox) {
@@ -485,7 +492,7 @@ export default {
<Banner
v-if="!currentChat.can_reply"
color-scheme="alert"
class="mt-2 mx-2 rounded-lg overflow-hidden"
class="mx-2 mt-2 overflow-hidden rounded-lg"
:banner-message="replyWindowBannerMessage"
:href-link="replyWindowLink"
:href-link-text="replyWindowLinkText"
@@ -1195,6 +1195,7 @@ export default {
:mode="replyType"
:on-file-upload="onFileUpload"
:on-send="onSendReply"
:conversation-type="conversationType"
:recording-audio-duration-text="recordingAudioDurationText"
:recording-audio-state="recordingAudioState"
:send-button-text="replyButtonLabel"
+6
View File
@@ -9,6 +9,7 @@ export const INBOX_TYPES = {
TELEGRAM: 'Channel::Telegram',
LINE: 'Channel::Line',
SMS: 'Channel::Sms',
INSTAGRAM: 'Channel::Instagram',
};
const INBOX_ICON_MAP_FILL = {
@@ -20,6 +21,7 @@ const INBOX_ICON_MAP_FILL = {
[INBOX_TYPES.EMAIL]: 'i-ri-mail-fill',
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-fill',
[INBOX_TYPES.LINE]: 'i-ri-line-fill',
[INBOX_TYPES.INSTAGRAM]: 'i-ri-instagram-fill',
};
const DEFAULT_ICON_FILL = 'i-ri-chat-1-fill';
@@ -33,6 +35,7 @@ const INBOX_ICON_MAP_LINE = {
[INBOX_TYPES.EMAIL]: 'i-ri-mail-line',
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-line',
[INBOX_TYPES.LINE]: 'i-ri-line-line',
[INBOX_TYPES.INSTAGRAM]: 'i-ri-instagram-line',
};
const DEFAULT_ICON_LINE = 'i-ri-chat-1-line';
@@ -118,6 +121,9 @@ export const getInboxClassByType = (type, phoneNumber) => {
case INBOX_TYPES.LINE:
return 'brand-line';
case INBOX_TYPES.INSTAGRAM:
return 'brand-instagram';
default:
return 'chat';
}
@@ -36,7 +36,7 @@ describe('#resolveActionName', () => {
expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).not.toEqual(
MACRO_ACTION_TYPES[0].label
);
expect(resolveActionName('change_priority')).toEqual('Change Priority');
expect(resolveActionName('change_priority')).toEqual('CHANGE_PRIORITY'); // Translated
});
});
@@ -126,6 +126,44 @@
"ATLEAST_ONE_CONDITION_REQUIRED": "At least one condition is required",
"ATLEAST_ONE_ACTION_REQUIRED": "At least one action is required"
},
"NONE_OPTION": "None"
"NONE_OPTION": "None",
"EVENTS": {
"CONVERSATION_CREATED": "Conversation Created",
"CONVERSATION_UPDATED": "Conversation Updated",
"MESSAGE_CREATED": "Message Created",
"CONVERSATION_OPENED": "Conversation Opened"
},
"ACTIONS": {
"ASSIGN_AGENT": "Assign to Agent",
"ASSIGN_TEAM": "Assign a Team",
"ADD_LABEL": "Add a Label",
"REMOVE_LABEL": "Remove a Label",
"SEND_EMAIL_TO_TEAM": "Send an Email to Team",
"SEND_EMAIL_TRANSCRIPT": "Send an Email Transcript",
"MUTE_CONVERSATION": "Mute Conversation",
"SNOOZE_CONVERSATION": "Snooze Conversation",
"RESOLVE_CONVERSATION": "Resolve Conversation",
"SEND_WEBHOOK_EVENT": "Send Webhook Event",
"SEND_ATTACHMENT": "Send Attachment",
"SEND_MESSAGE": "Send a Message",
"CHANGE_PRIORITY": "Change Priority",
"ADD_SLA": "Add SLA"
},
"ATTRIBUTES": {
"MESSAGE_TYPE": "Message Type",
"MESSAGE_CONTAINS": "Message Contains",
"EMAIL": "Email",
"INBOX": "Inbox",
"CONVERSATION_LANGUAGE": "Conversation Language",
"PHONE_NUMBER": "Phone Number",
"STATUS": "Status",
"BROWSER_LANGUAGE": "Browser Language",
"MAIL_SUBJECT": "Email Subject",
"COUNTRY_NAME": "Country",
"REFERER_LINK": "Referrer Link",
"ASSIGNEE_NAME": "Assignee",
"TEAM_NAME": "Team",
"PRIORITY": "Priority"
}
}
}
@@ -14,6 +14,26 @@
"ERROR": "Could not update settings, try again!",
"SUCCESS": "Successfully updated account settings"
},
"ACCOUNT_DELETE_SECTION": {
"TITLE": "Delete your Account",
"NOTE": "Once you delete your account, all your data will be deleted.",
"BUTTON_TEXT": "Delete Your Account",
"CONFIRM": {
"TITLE": "Delete Account",
"MESSAGE": "Deleting your Account is irreversible. Enter your account name below to confirm you want to permanently delete it.",
"BUTTON_TEXT": "Delete",
"DISMISS": "Cancel",
"PLACE_HOLDER": "Please type {accountName} to confirm"
},
"SUCCESS": "Account marked for deletion",
"FAILURE": "Could not delete account, try again!",
"SCHEDULED_DELETION": {
"TITLE": "Account Scheduled for Deletion",
"MESSAGE_MANUAL": "This account is scheduled for deletion on {deletionDate}. This was requested by an administrator. You can cancel the deletion before this date.",
"MESSAGE_INACTIVITY": "This account is scheduled for deletion on {deletionDate} due to account inactivity. You can cancel the deletion before this date.",
"CLEAR_BUTTON": "Cancel Scheduled Deletion"
}
},
"FORM": {
"ERROR": "Please fix form errors",
"GENERAL_SECTION": {
@@ -43,7 +43,14 @@
"INBOX_NAME": "Inbox Name",
"ADD_NAME": "Add a name for your inbox",
"PICK_NAME": "Pick a Name for your Inbox",
"PICK_A_VALUE": "Pick a value"
"PICK_A_VALUE": "Pick a value",
"CREATE_INBOX": "Create Inbox"
},
"INSTAGRAM": {
"CONTINUE_WITH_INSTAGRAM": "Continue with Instagram",
"HELP": "To add your Instagram profile as a channel, you need to authenticate your Instagram Profile by clicking on 'Continue with Instagram' ",
"ERROR_MESSAGE": "There was an error connecting to Instagram, please try again",
"ERROR_AUTH": "There was an error connecting to Instagram, please try again"
},
"TWITTER": {
"HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' ",
@@ -753,7 +760,8 @@
"EMAIL": "Email",
"TELEGRAM": "Telegram",
"LINE": "Line",
"API": "API Channel"
"API": "API Channel",
"INSTAGRAM": "Instagram"
}
}
}
@@ -83,6 +83,22 @@
"ACTION_PARAMETERS_REQUIRED": "Action parameters are required",
"ATLEAST_ONE_CONDITION_REQUIRED": "At least one condition is required",
"ATLEAST_ONE_ACTION_REQUIRED": "At least one action is required"
},
"ACTIONS": {
"ASSIGN_TEAM": "Assign a Team",
"ASSIGN_AGENT": "Assign an Agent",
"ADD_LABEL": "Add a Label",
"REMOVE_LABEL": "Remove a Label",
"REMOVE_ASSIGNED_TEAM": "Remove Assigned Team",
"SEND_EMAIL_TRANSCRIPT": "Send an Email Transcript",
"MUTE_CONVERSATION": "Mute Conversation",
"SNOOZE_CONVERSATION": "Snooze Conversation",
"RESOLVE_CONVERSATION": "Resolve Conversation",
"SEND_ATTACHMENT": "Send Attachment",
"SEND_MESSAGE": "Send a Message",
"CHANGE_PRIORITY": "Change Priority",
"ADD_PRIVATE_NOTE": "Add a Private Note",
"SEND_WEBHOOK_EVENT": "Send Webhook Event"
}
}
}
@@ -65,7 +65,7 @@ const resolvedMacro = computed(() => {
class="absolute -left-[0.21875rem] top-[0.2734375rem] w-2 h-2 rounded-full bg-n-solid-1 border-2 border-solid border-n-weak dark:border-slate-600"
/>
<p class="mb-1 text-xs text-n-slate-11">
{{ action.actionName }}
{{ $t(`MACROS.ACTIONS.${action.actionName}`) }}
</p>
<p class="text-n-slate-12 text-sm">{{ action.actionValue }}</p>
</div>
@@ -11,11 +11,15 @@ import semver from 'semver';
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import V4Button from 'dashboard/components-next/button/Button.vue';
import WootConfirmDeleteModal from 'dashboard/components/widgets/modal/ConfirmDeleteModal.vue';
import NextButton from 'dashboard/components-next/button/Button.vue';
export default {
components: {
BaseSettingsHeader,
V4Button,
WootConfirmDeleteModal,
NextButton,
},
setup() {
const { updateUISettings } = useUISettings();
@@ -35,6 +39,7 @@ export default {
features: {},
autoResolveDuration: null,
latestChatwootVersion: null,
showDeletePopup: false,
};
},
validations: {
@@ -55,6 +60,7 @@ export default {
getAccount: 'accounts/getAccount',
uiFlags: 'accounts/getUIFlags',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
}),
showAutoResolutionConfig() {
return this.isFeatureEnabledonAccount(
@@ -101,6 +107,34 @@ export default {
getAccountId() {
return this.id.toString();
},
confirmPlaceHolderText() {
return `${this.$t(
'GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.PLACE_HOLDER',
{
accountName: this.name,
}
)}`;
},
isMarkedForDeletion() {
const { custom_attributes = {} } = this.currentAccount;
return !!custom_attributes.marked_for_deletion_at;
},
markedForDeletionDate() {
const { custom_attributes = {} } = this.currentAccount;
if (!custom_attributes.marked_for_deletion_at) return null;
return new Date(custom_attributes.marked_for_deletion_at);
},
markedForDeletionReason() {
const { custom_attributes = {} } = this.currentAccount;
return custom_attributes.marked_for_deletion_reason || 'manual_deletion';
},
formattedDeletionDate() {
if (!this.markedForDeletionDate) return '';
return this.markedForDeletionDate.toLocaleString();
},
currentAccount() {
return this.getAccount(this.accountId) || {};
},
},
mounted() {
this.initializeAccount();
@@ -162,6 +196,56 @@ export default {
rtl_view: isRTLSupported,
});
},
// Delete Function
openDeletePopup() {
this.showDeletePopup = true;
},
closeDeletePopup() {
this.showDeletePopup = false;
},
async markAccountForDeletion() {
this.closeDeletePopup();
try {
// Use the enterprise API to toggle deletion with delete action
await this.$store.dispatch('accounts/toggleDeletion', {
action_type: 'delete',
});
// Refresh account data
await this.$store.dispatch('accounts/get');
useAlert(this.$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SUCCESS'));
} catch (error) {
// Handle error message
this.handleDeletionError(error);
}
},
handleDeletionError(error) {
const errorKey = error.response?.data?.error_key;
if (errorKey) {
useAlert(
this.$t(`GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.${errorKey}`)
);
return;
}
const message = error.response?.data?.message;
if (message) {
useAlert(message);
return;
}
useAlert(this.$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.FAILURE'));
},
async clearDeletionMark() {
try {
// Use the enterprise API to toggle deletion with undelete action
await this.$store.dispatch('accounts/toggleDeletion', {
action_type: 'undelete',
});
// Refresh account data
await this.$store.dispatch('accounts/get');
useAlert(this.$t('GENERAL_SETTINGS.UPDATE.SUCCESS'));
} catch (error) {
useAlert(this.$t('GENERAL_SETTINGS.UPDATE.ERROR'));
}
},
},
};
</script>
@@ -175,7 +259,7 @@ export default {
</V4Button>
</template>
</BaseSettingsHeader>
<div class="flex-grow flex-shrink min-w-0 overflow-auto mt-3">
<div class="flex-grow flex-shrink min-w-0 mt-3 overflow-auto">
<form v-if="!uiFlags.isFetchingItem" @submit.prevent="updateAccount">
<div
class="flex flex-row border-b border-slate-25 dark:border-slate-800"
@@ -279,6 +363,73 @@ export default {
<woot-code :script="getAccountId" />
</div>
</div>
<div v-if="!uiFlags.isFetchingItem && isOnChatwootCloud">
<div
class="flex flex-row pt-4 mt-2 border-t border-slate-25 dark:border-slate-800 text-black-900 dark:text-slate-300"
>
<div
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0"
>
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
{{ $t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.TITLE') }}
</h4>
<p>
{{ $t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.NOTE') }}
</p>
</div>
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
<div v-if="isMarkedForDeletion">
<div
class="p-4 flex-grow-0 flex-shrink-0 flex-[50%] bg-red-50 dark:bg-red-900 rounded"
>
<p class="mb-4">
{{
$t(
`GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.MESSAGE_${markedForDeletionReason === 'manual_deletion' ? 'MANUAL' : 'INACTIVITY'}`,
{
deletionDate: formattedDeletionDate,
}
)
}}
</p>
<NextButton
:label="
$t(
'GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.CLEAR_BUTTON'
)
"
color="ruby"
:is-loading="uiFlags.isUpdating"
@click="clearDeletionMark"
/>
</div>
</div>
<div v-if="!isMarkedForDeletion">
<NextButton
:label="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.BUTTON_TEXT')"
color="ruby"
@click="openDeletePopup()"
/>
</div>
</div>
</div>
<WootConfirmDeleteModal
v-if="showDeletePopup"
v-model:show="showDeletePopup"
:title="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.TITLE')"
:message="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.MESSAGE')"
:confirm-text="
$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.BUTTON_TEXT')
"
:reject-text="
$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.DISMISS')
"
:confirm-value="name"
:confirm-place-holder-text="confirmPlaceHolderText"
@on-confirm="markAccountForDeletion"
@on-close="closeDeletePopup"
/>
</div>
<div class="p-4 text-sm text-center">
<div>{{ `v${globalConfig.appVersion}` }}</div>
<div v-if="hasAnUpdateAvailable && globalConfig.displayManifest">
@@ -82,7 +82,6 @@ export default {
data() {
return {
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
automationRuleEvents: AUTOMATION_RULE_EVENTS,
automationMutated: false,
show: true,
showDeleteConfirmationModal: false,
@@ -96,6 +95,12 @@ export default {
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
automationRuleEvents() {
return AUTOMATION_RULE_EVENTS.map(event => ({
...event,
value: this.$t(`AUTOMATION.EVENTS.${event.value}`),
}));
},
hasAutomationMutated() {
if (
this.automation.conditions[0].values ||
@@ -105,10 +110,14 @@ export default {
return false;
},
automationActionTypes() {
const isSLAEnabled = this.isFeatureEnabled('sla');
return isSLAEnabled
const actionTypes = this.isFeatureEnabled('sla')
? AUTOMATION_ACTION_TYPES
: AUTOMATION_ACTION_TYPES.filter(action => action.key !== 'add_sla');
: AUTOMATION_ACTION_TYPES.filter(({ key }) => key !== 'add_sla');
return actionTypes.map(action => ({
...action,
label: this.$t(`AUTOMATION.ACTIONS.${action.label}`),
}));
},
},
mounted() {
@@ -137,6 +146,26 @@ export default {
this.$emit('saveAutomation', automation, this.mode);
}
},
getTranslatedAttributes(type, event) {
return getAttributes(type, event).map(attribute => {
// Skip translation
// 1. If customAttributeType key is present then its rendering attributes from API
// 2. If contact_custom_attribute or conversation_custom_attribute is present then its rendering section title
const skipTranslation =
attribute.customAttributeType ||
[
'contact_custom_attribute',
'conversation_custom_attribute',
].includes(attribute.key);
return {
...attribute,
name: skipTranslation
? attribute.name
: this.$t(`AUTOMATION.ATTRIBUTES.${attribute.name}`),
};
});
},
},
};
</script>
@@ -204,7 +233,7 @@ export default {
:key="i"
v-model="automation.conditions[i]"
:filter-attributes="
getAttributes(automationTypes, automation.event_name)
getTranslatedAttributes(automationTypes, automation.event_name)
"
:input-type="
getInputType(
@@ -70,7 +70,6 @@ export default {
data() {
return {
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
automationRuleEvents: AUTOMATION_RULE_EVENTS,
automationMutated: false,
show: true,
showDeleteConfirmationModal: false,
@@ -84,6 +83,12 @@ export default {
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
automationRuleEvents() {
return AUTOMATION_RULE_EVENTS.map(event => ({
...event,
value: this.$t(`AUTOMATION.EVENTS.${event.value}`),
}));
},
hasAutomationMutated() {
if (
this.automation.conditions[0].values ||
@@ -93,10 +98,14 @@ export default {
return false;
},
automationActionTypes() {
const isSLAEnabled = this.isFeatureEnabled('sla');
return isSLAEnabled
const actionTypes = this.isFeatureEnabled('sla')
? AUTOMATION_ACTION_TYPES
: AUTOMATION_ACTION_TYPES.filter(action => action.key !== 'add_sla');
: AUTOMATION_ACTION_TYPES.filter(({ key }) => key !== 'add_sla');
return actionTypes.map(action => ({
...action,
label: this.$t(`AUTOMATION.ACTIONS.${action.label}`),
}));
},
},
mounted() {
@@ -127,6 +136,26 @@ export default {
this.$emit('saveAutomation', automation, this.mode);
}
},
getTranslatedAttributes(type, event) {
return getAttributes(type, event).map(attribute => {
// Skip translation
// 1. If customAttributeType key is present then its rendering attributes from API
// 2. If contact_custom_attribute or conversation_custom_attribute is present then its rendering section title
const skipTranslation =
attribute.customAttributeType ||
[
'contact_custom_attribute',
'conversation_custom_attribute',
].includes(attribute.key);
return {
...attribute,
name: skipTranslation
? attribute.name
: this.$t(`AUTOMATION.ATTRIBUTES.${attribute.name}`),
};
});
},
},
};
</script>
@@ -187,7 +216,7 @@ export default {
:key="i"
v-model="automation.conditions[i]"
:filter-attributes="
getAttributes(automationTypes, automation.event_name)
getTranslatedAttributes(automationTypes, automation.event_name)
"
:input-type="
getInputType(
@@ -10,43 +10,37 @@ export const AUTOMATIONS = {
conditions: [
{
key: 'message_type',
name: 'Message Type',
attributeI18nKey: 'MESSAGE_TYPE',
name: 'MESSAGE_TYPE',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'content',
name: 'Message Content',
attributeI18nKey: 'MESSAGE_CONTAINS',
name: 'MESSAGE_CONTAINS',
inputType: 'comma_separated_plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'email',
name: 'Email',
attributeI18nKey: 'EMAIL',
name: 'EMAIL',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'inbox_id',
name: 'Inbox',
attributeI18nKey: 'INBOX',
name: 'INBOX',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'conversation_language',
name: 'Conversation Language',
attributeI18nKey: 'CONVERSATION_LANGUAGE',
name: 'CONVERSATION_LANGUAGE',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'phone_number',
name: 'Phone Number',
attributeI18nKey: 'PHONE_NUMBER',
name: 'PHONE_NUMBER',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_6,
},
@@ -54,64 +48,52 @@ export const AUTOMATIONS = {
actions: [
{
key: 'assign_agent',
name: 'Assign to agent',
attributeI18nKey: 'ASSIGN_AGENT',
name: 'ASSIGN_AGENT',
},
{
key: 'assign_team',
name: 'Assign a team',
attributeI18nKey: 'ASSIGN_TEAM',
name: 'ASSIGN_TEAM',
},
{
key: 'add_label',
name: 'Add a label',
attributeI18nKey: 'ADD_LABEL',
name: 'ADD_LABEL',
},
{
key: 'remove_label',
name: 'Remove a label',
attributeI18nKey: 'REMOVE_LABEL',
name: 'REMOVE_LABEL',
},
{
key: 'send_email_to_team',
name: 'Send an email to team',
attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
name: 'SEND_EMAIL_TO_TEAM',
},
{
key: 'send_message',
name: 'Send a message',
attributeI18nKey: 'SEND_MESSAGE',
name: 'SEND_MESSAGE',
},
{
key: 'send_email_transcript',
name: 'Send an email transcript',
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
name: 'SEND_EMAIL_TRANSCRIPT',
},
{
key: 'mute_conversation',
name: 'Mute conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
name: 'MUTE_CONVERSATION',
},
{
key: 'snooze_conversation',
name: 'Snooze conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
name: 'SNOOZE_CONVERSATION',
},
{
key: 'resolve_conversation',
name: 'Resolve conversation',
attributeI18nKey: 'RESOLVE_CONVERSATION',
name: 'RESOLVE_CONVERSATION',
},
{
key: 'send_webhook_event',
name: 'Send Webhook Event',
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
name: 'SEND_WEBHOOK_EVENT',
},
{
key: 'send_attachment',
name: 'Send Attachment',
attributeI18nKey: 'SEND_ATTACHMENT',
name: 'SEND_ATTACHMENT',
},
],
},
@@ -119,71 +101,61 @@ export const AUTOMATIONS = {
conditions: [
{
key: 'status',
name: 'Status',
attributeI18nKey: 'STATUS',
name: 'STATUS',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'browser_language',
name: 'Browser Language',
attributeI18nKey: 'BROWSER_LANGUAGE',
name: 'BROWSER_LANGUAGE',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'mail_subject',
name: 'Email Subject',
attributeI18nKey: 'MAIL_SUBJECT',
name: 'MAIL_SUBJECT',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'country_code',
name: 'Country',
attributeI18nKey: 'COUNTRY_NAME',
name: 'COUNTRY_NAME',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'phone_number',
name: 'Phone Number',
attributeI18nKey: 'PHONE_NUMBER',
name: 'PHONE_NUMBER',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_6,
},
{
key: 'referer',
name: 'Referrer Link',
attributeI18nKey: 'REFERER_LINK',
name: 'REFERER_LINK',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'email',
name: 'Email',
attributeI18nKey: 'EMAIL',
name: 'EMAIL',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'inbox_id',
name: 'Inbox',
attributeI18nKey: 'INBOX',
name: 'INBOX',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'conversation_language',
name: 'Conversation Language',
attributeI18nKey: 'CONVERSATION_LANGUAGE',
name: 'CONVERSATION_LANGUAGE',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'priority',
name: 'Priority',
attributeI18nKey: 'PRIORITY',
name: 'PRIORITY',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
@@ -191,58 +163,47 @@ export const AUTOMATIONS = {
actions: [
{
key: 'assign_agent',
name: 'Assign to agent',
attributeI18nKey: 'ASSIGN_AGENT',
name: 'ASSIGN_AGENT',
},
{
key: 'assign_team',
name: 'Assign a team',
attributeI18nKey: 'ASSIGN_TEAM',
name: 'ASSIGN_TEAM',
},
{
key: 'assign_agent',
name: 'Assign an agent',
attributeI18nKey: 'ASSIGN_AGENT',
name: 'ASSIGN_AGENT',
},
{
key: 'send_email_to_team',
name: 'Send an email to team',
attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
name: 'SEND_EMAIL_TO_TEAM',
},
{
key: 'send_message',
name: 'Send a message',
attributeI18nKey: 'SEND_MESSAGE',
name: 'SEND_MESSAGE',
},
{
key: 'send_email_transcript',
name: 'Send an email transcript',
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
name: 'SEND_EMAIL_TRANSCRIPT',
},
{
key: 'mute_conversation',
name: 'Mute conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
name: 'MUTE_CONVERSATION',
},
{
key: 'snooze_conversation',
name: 'Snooze conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
name: 'SNOOZE_CONVERSATION',
},
{
key: 'resolve_conversation',
name: 'Resolve conversation',
attributeI18nKey: 'RESOLVE_CONVERSATION',
name: 'RESOLVE_CONVERSATION',
},
{
key: 'send_webhook_event',
name: 'Send Webhook Event',
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
name: 'SEND_WEBHOOK_EVENT',
},
{
key: 'send_attachment',
name: 'Send Attachment',
attributeI18nKey: 'SEND_ATTACHMENT',
name: 'SEND_ATTACHMENT',
},
],
},
@@ -250,85 +211,73 @@ export const AUTOMATIONS = {
conditions: [
{
key: 'status',
name: 'Status',
attributeI18nKey: 'STATUS',
name: 'STATUS',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'browser_language',
name: 'Browser Language',
attributeI18nKey: 'BROWSER_LANGUAGE',
name: 'BROWSER_LANGUAGE',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'mail_subject',
name: 'Email Subject',
attributeI18nKey: 'MAIL_SUBJECT',
name: 'MAIL_SUBJECT',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'country_code',
name: 'Country',
attributeI18nKey: 'COUNTRY_NAME',
name: 'COUNTRY_NAME',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'referer',
name: 'Referrer Link',
attributeI18nKey: 'REFERER_LINK',
name: 'REFERER_LINK',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'phone_number',
name: 'Phone Number',
attributeI18nKey: 'PHONE_NUMBER',
name: 'PHONE_NUMBER',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_6,
},
{
key: 'assignee_id',
name: 'Assignee',
attributeI18nKey: 'ASSIGNEE_NAME',
name: 'ASSIGNEE_NAME',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_3,
},
{
key: 'team_id',
name: 'Team',
attributeI18nKey: 'TEAM_NAME',
name: 'TEAM_NAME',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_3,
},
{
key: 'email',
name: 'Email',
attributeI18nKey: 'EMAIL',
name: 'EMAIL',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'inbox_id',
name: 'Inbox',
attributeI18nKey: 'INBOX',
name: 'INBOX',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'conversation_language',
name: 'Conversation Language',
attributeI18nKey: 'CONVERSATION_LANGUAGE',
name: 'CONVERSATION_LANGUAGE',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'priority',
name: 'Priority',
attributeI18nKey: 'PRIORITY',
name: 'PRIORITY',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
@@ -336,58 +285,47 @@ export const AUTOMATIONS = {
actions: [
{
key: 'assign_agent',
name: 'Assign to agent',
attributeI18nKey: 'ASSIGN_AGENT',
name: 'ASSIGN_AGENT',
},
{
key: 'assign_team',
name: 'Assign a team',
attributeI18nKey: 'ASSIGN_TEAM',
name: 'ASSIGN_TEAM',
},
{
key: 'assign_agent',
name: 'Assign an agent',
attributeI18nKey: 'ASSIGN_AGENT',
name: 'ASSIGN_AGENT',
},
{
key: 'send_email_to_team',
name: 'Send an email to team',
attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
name: 'SEND_EMAIL_TO_TEAM',
},
{
key: 'send_message',
name: 'Send a message',
attributeI18nKey: 'SEND_MESSAGE',
name: 'SEND_MESSAGE',
},
{
key: 'send_email_transcript',
name: 'Send an email transcript',
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
name: 'SEND_EMAIL_TRANSCRIPT',
},
{
key: 'mute_conversation',
name: 'Mute conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
name: 'MUTE_CONVERSATION',
},
{
key: 'snooze_conversation',
name: 'Snooze conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
name: 'SNOOZE_CONVERSATION',
},
{
key: 'resolve_conversation',
name: 'Resolve conversation',
attributeI18nKey: 'RESOLVE_CONVERSATION',
name: 'RESOLVE_CONVERSATION',
},
{
key: 'send_webhook_event',
name: 'Send Webhook Event',
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
name: 'SEND_WEBHOOK_EVENT',
},
{
key: 'send_attachment',
name: 'Send Attachment',
attributeI18nKey: 'SEND_ATTACHMENT',
name: 'SEND_ATTACHMENT',
},
],
},
@@ -395,78 +333,67 @@ export const AUTOMATIONS = {
conditions: [
{
key: 'browser_language',
name: 'Browser Language',
attributeI18nKey: 'BROWSER_LANGUAGE',
name: 'BROWSER_LANGUAGE',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'email',
name: 'Email',
attributeI18nKey: 'EMAIL',
name: 'EMAIL',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'mail_subject',
name: 'Email Subject',
attributeI18nKey: 'MAIL_SUBJECT',
name: 'MAIL_SUBJECT',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'country_code',
name: 'Country',
attributeI18nKey: 'COUNTRY_NAME',
name: 'COUNTRY_NAME',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'referer',
name: 'Referrer Link',
attributeI18nKey: 'REFERER_LINK',
name: 'REFERER_LINK',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_2,
},
{
key: 'assignee_id',
name: 'Assignee',
attributeI18nKey: 'ASSIGNEE_NAME',
name: 'ASSIGNEE_NAME',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_3,
},
{
key: 'phone_number',
name: 'Phone Number',
attributeI18nKey: 'PHONE_NUMBER',
name: 'PHONE_NUMBER',
inputType: 'plain_text',
filterOperators: OPERATOR_TYPES_6,
},
{
key: 'team_id',
name: 'Team',
attributeI18nKey: 'TEAM_NAME',
name: 'TEAM_NAME',
inputType: 'search_select',
filterOperators: OPERATOR_TYPES_3,
},
{
key: 'inbox_id',
name: 'Inbox',
attributeI18nKey: 'INBOX',
name: 'INBOX',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'conversation_language',
name: 'Conversation Language',
attributeI18nKey: 'CONVERSATION_LANGUAGE',
name: 'CONVERSATION_LANGUAGE',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
{
key: 'priority',
name: 'Priority',
attributeI18nKey: 'PRIORITY',
name: 'PRIORITY',
inputType: 'multi_select',
filterOperators: OPERATOR_TYPES_1,
},
@@ -474,53 +401,43 @@ export const AUTOMATIONS = {
actions: [
{
key: 'assign_agent',
name: 'Assign to agent',
attributeI18nKey: 'ASSIGN_AGENT',
name: 'ASSIGN_AGENT',
},
{
key: 'assign_team',
name: 'Assign a team',
attributeI18nKey: 'ASSIGN_TEAM',
name: 'ASSIGN_TEAM',
},
{
key: 'assign_agent',
name: 'Assign an agent',
attributeI18nKey: 'ASSIGN_AGENT',
name: 'ASSIGN_AGENT',
},
{
key: 'send_email_to_team',
name: 'Send an email to team',
attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
name: 'SEND_EMAIL_TO_TEAM',
},
{
key: 'send_message',
name: 'Send a message',
attributeI18nKey: 'SEND_MESSAGE',
name: 'SEND_MESSAGE',
},
{
key: 'send_email_transcript',
name: 'Send an email transcript',
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
name: 'SEND_EMAIL_TRANSCRIPT',
},
{
key: 'mute_conversation',
name: 'Mute conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
name: 'MUTE_CONVERSATION',
},
{
key: 'snooze_conversation',
name: 'Snooze conversation',
attributeI18nKey: 'MUTE_CONVERSATION',
name: 'SNOOZE_CONVERSATION',
},
{
key: 'send_webhook_event',
name: 'Send Webhook Event',
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
name: 'SEND_WEBHOOK_EVENT',
},
{
key: 'send_attachment',
name: 'Send Attachment',
attributeI18nKey: 'SEND_ATTACHMENT',
name: 'SEND_ATTACHMENT',
},
],
},
@@ -529,91 +446,91 @@ export const AUTOMATIONS = {
export const AUTOMATION_RULE_EVENTS = [
{
key: 'conversation_created',
value: 'Conversation Created',
value: 'CONVERSATION_CREATED',
},
{
key: 'conversation_updated',
value: 'Conversation Updated',
value: 'CONVERSATION_UPDATED',
},
{
key: 'message_created',
value: 'Message Created',
value: 'MESSAGE_CREATED',
},
{
key: 'conversation_opened',
value: 'Conversation Opened',
value: 'CONVERSATION_OPENED',
},
];
export const AUTOMATION_ACTION_TYPES = [
{
key: 'assign_agent',
label: 'Assign to agent',
label: 'ASSIGN_AGENT',
inputType: 'search_select',
},
{
key: 'assign_team',
label: 'Assign a team',
label: 'ASSIGN_TEAM',
inputType: 'search_select',
},
{
key: 'add_label',
label: 'Add a label',
label: 'ADD_LABEL',
inputType: 'multi_select',
},
{
key: 'remove_label',
label: 'Remove a label',
label: 'REMOVE_LABEL',
inputType: 'multi_select',
},
{
key: 'send_email_to_team',
label: 'Send an email to team',
label: 'SEND_EMAIL_TO_TEAM',
inputType: 'team_message',
},
{
key: 'send_email_transcript',
label: 'Send an email transcript',
label: 'SEND_EMAIL_TRANSCRIPT',
inputType: 'email',
},
{
key: 'mute_conversation',
label: 'Mute conversation',
label: 'MUTE_CONVERSATION',
inputType: null,
},
{
key: 'snooze_conversation',
label: 'Snooze conversation',
label: 'SNOOZE_CONVERSATION',
inputType: null,
},
{
key: 'resolve_conversation',
label: 'Resolve conversation',
label: 'RESOLVE_CONVERSATION',
inputType: null,
},
{
key: 'send_webhook_event',
label: 'Send Webhook Event',
label: 'SEND_WEBHOOK_EVENT',
inputType: 'url',
},
{
key: 'send_attachment',
label: 'Send Attachment',
label: 'SEND_ATTACHMENT',
inputType: 'attachment',
},
{
key: 'send_message',
label: 'Send a message',
label: 'SEND_MESSAGE',
inputType: 'textarea',
},
{
key: 'change_priority',
label: 'Change Priority',
label: 'CHANGE_PRIORITY',
inputType: 'search_select',
},
{
key: 'add_sla',
label: 'Add SLA',
label: 'ADD_SLA',
inputType: 'search_select',
},
];
@@ -9,6 +9,7 @@ import Sms from './channels/Sms.vue';
import Whatsapp from './channels/Whatsapp.vue';
import Line from './channels/Line.vue';
import Telegram from './channels/Telegram.vue';
import Instagram from './channels/Instagram.vue';
const channelViewList = {
facebook: Facebook,
@@ -20,6 +21,7 @@ const channelViewList = {
whatsapp: Whatsapp,
line: Line,
telegram: Telegram,
instagram: Instagram,
};
export default defineComponent({
@@ -7,6 +7,7 @@ import SettingIntroBanner from 'dashboard/components/widgets/SettingIntroBanner.
import SettingsSection from '../../../../components/SettingsSection.vue';
import inboxMixin from 'shared/mixins/inboxMixin';
import FacebookReauthorize from './facebook/Reauthorize.vue';
import InstagramReauthorize from './channels/instagram/Reauthorize.vue';
import MicrosoftReauthorize from './channels/microsoft/Reauthorize.vue';
import GoogleReauthorize from './channels/google/Reauthorize.vue';
import PreChatFormSettings from './PreChatForm/Settings.vue';
@@ -36,6 +37,7 @@ export default {
MicrosoftReauthorize,
GoogleReauthorize,
NextButton,
InstagramReauthorize,
},
mixins: [inboxMixin],
setup() {
@@ -202,6 +204,9 @@ export default {
return true;
return false;
},
instagramUnauthorized() {
return this.isAInstagramChannel && this.inbox.reauthorization_required;
},
microsoftUnauthorized() {
return this.isAMicrosoftInbox && this.inbox.reauthorization_required;
},
@@ -383,10 +388,11 @@ export default {
/>
</woot-tabs>
</SettingIntroBanner>
<section class="max-w-6xl mx-auto w-full">
<section class="w-full max-w-6xl mx-auto">
<MicrosoftReauthorize v-if="microsoftUnauthorized" :inbox="inbox" />
<FacebookReauthorize v-if="facebookUnauthorized" :inbox="inbox" />
<GoogleReauthorize v-if="googleUnauthorized" :inbox="inbox" />
<InstagramReauthorize v-if="instagramUnauthorized" :inbox="inbox" />
<div v-if="selectedTabKey === 'inbox_settings'" class="mx-8">
<SettingsSection
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_UPDATE_TITLE')"
@@ -11,6 +11,7 @@ import ChannelApi from '../../../../../api/channels';
import PageHeader from '../../SettingsSubPageHeader.vue';
import router from '../../../../index';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import NextButton from 'dashboard/components-next/button/Button.vue';
import { loadScript } from 'dashboard/helper/DOMHelpers';
import * as Sentry from '@sentry/vue';
@@ -19,6 +20,7 @@ export default {
components: {
LoadingState,
PageHeader,
NextButton,
},
mixins: [globalConfigMixin],
setup() {
@@ -207,7 +209,7 @@ export default {
<template>
<div
class="border border-n-weak bg-n-solid-1 rounded-t-lg border-b-0 h-full w-full p-6 col-span-6 overflow-auto"
class="w-full h-full col-span-6 p-6 overflow-auto border border-b-0 rounded-t-lg border-n-weak bg-n-solid-1"
>
<div
v-if="!hasLoginStarted"
@@ -240,7 +242,7 @@ export default {
<LoadingState v-else-if="showLoader" :message="emptyStateMessage" />
<form
v-else
class="flex flex-wrap flex-col mx-0"
class="flex flex-col flex-wrap mx-0"
@submit.prevent="createChannel()"
>
<div class="w-full">
@@ -291,7 +293,7 @@ export default {
</label>
</div>
<div class="w-full text-right">
<input type="submit" value="Create Inbox" class="button" />
<NextButton :label="$t('INBOX_MGMT.ADD.FB.CREATE_INBOX')" />
</div>
</div>
</form>
@@ -0,0 +1,129 @@
<script>
import { useVuelidate } from '@vuelidate/core';
import { useAccount } from 'dashboard/composables/useAccount';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import instagramClient from 'dashboard/api/channel/instagramClient';
export default {
mixins: [globalConfigMixin],
setup() {
const { accountId } = useAccount();
return {
accountId,
v$: useVuelidate(),
};
},
data() {
return {
isCreating: false,
hasError: false,
errorStateMessage: '',
errorStateDescription: '',
isRequestingAuthorization: false,
};
},
mounted() {
const urlParams = new URLSearchParams(window.location.search);
// TODO: Handle error type
// const errorType = urlParams.get('error_type');
const errorCode = urlParams.get('code');
const errorMessage = urlParams.get('error_message');
if (errorMessage) {
this.hasError = true;
if (errorCode === '400') {
this.errorStateMessage = errorMessage;
this.errorStateDescription = this.$t(
'INBOX_MGMT.ADD.INSTAGRAM.ERROR_AUTH'
);
} else {
this.errorStateMessage = this.$t(
'INBOX_MGMT.ADD.INSTAGRAM.ERROR_MESSAGE'
);
this.errorStateDescription = errorMessage;
}
}
// User need to remove the error params from the url to avoid the error to be shown again after page reload, so that user can try again
const cleanURL = window.location.pathname;
window.history.replaceState({}, document.title, cleanURL);
},
methods: {
async requestAuthorization() {
this.isRequestingAuthorization = true;
const response = await instagramClient.generateAuthorization();
const {
data: { url },
} = response;
window.location.href = url;
},
},
};
</script>
<template>
<div
class="border border-slate-25 dark:border-slate-800/60 bg-white dark:bg-slate-900 h-full p-6 w-full max-w-full md:w-3/4 md:max-w-[75%] flex-shrink-0 flex-grow-0"
>
<div class="flex flex-col items-center justify-center h-full text-center">
<div v-if="hasError" class="max-w-lg mx-auto text-center">
<h5>{{ errorStateMessage }}</h5>
<p
v-if="errorStateDescription"
v-dompurify-html="errorStateDescription"
/>
</div>
<div
v-else
class="flex flex-col items-center justify-center h-full text-center"
>
<button
class="flex items-center justify-center px-8 py-3.5 text-white rounded-full bg-gradient-to-r from-[#833AB4] via-[#FD1D1D] to-[#FCAF45] hover:shadow-lg transition-all duration-300 min-w-[240px] overflow-hidden"
:disabled="isRequestingAuthorization"
@click="requestAuthorization()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-5 h-5 mr-2"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"
/>
</svg>
<span class="text-base font-medium">
{{ $t('INBOX_MGMT.ADD.INSTAGRAM.CONTINUE_WITH_INSTAGRAM') }}
</span>
<span v-if="isRequestingAuthorization" class="ml-2">
<svg
class="w-5 h-5 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</span>
</button>
<p class="py-6">
{{ $t('INBOX_MGMT.ADD.INSTAGRAM.HELP') }}
</p>
</div>
</div>
</div>
</template>
@@ -0,0 +1,37 @@
<script setup>
import { ref } from 'vue';
import InboxReconnectionRequired from '../../components/InboxReconnectionRequired.vue';
import instagramClient from 'dashboard/api/channel/instagramClient';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
const { t } = useI18n();
const isRequestingAuthorization = ref(false);
async function requestAuthorization() {
try {
isRequestingAuthorization.value = true;
const response = await instagramClient.generateAuthorization();
const {
data: { url },
} = response;
window.location.href = url;
} catch (error) {
useAlert(t('INBOX_MGMT.ADD.INSTAGRAM.ERROR_AUTH'));
} finally {
isRequestingAuthorization.value = false;
}
}
</script>
<template>
<InboxReconnectionRequired
class="mx-8 mt-5"
@reauthorize="requestAuthorization"
/>
</template>
@@ -28,6 +28,7 @@ const i18nMap = {
'Channel::Telegram': 'TELEGRAM',
'Channel::Line': 'LINE',
'Channel::Api': 'API',
'Channel::Instagram': 'INSTAGRAM',
};
const twilioChannelName = () => {
@@ -21,7 +21,13 @@ const { getMacroDropdownValues } = useMacros();
const macro = ref(null);
const mode = ref('CREATE');
const macroActionTypes = MACRO_ACTION_TYPES;
const macroActionTypes = computed(() => {
return MACRO_ACTION_TYPES.map(type => ({
...type,
label: t(`MACROS.ACTIONS.${type.label}`),
}));
});
provide('macroActionTypes', macroActionTypes);
@@ -38,7 +44,7 @@ const formatMacro = macroData => {
const formattedActions = macroData.actions.map(action => {
let actionParams = [];
if (action.action_params.length) {
const inputType = macroActionTypes.find(
const inputType = macroActionTypes.value.find(
item => item.key === action.action_name
).inputType;
if (inputType === 'multi_select' || inputType === 'search_select') {
@@ -42,7 +42,7 @@ const showActionInput = computed(() => {
actionData.value.action_name === 'send_message'
)
return false;
const type = macroActionTypes.find(
const type = macroActionTypes.value.find(
action => action.key === actionData.value.action_name
).inputType;
return !!type;
@@ -1,67 +1,72 @@
export const MACRO_ACTION_TYPES = [
{
key: 'assign_team',
label: 'Assign a team',
label: 'ASSIGN_TEAM',
inputType: 'search_select',
},
{
key: 'assign_agent',
label: 'Assign an agent',
label: 'ASSIGN_AGENT',
inputType: 'search_select',
},
{
key: 'add_label',
label: 'Add a label',
label: 'ADD_LABEL',
inputType: 'multi_select',
},
{
key: 'remove_label',
label: 'Remove a label',
label: 'REMOVE_LABEL',
inputType: 'multi_select',
},
{
key: 'remove_assigned_team',
label: 'Remove Assigned Team',
label: 'REMOVE_ASSIGNED_TEAM',
inputType: null,
},
{
key: 'send_email_transcript',
label: 'Send an email transcript',
label: 'SEND_EMAIL_TRANSCRIPT',
inputType: 'email',
},
{
key: 'mute_conversation',
label: 'Mute conversation',
label: 'MUTE_CONVERSATION',
inputType: null,
},
{
key: 'snooze_conversation',
label: 'Snooze conversation',
label: 'SNOOZE_CONVERSATION',
inputType: null,
},
{
key: 'resolve_conversation',
label: 'Resolve conversation',
label: 'RESOLVE_CONVERSATION',
inputType: null,
},
{
key: 'send_attachment',
label: 'Send Attachment',
label: 'SEND_ATTACHMENT',
inputType: 'attachment',
},
{
key: 'send_message',
label: 'Send a message',
label: 'SEND_MESSAGE',
inputType: 'textarea',
},
{
key: 'add_private_note',
label: 'Add a private note',
label: 'ADD_PRIVATE_NOTE',
inputType: 'textarea',
},
{
key: 'change_priority',
label: 'Change Priority',
label: 'CHANGE_PRIORITY',
inputType: 'search_select',
},
{
key: 'send_webhook_event',
label: 'SEND_WEBHOOK_EVENT',
inputType: 'url',
},
];
@@ -73,6 +73,29 @@ export const actions = {
throw new Error(error);
}
},
delete: async ({ commit }, { id }) => {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true });
try {
await AccountAPI.delete(id);
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false });
} catch (error) {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false });
throw new Error(error);
}
},
toggleDeletion: async (
{ commit },
{ action_type } = { action_type: 'delete' }
) => {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true });
try {
await EnterpriseAccountAPI.toggleDeletion(action_type);
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false });
} catch (error) {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false });
throw new Error(error);
}
},
create: async ({ commit }, accountInfo) => {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isCreating: true });
try {
@@ -80,4 +80,41 @@ describe('#actions', () => {
]);
});
});
describe('#toggleDeletion', () => {
it('sends correct actions with delete action if API is success', async () => {
axios.post.mockResolvedValue({});
await actions.toggleDeletion({ commit }, { action_type: 'delete' });
expect(commit.mock.calls).toEqual([
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true }],
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false }],
]);
expect(axios.post.mock.calls[0][1]).toEqual({
action_type: 'delete',
});
});
it('sends correct actions with undelete action if API is success', async () => {
axios.post.mockResolvedValue({});
await actions.toggleDeletion({ commit }, { action_type: 'undelete' });
expect(commit.mock.calls).toEqual([
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true }],
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false }],
]);
expect(axios.post.mock.calls[0][1]).toEqual({
action_type: 'undelete',
});
});
it('sends correct actions if API is error', async () => {
axios.post.mockRejectedValue({ message: 'Incorrect header' });
await expect(
actions.toggleDeletion({ commit }, { action_type: 'delete' })
).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true }],
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false }],
]);
});
});
});
+2
View File
@@ -3,4 +3,6 @@ export const REPLY_POLICY = {
'https://developers.facebook.com/docs/messenger-platform/policy/policy-overview/',
TWILIO_WHATSAPP:
'https://www.twilio.com/docs/whatsapp/tutorial/send-whatsapp-notification-messages-templates#sending-non-template-messages-within-a-24-hour-session',
WHATSAPP_CLOUD:
'https://business.whatsapp.com/policy#:~:text=You%20may%20reply%20to%20a,messages%20via%20approved%20Message%20Templates.',
};
@@ -57,6 +57,10 @@ export const ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP =
// https://developers.line.biz/en/reference/messaging-api/#image-message, https://developers.line.biz/en/reference/messaging-api/#video-message
export const ALLOWED_FILE_TYPES_FOR_LINE = 'image/png, image/jpeg,video/mp4';
// https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/messaging-api#requirements
export const ALLOWED_FILE_TYPES_FOR_INSTAGRAM =
'image/png, image/jpeg, video/mp4, video/mov, video/webm';
export const CSAT_RATINGS = [
{
key: 'disappointed',
@@ -121,6 +121,9 @@ export default {
this.isATwilioWhatsAppChannel
);
},
isAInstagramChannel() {
return this.channelType === INBOX_TYPES.INSTAGRAM;
},
},
methods: {
inboxHasFeature(feature) {
@@ -85,10 +85,10 @@ export default {
if (!password.$error) {
return '';
}
if (!password.minLength) {
if (password.minLength.$invalid) {
return this.$t('REGISTER.PASSWORD.ERROR');
}
if (!password.isValidPassword) {
if (password.isValidPassword.$invalid) {
return this.$t('REGISTER.PASSWORD.IS_INVALID_PASSWORD');
}
return '';
@@ -96,6 +96,9 @@ export default {
showGoogleOAuth() {
return Boolean(window.chatwootConfig.googleOAuthClientId);
},
isFormValid() {
return !this.v$.$invalid && this.hasAValidCaptcha;
},
},
methods: {
async submit() {
@@ -120,6 +123,7 @@ export default {
onRecaptchaVerified(token) {
this.credentials.hCaptchaClientResponse = token;
this.didCaptchaReset = false;
this.v$.$touch();
},
resetCaptcha() {
if (!this.globalConfig.hCaptchaSiteKey) {
@@ -198,7 +202,7 @@ export default {
</div>
<SubmitButton
:button-text="$t('REGISTER.SUBMIT')"
:disabled="isSignupInProgress || !hasAValidCaptcha"
:disabled="isSignupInProgress || !isFormValid"
:loading="isSignupInProgress"
icon-class="arrow-chevron-right"
/>