feat(channels): add Shangwutong connector
This commit is contained in:
@@ -4,6 +4,7 @@ import { isVoiceCallEnabled } from 'dashboard/helper/inbox';
|
||||
export function useChannelIcon(inbox) {
|
||||
const channelTypeIconMap = {
|
||||
'Channel::Api': 'i-woot-api',
|
||||
'Channel::Shangwutong': 'i-ri-customer-service-2-line',
|
||||
'Channel::Email': 'i-woot-mail',
|
||||
'Channel::FacebookPage': 'i-woot-messenger',
|
||||
'Channel::Line': 'i-woot-line',
|
||||
|
||||
@@ -5,6 +5,7 @@ export const INBOX_TYPES = {
|
||||
TWILIO: 'Channel::TwilioSms',
|
||||
WHATSAPP: 'Channel::Whatsapp',
|
||||
API: 'Channel::Api',
|
||||
SHANGWUTONG: 'Channel::Shangwutong',
|
||||
EMAIL: 'Channel::Email',
|
||||
TELEGRAM: 'Channel::Telegram',
|
||||
LINE: 'Channel::Line',
|
||||
@@ -45,6 +46,7 @@ const INBOX_ICON_MAP_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.SHANGWUTONG]: 'i-ri-customer-service-2-fill',
|
||||
[INBOX_TYPES.EMAIL]: 'i-ri-mail-fill',
|
||||
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-fill',
|
||||
[INBOX_TYPES.LINE]: 'i-ri-line-fill',
|
||||
@@ -60,6 +62,7 @@ const INBOX_ICON_MAP_LINE = {
|
||||
[INBOX_TYPES.TWITTER]: 'i-woot-x',
|
||||
[INBOX_TYPES.WHATSAPP]: 'i-woot-whatsapp',
|
||||
[INBOX_TYPES.API]: 'i-woot-api',
|
||||
[INBOX_TYPES.SHANGWUTONG]: 'i-ri-customer-service-2-line',
|
||||
[INBOX_TYPES.EMAIL]: 'i-woot-mail',
|
||||
[INBOX_TYPES.TELEGRAM]: 'i-woot-telegram',
|
||||
[INBOX_TYPES.LINE]: 'i-woot-line',
|
||||
@@ -105,6 +108,9 @@ export const getReadableInboxByType = (type, phoneNumber) => {
|
||||
case INBOX_TYPES.API:
|
||||
return 'api';
|
||||
|
||||
case INBOX_TYPES.SHANGWUTONG:
|
||||
return 'shangwutong';
|
||||
|
||||
case INBOX_TYPES.EMAIL:
|
||||
return 'email';
|
||||
|
||||
@@ -141,6 +147,9 @@ export const getInboxClassByType = (type, phoneNumber) => {
|
||||
case INBOX_TYPES.API:
|
||||
return 'cloud';
|
||||
|
||||
case INBOX_TYPES.SHANGWUTONG:
|
||||
return 'customer-service-2';
|
||||
|
||||
case INBOX_TYPES.EMAIL:
|
||||
return 'mail';
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ describe('#Inbox Helpers', () => {
|
||||
it('should return correct class for Api', () => {
|
||||
expect(getInboxClassByType('Channel::Api')).toEqual('cloud');
|
||||
});
|
||||
it('should return correct class for Shangwutong', () => {
|
||||
expect(getInboxClassByType(INBOX_TYPES.SHANGWUTONG)).toEqual(
|
||||
'customer-service-2'
|
||||
);
|
||||
});
|
||||
it('should return correct class for Email', () => {
|
||||
expect(getInboxClassByType('Channel::Email')).toEqual('mail');
|
||||
});
|
||||
@@ -69,6 +74,12 @@ describe('#Inbox Helpers', () => {
|
||||
expect(getInboxIconByType(INBOX_TYPES.API)).toBe('i-ri-cloudy-fill');
|
||||
});
|
||||
|
||||
it('returns correct icon for Shangwutong', () => {
|
||||
expect(getInboxIconByType(INBOX_TYPES.SHANGWUTONG)).toBe(
|
||||
'i-ri-customer-service-2-fill'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns correct icon for Email', () => {
|
||||
expect(getInboxIconByType(INBOX_TYPES.EMAIL)).toBe('i-ri-mail-fill');
|
||||
});
|
||||
|
||||
@@ -384,6 +384,21 @@
|
||||
"ERROR_MESSAGE": "We were not able to save the api channel"
|
||||
}
|
||||
},
|
||||
"SHANGWUTONG_CHANNEL": {
|
||||
"TITLE": "Shangwutong channel",
|
||||
"DESC": "Connect a Shangwutong site account to a shared GoChat inbox.",
|
||||
"CHANNEL_NAME": "Inbox name",
|
||||
"SESSION_ID": "Site session ID",
|
||||
"SESSION_ID_ERROR": "Enter the 11-letter or digit Shangwutong session ID.",
|
||||
"USERNAME": "Login username",
|
||||
"PASSWORD": "Login password",
|
||||
"DESIRED_PRESENCE": "Initial presence",
|
||||
"WEBHOOK_URL": "Connector webhook URL",
|
||||
"SUBMIT_BUTTON": "Create Shangwutong channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the Shangwutong channel"
|
||||
}
|
||||
},
|
||||
"EMAIL_CHANNEL": {
|
||||
"TITLE": "Email Channel",
|
||||
"DESC": "Integrate your email inbox.",
|
||||
@@ -479,6 +494,10 @@
|
||||
"TITLE": "API",
|
||||
"DESCRIPTION": "Make a custom channel using our API"
|
||||
},
|
||||
"SHANGWUTONG": {
|
||||
"TITLE": "Shangwutong",
|
||||
"DESCRIPTION": "Connect a Shangwutong site account"
|
||||
},
|
||||
"TELEGRAM": {
|
||||
"TITLE": "Telegram",
|
||||
"DESCRIPTION": "Configure Telegram channel using Bot token"
|
||||
@@ -1155,6 +1174,17 @@
|
||||
"DESCRIPTION": "Connect with Other Providers"
|
||||
}
|
||||
},
|
||||
"SHANGWUTONG_SETTINGS": {
|
||||
"RUNTIME_STATUS": "Connector status",
|
||||
"RUNTIME_STATUS_HELP": "The latest connection and credential state reported by the Connector.",
|
||||
"REFRESH": "Refresh status",
|
||||
"DESIRED_PRESENCE": "Desired presence",
|
||||
"DESIRED_PRESENCE_HELP": "Changes are applied online. Offline logs out only this Shangwutong account.",
|
||||
"PASSWORD": "Replace password",
|
||||
"PASSWORD_HELP": "Leave blank to keep the current password. A new password is verified online before replacing the working credential.",
|
||||
"WEBHOOK_URL": "Connector webhook URL",
|
||||
"WEBHOOK_URL_HELP": "All enabled Shangwutong inboxes must use the same Connector URL."
|
||||
},
|
||||
"CHANNELS": {
|
||||
"MESSENGER": "Messenger",
|
||||
"WEB_WIDGET": "Website",
|
||||
@@ -1166,6 +1196,7 @@
|
||||
"TELEGRAM": "Telegram",
|
||||
"LINE": "Line",
|
||||
"API": "API Channel",
|
||||
"SHANGWUTONG": "Shangwutong",
|
||||
"INSTAGRAM": "Instagram",
|
||||
"TIKTOK": "TikTok",
|
||||
"VOICE": "Voice"
|
||||
|
||||
@@ -384,6 +384,21 @@
|
||||
"ERROR_MESSAGE": "我们无法保存 api 频道"
|
||||
}
|
||||
},
|
||||
"SHANGWUTONG_CHANNEL": {
|
||||
"TITLE": "商务通渠道",
|
||||
"DESC": "将一个商务通站点账号接入共享的 GoChat 收件箱。",
|
||||
"CHANNEL_NAME": "收件箱名称",
|
||||
"SESSION_ID": "站点 Session ID",
|
||||
"SESSION_ID_ERROR": "请输入由 11 位字母或数字组成的商务通 Session ID。",
|
||||
"USERNAME": "登录账号",
|
||||
"PASSWORD": "登录密码",
|
||||
"DESIRED_PRESENCE": "初始在线状态",
|
||||
"WEBHOOK_URL": "Connector Webhook 地址",
|
||||
"SUBMIT_BUTTON": "创建商务通渠道",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "无法保存商务通渠道"
|
||||
}
|
||||
},
|
||||
"EMAIL_CHANNEL": {
|
||||
"TITLE": "电子邮件频道",
|
||||
"DESC": "集成您的电子邮件收件箱。",
|
||||
@@ -479,6 +494,10 @@
|
||||
"TITLE": "API",
|
||||
"DESCRIPTION": "使用我们的 API 创建一个自定义频道"
|
||||
},
|
||||
"SHANGWUTONG": {
|
||||
"TITLE": "商务通",
|
||||
"DESCRIPTION": "连接一个商务通站点账号"
|
||||
},
|
||||
"TELEGRAM": {
|
||||
"TITLE": "Telegram",
|
||||
"DESCRIPTION": "使用 Bot 令牌配置 Telegram 频道"
|
||||
@@ -1155,6 +1174,17 @@
|
||||
"DESCRIPTION": "与其他提供商关联"
|
||||
}
|
||||
},
|
||||
"SHANGWUTONG_SETTINGS": {
|
||||
"RUNTIME_STATUS": "Connector 运行状态",
|
||||
"RUNTIME_STATUS_HELP": "显示 Connector 最近回报的连接、在线与凭据状态。",
|
||||
"REFRESH": "刷新状态",
|
||||
"DESIRED_PRESENCE": "期望在线状态",
|
||||
"DESIRED_PRESENCE_HELP": "状态在线切换;选择 offline 只会退出当前商务通账号。",
|
||||
"PASSWORD": "修改密码",
|
||||
"PASSWORD_HELP": "留空表示保持原密码。新密码在线验证成功后才替换当前可用凭据。",
|
||||
"WEBHOOK_URL": "Connector Webhook 地址",
|
||||
"WEBHOOK_URL_HELP": "所有启用的商务通收件箱必须使用同一个 Connector 地址。"
|
||||
},
|
||||
"CHANNELS": {
|
||||
"MESSENGER": "Messenger",
|
||||
"WEB_WIDGET": "网站",
|
||||
@@ -1166,6 +1196,7 @@
|
||||
"TELEGRAM": "Telegram",
|
||||
"LINE": "Line",
|
||||
"API": "API 频道",
|
||||
"SHANGWUTONG": "商务通",
|
||||
"INSTAGRAM": "Instagram",
|
||||
"TIKTOK": "TikTok",
|
||||
"VOICE": "语音"
|
||||
|
||||
@@ -4,6 +4,7 @@ import Facebook from './channels/Facebook.vue';
|
||||
import Website from './channels/Website.vue';
|
||||
import Twitter from './channels/Twitter.vue';
|
||||
import Api from './channels/Api.vue';
|
||||
import Shangwutong from './channels/Shangwutong.vue';
|
||||
import Email from './channels/Email.vue';
|
||||
import Sms from './channels/Sms.vue';
|
||||
import Whatsapp from './channels/Whatsapp.vue';
|
||||
@@ -18,6 +19,7 @@ const channelViewList = {
|
||||
website: Website,
|
||||
twitter: Twitter,
|
||||
api: Api,
|
||||
shangwutong: Shangwutong,
|
||||
email: Email,
|
||||
sms: Sms,
|
||||
whatsapp: Whatsapp,
|
||||
|
||||
@@ -55,6 +55,14 @@ const channelList = computed(() => {
|
||||
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.API.DESCRIPTION'),
|
||||
icon: 'i-woot-api',
|
||||
},
|
||||
{
|
||||
key: 'shangwutong',
|
||||
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.SHANGWUTONG.TITLE'),
|
||||
description: t(
|
||||
'INBOX_MGMT.ADD.AUTH.CHANNEL.SHANGWUTONG.DESCRIPTION'
|
||||
),
|
||||
icon: 'i-ri-customer-service-2-line',
|
||||
},
|
||||
{
|
||||
key: 'telegram',
|
||||
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.TELEGRAM.TITLE'),
|
||||
|
||||
@@ -199,6 +199,7 @@ export default {
|
||||
this.isATwilioChannel ||
|
||||
this.isALineChannel ||
|
||||
this.isAPIInbox ||
|
||||
this.isShangwutongInbox ||
|
||||
(this.isAnEmailChannel && !this.inbox.provider) ||
|
||||
this.shouldShowWhatsAppConfiguration ||
|
||||
this.isAWebWidgetInbox
|
||||
@@ -292,6 +293,7 @@ export default {
|
||||
this.isAWhatsAppChannel ||
|
||||
this.isAFacebookInbox ||
|
||||
this.isAPIInbox ||
|
||||
this.isShangwutongInbox ||
|
||||
this.isAnInstagramChannel ||
|
||||
this.isALineChannel ||
|
||||
this.isATiktokChannel ||
|
||||
@@ -591,6 +593,9 @@ export default {
|
||||
this.isInboundEmailEnabled && this.continuityViaEmail,
|
||||
},
|
||||
};
|
||||
if (this.isShangwutongInbox) {
|
||||
payload.channel = {};
|
||||
}
|
||||
if (this.avatarFile) {
|
||||
payload.avatar = this.avatarFile;
|
||||
}
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required } from '@vuelidate/validators';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import router from '../../../../index';
|
||||
import PageHeader from '../../SettingsSubPageHeader.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
const validSessionId = value => /^[A-Za-z0-9]{11}$/.test(value || '');
|
||||
const validWebhookUrl = value => {
|
||||
try {
|
||||
const parsed = new URL(value);
|
||||
return (
|
||||
['http:', 'https:'].includes(parsed.protocol) &&
|
||||
!parsed.username &&
|
||||
!parsed.password &&
|
||||
!parsed.hash
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
components: { PageHeader, NextButton },
|
||||
setup() {
|
||||
return { v$: useVuelidate() };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
channelName: '',
|
||||
sessionId: '',
|
||||
username: '',
|
||||
password: '',
|
||||
desiredPresence: 'online',
|
||||
webhookUrl: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ uiFlags: 'inboxes/getUIFlags' }),
|
||||
},
|
||||
validations: {
|
||||
channelName: { required },
|
||||
sessionId: { required, validSessionId },
|
||||
username: { required },
|
||||
password: { required },
|
||||
webhookUrl: { required, validWebhookUrl },
|
||||
},
|
||||
methods: {
|
||||
async createChannel() {
|
||||
this.v$.$touch();
|
||||
if (this.v$.$invalid) return;
|
||||
|
||||
try {
|
||||
const inbox = await this.$store.dispatch(
|
||||
'inboxes/createChannel',
|
||||
{
|
||||
name: this.channelName.trim(),
|
||||
channel: {
|
||||
type: 'shangwutong',
|
||||
session_id: this.sessionId.trim(),
|
||||
username: this.username.trim(),
|
||||
password: this.password,
|
||||
desired_presence: this.desiredPresence,
|
||||
webhook_url: this.webhookUrl.trim(),
|
||||
},
|
||||
}
|
||||
);
|
||||
router.replace({
|
||||
name: 'settings_inboxes_add_agents',
|
||||
params: { page: 'new', inbox_id: inbox.id },
|
||||
});
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message ||
|
||||
this.$t(
|
||||
'INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.API.ERROR_MESSAGE'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full w-full p-6 col-span-6">
|
||||
<PageHeader
|
||||
:header-title="$t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.TITLE')"
|
||||
:header-content="$t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.DESC')"
|
||||
/>
|
||||
<form class="flex flex-col" @submit.prevent="createChannel">
|
||||
<label :class="{ error: v$.channelName.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.CHANNEL_NAME') }}
|
||||
<input
|
||||
v-model="channelName"
|
||||
type="text"
|
||||
@blur="v$.channelName.$touch"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label :class="{ error: v$.sessionId.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.SESSION_ID') }}
|
||||
<input
|
||||
v-model="sessionId"
|
||||
type="text"
|
||||
maxlength="11"
|
||||
autocomplete="off"
|
||||
@blur="v$.sessionId.$touch"
|
||||
/>
|
||||
<span v-if="v$.sessionId.$error" class="message">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.SESSION_ID_ERROR'
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label :class="{ error: v$.username.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.USERNAME') }}
|
||||
<input
|
||||
v-model="username"
|
||||
type="text"
|
||||
maxlength="255"
|
||||
autocomplete="username"
|
||||
@blur="v$.username.$touch"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label :class="{ error: v$.password.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.PASSWORD') }}
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
maxlength="4096"
|
||||
autocomplete="new-password"
|
||||
@blur="v$.password.$touch"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.DESIRED_PRESENCE') }}
|
||||
<select v-model="desiredPresence">
|
||||
<option value="online">online</option>
|
||||
<option value="busy">busy</option>
|
||||
<option value="away">away</option>
|
||||
<option value="offline">offline</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label :class="{ error: v$.webhookUrl.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.WEBHOOK_URL') }}
|
||||
<input
|
||||
v-model="webhookUrl"
|
||||
type="url"
|
||||
placeholder="http://shangwutong-connector:9100/webhooks/gochat/v1"
|
||||
@blur="v$.webhookUrl.$touch"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div class="w-full mt-4">
|
||||
<NextButton
|
||||
type="submit"
|
||||
solid
|
||||
blue
|
||||
:is-loading="uiFlags.isCreating"
|
||||
:label="
|
||||
$t('INBOX_MGMT.ADD.SHANGWUTONG_CHANNEL.SUBMIT_BUTTON')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
<script>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import InboxHealthAPI from 'dashboard/api/inboxHealth';
|
||||
import SettingsFieldSection from 'dashboard/components-next/Settings/SettingsFieldSection.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
components: { SettingsFieldSection, NextButton },
|
||||
props: {
|
||||
inbox: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
desiredPresence: 'online',
|
||||
password: '',
|
||||
webhookUrl: '',
|
||||
health: null,
|
||||
isSaving: false,
|
||||
isLoadingHealth: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
inbox: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.desiredPresence = value.desired_presence || 'online';
|
||||
this.webhookUrl = value.webhook_url || '';
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchHealth();
|
||||
},
|
||||
methods: {
|
||||
async fetchHealth() {
|
||||
this.isLoadingHealth = true;
|
||||
try {
|
||||
const response = await InboxHealthAPI.getHealthStatus(
|
||||
this.inbox.id
|
||||
);
|
||||
this.health = response.data;
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message ||
|
||||
this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE')
|
||||
);
|
||||
} finally {
|
||||
this.isLoadingHealth = false;
|
||||
}
|
||||
},
|
||||
async save() {
|
||||
this.isSaving = true;
|
||||
const channel = {
|
||||
desired_presence: this.desiredPresence,
|
||||
webhook_url: this.webhookUrl.trim(),
|
||||
};
|
||||
if (this.password) channel.password = this.password;
|
||||
try {
|
||||
await this.$store.dispatch('inboxes/updateInbox', {
|
||||
id: this.inbox.id,
|
||||
formData: false,
|
||||
channel,
|
||||
});
|
||||
this.password = '';
|
||||
useAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
|
||||
await this.fetchHealth();
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message ||
|
||||
this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE')
|
||||
);
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.RUNTIME_STATUS')"
|
||||
:help-text="
|
||||
$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.RUNTIME_STATUS_HELP')
|
||||
"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
connection_status
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
health?.connection_status ||
|
||||
inbox.connection_status ||
|
||||
'pending'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
actual_presence
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
health?.actual_presence ||
|
||||
inbox.actual_presence ||
|
||||
'offline'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
credential_status
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
health?.credential_status ||
|
||||
inbox.credential_status ||
|
||||
'pending'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
last_heartbeat_at
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
health?.last_heartbeat_at ||
|
||||
inbox.last_heartbeat_at ||
|
||||
'—'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="health?.last_error_code"
|
||||
class="mt-3 text-sm text-n-ruby-11"
|
||||
>
|
||||
{{ health.last_error_code }}
|
||||
</p>
|
||||
<NextButton
|
||||
class="mt-3"
|
||||
:is-loading="isLoadingHealth"
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.REFRESH')"
|
||||
@click="fetchHealth"
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.DESIRED_PRESENCE')"
|
||||
:help-text="
|
||||
$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.DESIRED_PRESENCE_HELP')
|
||||
"
|
||||
>
|
||||
<select v-model="desiredPresence" class="w-full">
|
||||
<option value="online">online</option>
|
||||
<option value="busy">busy</option>
|
||||
<option value="away">away</option>
|
||||
<option value="offline">offline</option>
|
||||
</select>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PASSWORD')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PASSWORD_HELP')"
|
||||
>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
maxlength="4096"
|
||||
autocomplete="new-password"
|
||||
class="w-full"
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.WEBHOOK_URL')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.WEBHOOK_URL_HELP')"
|
||||
>
|
||||
<input v-model="webhookUrl" type="url" class="w-full" />
|
||||
</SettingsFieldSection>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<NextButton
|
||||
:is-loading="isSaving"
|
||||
:label="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
|
||||
@click="save"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
+1
@@ -32,6 +32,7 @@ const i18nMap = {
|
||||
'Channel::Telegram': 'TELEGRAM',
|
||||
'Channel::Line': 'LINE',
|
||||
'Channel::Api': 'API',
|
||||
'Channel::Shangwutong': 'SHANGWUTONG',
|
||||
'Channel::Instagram': 'INSTAGRAM',
|
||||
'Channel::Tiktok': 'TIKTOK',
|
||||
};
|
||||
|
||||
+3
@@ -11,6 +11,7 @@ import { required } from '@vuelidate/validators';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import TextArea from 'next/textarea/TextArea.vue';
|
||||
import WhatsappReauthorize from '../channels/whatsapp/Reauthorize.vue';
|
||||
import ShangwutongConfiguration from '../channels/ShangwutongConfiguration.vue';
|
||||
import { sanitizeAllowedDomains } from 'dashboard/helper/URLHelper';
|
||||
|
||||
export default {
|
||||
@@ -23,6 +24,7 @@ export default {
|
||||
NextButton,
|
||||
TextArea,
|
||||
WhatsappReauthorize,
|
||||
ShangwutongConfiguration,
|
||||
},
|
||||
mixins: [inboxMixin],
|
||||
props: {
|
||||
@@ -189,6 +191,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ShangwutongConfiguration v-if="isShangwutongInbox" :inbox="inbox" />
|
||||
<div v-if="isATwilioChannel">
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.TITLE')"
|
||||
|
||||
@@ -310,11 +310,9 @@ const actions = {
|
||||
: await MessageApi.create(pendingMessage);
|
||||
commit(types.ADD_MESSAGE, {
|
||||
...response.data,
|
||||
status: MESSAGE_STATUS.SENT,
|
||||
});
|
||||
commit(types.ADD_CONVERSATION_ATTACHMENTS, {
|
||||
...response.data,
|
||||
status: MESSAGE_STATUS.SENT,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage = error.response
|
||||
|
||||
@@ -290,6 +290,39 @@ describe('#actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#sendMessageWithData', () => {
|
||||
it('keeps the status returned by the server', async () => {
|
||||
const localCommit = vi.fn();
|
||||
const pendingMessage = {
|
||||
id: 'echo-1',
|
||||
conversation_id: 7,
|
||||
message: 'hello',
|
||||
status: 'progress',
|
||||
content_attributes: {},
|
||||
};
|
||||
const responseMessage = {
|
||||
id: 42,
|
||||
conversation_id: 7,
|
||||
content: 'hello',
|
||||
status: 'progress',
|
||||
};
|
||||
axios.mockResolvedValue({ data: responseMessage });
|
||||
|
||||
await actions.sendMessageWithData({ commit: localCommit }, pendingMessage);
|
||||
|
||||
expect(localCommit).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
types.ADD_MESSAGE,
|
||||
responseMessage
|
||||
);
|
||||
expect(localCommit).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
types.ADD_CONVERSATION_ATTACHMENTS,
|
||||
responseMessage
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#markMessagesRead', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
@@ -16,6 +16,7 @@ export const INBOX_FEATURE_MAP = {
|
||||
INBOX_TYPES.TELEGRAM,
|
||||
INBOX_TYPES.TIKTOK,
|
||||
INBOX_TYPES.API,
|
||||
INBOX_TYPES.SHANGWUTONG,
|
||||
],
|
||||
[INBOX_FEATURES.REPLY_TO_OUTGOING]: [
|
||||
INBOX_TYPES.WEB,
|
||||
@@ -24,6 +25,7 @@ export const INBOX_FEATURE_MAP = {
|
||||
INBOX_TYPES.TELEGRAM,
|
||||
INBOX_TYPES.TIKTOK,
|
||||
INBOX_TYPES.API,
|
||||
INBOX_TYPES.SHANGWUTONG,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -44,6 +46,9 @@ export default {
|
||||
isAPIInbox() {
|
||||
return this.channelType === INBOX_TYPES.API;
|
||||
},
|
||||
isShangwutongInbox() {
|
||||
return this.channelType === INBOX_TYPES.SHANGWUTONG;
|
||||
},
|
||||
isATwitterInbox() {
|
||||
return this.channelType === INBOX_TYPES.TWITTER;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user