Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
import { toRef } from 'vue';
|
||||
import { useChannelIcon } from './provider';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
inbox: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const channelIcon = useChannelIcon(toRef(props, 'inbox'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon :icon="channelIcon" />
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import FileIcon from './FileIcon.vue';
|
||||
const files = [
|
||||
{ name: 'file.7z', type: '7z' },
|
||||
{ name: 'file.zip', type: 'zip' },
|
||||
{ name: 'file.rar', type: 'rar' },
|
||||
{ name: 'file.tar', type: 'tar' },
|
||||
{ name: 'file.csv', type: 'csv' },
|
||||
{ name: 'file.docx', type: 'docx' },
|
||||
{ name: 'file.doc', type: 'doc' },
|
||||
{ name: 'file.odt', type: 'odt' },
|
||||
{ name: 'file.pdf', type: 'pdf' },
|
||||
{ name: 'file.ppt', type: 'ppt' },
|
||||
{ name: 'file.pptx', type: 'pptx' },
|
||||
{ name: 'file.rtf', type: 'rtf' },
|
||||
{ name: 'file.json', type: 'json' },
|
||||
{ name: 'file.txt', type: 'txt' },
|
||||
{ name: 'file.xls', type: 'xls' },
|
||||
{ name: 'file.xlsx', type: 'xlsx' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story title="Components/Icons/FileIcon">
|
||||
<div class="grid grid-cols-4 gap-5">
|
||||
<div
|
||||
v-for="file in files"
|
||||
:key="file.type"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<FileIcon :file-type="file.type" class="size-6" />
|
||||
<p>{{ file.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Story>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
|
||||
const { fileType } = defineProps({
|
||||
fileType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const fileTypeIcon = computed(() => {
|
||||
const fileIconMap = {
|
||||
'7z': 'i-woot-file-zip',
|
||||
csv: 'i-woot-file-csv',
|
||||
doc: 'i-woot-file-doc',
|
||||
docx: 'i-woot-file-doc',
|
||||
json: 'i-woot-file-txt',
|
||||
odt: 'i-woot-file-doc',
|
||||
pdf: 'i-woot-file-pdf',
|
||||
ppt: 'i-woot-file-ppt',
|
||||
pptx: 'i-woot-file-ppt',
|
||||
rar: 'i-woot-file-zip',
|
||||
rtf: 'i-woot-file-doc',
|
||||
tar: 'i-woot-file-zip',
|
||||
txt: 'i-woot-file-txt',
|
||||
xls: 'i-woot-file-xls',
|
||||
xlsx: 'i-woot-file-xls',
|
||||
zip: 'i-woot-file-zip',
|
||||
};
|
||||
|
||||
return fileIconMap[fileType] || 'i-teenyicons-text-document-solid';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon :icon="fileTypeIcon" />
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import { h, isVNode } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
icon: { type: [String, Object, Function], required: true },
|
||||
});
|
||||
|
||||
const renderIcon = () => {
|
||||
if (!props.icon) return null;
|
||||
if (typeof props.icon === 'function' || isVNode(props.icon)) {
|
||||
return props.icon;
|
||||
}
|
||||
return h('span', { class: props.icon });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="renderIcon" />
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { useAttrs } from 'vue';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
const attrs = useAttrs();
|
||||
const globalConfig = useMapGetter('globalConfig/get');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
v-if="globalConfig.logoThumbnail"
|
||||
v-bind="attrs"
|
||||
:src="globalConfig.logoThumbnail"
|
||||
/>
|
||||
<svg
|
||||
v-else
|
||||
v-once
|
||||
v-bind="attrs"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#woot-logo-clip-2342424e23u32098)">
|
||||
<path
|
||||
d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z"
|
||||
fill="#2781F6"
|
||||
/>
|
||||
<path
|
||||
d="M11.4172 11.4172H7.70831C5.66383 11.4172 4 9.75328 4 7.70828C4 5.66394 5.66383 4 7.70835 4C9.75339 4 11.4172 5.66394 11.4172 7.70828V11.4172Z"
|
||||
fill="white"
|
||||
stroke="white"
|
||||
stroke-width="0.1875"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="woot-logo-clip-2342424e23u32098">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,45 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
export function useChannelIcon(inbox) {
|
||||
const channelTypeIconMap = {
|
||||
'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-woot-x',
|
||||
'Channel::WebWidget': 'i-woot-website',
|
||||
'Channel::Whatsapp': 'i-woot-whatsapp',
|
||||
'Channel::Instagram': 'i-woot-instagram',
|
||||
'Channel::Tiktok': 'i-woot-tiktok',
|
||||
'Channel::Voice': 'i-woot-voice',
|
||||
};
|
||||
|
||||
const providerIconMap = {
|
||||
microsoft: 'i-woot-outlook',
|
||||
google: 'i-woot-gmail',
|
||||
};
|
||||
|
||||
const channelIcon = computed(() => {
|
||||
const inboxDetails = inbox.value || inbox;
|
||||
const type = inboxDetails.channel_type;
|
||||
let icon = channelTypeIconMap[type];
|
||||
|
||||
if (type === 'Channel::Email' && inboxDetails.provider) {
|
||||
if (Object.keys(providerIconMap).includes(inboxDetails.provider)) {
|
||||
icon = providerIconMap[inboxDetails.provider];
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for Twilio whatsapp
|
||||
if (type === 'Channel::TwilioSms' && inboxDetails.medium === 'whatsapp') {
|
||||
icon = 'i-woot-whatsapp';
|
||||
}
|
||||
|
||||
return icon ?? 'i-ri-global-fill';
|
||||
});
|
||||
|
||||
return channelIcon;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { useChannelIcon } from '../provider';
|
||||
|
||||
describe('useChannelIcon', () => {
|
||||
it('returns correct icon for API channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Api' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
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-woot-messenger');
|
||||
});
|
||||
|
||||
it('returns correct icon for WhatsApp channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Whatsapp' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-whatsapp');
|
||||
});
|
||||
|
||||
it('returns correct icon for Voice channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Voice' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-voice');
|
||||
});
|
||||
|
||||
it('returns correct icon for Line channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Line' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
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-woot-sms');
|
||||
});
|
||||
|
||||
it('returns correct icon for Telegram channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Telegram' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-telegram');
|
||||
});
|
||||
|
||||
it('returns correct icon for Twitter channel', () => {
|
||||
const inbox = { channel_type: 'Channel::TwitterProfile' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-x');
|
||||
});
|
||||
|
||||
it('returns correct icon for WebWidget channel', () => {
|
||||
const inbox = { channel_type: 'Channel::WebWidget' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
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-woot-instagram');
|
||||
});
|
||||
|
||||
it('returns correct icon for TikTok channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Tiktok' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-tiktok');
|
||||
});
|
||||
|
||||
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-woot-sms');
|
||||
});
|
||||
|
||||
it('returns WhatsApp icon for Twilio SMS with WhatsApp medium', () => {
|
||||
const inbox = {
|
||||
channel_type: 'Channel::TwilioSms',
|
||||
medium: 'whatsapp',
|
||||
};
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-whatsapp');
|
||||
});
|
||||
|
||||
it('returns chat icon for Twilio SMS with non-WhatsApp medium', () => {
|
||||
const inbox = {
|
||||
channel_type: 'Channel::TwilioSms',
|
||||
medium: 'sms',
|
||||
};
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-sms');
|
||||
});
|
||||
|
||||
it('returns chat icon for Twilio SMS with undefined medium', () => {
|
||||
const inbox = {
|
||||
channel_type: 'Channel::TwilioSms',
|
||||
medium: undefined,
|
||||
};
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-sms');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Email channel', () => {
|
||||
it('returns mail icon for generic email channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Email' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-mail');
|
||||
});
|
||||
|
||||
it('returns Microsoft icon for Microsoft email provider', () => {
|
||||
const inbox = {
|
||||
channel_type: 'Channel::Email',
|
||||
provider: 'microsoft',
|
||||
};
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-outlook');
|
||||
});
|
||||
|
||||
it('returns Google icon for Google email provider', () => {
|
||||
const inbox = {
|
||||
channel_type: 'Channel::Email',
|
||||
provider: 'google',
|
||||
};
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-woot-gmail');
|
||||
});
|
||||
});
|
||||
|
||||
it('returns default icon for unknown channel type', () => {
|
||||
const inbox = { channel_type: 'Channel::Unknown' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-ri-global-fill');
|
||||
});
|
||||
|
||||
it('returns default icon when channel type is undefined', () => {
|
||||
const inbox = {};
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-ri-global-fill');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user