feat: Update the design for integration page (#9825)

Combine integrations and applications page into one page. 

<img width="1182" alt="Screenshot 2024-07-23 at 3 30 51 PM"
src="https://github.com/user-attachments/assets/50920a6f-606f-44b3-b1e4-641046a14444">

Major changes:

- The app enabled?, active? checks are all moved to backend.
- The dashboard_apps integration is also now part of the apps.yml file.
- Updated the header design for the new settings pages.
- Merged the folders integrationapps and integrations. 
- Updated the copy to match the size of the card and provide clear
instruction.
- Only the list page is updated in this PR, rest of the pages are yet to
be migrated.


| Integration | Verified | 
| -- | -- |
| Dashboard Apps |  | 
| Dyte |  | 
| Slack |  | 
| Webhooks |  | 
| Dialogflow |  | 
| Google Translate |  | 
| OpenAI |  | 
| Linear |  |

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Pranav
2024-07-24 16:35:40 -07:00
committed by GitHub
parent 8e2b329202
commit 8b1d98af52
35 changed files with 318 additions and 438 deletions

View File

@@ -3,6 +3,9 @@
@import 'tailwindcss/utilities';
@import 'shared/assets/fonts/plus-jakarta';
@import 'shared/assets/fonts/InterDisplay/inter-display';
@import 'shared/assets/fonts/inter';
@import 'shared/assets/stylesheets/animations';
@import 'shared/assets/stylesheets/colors';
@import 'shared/assets/stylesheets/spacing';
@@ -42,6 +45,7 @@
}
@layer base {
// scss-lint:disable PropertySortOrder
:root {
--color-amber-25: 254 253 251;
@@ -213,6 +217,7 @@
--color-orange-800: 204 78 0;
--color-orange-900: 88 45 29;
}
// scss-lint:disable QualifyingElement
body.dark {
--color-amber-25: 31 19 0;

View File

@@ -0,0 +1,25 @@
<script setup>
import { useStoreGetters } from 'dashboard/composables/store';
import { computed } from 'vue';
const props = defineProps({
showOnCustomBrandedInstance: {
type: Boolean,
default: true,
},
});
const getters = useStoreGetters();
const isACustomBrandedInstance =
getters['globalConfig/isACustomBrandedInstance'];
const shouldShowContent = computed(
() => props.showOnCustomBrandedInstance || !isACustomBrandedInstance.value
);
</script>
<template>
<div v-if="shouldShowContent">
<slot />
</div>
</template>

View File

@@ -163,17 +163,6 @@ const settings = accountId => ({
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
toStateName: 'settings_integrations',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
},
{
icon: 'star-emphasis',
label: 'APPLICATIONS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/applications`),
toStateName: 'settings_applications',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
},

View File

@@ -1,8 +1,6 @@
{
"INTEGRATION_APPS": {
"FETCHING": "Fetching Integrations",
"NO_HOOK_CONFIGURED": "There are no %{integrationId} integrations configured in this account.",
"HEADER": "Applications",
"STATUS": {
"ENABLED": "Enabled",
"DISABLED": "Disabled"

View File

@@ -1,6 +1,7 @@
{
"INTEGRATION_SETTINGS": {
"HEADER": "Integrations",
"LOADING": "Fetching integrations",
"WEBHOOK": {
"SUBSCRIBED_EVENTS": "Subscribed Events",
"FORM": {

View File

@@ -1,6 +1,9 @@
{
"INTEGRATION_SETTINGS": {
"HEADER": "Integrations",
"DESCRIPTION": "Chatwoot integrates with multiple tools and services to improve your team's efficiency. Explore the list below to configure your favorite apps.",
"LEARN_MORE": "Learn more about integrations",
"LOADING": "Fetching integrations",
"WEBHOOK": {
"SUBSCRIBED_EVENTS": "Subscribed Events",
"FORM": {
@@ -37,7 +40,10 @@
"LIST": {
"404": "There are no webhooks configured for this account.",
"TITLE": "Manage webhooks",
"TABLE_HEADER": ["Webhook endpoint", "Actions"]
"TABLE_HEADER": [
"Webhook endpoint",
"Actions"
]
},
"EDIT": {
"BUTTON_TEXT": "Edit",
@@ -169,7 +175,10 @@
"LIST": {
"404": "There are no dashboard apps configured on this account yet",
"LOADING": "Fetching dashboard apps...",
"TABLE_HEADER": ["Name", "Endpoint"],
"TABLE_HEADER": [
"Name",
"Endpoint"
],
"EDIT_TOOLTIP": "Edit app",
"DELETE_TOOLTIP": "Delete app"
},

View File

@@ -1,6 +1,6 @@
{
"SLA": {
"HEADER": "SLA",
"HEADER": "Service Level Agreements",
"ADD_ACTION": "Add SLA",
"ADD_ACTION_LONG": "Create a new SLA Policy",
"DESCRIPTION": "Service Level Agreements (SLAs) are contracts that define clear expectations between your team and customers. They establish standards for response and resolution times, creating a framework for accountability and ensures a consistent, high-quality experience.",
@@ -105,4 +105,4 @@
"HIDE": "Hide {count} rows"
}
}
}
}

View File

@@ -9,9 +9,9 @@ defineProps({
<template>
<div
class="flex flex-col w-full h-full px-5 pt-8 pb-3 m-0 overflow-auto bg-white sm:px-16 sm:pt-16 dark:bg-slate-900"
class="flex flex-col w-full h-full m-0 px-8 lg:px-16 py-8 overflow-auto bg-white dark:bg-slate-900"
>
<div class="flex items-start max-w-[900px] w-full">
<div class="flex items-start w-full max-w-6xl mx-auto">
<keep-alive v-if="keepAlive">
<router-view />
</keep-alive>

View File

@@ -1,5 +1,7 @@
<script setup>
defineProps({
import CustomBrandPolicyWrapper from 'dashboard/components/CustomBrandPolicyWrapper.vue';
import { getHelpUrlForFeature } from '../../../../helper/featureHelper';
const props = defineProps({
title: {
type: String,
required: true,
@@ -9,10 +11,6 @@ defineProps({
required: true,
},
iconName: {
type: String,
required: true,
},
href: {
type: String,
default: '',
},
@@ -20,8 +18,14 @@ defineProps({
type: String,
default: '',
},
featureName: {
type: String,
default: '',
},
});
const helpURL = getHelpUrlForFeature(props.featureName);
const openInNewTab = url => {
if (!url) return;
window.open(url, '_blank', 'noopener noreferrer');
@@ -29,12 +33,11 @@ const openInNewTab = url => {
</script>
<template>
<div class="flex flex-col items-start w-full gap-4">
<!-- Header section with icon, title and action button -->
<div class="flex flex-col items-start w-full gap-3 pt-4">
<div class="flex items-center justify-between w-full gap-4">
<!-- Icon and title container -->
<div class="flex items-center gap-3">
<div
v-if="iconName"
class="flex items-center w-10 h-10 p-1 rounded-full bg-woot-25/60 dark:bg-woot-900/60"
>
<div
@@ -49,7 +52,7 @@ const openInNewTab = url => {
</div>
</div>
<h1
class="text-2xl font-medium tracking-[-1.5%] text-slate-900 dark:text-slate-25"
class="text-2xl font-semibold font-interDisplay tracking-[0.3px] text-slate-900 dark:text-slate-25"
>
{{ title }}
</h1>
@@ -59,44 +62,43 @@ const openInNewTab = url => {
<slot name="actions" />
</div>
</div>
<!-- Description and optional link -->
<div
class="flex flex-col gap-2 text-slate-600 dark:text-slate-300 max-w-[721px] w-full"
>
<div class="flex flex-col gap-3 text-slate-600 dark:text-slate-300 w-full">
<p
class="mb-0 text-sm font-normal tracking-[0.5%] line-clamp-5 sm:line-clamp-none"
class="mb-0 text-base font-normal line-clamp-5 sm:line-clamp-none max-w-3xl"
>
<slot name="description">{{ description }}</slot>
</p>
<!-- Conditional link -->
<a
v-if="href && linkText"
:href="href"
target="_blank"
rel="noopener noreferrer"
class="sm:inline-flex hidden tracking-[-0.6%] gap-1 w-fit items-center text-woot-500 dark:text-woot-500 text-sm font-medium tracking=[-0.6%] hover:underline"
>
{{ linkText }}
<fluent-icon
size="16"
icon="chevron-right"
type="outline"
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
/>
</a>
<CustomBrandPolicyWrapper :show-on-custom-branded-instance="false">
<a
v-if="helpURL && linkText"
:href="helpURL"
target="_blank"
rel="noopener noreferrer"
class="sm:inline-flex hidden tracking-[-0.6%] gap-1 w-fit items-center text-woot-500 dark:text-woot-500 text-sm font-medium tracking=[-0.6%] hover:underline"
>
{{ linkText }}
<fluent-icon
size="16"
icon="chevron-right"
type="outline"
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
/>
</a>
</CustomBrandPolicyWrapper>
</div>
<!-- Mobile view for actions and link -->
<div class="flex items-start justify-start w-full gap-3 sm:hidden">
<slot name="actions" />
<woot-button
v-if="href && linkText"
color-scheme="secondary"
icon="arrow-outwards"
class="flex-row-reverse rounded-xl min-w-0 !bg-slate-50 !text-slate-900 dark:!text-white dark:!bg-slate-800"
@click="openInNewTab(href)"
>
{{ linkText }}
</woot-button>
<CustomBrandPolicyWrapper :show-on-custom-branded-instance="false">
<woot-button
v-if="helpURL && linkText"
color-scheme="secondary"
icon="arrow-outwards"
class="flex-row-reverse rounded-xl min-w-0 !bg-slate-50 !text-slate-900 dark:!text-white dark:!bg-slate-800"
@click="openInNewTab(helpURL)"
>
{{ linkText }}
</woot-button>
</CustomBrandPolicyWrapper>
</div>
</div>
</template>

View File

@@ -12,16 +12,15 @@ defineProps({
</script>
<template>
<div
class="flex relative flex-col sm:flex-row p-4 gap-4 sm:p-6 justify-between shadow-sm group bg-white border border-solid rounded-xl dark:bg-slate-800 border-slate-75 dark:border-slate-700/50 max-w-[900px] w-full"
class="flex relative flex-col sm:flex-row p-4 gap-4 sm:p-6 justify-between shadow-sm group bg-white border border-solid rounded-xl dark:bg-slate-800 border-slate-75 dark:border-slate-700/50 w-full"
>
<!-- left side section -->
<slot name="leftSection">
<div class="flex flex-col min-w-0 items-start gap-3 max-w-[480px] w-full">
<div
class="flex items-center justify-between w-full gap-3 sm:justify-normal whitespace-nowrap"
>
<h3
class="justify-between text-sm tracking-[-0.6%] font-medium truncate w-fit sm:justify-normal text-slate-900 dark:text-slate-25"
class="justify-between text-sm font-medium truncate w-fit sm:justify-normal text-slate-900 dark:text-slate-25"
>
<slot name="title">
{{ title }}
@@ -30,7 +29,7 @@ defineProps({
<slot name="label" />
</div>
<p
class="text-sm text-slate-600 tracking-[0.5%] dark:text-slate-300 max-w-[400px] w-full line-clamp-2"
class="text-base text-slate-600 dark:text-slate-300 max-w-[400px] w-full line-clamp-2"
>
<slot name="description">
{{ description }}

View File

@@ -1,62 +0,0 @@
<template>
<div class="flex-grow flex-shrink p-4 overflow-auto">
<div class="flex flex-col">
<div v-if="uiFlags.isFetching" class="mx-auto my-0">
<woot-loading-state :message="$t('INTEGRATION_APPS.FETCHING')" />
</div>
<div v-else class="w-full">
<div>
<div
v-for="item in enabledIntegrations"
:key="item.id"
class="p-4 mb-4 bg-white border border-solid rounded-sm dark:bg-slate-800 border-slate-75 dark:border-slate-700/50"
>
<integration-item
:integration-id="item.id"
:integration-logo="item.logo"
:integration-name="item.name"
:integration-description="item.description"
:integration-enabled="item.hooks.length"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useStoreGetters, useStore } from 'dashboard/composables/store';
import { computed, onMounted } from 'vue';
import IntegrationItem from './IntegrationItem.vue';
const store = useStore();
const getters = useStoreGetters();
const uiFlags = getters['integrations/getUIFlags'];
const accountId = getters.getCurrentAccountId;
const integrationList = computed(() => {
return getters['integrations/getAppIntegrations'].value;
});
const isLinearIntegrationEnabled = computed(() => {
return getters['accounts/isFeatureEnabledonAccount'].value(
accountId.value,
'linear_integration'
);
});
const enabledIntegrations = computed(() => {
if (!isLinearIntegrationEnabled.value) {
return integrationList.value.filter(
integration => integration.id !== 'linear'
);
}
return integrationList.value;
});
onMounted(() => {
store.dispatch('integrations/get');
});
</script>

View File

@@ -1,95 +0,0 @@
<template>
<div class="flex">
<div class="flex h-[6.25rem] w-[6.25rem]">
<img
:src="`/dashboard/images/integrations/${integrationId}.png`"
class="max-w-full p-6"
/>
</div>
<div class="flex flex-col justify-center m-0 mx-4 flex-1">
<h3 class="text-xl font-medium mb-1 text-slate-800 dark:text-slate-100">
{{ integrationName }}
</h3>
<p class="text-slate-700 dark:text-slate-200">
{{
useInstallationName(
integrationDescription,
globalConfig.installationName
)
}}
</p>
</div>
<div class="flex justify-center items-center mb-0 w-[15%]">
<woot-label
:title="labelText"
:color-scheme="labelColor"
class="text-xs rounded-sm"
/>
</div>
<div class="flex justify-center items-center mb-0 w-[15%]">
<router-link
:to="
frontendURL(
`accounts/${accountId}/settings/applications/` + integrationId
)
"
>
<woot-button icon="settings">
{{ $t('INTEGRATION_APPS.CONFIGURE') }}
</woot-button>
</router-link>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { frontendURL } from '../../../../helper/URLHelper';
import WootLabel from 'dashboard/components/ui/Label.vue';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
export default {
components: {
WootLabel,
},
mixins: [globalConfigMixin],
props: {
integrationId: {
type: [String, Number],
required: true,
},
integrationLogo: {
type: String,
default: '',
},
integrationName: {
type: String,
default: '',
},
integrationDescription: {
type: String,
default: '',
},
integrationEnabled: {
type: Number,
default: 0,
},
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
globalConfig: 'globalConfig/get',
}),
labelText() {
return this.integrationEnabled
? this.$t('INTEGRATION_APPS.STATUS.ENABLED')
: this.$t('INTEGRATION_APPS.STATUS.DISABLED');
},
labelColor() {
return this.integrationEnabled ? 'success' : 'secondary';
},
},
methods: {
frontendURL,
},
};
</script>

View File

@@ -1,47 +0,0 @@
import { frontendURL } from '../../../../helper/URLHelper';
const SettingsContent = () => import('../Wrapper.vue');
const IntegrationHooks = () => import('./IntegrationHooks.vue');
const Index = () => import('./Index.vue');
export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/applications'),
component: SettingsContent,
props: params => {
const showBackButton = params.name !== 'settings_applications';
const backUrl =
params.name === 'settings_applications_integration'
? { name: 'settings_applications' }
: '';
return {
headerTitle: 'INTEGRATION_APPS.HEADER',
icon: 'star-emphasis',
showBackButton,
backUrl,
};
},
children: [
{
path: '',
name: 'settings_applications',
component: Index,
meta: {
permissions: ['administrator'],
},
},
{
path: ':integration_id',
name: 'settings_applications_integration',
component: IntegrationHooks,
meta: {
permissions: ['administrator'],
},
props: route => ({
integrationId: route.params.integration_id,
}),
},
],
},
],
};

View File

@@ -1,57 +1,51 @@
<script setup>
import { useStoreGetters, useStore } from 'dashboard/composables/store';
import { computed, onMounted } from 'vue';
import IntegrationItem from './IntegrationItem.vue';
import SettingsLayout from '../SettingsLayout.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
const store = useStore();
const getters = useStoreGetters();
const uiFlags = getters['integrations/getUIFlags'];
const integrationList = computed(
() => getters['integrations/getAppIntegrations'].value
);
onMounted(() => {
store.dispatch('integrations/get');
});
</script>
<template>
<div class="flex-shrink flex-grow overflow-auto p-4">
<div class="flex flex-col">
<div class="flex flex-col">
<div>
<div
v-for="item in integrationsList"
<SettingsLayout
:is-loading="uiFlags.isFetching"
:loading-message="$t('INTEGRATION_SETTINGS.LOADING')"
>
<template #header>
<BaseSettingsHeader
:title="$t('INTEGRATION_SETTINGS.HEADER')"
:description="$t('INTEGRATION_SETTINGS.DESCRIPTION')"
:link-text="$t('INTEGRATION_SETTINGS.LEARN_MORE')"
feature-name="integrations"
/>
</template>
<template #body>
<div class="flex-grow flex-shrink overflow-auto font-inter">
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<integration-item
v-for="item in integrationList"
:id="item.id"
:key="item.id"
class="bg-white dark:bg-slate-800 border border-solid border-slate-75 dark:border-slate-700/50 rounded-sm mb-4 p-4"
>
<integration
:integration-id="item.id"
:integration-logo="item.logo"
:integration-name="item.name"
:integration-description="item.description"
:integration-enabled="item.enabled"
:integration-action="item.action"
/>
</div>
<div
class="bg-white dark:bg-slate-800 border border-solid border-slate-75 dark:border-slate-700/50 rounded-sm mb-4 p-4"
>
<integration
integration-id="dashboard_apps"
:integration-name="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.TITLE')
"
:integration-description="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DESCRIPTION')
"
integration-enabled
integration-action="/dashboard-apps"
/>
</div>
:logo="item.logo"
:name="item.name"
:description="item.description"
:enabled="item.enabled"
/>
</div>
</div>
</div>
</div>
</template>
</SettingsLayout>
</template>
<script>
import { mapGetters } from 'vuex';
import Integration from './Integration.vue';
export default {
components: {
Integration,
},
computed: {
...mapGetters({
integrationsList: 'integrations/getIntegrations',
}),
},
mounted() {
this.$store.dispatch('integrations/get');
},
};
</script>

View File

@@ -1 +0,0 @@
<!-- to be removed non using component -->

View File

@@ -0,0 +1,86 @@
<script setup>
import { computed } from 'vue';
import { useStoreGetters } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { frontendURL } from 'dashboard/helper/URLHelper';
import { useInstallationName } from 'shared/mixins/globalConfigMixin';
const props = defineProps({
id: {
type: [String, Number],
required: true,
},
name: {
type: String,
default: '',
},
description: {
type: String,
default: '',
},
enabled: {
type: Boolean,
default: false,
},
});
const getters = useStoreGetters();
const accountId = getters.getCurrentAccountId;
const globalConfig = getters['globalConfig/get'];
const { t } = useI18n();
const integrationStatus = computed(() =>
props.enabled
? t('INTEGRATION_APPS.STATUS.ENABLED')
: t('INTEGRATION_APPS.STATUS.DISABLED')
);
const integrationStatusColor = computed(() =>
props.enabled ? 'bg-green-500' : 'bg-slate-200'
);
const actionURL = computed(() =>
frontendURL(`accounts/${accountId.value}/settings/integrations/${props.id}`)
);
</script>
<template>
<div
class="flex flex-col flex-1 p-6 bg-white border border-solid rounded-md dark:bg-slate-800 border-slate-50 dark:border-slate-700/50"
>
<div class="flex items-start justify-between">
<div class="flex h-12 w-12 mb-4">
<img
:src="`/dashboard/images/integrations/${id}.png`"
class="max-w-full rounded-md border border-slate-50 dark:border-slate-700/50 shadow-sm block dark:hidden bg-white dark:bg-slate-900"
/>
<img
:src="`/dashboard/images/integrations/${id}-dark.png`"
class="max-w-full rounded-md border border-slate-50 dark:border-slate-700/50 shadow-sm hidden dark:block bg-white dark:bg-slate-900"
/>
</div>
<fluent-icon
v-tooltip="integrationStatus"
size="20"
class="text-white p-0.5 rounded-full"
:class="integrationStatusColor"
icon="checkmark"
/>
</div>
<div class="flex flex-col m-0 flex-1">
<div
class="font-medium mb-2 text-slate-800 dark:text-slate-100 flex justify-between items-center"
>
<span class="text-base font-semibold">{{ name }}</span>
<router-link :to="actionURL">
<woot-button class="clear link">
{{ $t('INTEGRATION_APPS.CONFIGURE') }}
</woot-button>
</router-link>
</div>
<p class="text-slate-700 dark:text-slate-200">
{{ useInstallationName(description, globalConfig.installationName) }}
</p>
</div>
</div>
</template>

View File

@@ -1,14 +1,30 @@
import { frontendURL } from '../../../../helper/URLHelper';
const SettingsContent = () => import('../Wrapper.vue');
const SettingsWrapper = () => import('../SettingsWrapper.vue');
const IntegrationHooks = () => import('./IntegrationHooks.vue');
const Index = () => import('./Index.vue');
const Webhook = () => import('./Webhooks/Index.vue');
const DashboardApps = () => import('./DashboardApps/Index.vue');
const ShowIntegration = () => import('./ShowIntegration.vue');
const Slack = () => import('./Slack.vue');
const Index = () => import('./Index.vue');
const SettingsContent = () => import('../Wrapper.vue');
export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/integrations'),
component: SettingsWrapper,
props: {},
children: [
{
path: '',
name: 'settings_applications',
component: Index,
meta: {
permissions: ['administrator'],
},
},
],
},
{
path: frontendURL('accounts/:accountId/settings/integrations'),
component: SettingsContent,
@@ -26,14 +42,6 @@ export default {
};
},
children: [
{
path: '',
name: 'settings_integrations',
component: Index,
meta: {
permissions: ['administrator'],
},
},
{
path: 'webhook',
component: Webhook,
@@ -61,17 +69,14 @@ export default {
},
{
path: ':integration_id',
name: 'settings_integrations_integration',
component: ShowIntegration,
name: 'settings_applications_integration',
component: IntegrationHooks,
meta: {
permissions: ['administrator'],
},
props: route => {
return {
integrationId: route.params.integration_id,
code: route.query.code,
};
},
props: route => ({
integrationId: route.params.integration_id,
}),
},
],
},

View File

@@ -9,7 +9,6 @@ import billing from './billing/billing.routes';
import campaigns from './campaigns/campaigns.routes';
import canned from './canned/canned.routes';
import inbox from './inbox/inbox.routes';
import integrationapps from './integrationapps/integrations.routes';
import integrations from './integrations/integrations.routes';
import labels from './labels/labels.routes';
import macros from './macros/macros.routes';
@@ -44,7 +43,6 @@ export default {
...campaigns.routes,
...canned.routes,
...inbox.routes,
...integrationapps.routes,
...integrations.routes,
...labels.routes,
...macros.routes,

View File

@@ -7,14 +7,15 @@ defineProps({
default: true,
},
});
defineEmits(['click']);
</script>
<template>
<base-settings-header
<BaseSettingsHeader
:title="$t('SLA.HEADER')"
:description="$t('SLA.DESCRIPTION')"
:link-text="$t('SLA.LEARN_MORE')"
href="/"
icon-name="document-list-clock"
feature-name="sla"
>
<template v-if="showActions" #actions>
<woot-button
@@ -26,5 +27,5 @@ defineProps({
{{ $t('SLA.ADD_ACTION') }}
</woot-button>
</template>
</base-settings-header>
</BaseSettingsHeader>
</template>

View File

@@ -20,21 +20,9 @@ const state = {
},
};
const isAValidAppIntegration = integration => {
return [
'dialogflow',
'dyte',
'google_translate',
'openai',
'linear',
].includes(integration.id);
};
export const getters = {
getIntegrations($state) {
return $state.records.filter(item => !isAValidAppIntegration(item));
},
getAppIntegrations($state) {
return $state.records.filter(item => isAValidAppIntegration(item));
return $state.records;
},
getIntegration: $state => integrationId => {
const [integration] = $state.records.filter(

View File

@@ -1,60 +1,9 @@
import { getters } from '../../integrations';
describe('#getters', () => {
it('getIntegrations', () => {
const state = {
records: [
{
id: 'test1',
name: 'test1',
logo: 'test',
enabled: true,
},
{
id: 'test2',
name: 'test2',
logo: 'test',
enabled: true,
},
{
id: 'dyte',
name: 'dyte',
logo: 'test',
enabled: true,
},
{
id: 'dialogflow',
name: 'dialogflow',
logo: 'test',
enabled: true,
},
],
};
expect(getters.getIntegrations(state)).toEqual([
{
id: 'test1',
name: 'test1',
logo: 'test',
enabled: true,
},
{
id: 'test2',
name: 'test2',
logo: 'test',
enabled: true,
},
]);
});
it('getAppIntegrations', () => {
const state = {
records: [
{
id: 'test1',
name: 'test1',
logo: 'test',
enabled: true,
},
{
id: 'dyte',
name: 'dyte',

View File

@@ -1,8 +1,12 @@
export const useInstallationName = (str, installationName) => {
if (str && installationName) {
return str.replace(/Chatwoot/g, installationName);
}
return str;
};
export default {
methods: {
// eslint-disable-next-line default-param-last
useInstallationName(str = '', installationName) {
return str.replace(/Chatwoot/g, installationName);
},
useInstallationName,
},
};