feat: Update Inbox/Team creation UI (#12305)

This commit is contained in:
Sivin Varghese
2025-09-02 14:11:38 +05:30
committed by GitHub
parent 8606aa1310
commit b0112a2869
34 changed files with 600 additions and 465 deletions

View File

@@ -2,23 +2,23 @@ import { computed } from 'vue';
export function useChannelIcon(inbox) {
const channelTypeIconMap = {
'Channel::Api': 'i-ri-cloudy-fill',
'Channel::Email': 'i-ri-mail-fill',
'Channel::FacebookPage': 'i-ri-messenger-fill',
'Channel::Line': 'i-ri-line-fill',
'Channel::Sms': 'i-ri-chat-1-fill',
'Channel::Telegram': 'i-ri-telegram-fill',
'Channel::TwilioSms': 'i-ri-chat-1-fill',
'Channel::Api': 'i-woot-api',
'Channel::Email': 'i-woot-mail',
'Channel::FacebookPage': 'i-woot-messenger',
'Channel::Line': 'i-woot-line',
'Channel::Sms': 'i-woot-sms',
'Channel::Telegram': 'i-woot-telegram',
'Channel::TwilioSms': 'i-woot-sms',
'Channel::TwitterProfile': 'i-ri-twitter-x-fill',
'Channel::WebWidget': 'i-ri-global-fill',
'Channel::Whatsapp': 'i-ri-whatsapp-fill',
'Channel::Instagram': 'i-ri-instagram-fill',
'Channel::WebWidget': 'i-woot-website',
'Channel::Whatsapp': 'i-woot-whatsapp',
'Channel::Instagram': 'i-woot-instagram',
'Channel::Voice': 'i-ri-phone-fill',
};
const providerIconMap = {
microsoft: 'i-ri-microsoft-fill',
google: 'i-ri-google-fill',
microsoft: 'i-woot-outlook',
google: 'i-woot-gmail',
};
const channelIcon = computed(() => {
@@ -34,7 +34,7 @@ export function useChannelIcon(inbox) {
// Special case for Twilio whatsapp
if (type === 'Channel::TwilioSms' && inboxDetails.medium === 'whatsapp') {
icon = 'i-ri-whatsapp-fill';
icon = 'i-woot-whatsapp';
}
return icon ?? 'i-ri-global-fill';

View File

@@ -4,19 +4,19 @@ describe('useChannelIcon', () => {
it('returns correct icon for API channel', () => {
const inbox = { channel_type: 'Channel::Api' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-cloudy-fill');
expect(icon).toBe('i-woot-api');
});
it('returns correct icon for Facebook channel', () => {
const inbox = { channel_type: 'Channel::FacebookPage' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-messenger-fill');
expect(icon).toBe('i-woot-messenger');
});
it('returns correct icon for WhatsApp channel', () => {
const inbox = { channel_type: 'Channel::Whatsapp' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-whatsapp-fill');
expect(icon).toBe('i-woot-whatsapp');
});
it('returns correct icon for Voice channel', () => {
@@ -28,19 +28,19 @@ describe('useChannelIcon', () => {
it('returns correct icon for Line channel', () => {
const inbox = { channel_type: 'Channel::Line' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-line-fill');
expect(icon).toBe('i-woot-line');
});
it('returns correct icon for SMS channel', () => {
const inbox = { channel_type: 'Channel::Sms' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-chat-1-fill');
expect(icon).toBe('i-woot-sms');
});
it('returns correct icon for Telegram channel', () => {
const inbox = { channel_type: 'Channel::Telegram' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-telegram-fill');
expect(icon).toBe('i-woot-telegram');
});
it('returns correct icon for Twitter channel', () => {
@@ -52,20 +52,20 @@ describe('useChannelIcon', () => {
it('returns correct icon for WebWidget channel', () => {
const inbox = { channel_type: 'Channel::WebWidget' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-global-fill');
expect(icon).toBe('i-woot-website');
});
it('returns correct icon for Instagram channel', () => {
const inbox = { channel_type: 'Channel::Instagram' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-instagram-fill');
expect(icon).toBe('i-woot-instagram');
});
describe('TwilioSms channel', () => {
it('returns chat icon for regular Twilio SMS channel', () => {
const inbox = { channel_type: 'Channel::TwilioSms' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-chat-1-fill');
expect(icon).toBe('i-woot-sms');
});
it('returns WhatsApp icon for Twilio SMS with WhatsApp medium', () => {
@@ -74,7 +74,7 @@ describe('useChannelIcon', () => {
medium: 'whatsapp',
};
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-whatsapp-fill');
expect(icon).toBe('i-woot-whatsapp');
});
it('returns chat icon for Twilio SMS with non-WhatsApp medium', () => {
@@ -83,7 +83,7 @@ describe('useChannelIcon', () => {
medium: 'sms',
};
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-chat-1-fill');
expect(icon).toBe('i-woot-sms');
});
it('returns chat icon for Twilio SMS with undefined medium', () => {
@@ -92,7 +92,7 @@ describe('useChannelIcon', () => {
medium: undefined,
};
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-chat-1-fill');
expect(icon).toBe('i-woot-sms');
});
});
@@ -100,7 +100,7 @@ describe('useChannelIcon', () => {
it('returns mail icon for generic email channel', () => {
const inbox = { channel_type: 'Channel::Email' };
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-mail-fill');
expect(icon).toBe('i-woot-mail');
});
it('returns Microsoft icon for Microsoft email provider', () => {
@@ -109,7 +109,7 @@ describe('useChannelIcon', () => {
provider: 'microsoft',
};
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-microsoft-fill');
expect(icon).toBe('i-woot-outlook');
});
it('returns Google icon for Google email provider', () => {
@@ -118,7 +118,7 @@ describe('useChannelIcon', () => {
provider: 'google',
};
const { value: icon } = useChannelIcon(inbox);
expect(icon).toBe('i-ri-google-fill');
expect(icon).toBe('i-woot-gmail');
});
});

View File

@@ -25,7 +25,7 @@ const reauthorizationRequired = computed(() => {
<template>
<span
class="size-4 grid place-content-center rounded-full bg-n-alpha-2"
class="size-5 grid place-content-center rounded-full bg-n-alpha-2"
:class="{ 'bg-n-solid-blue': active }"
>
<ChannelIcon :inbox="inbox" class="size-3" />

View File

@@ -1,50 +1,57 @@
<script>
export default {
props: {
title: {
type: String,
required: true,
},
src: {
type: String,
required: true,
},
isComingSoon: {
type: Boolean,
default: false,
},
<script setup>
import Icon from 'next/icon/Icon.vue';
defineProps({
title: {
type: String,
required: true,
},
};
description: {
type: String,
default: '',
},
icon: {
type: String,
required: true,
},
isComingSoon: {
type: Boolean,
default: false,
},
});
</script>
<template>
<button
class="relative bg-n-background cursor-pointer flex flex-col justify-end transition-all duration-200 ease-in -m-px py-4 px-0 items-center border border-solid border-n-weak hover:border-n-brand hover:shadow-md hover:z-50 disabled:opacity-60"
class="relative bg-n-solid-1 gap-6 cursor-pointer rounded-2xl flex flex-col justify-start transition-all duration-200 ease-in -m-px py-6 px-5 items-start border border-solid border-n-weak"
:class="{
'hover:enabled:border-n-blue-9 hover:enabled:shadow-md disabled:opacity-60 disabled:cursor-not-allowed':
!isComingSoon,
'cursor-not-allowed disabled:opacity-80': isComingSoon,
}"
>
<img :src="src" :alt="title" draggable="false" class="w-1/2 my-4 mx-auto" />
<h3 class="text-n-slate-12 text-base text-center capitalize">
{{ title }}
</h3>
<div
class="flex size-10 items-center justify-center rounded-full bg-n-alpha-2"
>
<Icon :icon="icon" class="text-n-slate-10 size-6" />
</div>
<div class="flex flex-col items-start gap-1.5">
<h3 class="text-n-slate-12 text-sm text-start font-medium capitalize">
{{ title }}
</h3>
<p class="text-n-slate-11 text-start text-sm">
{{ description }}
</p>
</div>
<div
v-if="isComingSoon"
class="absolute inset-0 flex items-center justify-center backdrop-blur-[2px] rounded-md bg-gradient-to-br from-n-background/90 via-n-background/70 to-n-background/95"
class="absolute inset-0 flex items-center justify-center backdrop-blur-[2px] rounded-2xl bg-gradient-to-br from-n-background/90 via-n-background/70 to-n-background/95 cursor-not-allowed"
>
<span class="text-n-slate-12 font-medium text-base">
<span class="text-n-slate-12 font-medium text-sm">
{{ $t('CHANNEL_SELECTOR.COMING_SOON') }} 🚀
</span>
</div>
</button>
</template>
<style scoped lang="scss">
.inactive {
img {
filter: grayscale(100%);
}
&:hover {
@apply border-n-strong shadow-none cursor-not-allowed;
}
}
</style>

View File

@@ -1,97 +1,75 @@
<script>
export default {
props: {
items: {
type: Array,
default: () => [],
},
},
computed: {
classObject() {
return 'w-full';
},
activeIndex() {
return this.items.findIndex(i => i.route === this.$route.name);
},
},
methods: {
isActive(item) {
return this.items.indexOf(item) === this.activeIndex;
},
isOver(item) {
return this.items.indexOf(item) < this.activeIndex;
},
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const props = defineProps({
items: {
type: Array,
default: () => [],
},
});
const route = useRoute();
const activeIndex = computed(() => {
return props.items.findIndex(i => i.route === route.name);
});
const isActive = item => {
return props.items.indexOf(item) === activeIndex.value;
};
const isOver = item => {
return props.items.indexOf(item) < activeIndex.value;
};
</script>
<template>
<transition-group
name="wizard-items"
tag="div"
class="wizard-box"
:class="classObject"
>
<transition-group tag="div">
<div
v-for="item in items"
v-for="(item, index) in items"
:key="item.route"
class="item"
:class="{ active: isActive(item), over: isOver(item) }"
class="cursor-pointer flex items-start gap-6 relative after:content-[''] after:absolute after:w-0.5 after:h-full after:top-5 ltr:after:left-4 rtl:after:right-4 before:content-[''] before:absolute before:w-0.5 before:h-4 before:top-0 before:left-4 rtl:before:right-4 last:after:hidden last:before:hidden"
:class="
isOver(item)
? 'after:bg-n-blue-9 before:bg-n-blue-9'
: 'after:bg-n-weak before:bg-n-weak'
"
>
<div class="flex items-center">
<h3
class="text-n-slate-12 text-base font-medium pl-6 overflow-hidden whitespace-nowrap mt-0.5 text-ellipsis leading-tight"
<div
class="rounded-2xl flex-shrink-0 size-8 flex items-center justify-center left-2 outline outline-2 leading-4 z-10 top-5 bg-n-background"
:class="
isActive(item) || isOver(item) ? 'outline-n-blue-9' : 'outline-n-weak'
"
>
<span
class="text-xs font-bold"
:class="
isActive(item) || isOver(item)
? 'text-n-blue-11'
: 'text-n-slate-11'
"
>
{{ item.title }}
</h3>
<span v-if="isOver(item)" class="mx-1 mt-0.5 text-n-teal-9">
<fluent-icon icon="checkmark" />
{{ index + 1 }}
</span>
</div>
<span class="step">
{{ items.indexOf(item) + 1 }}
</span>
<p class="pl-6 m-0 mt-1.5 text-sm text-n-slate-11">
{{ item.body }}
</p>
<div class="flex flex-col items-start gap-1.5 pb-10 pt-1">
<div class="flex items-center">
<h3
class="text-sm font-medium overflow-hidden whitespace-nowrap mt-0.5 text-ellipsis leading-tight"
:class="
isActive(item) || isOver(item)
? 'text-n-blue-11'
: 'text-n-slate-12'
"
>
{{ item.title }}
</h3>
</div>
<p class="m-0 mt-1.5 text-sm text-n-slate-11">
{{ item.body }}
</p>
</div>
</div>
</transition-group>
</template>
<style lang="scss" scoped>
.wizard-box {
.item {
@apply cursor-pointer after:bg-n-slate-6 before:bg-n-slate-6 py-4 ltr:pr-4 rtl:pl-4 ltr:pl-6 rtl:pr-6 relative before:h-4 before:top-0 last:before:h-0 first:before:h-0 last:after:h-0 before:content-[''] before:absolute before:w-0.5 after:content-[''] after:h-full after:absolute after:top-5 after:w-0.5 rtl:after:left-6 rtl:before:left-6;
&.active {
h3 {
@apply text-n-blue-text dark:text-n-blue-text;
}
.step {
@apply bg-n-brand dark:bg-n-brand;
}
}
&.over {
&::after {
@apply bg-n-brand dark:bg-n-brand;
}
.step {
@apply bg-n-brand dark:bg-n-brand;
}
& + .item {
&::before {
@apply bg-n-brand dark:bg-n-brand;
}
}
}
.step {
@apply bg-n-slate-7 rounded-2xl font-medium w-4 left-4 leading-4 z-10 absolute text-center text-white dark:text-white text-xxs top-5;
}
}
}
</style>

View File

@@ -1,91 +1,87 @@
<script>
<script setup>
import { computed } from 'vue';
import ChannelSelector from '../ChannelSelector.vue';
export default {
components: { ChannelSelector },
props: {
channel: {
type: Object,
required: true,
},
enabledFeatures: {
type: Object,
required: true,
},
},
emits: ['channelItemClick'],
computed: {
hasFbConfigured() {
return window.chatwootConfig?.fbAppId;
},
hasInstagramConfigured() {
return window.chatwootConfig?.instagramAppId;
},
isActive() {
const { key } = this.channel;
if (Object.keys(this.enabledFeatures).length === 0) {
return false;
}
if (key === 'website') {
return this.enabledFeatures.channel_website;
}
if (key === 'facebook') {
return this.enabledFeatures.channel_facebook && this.hasFbConfigured;
}
if (key === 'email') {
return this.enabledFeatures.channel_email;
}
if (key === 'instagram') {
return (
this.enabledFeatures.channel_instagram && this.hasInstagramConfigured
);
}
if (key === 'voice') {
return this.enabledFeatures.channel_voice;
}
return [
'website',
'twilio',
'api',
'whatsapp',
'sms',
'telegram',
'line',
'instagram',
'voice',
].includes(key);
},
isComingSoon() {
const { key } = this.channel;
// Show "Coming Soon" only if the channel is marked as coming soon
// and the corresponding feature flag is not enabled yet.
return ['voice'].includes(key) && !this.isActive;
},
const props = defineProps({
channel: {
type: Object,
required: true,
},
methods: {
getChannelThumbnail() {
if (this.channel.key === 'api' && this.channel.thumbnail) {
return this.channel.thumbnail;
}
return `/assets/images/dashboard/channels/${this.channel.key}.png`;
},
onItemClick() {
if (this.isActive) {
this.$emit('channelItemClick', this.channel.key);
}
},
enabledFeatures: {
type: Object,
required: true,
},
});
const emit = defineEmits(['channelItemClick']);
const hasFbConfigured = computed(() => {
return window.chatwootConfig?.fbAppId;
});
const hasInstagramConfigured = computed(() => {
return window.chatwootConfig?.instagramAppId;
});
const isActive = computed(() => {
const { key } = props.channel;
if (Object.keys(props.enabledFeatures).length === 0) {
return false;
}
if (key === 'website') {
return props.enabledFeatures.channel_website;
}
if (key === 'facebook') {
return props.enabledFeatures.channel_facebook && hasFbConfigured.value;
}
if (key === 'email') {
return props.enabledFeatures.channel_email;
}
if (key === 'instagram') {
return (
props.enabledFeatures.channel_instagram && hasInstagramConfigured.value
);
}
if (key === 'voice') {
return props.enabledFeatures.channel_voice;
}
return [
'website',
'twilio',
'api',
'whatsapp',
'sms',
'telegram',
'line',
'instagram',
'voice',
].includes(key);
});
const isComingSoon = computed(() => {
const { key } = props.channel;
// Show "Coming Soon" only if the channel is marked as coming soon
// and the corresponding feature flag is not enabled yet.
return ['voice'].includes(key) && !isActive.value;
});
const onItemClick = () => {
if (isActive.value) {
emit('channelItemClick', props.channel.key);
}
};
</script>
<template>
<ChannelSelector
:class="{ inactive: !isActive }"
:title="channel.name"
:src="getChannelThumbnail()"
:title="channel.title"
:description="channel.description"
:icon="channel.icon"
:is-coming-soon="isComingSoon"
:disabled="!isActive"
@click="onItemClick"
/>
</template>

View File

@@ -13,7 +13,7 @@ defineProps({
<div class="flex items-center text-n-slate-11 text-xs min-w-0">
<ChannelIcon
:inbox="inbox"
class="size-3 ltr:mr-0.5 rtl:ml-0.5 flex-shrink-0"
class="size-3 ltr:mr-1 rtl:ml-1 flex-shrink-0"
/>
<span class="truncate">
{{ inbox.name }}

View File

@@ -250,7 +250,6 @@ const deleteConversation = () => {
:src="currentContact.thumbnail"
:size="32"
:status="currentContact.availability_status"
:inbox="inbox"
:class="!showInboxName ? 'mt-4' : 'mt-8'"
hide-offline-status
rounded-full

View File

@@ -424,7 +424,51 @@
},
"AUTH": {
"TITLE": "Choose a channel",
"DESC": "Chatwoot supports live-chat widgets, Facebook Messenger, WhatsApp, Emails, etc., as channels. If you want to build a custom channel, you can create it using the API channel. To get started, choose one of the channels below."
"DESC": "Chatwoot supports live-chat widgets, Facebook Messenger, WhatsApp, Emails, etc., as channels. If you want to build a custom channel, you can create it using the API channel. To get started, choose one of the channels below.",
"TITLE_NEXT": "Complete the setup",
"TITLE_FINISH": "Voilà!",
"CHANNEL": {
"WEBSITE": {
"TITLE": "Website",
"DESCRIPTION": "Create a live-chat widget"
},
"FACEBOOK": {
"TITLE": "Facebook",
"DESCRIPTION": "Connect your Facebook page"
},
"WHATSAPP": {
"TITLE": "WhatsApp",
"DESCRIPTION": "Support your customers on WhatsApp"
},
"EMAIL": {
"TITLE": "Email",
"DESCRIPTION": "Connect with Gmail, Outlook, or other providers"
},
"SMS": {
"TITLE": "SMS",
"DESCRIPTION": "Integrate SMS channel with Twilio or bandwidth"
},
"API": {
"TITLE": "API",
"DESCRIPTION": "Make a custom channel using our API"
},
"TELEGRAM": {
"TITLE": "Telegram",
"DESCRIPTION": "Configure Telegram channel using Bot token"
},
"LINE": {
"TITLE": "Line",
"DESCRIPTION": "Integrate your Line channel"
},
"INSTAGRAM": {
"TITLE": "Instagram",
"DESCRIPTION": "Connect your instagram account"
},
"VOICE": {
"TITLE": "Voice",
"DESCRIPTION": "Integrate with Twilio Voice"
}
}
},
"AGENTS": {
"TITLE": "Agents",
@@ -871,9 +915,18 @@
"SCRIPT_SETTINGS": "\n window.chatwootSettings = {options};"
},
"EMAIL_PROVIDERS": {
"MICROSOFT": "Microsoft",
"GOOGLE": "Google",
"OTHER_PROVIDERS": "Other Providers"
"MICROSOFT": {
"TITLE": "Microsoft",
"DESCRIPTION": "Connect with Microsoft"
},
"GOOGLE": {
"TITLE": "Google",
"DESCRIPTION": "Connect with Google"
},
"OTHER_PROVIDERS": {
"TITLE": "Other Providers",
"DESCRIPTION": "Connect with Other Providers"
}
},
"CHANNELS": {
"MESSENGER": "Messenger",

View File

@@ -63,9 +63,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<form class="flex flex-wrap flex-col mx-0" @submit.prevent="addAgents()">
<div class="w-full">
<PageHeader

View File

@@ -1,83 +1,108 @@
<script>
import ChannelItem from 'dashboard/components/widgets/ChannelItem.vue';
import router from '../../../index';
import PageHeader from '../SettingsSubPageHeader.vue';
import { mapGetters } from 'vuex';
import { useBranding } from 'shared/composables/useBranding';
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useMapGetter } from 'dashboard/composables/store';
export default {
components: {
ChannelItem,
PageHeader,
},
setup() {
const { replaceInstallationName } = useBranding();
return {
replaceInstallationName,
};
},
data() {
return {
enabledFeatures: {},
};
},
computed: {
account() {
return this.$store.getters['accounts/getAccount'](this.accountId);
import { useAccount } from 'dashboard/composables/useAccount';
import ChannelItem from 'dashboard/components/widgets/ChannelItem.vue';
const { t } = useI18n();
const router = useRouter();
const { accountId, currentAccount } = useAccount();
const globalConfig = useMapGetter('globalConfig/get');
const enabledFeatures = ref({});
const channelList = computed(() => {
const { apiChannelName } = globalConfig.value;
return [
{
key: 'website',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.WEBSITE.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.WEBSITE.DESCRIPTION'),
icon: 'i-woot-website',
},
channelList() {
const { apiChannelName, apiChannelThumbnail } = this.globalConfig;
return [
{ key: 'website', name: 'Website' },
{ key: 'facebook', name: 'Messenger' },
{ key: 'whatsapp', name: 'WhatsApp' },
{ key: 'sms', name: 'SMS' },
{ key: 'email', name: 'Email' },
{
key: 'api',
name: apiChannelName || 'API',
thumbnail: apiChannelThumbnail,
},
{ key: 'telegram', name: 'Telegram' },
{ key: 'line', name: 'Line' },
{ key: 'instagram', name: 'Instagram' },
{ key: 'voice', name: 'Voice' },
];
{
key: 'facebook',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.FACEBOOK.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.FACEBOOK.DESCRIPTION'),
icon: 'i-woot-messenger',
},
...mapGetters({
accountId: 'getCurrentAccountId',
globalConfig: 'globalConfig/get',
}),
},
mounted() {
this.initializeEnabledFeatures();
},
methods: {
async initializeEnabledFeatures() {
this.enabledFeatures = this.account.features;
{
key: 'whatsapp',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.WHATSAPP.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.WHATSAPP.DESCRIPTION'),
icon: 'i-woot-whatsapp',
},
initChannelAuth(channel) {
const params = {
sub_page: channel,
accountId: this.accountId,
};
router.push({ name: 'settings_inboxes_page_channel', params });
{
key: 'sms',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.SMS.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.SMS.DESCRIPTION'),
icon: 'i-woot-sms',
},
},
{
key: 'email',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.EMAIL.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.EMAIL.DESCRIPTION'),
icon: 'i-woot-mail',
},
{
key: 'api',
title: apiChannelName || t('INBOX_MGMT.ADD.AUTH.CHANNEL.API.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.API.DESCRIPTION'),
icon: 'i-woot-api',
},
{
key: 'telegram',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.TELEGRAM.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.TELEGRAM.DESCRIPTION'),
icon: 'i-woot-telegram',
},
{
key: 'line',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.LINE.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.LINE.DESCRIPTION'),
icon: 'i-woot-line',
},
{
key: 'instagram',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.INSTAGRAM.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.INSTAGRAM.DESCRIPTION'),
icon: 'i-woot-instagram',
},
{
key: 'voice',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.VOICE.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.VOICE.DESCRIPTION'),
icon: 'i-ri-phone-fill',
},
];
});
const initializeEnabledFeatures = async () => {
enabledFeatures.value = currentAccount.value.features;
};
const initChannelAuth = channel => {
const params = {
sub_page: channel,
accountId: accountId.value,
};
router.push({ name: 'settings_inboxes_page_channel', params });
};
onMounted(() => {
initializeEnabledFeatures();
});
</script>
<template>
<div
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"
>
<PageHeader
class="max-w-4xl"
:header-title="$t('INBOX_MGMT.ADD.AUTH.TITLE')"
:header-content="replaceInstallationName($t('INBOX_MGMT.ADD.AUTH.DESC'))"
/>
<div class="w-full p-8 overflow-auto">
<div
class="grid max-w-3xl grid-cols-2 mx-0 mt-6 sm:grid-cols-3 lg:grid-cols-4"
class="grid max-w-3xl grid-cols-1 xs:grid-cols-2 mx-0 gap-6 sm:grid-cols-3"
>
<ChannelItem
v-for="channel in channelList"

View File

@@ -168,9 +168,7 @@ onMounted(() => {
</script>
<template>
<div
class="overflow-auto col-span-6 p-6 w-full h-full rounded-t-lg border border-b-0 border-n-weak bg-n-solid-1"
>
<div class="w-full h-full col-span-6 p-6 overflow-auto">
<DuplicateInboxBanner
v-if="hasDuplicateInstagramInbox"
:content="$t('INBOX_MGMT.ADD.INSTAGRAM.NEW_INBOX_SUGGESTION')"

View File

@@ -1,55 +1,106 @@
<script>
import { mapGetters } from 'vuex';
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useMapGetter } from 'dashboard/composables/store';
import { useBranding } from 'shared/composables/useBranding';
export default {
setup() {
const { replaceInstallationName } = useBranding();
import PageHeader from '../SettingsSubPageHeader.vue';
import Icon from 'next/icon/Icon.vue';
const { t } = useI18n();
const route = useRoute();
const { replaceInstallationName } = useBranding();
const globalConfig = useMapGetter('globalConfig/get');
const ALL_CHANNEL_ICONS = [
'i-woot-line',
'i-woot-facebook',
'i-woot-whatsapp',
'i-woot-instagram',
'i-woot-messenger',
'i-woot-website',
'i-woot-mail',
'i-woot-sms',
'i-woot-telegram',
'i-woot-api',
'i-woot-twilio',
'i-woot-gmail',
'i-woot-outlook',
];
const createFlowSteps = computed(() => {
const steps = ['CHANNEL', 'INBOX', 'AGENT', 'FINISH'];
const routes = {
CHANNEL: 'settings_inbox_new',
INBOX: 'settings_inboxes_page_channel',
AGENT: 'settings_inboxes_add_agents',
FINISH: 'settings_inbox_finish',
};
return steps.map(step => {
return {
replaceInstallationName,
title: t(`INBOX_MGMT.CREATE_FLOW.${step}.TITLE`),
body: t(`INBOX_MGMT.CREATE_FLOW.${step}.BODY`),
route: routes[step],
};
},
computed: {
...mapGetters({
globalConfig: 'globalConfig/get',
}),
createFlowSteps() {
const steps = ['CHANNEL', 'INBOX', 'AGENT', 'FINISH'];
});
});
const routes = {
CHANNEL: 'settings_inbox_new',
INBOX: 'settings_inboxes_page_channel',
AGENT: 'settings_inboxes_add_agents',
FINISH: 'settings_inbox_finish',
};
const isFirstStep = computed(() => {
return route.name === 'settings_inbox_new';
});
return steps.map(step => {
return {
title: this.$t(`INBOX_MGMT.CREATE_FLOW.${step}.TITLE`),
body: this.$t(`INBOX_MGMT.CREATE_FLOW.${step}.BODY`),
route: routes[step],
};
});
},
items() {
return this.createFlowSteps.map(item => ({
...item,
body: this.replaceInstallationName(item.body),
}));
},
},
};
const isFinishStep = computed(() => {
return route.name === 'settings_inbox_finish';
});
const pageTitle = computed(() => {
if (isFirstStep.value) {
return t('INBOX_MGMT.ADD.AUTH.TITLE');
}
if (isFinishStep.value) {
return t('INBOX_MGMT.ADD.AUTH.TITLE_FINISH');
}
return t('INBOX_MGMT.ADD.AUTH.TITLE_NEXT');
});
const items = computed(() => {
return createFlowSteps.value.map(item => ({
...item,
body: replaceInstallationName(item.body),
}));
});
</script>
<template>
<div class="grid grid-cols-1 md:grid-cols-8 overflow-auto h-full">
<woot-wizard
class="hidden md:block col-span-2"
:global-config="globalConfig"
:items="items"
/>
<div class="col-span-6">
<router-view />
<div class="mx-2 flex flex-col gap-6 mb-8">
<PageHeader class="block lg:hidden !mb-0" :header-title="pageTitle" />
<div class="hidden lg:grid grid-cols-1 lg:grid-cols-8 items-center gap-2">
<div class="col-span-2 w-full" />
<div class="flex items-center gap-2 col-span-6 ltr:ml-8 rtl:mr-8">
<div
v-for="icon in ALL_CHANNEL_ICONS"
:key="icon"
class="size-8 bg-n-alpha-2 flex items-center flex-shrink-0 justify-center rounded-full"
>
<Icon :icon="icon" class="size-4 text-n-slate-10" />
</div>
</div>
</div>
<div
class="grid grid-cols-1 lg:grid-cols-8 lg:divide-x lg:divide-n-weak rounded-xl border border-n-weak min-h-[52rem]"
>
<woot-wizard
class="hidden lg:block col-span-2 h-fit py-8 px-6"
:global-config="globalConfig"
:items="items"
/>
<div class="col-span-6 overflow-hidden">
<router-view />
</div>
</div>
</div>
</template>

View File

@@ -116,7 +116,7 @@ const openDelete = inbox => {
v-else
class="size-12 flex justify-center items-center bg-n-alpha-3 rounded-full p-2 ring ring-n-solid-1 border border-n-strong shadow-sm"
>
<ChannelIcon class="size-5" :inbox="inbox" />
<ChannelIcon class="size-5 text-n-slate-10" :inbox="inbox" />
</div>
<div>
<span class="block font-medium capitalize">

View File

@@ -65,9 +65,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<PageHeader
:header-title="$t('INBOX_MGMT.ADD.API_CHANNEL.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.API_CHANNEL.DESC')"

View File

@@ -20,22 +20,25 @@ const isAChatwootInstance = getters['globalConfig/isAChatwootInstance'];
const emailProviderList = computed(() => {
return [
{
title: t('INBOX_MGMT.EMAIL_PROVIDERS.MICROSOFT'),
title: t('INBOX_MGMT.EMAIL_PROVIDERS.MICROSOFT.TITLE'),
description: t('INBOX_MGMT.EMAIL_PROVIDERS.MICROSOFT.DESCRIPTION'),
isEnabled: !!globalConfig.value.azureAppId,
key: 'microsoft',
src: '/assets/images/dashboard/channels/microsoft.png',
icon: 'i-woot-outlook',
},
{
title: t('INBOX_MGMT.EMAIL_PROVIDERS.GOOGLE'),
title: t('INBOX_MGMT.EMAIL_PROVIDERS.GOOGLE.TITLE'),
description: t('INBOX_MGMT.EMAIL_PROVIDERS.GOOGLE.DESCRIPTION'),
isEnabled: !!window.chatwootConfig.googleOAuthClientId,
key: 'google',
src: '/assets/images/dashboard/channels/google.png',
icon: 'i-woot-gmail',
},
{
title: t('INBOX_MGMT.EMAIL_PROVIDERS.OTHER_PROVIDERS'),
title: t('INBOX_MGMT.EMAIL_PROVIDERS.OTHER_PROVIDERS.TITLE'),
description: t('INBOX_MGMT.EMAIL_PROVIDERS.OTHER_PROVIDERS.DESCRIPTION'),
isEnabled: true,
key: 'other_provider',
src: '/assets/images/dashboard/channels/email.png',
icon: 'i-woot-mail',
},
].filter(providerConfig => {
if (isAChatwootInstance.value) {
@@ -53,22 +56,20 @@ function onClick(emailProvider) {
</script>
<template>
<div
v-if="!provider"
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"
>
<div v-if="!provider" class="h-full w-full p-6 col-span-6">
<PageHeader
class="max-w-4xl"
:header-title="$t('INBOX_MGMT.ADD.EMAIL_PROVIDER.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.EMAIL_PROVIDER.DESCRIPTION')"
/>
<div class="grid max-w-3xl grid-cols-4 mx-0 mt-6">
<div class="grid max-w-3xl grid-cols-4 gap-6 mx-0 mt-6">
<ChannelSelector
v-for="emailProvider in emailProviderList"
:key="emailProvider.key"
:class="{ inactive: !emailProvider.isEnabled }"
:title="emailProvider.title"
:src="emailProvider.src"
:description="emailProvider.description"
:icon="emailProvider.icon"
:disabled="!emailProvider.isEnabled"
@click="() => onClick(emailProvider)"
/>
</div>

View File

@@ -206,16 +206,14 @@ export default {
</script>
<template>
<div
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 class="w-full h-full col-span-6 p-6 overflow-auto">
<div
v-if="!hasLoginStarted"
class="flex flex-col items-center justify-center h-full text-center"
>
<a href="#" @click="startLogin()">
<img
class="w-auto h-10"
class="w-auto h-10 rounded-md"
src="~dashboard/assets/images/channels/facebook_login.png"
alt="Facebook-logo"
/>

View File

@@ -71,9 +71,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<PageHeader
:header-title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.LINE_CHANNEL.DESC')"

View File

@@ -18,9 +18,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<PageHeader
:header-title="$t('INBOX_MGMT.ADD.SMS.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.SMS.DESC')"

View File

@@ -65,9 +65,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<PageHeader
:header-title="$t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.TELEGRAM_CHANNEL.DESC')"

View File

@@ -30,9 +30,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<div class="login-init h-full text-center">
<form @submit.prevent="requestAuthorization">
<NextButton

View File

@@ -93,9 +93,7 @@ async function createChannel() {
</script>
<template>
<div
class="overflow-auto col-span-6 p-6 w-full h-full rounded-t-lg border border-b-0 border-n-weak bg-n-solid-1"
>
<div class="overflow-auto col-span-6 p-6 w-full h-full">
<PageHeader
:header-title="t('INBOX_MGMT.ADD.VOICE.TITLE')"
:header-content="t('INBOX_MGMT.ADD.VOICE.DESC')"

View File

@@ -78,9 +78,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<PageHeader
:header-title="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.DESC')"

View File

@@ -7,6 +7,7 @@ import Twilio from './Twilio.vue';
import ThreeSixtyDialogWhatsapp from './360DialogWhatsapp.vue';
import CloudWhatsapp from './CloudWhatsapp.vue';
import WhatsappEmbeddedSignup from './WhatsappEmbeddedSignup.vue';
import ChannelSelector from 'dashboard/components/ChannelSelector.vue';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
const route = useRoute();
@@ -45,16 +46,16 @@ const showConfiguration = computed(() => Boolean(selectedProvider.value));
const availableProviders = computed(() => [
{
value: PROVIDER_TYPES.WHATSAPP,
label: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD'),
key: PROVIDER_TYPES.WHATSAPP,
title: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD'),
description: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.WHATSAPP_CLOUD_DESC'),
icon: '/assets/images/dashboard/channels/whatsapp.png',
icon: 'i-woot-whatsapp',
},
{
value: PROVIDER_TYPES.TWILIO,
label: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.TWILIO'),
key: PROVIDER_TYPES.TWILIO,
title: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.TWILIO'),
description: t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.TWILIO_DESC'),
icon: '/assets/images/dashboard/channels/twilio.png',
icon: 'i-woot-twilio',
},
]);
@@ -92,9 +93,7 @@ const shouldShowCloudWhatsapp = provider => {
</script>
<template>
<div
class="overflow-auto col-span-6 p-6 w-full h-full rounded-t-lg border border-b-0 border-n-weak bg-n-solid-1"
>
<div class="overflow-auto col-span-6 p-6 w-full h-full">
<div v-if="showProviderSelection">
<div class="mb-10 text-left">
<h1 class="mb-2 text-lg font-medium text-slate-12">
@@ -106,51 +105,30 @@ const shouldShowCloudWhatsapp = provider => {
</div>
<div class="flex gap-6 justify-start">
<div
<ChannelSelector
v-for="provider in availableProviders"
:key="provider.value"
class="gap-6 px-5 py-6 w-96 rounded-2xl border transition-all duration-200 cursor-pointer border-n-weak hover:bg-n-slate-3"
@click="selectProvider(provider.value)"
>
<div class="flex justify-start mb-5">
<div
class="flex justify-center items-center rounded-full size-10 bg-n-alpha-2"
>
<img
:src="provider.icon"
:alt="provider.label"
class="object-contain size-[26px]"
/>
</div>
</div>
<div class="text-start">
<h3 class="mb-1.5 text-sm font-medium text-slate-12">
{{ provider.label }}
</h3>
<p class="text-sm text-slate-11">
{{ provider.description }}
</p>
</div>
</div>
:key="provider.key"
:title="provider.title"
:description="provider.description"
:icon="provider.icon"
@click="selectProvider(provider.key)"
/>
</div>
</div>
<div v-else-if="showConfiguration">
<div class="px-6 py-5 rounded-2xl border bg-n-solid-2 border-n-weak">
<WhatsappEmbeddedSignup
v-if="shouldShowEmbeddedSignup(selectedProvider)"
/>
<CloudWhatsapp v-else-if="shouldShowCloudWhatsapp(selectedProvider)" />
<Twilio
v-else-if="selectedProvider === PROVIDER_TYPES.TWILIO"
type="whatsapp"
/>
<ThreeSixtyDialogWhatsapp
v-else-if="selectedProvider === PROVIDER_TYPES.THREE_SIXTY_DIALOG"
/>
<CloudWhatsapp v-else />
</div>
<WhatsappEmbeddedSignup
v-if="shouldShowEmbeddedSignup(selectedProvider)"
/>
<CloudWhatsapp v-else-if="shouldShowCloudWhatsapp(selectedProvider)" />
<Twilio
v-else-if="selectedProvider === PROVIDER_TYPES.TWILIO"
type="whatsapp"
/>
<ThreeSixtyDialogWhatsapp
v-else-if="selectedProvider === PROVIDER_TYPES.THREE_SIXTY_DIALOG"
/>
<CloudWhatsapp v-else />
</div>
</div>
</template>

View File

@@ -70,9 +70,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<PageHeader
:header-title="$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.DESC')"

View File

@@ -60,9 +60,7 @@ async function requestAuthorization() {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<SettingsSubPageHeader
:header-title="title"
:header-content="description"

View File

@@ -88,9 +88,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<form
class="flex flex-wrap mx-0 overflow-x-auto"
@submit.prevent="addAgents"

View File

@@ -37,9 +37,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<PageHeader
:header-title="$t('TEAMS_SETTINGS.CREATE_FLOW.CREATE.TITLE')"
:header-content="$t('TEAMS_SETTINGS.CREATE_FLOW.CREATE.DESC')"

View File

@@ -23,8 +23,15 @@ export default {
</script>
<template>
<div class="grid grid-cols-1 md:grid-cols-8 overflow-auto h-full px-5 flex-1">
<woot-wizard class="hidden md:block col-span-2" :items="items" />
<router-view />
<div class="mx-2 flex flex-col gap-6 mb-8">
<div
class="grid grid-cols-1 lg:grid-cols-8 lg:divide-x lg:divide-n-weak rounded-xl border border-n-weak min-h-[43rem]"
>
<woot-wizard
class="hidden lg:block col-span-2 h-fit py-8 px-6"
:items="items"
/>
<router-view />
</div>
</div>
</template>

View File

@@ -103,9 +103,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-8 col-span-6">
<form
class="flex flex-wrap mx-0 overflow-x-auto"
@submit.prevent="addAgents"

View File

@@ -57,9 +57,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-8 col-span-6">
<PageHeader
:header-title="$t('TEAMS_SETTINGS.EDIT_FLOW.CREATE.TITLE')"
:header-content="$t('TEAMS_SETTINGS.EDIT_FLOW.CREATE.DESC')"

View File

@@ -27,8 +27,15 @@ export default {
</script>
<template>
<div class="grid grid-cols-1 md:grid-cols-8 overflow-auto h-full px-5 flex-1">
<woot-wizard class="hidden md:block col-span-2" :items="items" />
<router-view />
<div class="mx-2 flex flex-col gap-6 mb-8">
<div
class="grid grid-cols-1 lg:grid-cols-8 lg:divide-x lg:divide-n-weak rounded-xl border border-n-weak min-h-[43rem]"
>
<woot-wizard
class="hidden lg:block col-span-2 h-fit py-8 px-6"
:items="items"
/>
<router-view />
</div>
</div>
</template>

View File

@@ -11,9 +11,7 @@ export default {
</script>
<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"
>
<div class="h-full w-full p-6 col-span-6">
<EmptyState
:title="$t('TEAMS_SETTINGS.FINISH.TITLE')"
:message="$t('TEAMS_SETTINGS.FINISH.MESSAGE')"

View File

@@ -113,4 +113,71 @@ export const icons = {
width: 16,
height: 20,
},
/** Channels Starts */
website: {
body: `<path d="m3.023 1.664.126-.001.418-.001 1.286-.004h.34l1.596-.003q.92 0 1.84-.004l1.425-.003.85-.002a108 108 0 0 1 1.092-.003c.628-.004 1.163.04 1.706.388.554.593.645 1.218.64 2.004v.192l-.002.624v.435l-.004 1.14-.002 1.166-.006 2.283a4 4 0 0 1-.798-.15l-.182-.056-.193-.061-.2-.062-.634-.198-1.03-.32q-.088-.03-.18-.057l-.158-.05c-.39-.117-.764-.18-1.151-.013-.261.15-.403.338-.513.616-.059.432.047.817.18 1.224l.055.177q.086.278.175.554l.285.904.05.157.044.138c.031.127.031.127.031.361a1215 1215 0 0 1-4.146.014 337 337 0 0 1-1.753.005l-.68.001-.2.002c-.606-.002-1.133-.094-1.603-.493a1.78 1.78 0 0 1-.54-1.298l-.002-.703-.002-1.102-.002-1.481q0-.69-.003-1.378l-.002-1.185-.001-.707v-.666l-.002-.358c.004-.622.125-1.103.557-1.563.395-.347.776-.462 1.293-.463m-.648 1.121c-.48.604-.293.955-.293 1.875h11.309c.135-1.332.135-1.332-.235-1.816-.303-.28-.518-.3-.924-.302h-.12l-.687-.002-.943-.003h-.325q-.762-.003-1.525-.003-.88 0-1.76-.005L5.51 2.527l-.813-.003a99 99 0 0 0-1.044-.002c-.675-.009-.675-.009-1.279.263" fill="currentColor"/><path d="M10.52 9.406c.193.044.193.044.396.105l.112.034.621.189.534.163a164 164 0 0 0 1.211.369l.252.076.554.17c.507.179.507.179.597.359.034.365.034.365-.092.532-.176.14-.355.197-.567.258l-.251.079q-.196.06-.394.118c-.498.13-.498.13-.908.416a2 2 0 0 0-.132.413q-.057.177-.117.351-.09.28-.176.561l-.058.183-.052.164a.93.93 0 0 1-.3.382c-.205.03-.205.03-.41 0-.226-.176-.292-.356-.374-.625l-.037-.118-.116-.384-.081-.268-.17-.562q-.107-.36-.218-.717a136 136 0 0 1-.36-1.19q-.03-.105-.064-.212a2 2 0 0 1-.045-.611c.174-.272.34-.252.645-.235M6.184 3.162c.175.033.175.033.318.158.092.168.092.168.092.351-.068.194-.11.253-.293.344-.147.026-.147.026-.293 0-.136-.095-.136-.095-.235-.234-.035-.215-.028-.31.096-.49.139-.096.139-.096.315-.129m-1.172.033c.139.096.139.096.234.235.027.186.034.296-.066.458a.74.74 0 0 1-.461.128c-.172-.121-.172-.121-.293-.293 0-.191.026-.3.128-.462.161-.1.272-.092.458-.066m-1.231.059c.117.117.117.117.14.348-.006.168-.01.222-.114.359-.203.078-.345.046-.553-.004-.117-.117-.117-.117-.14-.348.006-.168.01-.222.114-.359.203-.078.345-.046.553.004" fill="currentColor"/>`,
width: 16,
height: 16,
},
line: {
body: `<g clip-path="url(#a)"><path d="M2.778.778a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2zm4 2.25c2.344 0 4.25 1.528 4.25 3.407 0 .751-.295 1.429-.912 2.096-.892 1.014-2.885 2.249-3.339 2.437-.453.189-.386-.12-.367-.226l.06-.36c.014-.107.03-.273-.013-.38-.048-.116-.236-.177-.375-.206-2.042-.267-3.554-1.677-3.554-3.361 0-1.879 1.907-3.407 4.25-3.407M4.27 5.443a.22.22 0 0 0-.223.22v1.669c0 .122.1.22.223.22h.844a.22.22 0 0 0 .223-.22c0-.121-.1-.22-.223-.22h-.621V5.664c0-.122-.1-.22-.223-.22m1.495 0a.22.22 0 0 0-.223.22v1.669c0 .122.1.22.223.22a.22.22 0 0 0 .223-.22V5.664c0-.122-.1-.22-.223-.22m.766 0a.22.22 0 0 0-.224.22v1.669c0 .122.101.22.224.22a.22.22 0 0 0 .223-.22V6.3l.865 1.164a.225.225 0 0 0 .25.077.22.22 0 0 0 .151-.209V5.664c0-.122-.1-.22-.222-.22a.22.22 0 0 0-.223.22v1.032l-.866-1.164a.23.23 0 0 0-.178-.089m2.011 0a.22.22 0 0 0-.222.221v1.668c0 .122.1.22.222.22h.845a.22.22 0 0 0 .223-.22c0-.121-.1-.22-.223-.22h-.622v-.394h.622a.22.22 0 0 0 .223-.22c0-.122-.1-.22-.223-.22h-.622v-.394h.622a.22.22 0 0 0 .223-.22c0-.121-.1-.22-.223-.22z" fill="currentColor"/></g><defs><clipPath id="a"><path fill="currentColor" d="M.333.333h13.333v13.333H.333z"/></clipPath></defs>`,
width: 13,
height: 13,
},
facebook: {
body: `<path d="M7 .333a6.667 6.667 0 0 1 1 13.258v-4.59h.666a1 1 0 0 0 0-2H8V5.665c0-.183.15-.333.333-.333h.333a1 1 0 0 0 0-2h-.333A2.333 2.333 0 0 0 6 5.668V7h-.667a1 1 0 1 0 0 2H6v4.592A6.668 6.668 0 0 1 7 .333" fill="currentColor"/>`,
width: 14,
height: 14,
},
whatsapp: {
body: `<path d="M7 .333A6.667 6.667 0 1 1 3.554 12.71l-2.02.594a.673.673 0 0 1-.836-.836l.593-2.021A6.667 6.667 0 0 1 7 .334M5.524 3.84a.46.46 0 0 0-.456-.05 1.86 1.86 0 0 0-1.072 1.258l-.004.02c-.026.101-.047.22-.042.35.016.455.194 1.743 1.543 3.091 1.347 1.348 2.634 1.527 3.09 1.543.13.005.249-.017.35-.042l.023-.006a1.85 1.85 0 0 0 1.254-1.067.47.47 0 0 0-.06-.469c-.355-.455-.832-.784-1.256-1.077l-.085-.058a.474.474 0 0 0-.649.105l-.4.61a.153.153 0 0 1-.203.05 5 5 0 0 1-.951-.703 4.4 4.4 0 0 1-.671-.915.15.15 0 0 1 .045-.194l.615-.457a.474.474 0 0 0 .081-.626l-.09-.133c-.28-.413-.607-.898-1.062-1.23" fill="currentColor"/>`,
width: 14,
height: 14,
},
instagram: {
body: `<path d="M8.667 0A3.333 3.333 0 0 1 12 3.333v5.334A3.333 3.333 0 0 1 8.667 12H3.333A3.333 3.333 0 0 1 0 8.667V3.333A3.333 3.333 0 0 1 3.333 0zM6 3.333a2.667 2.667 0 1 0 0 5.334 2.667 2.667 0 0 0 0-5.334m0 1.334a1.333 1.333 0 1 1 0 2.666 1.333 1.333 0 0 1 0-2.666m3-2.334a.667.667 0 1 0 0 1.334.667.667 0 0 0 0-1.334" fill="currentColor"/>`,
width: 12,
height: 12,
},
messenger: {
body: `<path fill-rule="evenodd" clip-rule="evenodd" d="M.333 7a6.667 6.667 0 1 1 3.221 5.709l-2.033.597a.667.667 0 0 1-.827-.827l.598-2.033A6.64 6.64 0 0 1 .333 7M5.53 5.53c.26-.26.682-.26.942 0L8 7.057 9.529 5.53a.667.667 0 1 1 .942.943l-2 2a.667.667 0 0 1-.942 0L6 6.943 4.471 8.472a.667.667 0 1 1-.942-.943z" fill="currentColor"/>`,
width: 14,
height: 14,
},
mail: {
body: `<g clip-path="url(#a)" fill="currentColor"><path d="M0 4.676q.058.03.116.063l.065.036c.105.06.204.128.304.196l.068.047.54.366.083.056q.421.284.847.562.598.393 1.19.793.673.454 1.352.9a57 57 0 0 1 1.1.738c.525.356 1.087.543 1.725.437.46-.096.845-.36 1.225-.621q.193-.134.387-.265l.081-.055q.418-.284.843-.56.597-.392 1.19-.793.669-.452 1.345-.896.38-.249.757-.501l.076-.05.358-.24.127-.086.059-.04c.132-.087.132-.087.162-.087l.007 2.49v.354l.006 1.92.001.545.002.703c.004.486-.045.832-.387 1.2-.262.242-.547.369-.907.369h-.409l-.403.001h-.434l-.85.001h-.692l-1.96.001H7.76l-1.715.001-2.75.002a262 262 0 0 1-1.664 0l-.142.001c-.422.003-.775-.061-1.098-.354l-.063-.07-.04-.04c-.17-.181-.292-.427-.291-.678V9.407l.001-1.933v-.06z"/><path d="M1.278 1.743h.263l.549-.001h.434l1.542-.002H6.24q.858 0 1.715-.002a1231 1231 0 0 1 3.592-.002h.964c.422-.003.775.061 1.098.354l.063.07.04.04c.26.272.3.613.296.97-.013.348-.198.612-.44.85a2 2 0 0 1-.303.221l-.07.046-.15.097-.407.265-.084.055q-.421.276-.837.56-.63.425-1.265.842-.565.371-1.124.75a123 123 0 0 1-1.22.817c-.46.305-.85.525-1.419.433a1.6 1.6 0 0 1-.46-.213l-.056-.036q-.33-.211-.651-.433A98 98 0 0 0 4.21 6.55q-.634-.417-1.262-.843-.531-.358-1.065-.71l-.654-.43-.062-.041-.47-.312C.451 4.047.235 3.873.108 3.6l-.046-.097a1.37 1.37 0 0 1-.005-.94c.16-.352.434-.613.79-.76a1.5 1.5 0 0 1 .43-.061"/></g><defs><clipPath id="a"><path fill="currentColor" d="M0 0h14v14H0z"/></clipPath></defs>`,
width: 14,
height: 14,
},
sms: {
body: `<path fill-rule="evenodd" clip-rule="evenodd" d="M.333 2a2 2 0 0 1 2-2h9.334a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-1.448a.67.67 0 0 0-.472.195L8.414 11.53a2 2 0 0 1-2.828 0l-1.333-1.334A.67.67 0 0 0 3.78 10H2.333a2 2 0 0 1-2-2zm3.334.667a.667.667 0 1 0 0 1.333h6.666a.667.667 0 1 0 0-1.333zm0 2.666a.667.667 0 1 0 0 1.334H7a.667.667 0 1 0 0-1.334z" fill="currentColor"/>`,
width: 14,
height: 12,
},
telegram: {
body: `<path fill-rule="evenodd" clip-rule="evenodd" d="M12.185.287A1 1 0 0 1 13.56 1.37l-1.513 9.171c-.146.884-1.117 1.392-1.928.951a39 39 0 0 1-2.594-1.53c-.453-.296-1.841-1.245-1.67-1.921C6 7.464 8.332 5.292 9.666 4c.524-.507.285-.8-.334-.333C7.8 4.826 5.336 6.587 4.521 7.083c-.719.438-1.094.512-1.542.438a10.2 10.2 0 0 1-2.194-.604c-.836-.346-.795-1.496 0-1.83z" fill="currentColor"/>`,
width: 14,
height: 12,
},
api: {
body: `<path d="M4.468 11.5q-1.14 0-1.776-.636-.624-.636-.624-1.632v-.996q0-.504-.12-.792a.7.7 0 0 0-.348-.408q-.24-.132-.624-.132V5.488q.384 0 .624-.12a.7.7 0 0 0 .348-.408q.12-.3.12-.804V3.16q0-.996.624-1.62.636-.636 1.776-.636h.756v1.548q-.756-.036-1.104.204-.336.24-.336.792v.828q0 .852-.42 1.332-.408.48-.972.552v.072q.564.06.972.552.42.48.42 1.332v.84q0 .552.336.816.348.252 1.104.228v1.5zm6.855 0h-.756V10q.756.024 1.092-.228.348-.264.348-.816v-.84q0-.852.408-1.332.42-.492.984-.552V6.16q-.552-.072-.972-.552t-.42-1.332v-.828q0-.552-.348-.792-.336-.24-1.092-.204V.904h.756q1.152 0 1.776.636.624.624.624 1.62v.996q0 .504.108.804.12.288.36.408t.624.12v1.416q-.384 0-.624.132a.76.76 0 0 0-.36.408q-.108.288-.108.792v.996q0 .996-.624 1.632t-1.776.636" fill="currentColor"/>`,
width: 15,
height: 12,
},
twilio: {
body: `<path d="M8 .5c4.14 0 7.5 3.36 7.5 7.5s-3.36 7.5-7.5 7.5S.5 12.14.5 8 3.86.5 8 .5m0 1.98C4.94 2.48 2.48 4.94 2.48 8S4.94 13.52 8 13.52s5.52-2.46 5.52-5.52S11.06 2.48 8 2.48M9.86 8.3a1.56 1.56 0 1 1 0 3.12 1.56 1.56 0 0 1 0-3.12m-3.72 0a1.56 1.56 0 1 1 0 3.12 1.56 1.56 0 0 1 0-3.12m3.72-3.72a1.56 1.56 0 1 1 0 3.12 1.56 1.56 0 0 1 0-3.12m-3.72 0a1.56 1.56 0 1 1 0 3.12 1.56 1.56 0 0 1 0-3.12" fill="currentColor"/>`,
width: 16,
height: 16,
},
gmail: {
body: `<path d="M3.182 12.253H.955A.954.954 0 0 1 0 11.298V3.185c0-1.18 1.346-1.854 2.29-1.146L7 5.571l4.71-3.532c.942-.708 2.29-.035 2.29 1.146v8.113a.954.954 0 0 1-.955.955h-2.227v-5.41L7 9.708 3.182 6.844z" fill="currentColor"/>`,
width: 14,
height: 14,
},
outlook: {
body: `<path d="M6.18.365a1 1 0 0 0-.267.028l-4.405.978A1.505 1.505 0 0 0 .333 2.836v8.328c0 .7.492 1.313 1.175 1.465l4.405.98v-.001c.718.16 1.42-.404 1.42-1.14V1.532C7.333.888 6.796.376 6.18.365m2.153 2.319v3.713l1.17.826 4.494-3.1c-.03-.8-.69-1.44-1.497-1.44zM3.833 4.5c1.028 0 1.834 1.172 1.834 2.667S4.86 9.834 3.833 9.834 2 8.662 2 7.167 2.805 4.5 3.833 4.5M14 5.337 9.783 8.244a.49.49 0 0 1-.573-.003l-.877-.621v4.064H12.5c.827 0 1.5-.674 1.5-1.5zM3.833 5.5C3.44 5.5 3 6.213 3 7.167s.44 1.667.833 1.667c.394 0 .834-.713.834-1.667S4.227 5.5 3.833 5.5" fill="currentColor"/>`,
width: 14,
height: 14,
},
/** Ends */
};