mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 20:18:08 +00:00
chore: Update settings page to match the new design colors (#11072)
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Icon from 'next/icon/Icon.vue';
|
|
||||||
import router from '../../routes/index';
|
import router from '../../routes/index';
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
backUrl: {
|
backUrl: {
|
||||||
@@ -24,24 +23,16 @@ const goBack = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonStyleClass = props.compact
|
const buttonStyleClass = props.compact ? 'text-sm' : 'text-base';
|
||||||
? 'text-sm text-n-slate-11'
|
|
||||||
: 'text-base text-n-blue-text';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="flex items-center p-0 font-normal cursor-pointer gap-1"
|
class="flex items-center p-0 font-normal cursor-pointer text-n-slate-11"
|
||||||
:class="buttonStyleClass"
|
:class="buttonStyleClass"
|
||||||
@click.capture="goBack"
|
@click.capture="goBack"
|
||||||
>
|
>
|
||||||
<Icon
|
<fluent-icon icon="chevron-left" class="-ml-1" />
|
||||||
icon="i-lucide-chevron-left"
|
|
||||||
class="ltr:-ml-1 rtl:-mr-1"
|
|
||||||
:class="
|
|
||||||
props.compact ? 'text-n-slate-11 size-4' : 'text-n-blue-text size-5'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
{{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }}
|
{{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -54,11 +54,9 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex justify-between items-center h-14 min-h-[3.5rem] px-4 py-2 bg-n-background border-b border-n-weak"
|
class="flex justify-between items-center h-20 min-h-[3.5rem] px-4 py-2 bg-n-background"
|
||||||
>
|
>
|
||||||
<h1
|
<h1 class="flex items-center mb-0 text-2xl text-n-slate-12">
|
||||||
class="flex items-center mb-0 text-2xl text-slate-900 dark:text-slate-100"
|
|
||||||
>
|
|
||||||
<woot-sidemenu-icon v-if="showSidemenuIcon" />
|
<woot-sidemenu-icon v-if="showSidemenuIcon" />
|
||||||
<BackButton
|
<BackButton
|
||||||
v-if="showBackButton"
|
v-if="showBackButton"
|
||||||
@@ -66,21 +64,16 @@ export default {
|
|||||||
:back-url="backUrl"
|
:back-url="backUrl"
|
||||||
class="ml-2 mr-4"
|
class="ml-2 mr-4"
|
||||||
/>
|
/>
|
||||||
<fluent-icon
|
|
||||||
v-if="icon"
|
|
||||||
:icon="icon"
|
|
||||||
:class="iconClass"
|
|
||||||
class="hidden ml-1 mr-2 rtl:ml-2 rtl:mr-1 md:block"
|
|
||||||
/>
|
|
||||||
<slot />
|
<slot />
|
||||||
<span class="text-2xl font-medium text-slate-900 dark:text-slate-100">
|
<span class="text-xl font-medium text-slate-900 dark:text-slate-100">
|
||||||
{{ headerTitle }}
|
{{ headerTitle }}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="showNewButton && isAdmin"
|
v-if="showNewButton && isAdmin"
|
||||||
:to="buttonRoute"
|
:to="buttonRoute"
|
||||||
class="button success button--fixed-top px-3.5 py-1 rounded-[5px] flex gap-2"
|
class="button success button--fixed-top rounded-[5px] flex gap-2"
|
||||||
>
|
>
|
||||||
<fluent-icon icon="add-circle" />
|
<fluent-icon icon="add-circle" />
|
||||||
<span class="button__content">
|
<span class="button__content">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const props = defineProps({
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const showNewButton = computed(
|
const showNewButton = computed(
|
||||||
() => props.newButtonRoutes.length !== 0 && !props.showBackButton
|
() => props.newButtonRoutes.length && !props.showBackButton
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -25,21 +25,24 @@ const showNewButton = computed(
|
|||||||
<div
|
<div
|
||||||
class="flex flex-1 h-full justify-between flex-col m-0 bg-n-background overflow-auto"
|
class="flex flex-1 h-full justify-between flex-col m-0 bg-n-background overflow-auto"
|
||||||
>
|
>
|
||||||
<SettingsHeader
|
<div class="max-w-6xl mx-auto w-full h-full">
|
||||||
button-route="new"
|
<SettingsHeader
|
||||||
:icon="icon"
|
button-route="new"
|
||||||
:header-title="t(headerTitle)"
|
:icon="icon"
|
||||||
:button-text="t(headerButtonText)"
|
:header-title="t(headerTitle)"
|
||||||
:show-back-button="showBackButton"
|
:button-text="t(headerButtonText)"
|
||||||
:back-url="backUrl"
|
:show-back-button="showBackButton"
|
||||||
:show-new-button="showNewButton"
|
:back-url="backUrl"
|
||||||
:show-sidemenu-icon="showSidemenuIcon"
|
:show-new-button="showNewButton"
|
||||||
/>
|
:show-sidemenu-icon="showSidemenuIcon"
|
||||||
<router-view v-slot="{ Component }">
|
/>
|
||||||
<keep-alive v-if="keepAlive">
|
|
||||||
<component :is="Component" />
|
<router-view v-slot="{ Component }" class="px-5">
|
||||||
</keep-alive>
|
<component :is="Component" v-if="!keepAlive" :key="$route.fullPath" />
|
||||||
<component :is="Component" v-else />
|
<keep-alive v-else>
|
||||||
</router-view>
|
<component :is="Component" :key="$route.fullPath" />
|
||||||
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -9,8 +9,14 @@ import { useAccount } from 'dashboard/composables/useAccount';
|
|||||||
import { FEATURE_FLAGS } from '../../../../featureFlags';
|
import { FEATURE_FLAGS } from '../../../../featureFlags';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||||
|
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
||||||
|
import V4Button from 'dashboard/components-next/button/Button.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
BaseSettingsHeader,
|
||||||
|
V4Button,
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { updateUISettings } = useUISettings();
|
const { updateUISettings } = useUISettings();
|
||||||
const { enabledLanguages } = useConfig();
|
const { enabledLanguages } = useConfig();
|
||||||
@@ -161,131 +167,130 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-grow flex-shrink min-w-0 p-6 overflow-auto">
|
<div class="flex flex-col w-full">
|
||||||
<form v-if="!uiFlags.isFetchingItem" @submit.prevent="updateAccount">
|
<BaseSettingsHeader :title="$t('GENERAL_SETTINGS.TITLE')">
|
||||||
<div
|
<template #actions>
|
||||||
class="flex flex-row p-4 border-b border-slate-25 dark:border-slate-800"
|
<V4Button blue :loading="isUpdating" @click="updateAccount">
|
||||||
>
|
{{ $t('GENERAL_SETTINGS.SUBMIT') }}
|
||||||
|
</V4Button>
|
||||||
|
</template>
|
||||||
|
</BaseSettingsHeader>
|
||||||
|
<div class="flex-grow flex-shrink min-w-0 overflow-auto mt-3">
|
||||||
|
<form v-if="!uiFlags.isFetchingItem" @submit.prevent="updateAccount">
|
||||||
<div
|
<div
|
||||||
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0"
|
class="flex flex-row border-b border-slate-25 dark:border-slate-800"
|
||||||
>
|
>
|
||||||
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
|
<div
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.TITLE') }}
|
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0"
|
||||||
</h4>
|
|
||||||
<p>{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.NOTE') }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
|
|
||||||
<label :class="{ error: v$.name.$error }">
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.NAME.LABEL') }}
|
|
||||||
<input
|
|
||||||
v-model="name"
|
|
||||||
type="text"
|
|
||||||
:placeholder="$t('GENERAL_SETTINGS.FORM.NAME.PLACEHOLDER')"
|
|
||||||
@blur="v$.name.$touch"
|
|
||||||
/>
|
|
||||||
<span v-if="v$.name.$error" class="message">
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.NAME.ERROR') }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label :class="{ error: v$.locale.$error }">
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.LANGUAGE.LABEL') }}
|
|
||||||
<select v-model="locale">
|
|
||||||
<option
|
|
||||||
v-for="lang in languagesSortedByCode"
|
|
||||||
:key="lang.iso_639_1_code"
|
|
||||||
:value="lang.iso_639_1_code"
|
|
||||||
>
|
|
||||||
{{ lang.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<span v-if="v$.locale.$error" class="message">
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.LANGUAGE.ERROR') }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<label v-if="featureInboundEmailEnabled">
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.FEATURES.INBOUND_EMAIL_ENABLED') }}
|
|
||||||
</label>
|
|
||||||
<label v-if="featureCustomReplyDomainEnabled">
|
|
||||||
{{
|
|
||||||
$t('GENERAL_SETTINGS.FORM.FEATURES.CUSTOM_EMAIL_DOMAIN_ENABLED')
|
|
||||||
}}
|
|
||||||
</label>
|
|
||||||
<label v-if="featureCustomReplyDomainEnabled">
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.DOMAIN.LABEL') }}
|
|
||||||
<input
|
|
||||||
v-model="domain"
|
|
||||||
type="text"
|
|
||||||
:placeholder="$t('GENERAL_SETTINGS.FORM.DOMAIN.PLACEHOLDER')"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label v-if="featureCustomReplyEmailEnabled">
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.SUPPORT_EMAIL.LABEL') }}
|
|
||||||
<input
|
|
||||||
v-model="supportEmail"
|
|
||||||
type="text"
|
|
||||||
:placeholder="
|
|
||||||
$t('GENERAL_SETTINGS.FORM.SUPPORT_EMAIL.PLACEHOLDER')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
v-if="showAutoResolutionConfig"
|
|
||||||
:class="{ error: v$.autoResolveDuration.$error }"
|
|
||||||
>
|
>
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.LABEL') }}
|
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
|
||||||
<input
|
{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.TITLE') }}
|
||||||
v-model="autoResolveDuration"
|
</h4>
|
||||||
type="number"
|
<p>{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.NOTE') }}</p>
|
||||||
:placeholder="
|
</div>
|
||||||
$t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.PLACEHOLDER')
|
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
|
||||||
"
|
<label :class="{ error: v$.name.$error }">
|
||||||
@blur="v$.autoResolveDuration.$touch"
|
{{ $t('GENERAL_SETTINGS.FORM.NAME.LABEL') }}
|
||||||
/>
|
<input
|
||||||
<span v-if="v$.autoResolveDuration.$error" class="message">
|
v-model="name"
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.ERROR') }}
|
type="text"
|
||||||
</span>
|
:placeholder="$t('GENERAL_SETTINGS.FORM.NAME.PLACEHOLDER')"
|
||||||
</label>
|
@blur="v$.name.$touch"
|
||||||
|
/>
|
||||||
|
<span v-if="v$.name.$error" class="message">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.NAME.ERROR') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label :class="{ error: v$.locale.$error }">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.LANGUAGE.LABEL') }}
|
||||||
|
<select v-model="locale">
|
||||||
|
<option
|
||||||
|
v-for="lang in languagesSortedByCode"
|
||||||
|
:key="lang.iso_639_1_code"
|
||||||
|
:value="lang.iso_639_1_code"
|
||||||
|
>
|
||||||
|
{{ lang.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<span v-if="v$.locale.$error" class="message">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.LANGUAGE.ERROR') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label v-if="featureInboundEmailEnabled">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.FEATURES.INBOUND_EMAIL_ENABLED') }}
|
||||||
|
</label>
|
||||||
|
<label v-if="featureCustomReplyDomainEnabled">
|
||||||
|
{{
|
||||||
|
$t('GENERAL_SETTINGS.FORM.FEATURES.CUSTOM_EMAIL_DOMAIN_ENABLED')
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
<label v-if="featureCustomReplyDomainEnabled">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.DOMAIN.LABEL') }}
|
||||||
|
<input
|
||||||
|
v-model="domain"
|
||||||
|
type="text"
|
||||||
|
:placeholder="$t('GENERAL_SETTINGS.FORM.DOMAIN.PLACEHOLDER')"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label v-if="featureCustomReplyEmailEnabled">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.SUPPORT_EMAIL.LABEL') }}
|
||||||
|
<input
|
||||||
|
v-model="supportEmail"
|
||||||
|
type="text"
|
||||||
|
:placeholder="
|
||||||
|
$t('GENERAL_SETTINGS.FORM.SUPPORT_EMAIL.PLACEHOLDER')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
v-if="showAutoResolutionConfig"
|
||||||
|
:class="{ error: v$.autoResolveDuration.$error }"
|
||||||
|
>
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.LABEL') }}
|
||||||
|
<input
|
||||||
|
v-model="autoResolveDuration"
|
||||||
|
type="number"
|
||||||
|
:placeholder="
|
||||||
|
$t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.PLACEHOLDER')
|
||||||
|
"
|
||||||
|
@blur="v$.autoResolveDuration.$touch"
|
||||||
|
/>
|
||||||
|
<span v-if="v$.autoResolveDuration.$error" class="message">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.ERROR') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
<div
|
<woot-loading-state v-if="uiFlags.isFetchingItem" />
|
||||||
class="flex flex-row p-4 border-slate-25 dark:border-slate-700 text-black-900 dark:text-slate-300"
|
</div>
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0"
|
|
||||||
>
|
|
||||||
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.TITLE') }}
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.NOTE') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
|
|
||||||
<woot-code :script="getAccountId" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-4 text-sm text-center">
|
|
||||||
<div>{{ `v${globalConfig.appVersion}` }}</div>
|
|
||||||
<div v-if="hasAnUpdateAvailable && globalConfig.displayManifest">
|
|
||||||
{{
|
|
||||||
$t('GENERAL_SETTINGS.UPDATE_CHATWOOT', {
|
|
||||||
latestChatwootVersion: latestChatwootVersion,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div class="build-id">
|
|
||||||
<div>{{ `Build ${globalConfig.gitSha}` }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<woot-submit-button
|
<div class="flex flex-row">
|
||||||
class="button nice success button--fixed-top"
|
<div class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0">
|
||||||
:button-text="$t('GENERAL_SETTINGS.SUBMIT')"
|
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
|
||||||
:loading="isUpdating"
|
{{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.TITLE') }}
|
||||||
/>
|
</h4>
|
||||||
</form>
|
<p>
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.NOTE') }}
|
||||||
<woot-loading-state v-if="uiFlags.isFetchingItem" />
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
|
||||||
|
<woot-code :script="getAccountId" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 text-sm text-center">
|
||||||
|
<div>{{ `v${globalConfig.appVersion}` }}</div>
|
||||||
|
<div v-if="hasAnUpdateAvailable && globalConfig.displayManifest">
|
||||||
|
{{
|
||||||
|
$t('GENERAL_SETTINGS.UPDATE_CHATWOOT', {
|
||||||
|
latestChatwootVersion: latestChatwootVersion,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="build-id">
|
||||||
|
<div>{{ `Build ${globalConfig.gitSha}` }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { frontendURL } from '../../../../helper/URLHelper';
|
import { frontendURL } from '../../../../helper/URLHelper';
|
||||||
import SettingsContent from '../Wrapper.vue';
|
|
||||||
import Index from './Index.vue';
|
import Index from './Index.vue';
|
||||||
|
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
routes: [
|
routes: [
|
||||||
@@ -9,12 +9,7 @@ export default {
|
|||||||
meta: {
|
meta: {
|
||||||
permissions: ['administrator'],
|
permissions: ['administrator'],
|
||||||
},
|
},
|
||||||
component: SettingsContent,
|
component: SettingsWrapper,
|
||||||
props: {
|
|
||||||
headerTitle: 'GENERAL_SETTINGS.TITLE',
|
|
||||||
icon: 'briefcase',
|
|
||||||
showNewButton: false,
|
|
||||||
},
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const openInNewTab = url => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-start w-full gap-2 pt-4">
|
<div class="flex flex-col items-start w-full gap-2">
|
||||||
<BackButton
|
<BackButton
|
||||||
v-if="backButtonLabel"
|
v-if="backButtonLabel"
|
||||||
compact
|
compact
|
||||||
@@ -60,13 +60,11 @@ const openInNewTab = url => {
|
|||||||
size="14"
|
size="14"
|
||||||
:icon="iconName"
|
:icon="iconName"
|
||||||
type="outline"
|
type="outline"
|
||||||
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
|
class="flex-shrink-0 text-n-brand"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1
|
<h1 class="text-xl font-medium tracking-tight text-n-slate-12">
|
||||||
class="text-2xl font-semibold font-interDisplay tracking-[0.3px] text-slate-900 dark:text-slate-25"
|
|
||||||
>
|
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,9 +73,9 @@ const openInNewTab = url => {
|
|||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-full gap-3 text-slate-600 dark:text-slate-300">
|
<div class="flex flex-col w-full gap-3 text-n-slate-11">
|
||||||
<p
|
<p
|
||||||
class="mb-0 text-base font-normal line-clamp-5 sm:line-clamp-none max-w-3xl tracking-[-0.1px]"
|
class="mb-0 text-sm font-normal line-clamp-5 sm:line-clamp-none max-w-3xl"
|
||||||
>
|
>
|
||||||
<slot name="description">{{ description }}</slot>
|
<slot name="description">{{ description }}</slot>
|
||||||
</p>
|
</p>
|
||||||
@@ -87,7 +85,7 @@ const openInNewTab = url => {
|
|||||||
:href="helpURL"
|
:href="helpURL"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="items-center hidden gap-1 text-sm font-medium sm:inline-flex w-fit text-n-brand dark:text-n-brand hover:underline"
|
class="items-center hidden gap-1 text-sm font-medium sm:inline-flex w-fit text-n-brand hover:underline"
|
||||||
>
|
>
|
||||||
{{ linkText }}
|
{{ linkText }}
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ defineProps({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<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 w-full"
|
class="flex relative flex-col sm:flex-row p-4 gap-4 sm:p-6 justify-between group outline outline-n-container outline-1 bg-n-alpha-3 rounded-2xl shadow w-full"
|
||||||
>
|
>
|
||||||
<slot name="leftSection">
|
<slot name="leftSection">
|
||||||
<div class="flex flex-col min-w-0 items-start gap-3 max-w-[480px] w-full">
|
<div class="flex flex-col min-w-0 items-start gap-3 max-w-[480px] w-full">
|
||||||
@@ -21,7 +21,7 @@ defineProps({
|
|||||||
class="flex items-center justify-between w-full gap-3 sm:justify-normal whitespace-nowrap"
|
class="flex items-center justify-between w-full gap-3 sm:justify-normal whitespace-nowrap"
|
||||||
>
|
>
|
||||||
<h3
|
<h3
|
||||||
class="justify-between text-sm font-medium truncate w-fit sm:justify-normal text-slate-900 dark:text-slate-25"
|
class="justify-between tracking-tight font-medium truncate w-fit sm:justify-normal text-slate-900 dark:text-slate-25"
|
||||||
>
|
>
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex flex-row overflow-auto h-full">
|
||||||
class="flex flex-row overflow-auto p-4 h-full bg-n-alpha-2 dark:bg-n-solid-1"
|
|
||||||
>
|
|
||||||
<woot-wizard
|
<woot-wizard
|
||||||
class="hidden md:block w-1/4"
|
class="hidden md:block w-1/4"
|
||||||
:global-config="globalConfig"
|
:global-config="globalConfig"
|
||||||
|
|||||||
@@ -1,78 +1,76 @@
|
|||||||
<script>
|
<script setup>
|
||||||
import { mapGetters } from 'vuex';
|
import { ref, computed } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
import { frontendURL } from '../../../../helper/URLHelper';
|
import { frontendURL } from '../../../../helper/URLHelper';
|
||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
import { useInstallationName } from 'shared/mixins/globalConfigMixin';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
mixins: [globalConfigMixin],
|
integrationId: {
|
||||||
props: {
|
type: [String, Number],
|
||||||
integrationId: {
|
required: true,
|
||||||
type: [String, Number],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
integrationName: { type: String, default: '' },
|
|
||||||
integrationDescription: { type: String, default: '' },
|
|
||||||
integrationEnabled: { type: Boolean, default: false },
|
|
||||||
integrationAction: { type: String, default: '' },
|
|
||||||
actionButtonText: { type: String, default: '' },
|
|
||||||
deleteConfirmationText: { type: Object, default: () => ({}) },
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showDeleteConfirmationPopup: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters({
|
|
||||||
accountId: 'getCurrentAccountId',
|
|
||||||
globalConfig: 'globalConfig/get',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
frontendURL,
|
|
||||||
openDeletePopup() {
|
|
||||||
this.showDeleteConfirmationPopup = true;
|
|
||||||
},
|
|
||||||
closeDeletePopup() {
|
|
||||||
this.showDeleteConfirmationPopup = false;
|
|
||||||
},
|
|
||||||
confirmDeletion() {
|
|
||||||
this.closeDeletePopup();
|
|
||||||
this.deleteIntegration(this.deleteIntegration);
|
|
||||||
this.$router.push({ name: 'settings_integrations' });
|
|
||||||
},
|
|
||||||
async deleteIntegration() {
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch(
|
|
||||||
'integrations/deleteIntegration',
|
|
||||||
this.integrationId
|
|
||||||
);
|
|
||||||
useAlert(this.$t('INTEGRATION_SETTINGS.DELETE.API.SUCCESS_MESSAGE'));
|
|
||||||
} catch (error) {
|
|
||||||
useAlert(
|
|
||||||
this.$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.API.ERROR_MESSAGE')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
integrationName: { type: String, default: '' },
|
||||||
|
integrationDescription: { type: String, default: '' },
|
||||||
|
integrationEnabled: { type: Boolean, default: false },
|
||||||
|
integrationAction: { type: String, default: '' },
|
||||||
|
actionButtonText: { type: String, default: '' },
|
||||||
|
deleteConfirmationText: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const showDeleteConfirmationPopup = ref(false);
|
||||||
|
|
||||||
|
const accountId = computed(() => store.getters.getCurrentAccountId);
|
||||||
|
const globalConfig = computed(() => store.getters['globalConfig/get']);
|
||||||
|
|
||||||
|
const openDeletePopup = () => {
|
||||||
|
showDeleteConfirmationPopup.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDeletePopup = () => {
|
||||||
|
showDeleteConfirmationPopup.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteIntegration = async () => {
|
||||||
|
try {
|
||||||
|
await store.dispatch('integrations/deleteIntegration', props.integrationId);
|
||||||
|
useAlert('INTEGRATION_SETTINGS.DELETE.API.SUCCESS_MESSAGE');
|
||||||
|
} catch (error) {
|
||||||
|
useAlert('INTEGRATION_SETTINGS.WEBHOOK.DELETE.API.ERROR_MESSAGE');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDeletion = () => {
|
||||||
|
closeDeletePopup();
|
||||||
|
deleteIntegration();
|
||||||
|
router.push({ name: 'settings_integrations' });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-start justify-between md:flex-row md:items-center"
|
class="flex flex-col items-start justify-between md:flex-row md:items-center p-4 outline outline-n-container outline-1 bg-n-alpha-3 rounded-md shadow"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-start flex-1 m-0 mx-4">
|
<div class="flex items-center justify-start flex-1 m-0 mx-4 gap-6">
|
||||||
<img
|
<div class="flex h-16 w-16 items-center justify-center">
|
||||||
:src="`/dashboard/images/integrations/${integrationId}.png`"
|
<img
|
||||||
class="w-16 h-16 p-2 mr-4"
|
:src="`/dashboard/images/integrations/${integrationId}.png`"
|
||||||
/>
|
class="max-w-full rounded-md border border-n-weak shadow-sm block dark:hidden bg-n-alpha-3 dark:bg-n-alpha-2"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
:src="`/dashboard/images/integrations/${integrationId}-dark.png`"
|
||||||
|
class="max-w-full rounded-md border border-n-weak shadow-sm hidden dark:block bg-n-alpha-3 dark:bg-n-alpha-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-1 text-xl font-medium text-slate-800 dark:text-slate-100">
|
<h3 class="mb-1 text-xl font-medium text-n-slate-12">
|
||||||
{{ integrationName }}
|
{{ integrationName }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-slate-700 dark:text-slate-200">
|
<p class="text-n-slate-11 text-sm leading-6">
|
||||||
{{
|
{{
|
||||||
useInstallationName(
|
useInstallationName(
|
||||||
integrationDescription,
|
integrationDescription,
|
||||||
|
|||||||
@@ -108,7 +108,9 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="overflow-auto p-4 max-w-full my-auto flex flex-wrap h-full">
|
<div
|
||||||
|
class="overflow-auto p-4 max-w-6xl mx-auto my-auto flex flex-wrap h-full"
|
||||||
|
>
|
||||||
<woot-button
|
<woot-button
|
||||||
v-if="showAddButton"
|
v-if="showAddButton"
|
||||||
color-scheme="success"
|
color-scheme="success"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const integrationStatus = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const integrationStatusColor = computed(() =>
|
const integrationStatusColor = computed(() =>
|
||||||
props.enabled ? 'bg-green-500' : 'bg-slate-200'
|
props.enabled ? 'bg-n-teal-9' : 'bg-n-slate-8'
|
||||||
);
|
);
|
||||||
|
|
||||||
const actionURL = computed(() =>
|
const actionURL = computed(() =>
|
||||||
@@ -47,30 +47,30 @@ const actionURL = computed(() =>
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<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"
|
class="flex flex-col flex-1 p-6 m-[1px] outline outline-n-container outline-1 bg-n-alpha-3 rounded-md shadow"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div class="flex h-12 w-12 mb-4">
|
<div class="flex h-12 w-12 mb-4">
|
||||||
<img
|
<img
|
||||||
:src="`/dashboard/images/integrations/${id}.png`"
|
: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"
|
class="max-w-full rounded-md border border-n-weak shadow-sm block dark:hidden bg-n-alpha-3 dark:bg-n-alpha-2"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
:src="`/dashboard/images/integrations/${id}-dark.png`"
|
: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"
|
class="max-w-full rounded-md border border-n-weak shadow-sm hidden dark:block bg-n-alpha-3 dark:bg-n-alpha-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<fluent-icon
|
<span
|
||||||
v-tooltip="integrationStatus"
|
v-tooltip="integrationStatus"
|
||||||
size="20"
|
class="text-white p-0.5 rounded-full w-5 h-5 flex items-center justify-center"
|
||||||
class="text-white p-0.5 rounded-full"
|
|
||||||
:class="integrationStatusColor"
|
:class="integrationStatusColor"
|
||||||
icon="checkmark"
|
>
|
||||||
/>
|
<i class="i-ph-check-bold text-sm" />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col m-0 flex-1">
|
<div class="flex flex-col m-0 flex-1">
|
||||||
<div
|
<div
|
||||||
class="font-medium mb-2 text-slate-800 dark:text-slate-100 flex justify-between items-center"
|
class="font-medium mb-2 text-n-slate-12 flex justify-between items-center"
|
||||||
>
|
>
|
||||||
<span class="text-base font-semibold">{{ name }}</span>
|
<span class="text-base font-semibold">{{ name }}</span>
|
||||||
<router-link :to="actionURL">
|
<router-link :to="actionURL">
|
||||||
@@ -79,7 +79,7 @@ const actionURL = computed(() =>
|
|||||||
</woot-button>
|
</woot-button>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-slate-700 dark:text-slate-200">
|
<p class="text-n-slate-11">
|
||||||
{{ useInstallationName(description, globalConfig.installationName) }}
|
{{ useInstallationName(description, globalConfig.installationName) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,32 +35,23 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-grow flex-shrink p-4 overflow-auto">
|
<div class="flex-grow flex-shrink p-4 overflow-auto max-w-6xl mx-auto">
|
||||||
<div class="flex flex-col">
|
<div v-if="integrationLoaded && !uiFlags.isCreatingLinear">
|
||||||
<div class="flex flex-col">
|
<Integration
|
||||||
<div>
|
:integration-id="integration.id"
|
||||||
<div
|
:integration-logo="integration.logo"
|
||||||
v-if="integrationLoaded && !uiFlags.isCreatingLinear"
|
:integration-name="integration.name"
|
||||||
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-description="integration.description"
|
||||||
>
|
:integration-enabled="integration.enabled"
|
||||||
<Integration
|
:integration-action="integrationAction"
|
||||||
:integration-id="integration.id"
|
:delete-confirmation-text="{
|
||||||
:integration-logo="integration.logo"
|
title: $t('INTEGRATION_SETTINGS.LINEAR.DELETE.TITLE'),
|
||||||
:integration-name="integration.name"
|
message: $t('INTEGRATION_SETTINGS.LINEAR.DELETE.MESSAGE'),
|
||||||
:integration-description="integration.description"
|
}"
|
||||||
:integration-enabled="integration.enabled"
|
/>
|
||||||
:integration-action="integrationAction"
|
</div>
|
||||||
:delete-confirmation-text="{
|
<div v-else class="flex items-center justify-center flex-1">
|
||||||
title: $t('INTEGRATION_SETTINGS.LINEAR.DELETE.TITLE'),
|
<Spinner size="" color-scheme="primary" />
|
||||||
message: $t('INTEGRATION_SETTINGS.LINEAR.DELETE.MESSAGE'),
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex items-center justify-center flex-1">
|
|
||||||
<Spinner size="" color-scheme="primary" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -47,31 +47,19 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-grow flex-shrink p-4 overflow-auto">
|
<div class="max-w-6xl">
|
||||||
<div class="flex flex-col">
|
<div v-if="integrationLoaded">
|
||||||
<div class="flex flex-col">
|
<Integration
|
||||||
<div>
|
:integration-id="integration.id"
|
||||||
<div
|
:integration-logo="integration.logo"
|
||||||
v-if="integrationLoaded"
|
:integration-name="integration.name"
|
||||||
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-description="integration.description"
|
||||||
>
|
:integration-enabled="integration.enabled"
|
||||||
<Integration
|
:integration-action="integrationAction()"
|
||||||
:integration-id="integration.id"
|
/>
|
||||||
:integration-logo="integration.logo"
|
</div>
|
||||||
:integration-name="integration.name"
|
<div v-if="integration.enabled">
|
||||||
:integration-description="integration.description"
|
<IntegrationHelpText />
|
||||||
:integration-enabled="integration.enabled"
|
|
||||||
:integration-action="integrationAction()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="integration.enabled"
|
|
||||||
class="p-4 mb-4 bg-white border border-solid rounded-sm dark:bg-slate-800 border-slate-75 dark:border-slate-700/50"
|
|
||||||
>
|
|
||||||
<IntegrationHelpText />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,63 +1,62 @@
|
|||||||
<script>
|
<script setup>
|
||||||
|
import { defineProps, defineEmits } from 'vue';
|
||||||
import { useIntegrationHook } from 'dashboard/composables/useIntegrationHook';
|
import { useIntegrationHook } from 'dashboard/composables/useIntegrationHook';
|
||||||
export default {
|
import Button from 'dashboard/components-next/button/Button.vue';
|
||||||
props: {
|
|
||||||
integrationId: {
|
const props = defineProps({
|
||||||
type: String,
|
integrationId: {
|
||||||
required: true,
|
type: String,
|
||||||
},
|
required: true,
|
||||||
},
|
},
|
||||||
emits: ['add', 'delete'],
|
});
|
||||||
setup(props) {
|
|
||||||
const { integration, hasConnectedHooks } = useIntegrationHook(
|
defineEmits(['add', 'delete']);
|
||||||
props.integrationId
|
|
||||||
);
|
const { integration, hasConnectedHooks } = useIntegrationHook(
|
||||||
return { integration, hasConnectedHooks };
|
props.integrationId
|
||||||
},
|
);
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-shrink flex-grow overflow-auto p-4">
|
<div
|
||||||
<div class="flex flex-col">
|
class="outline outline-n-container outline-1 bg-n-alpha-3 rounded-md shadow flex-grow overflow-auto p-4"
|
||||||
<div
|
>
|
||||||
class="bg-white dark:bg-slate-800 border border-solid border-slate-75 dark:border-slate-700/50 rounded-xl mb-4 p-4"
|
<div class="flex items-center justify-center">
|
||||||
>
|
<div class="flex h-16 w-16 items-center justify-center">
|
||||||
<div class="flex">
|
<img
|
||||||
<div class="flex h-[6.25rem] w-[6.25rem]">
|
:src="`/dashboard/images/integrations/${integrationId}.png`"
|
||||||
<img
|
class="max-w-full rounded-md border border-n-weak shadow-sm block dark:hidden bg-n-alpha-3 dark:bg-n-alpha-2"
|
||||||
:src="`/dashboard/images/integrations/${integration.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/${integrationId}-dark.png`"
|
||||||
<img
|
class="max-w-full rounded-md border border-n-weak shadow-sm hidden dark:block bg-n-alpha-3 dark:bg-n-alpha-2"
|
||||||
:src="`/dashboard/images/integrations/${integration.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>
|
||||||
|
<div class="flex flex-col justify-center m-0 mx-4 flex-1">
|
||||||
|
<h3 class="mb-1 text-xl font-medium text-n-slate-12">
|
||||||
|
{{ integration.name }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-n-slate-11 text-sm leading-6">
|
||||||
|
{{ integration.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center items-center mb-0 w-[15%]">
|
||||||
|
<div v-if="hasConnectedHooks">
|
||||||
|
<div @click="$emit('delete', integration.hooks[0])">
|
||||||
|
<Button
|
||||||
|
ruby
|
||||||
|
faded
|
||||||
|
:label="$t('INTEGRATION_APPS.DISCONNECT.BUTTON_TEXT')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center m-0 mx-4 flex-1">
|
</div>
|
||||||
<h3
|
<div v-else>
|
||||||
class="text-xl font-medium mb-1 text-slate-800 dark:text-slate-100"
|
<Button
|
||||||
>
|
blue
|
||||||
{{ integration.name }}
|
faded
|
||||||
</h3>
|
:label="$t('INTEGRATION_APPS.CONNECT.BUTTON_TEXT')"
|
||||||
<p class="text-slate-700 dark:text-slate-200">
|
@click="$emit('add')"
|
||||||
{{ integration.description }}
|
/>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center items-center mb-0 w-[15%]">
|
|
||||||
<div v-if="hasConnectedHooks">
|
|
||||||
<div @click="$emit('delete', integration.hooks[0])">
|
|
||||||
<woot-button class="nice alert">
|
|
||||||
{{ $t('INTEGRATION_APPS.DISCONNECT.BUTTON_TEXT') }}
|
|
||||||
</woot-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<woot-button class="button nice" @click="$emit('add')">
|
|
||||||
{{ $t('INTEGRATION_APPS.CONNECT.BUTTON_TEXT') }}
|
|
||||||
</woot-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,102 +1,99 @@
|
|||||||
<script>
|
<script setup>
|
||||||
import { mapGetters } from 'vuex';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
import { useStore } from 'vuex';
|
||||||
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import Integration from './Integration.vue';
|
import Integration from './Integration.vue';
|
||||||
import SelectChannelWarning from './Slack/SelectChannelWarning.vue';
|
import SelectChannelWarning from './Slack/SelectChannelWarning.vue';
|
||||||
import SlackIntegrationHelpText from './Slack/SlackIntegrationHelpText.vue';
|
import SlackIntegrationHelpText from './Slack/SlackIntegrationHelpText.vue';
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Spinner,
|
|
||||||
Integration,
|
|
||||||
SelectChannelWarning,
|
|
||||||
SlackIntegrationHelpText,
|
|
||||||
},
|
|
||||||
mixins: [globalConfigMixin],
|
|
||||||
props: {
|
|
||||||
code: { type: String, default: '' },
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return { integrationLoaded: false };
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
integration() {
|
|
||||||
return this.$store.getters['integrations/getIntegration']('slack');
|
|
||||||
},
|
|
||||||
areHooksAvailable() {
|
|
||||||
const { hooks = [] } = this.integration || {};
|
|
||||||
return !!hooks.length;
|
|
||||||
},
|
|
||||||
hook() {
|
|
||||||
const { hooks = [] } = this.integration || {};
|
|
||||||
const [hook] = hooks;
|
|
||||||
return hook || {};
|
|
||||||
},
|
|
||||||
isIntegrationHookEnabled() {
|
|
||||||
return this.hook.status || false;
|
|
||||||
},
|
|
||||||
hasConnectedAChannel() {
|
|
||||||
return !!this.hook.reference_id;
|
|
||||||
},
|
|
||||||
selectedChannelName() {
|
|
||||||
if (this.hook.status) {
|
|
||||||
const { settings: { channel_name: channelName = '' } = {} } = this.hook;
|
|
||||||
return channelName || 'customer-conversations';
|
|
||||||
}
|
|
||||||
return this.$t('INTEGRATION_SETTINGS.SLACK.HELP_TEXT.SELECTED');
|
|
||||||
},
|
|
||||||
...mapGetters({
|
|
||||||
uiFlags: 'integrations/getUIFlags',
|
|
||||||
}),
|
|
||||||
|
|
||||||
integrationAction() {
|
const props = defineProps({
|
||||||
if (this.integration.enabled) {
|
code: { type: String, default: '' },
|
||||||
return 'disconnect';
|
});
|
||||||
}
|
|
||||||
return this.integration.action;
|
const store = useStore();
|
||||||
},
|
const router = useRouter();
|
||||||
},
|
const route = useRoute();
|
||||||
mounted() {
|
const { t } = useI18n();
|
||||||
this.intializeSlackIntegration();
|
|
||||||
},
|
const integrationLoaded = ref(false);
|
||||||
methods: {
|
|
||||||
async intializeSlackIntegration() {
|
const integration = computed(() => {
|
||||||
await this.$store.dispatch('integrations/get', 'slack');
|
return store.getters['integrations/getIntegration']('slack');
|
||||||
if (this.code) {
|
});
|
||||||
await this.$store.dispatch('integrations/connectSlack', this.code);
|
|
||||||
// Clear the query param `code` from the URL as the
|
const areHooksAvailable = computed(() => {
|
||||||
// subsequent reloads would result in an error
|
const { hooks = [] } = integration.value || {};
|
||||||
this.$router.replace(this.$route.path);
|
return !!hooks.length;
|
||||||
}
|
});
|
||||||
this.integrationLoaded = true;
|
|
||||||
},
|
const hook = computed(() => {
|
||||||
},
|
const { hooks = [] } = integration.value || {};
|
||||||
|
const [firstHook] = hooks;
|
||||||
|
return firstHook || {};
|
||||||
|
});
|
||||||
|
|
||||||
|
const isIntegrationHookEnabled = computed(() => {
|
||||||
|
return hook.value.status || false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasConnectedAChannel = computed(() => {
|
||||||
|
return !!hook.value.reference_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedChannelName = computed(() => {
|
||||||
|
if (hook.value.status) {
|
||||||
|
const { settings: { channel_name: channelName = '' } = {} } = hook.value;
|
||||||
|
return channelName || 'customer-conversations';
|
||||||
|
}
|
||||||
|
return t('INTEGRATION_SETTINGS.SLACK.HELP_TEXT.SELECTED');
|
||||||
|
});
|
||||||
|
|
||||||
|
const uiFlags = computed(() => store.getters['integrations/getUIFlags']);
|
||||||
|
|
||||||
|
const integrationAction = computed(() => {
|
||||||
|
if (integration.value.enabled) {
|
||||||
|
return 'disconnect';
|
||||||
|
}
|
||||||
|
return integration.value.action;
|
||||||
|
});
|
||||||
|
|
||||||
|
const intializeSlackIntegration = async () => {
|
||||||
|
await store.dispatch('integrations/get', 'slack');
|
||||||
|
if (props.code) {
|
||||||
|
await store.dispatch('integrations/connectSlack', props.code);
|
||||||
|
// Clear the query param `code` from the URL as the
|
||||||
|
// subsequent reloads would result in an error
|
||||||
|
router.replace(route.path);
|
||||||
|
}
|
||||||
|
integrationLoaded.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
intializeSlackIntegration();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="integrationLoaded && !uiFlags.isCreatingSlack"
|
v-if="integrationLoaded && !uiFlags.isCreatingSlack"
|
||||||
class="flex flex-col flex-1 overflow-auto"
|
class="flex flex-col flex-1 overflow-auto gap-5 pt-1 pb-10"
|
||||||
>
|
>
|
||||||
<div
|
<Integration
|
||||||
class="p-4 bg-white border-b border-solid rounded-sm dark:bg-slate-800 border-slate-75 dark:border-slate-700/50"
|
:integration-id="integration.id"
|
||||||
>
|
:integration-logo="integration.logo"
|
||||||
<Integration
|
:integration-name="integration.name"
|
||||||
:integration-id="integration.id"
|
:integration-description="integration.description"
|
||||||
:integration-logo="integration.logo"
|
:integration-enabled="integration.enabled"
|
||||||
:integration-name="integration.name"
|
:integration-action="integrationAction"
|
||||||
:integration-description="integration.description"
|
:action-button-text="$t('INTEGRATION_SETTINGS.SLACK.DELETE')"
|
||||||
:integration-enabled="integration.enabled"
|
:delete-confirmation-text="{
|
||||||
:integration-action="integrationAction"
|
title: $t('INTEGRATION_SETTINGS.SLACK.DELETE_CONFIRMATION.TITLE'),
|
||||||
:action-button-text="$t('INTEGRATION_SETTINGS.SLACK.DELETE')"
|
message: $t('INTEGRATION_SETTINGS.SLACK.DELETE_CONFIRMATION.MESSAGE'),
|
||||||
:delete-confirmation-text="{
|
}"
|
||||||
title: $t('INTEGRATION_SETTINGS.SLACK.DELETE_CONFIRMATION.TITLE'),
|
/>
|
||||||
message: $t('INTEGRATION_SETTINGS.SLACK.DELETE_CONFIRMATION.MESSAGE'),
|
<div v-if="areHooksAvailable" class="flex-1">
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="areHooksAvailable" class="flex-1 p-6">
|
|
||||||
<SelectChannelWarning
|
<SelectChannelWarning
|
||||||
v-if="!isIntegrationHookEnabled"
|
v-if="!isIntegrationHookEnabled"
|
||||||
:has-connected-a-channel="hasConnectedAChannel"
|
:has-connected-a-channel="hasConnectedAChannel"
|
||||||
|
|||||||
@@ -1,109 +1,99 @@
|
|||||||
<script>
|
<script setup>
|
||||||
import { mapGetters } from 'vuex';
|
import { ref, computed } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
import { useInstallationName } from 'shared/mixins/globalConfigMixin';
|
||||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||||
|
import Button from 'dashboard/components-next/button/Button.vue';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
mixins: [globalConfigMixin],
|
hasConnectedAChannel: {
|
||||||
props: {
|
type: Boolean,
|
||||||
hasConnectedAChannel: {
|
default: true,
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const { formatMessage } = useMessageFormatter();
|
|
||||||
return {
|
|
||||||
formatMessage,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return { selectedChannelId: '', availableChannels: [] };
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters({
|
|
||||||
globalConfig: 'globalConfig/get',
|
|
||||||
uiFlags: 'integrations/getUIFlags',
|
|
||||||
}),
|
|
||||||
errorDescription() {
|
|
||||||
return !this.hasConnectedAChannel
|
|
||||||
? this.$t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.DESCRIPTION')
|
|
||||||
: this.$t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.EXPIRED');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchChannels() {
|
|
||||||
try {
|
|
||||||
this.availableChannels = await this.$store.dispatch(
|
|
||||||
'integrations/listAllSlackChannels'
|
|
||||||
);
|
|
||||||
this.availableChannels.sort((c1, c2) => c1.name - c2.name);
|
|
||||||
} catch {
|
|
||||||
this.$t('INTEGRATION_SETTINGS.SLACK.FAILED_TO_FETCH_CHANNELS');
|
|
||||||
this.availableChannels = [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async updateIntegration() {
|
|
||||||
try {
|
|
||||||
await this.$store.dispatch('integrations/updateSlack', {
|
|
||||||
referenceId: this.selectedChannelId,
|
|
||||||
});
|
|
||||||
useAlert(this.$t('INTEGRATION_SETTINGS.SLACK.UPDATE_SUCCESS'));
|
|
||||||
} catch (error) {
|
|
||||||
useAlert(error.message || 'INTEGRATION_SETTINGS.SLACK.UPDATE_ERROR');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const { formatMessage } = useMessageFormatter();
|
||||||
|
|
||||||
|
const selectedChannelId = ref('');
|
||||||
|
const availableChannels = ref([]);
|
||||||
|
|
||||||
|
const uiFlags = computed(() => store.getters['integrations/getUIFlags']);
|
||||||
|
|
||||||
|
const errorDescription = computed(() => {
|
||||||
|
return !props.hasConnectedAChannel
|
||||||
|
? t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.DESCRIPTION')
|
||||||
|
: t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.EXPIRED');
|
||||||
|
});
|
||||||
|
const globalConfig = computed(() => store.getters['globalConfig/get']);
|
||||||
|
|
||||||
|
const formattedErrorMessage = computed(() => {
|
||||||
|
return formatMessage(
|
||||||
|
useInstallationName(
|
||||||
|
errorDescription.value,
|
||||||
|
globalConfig.value.installationName
|
||||||
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchChannels = async () => {
|
||||||
|
try {
|
||||||
|
availableChannels.value = await store.dispatch(
|
||||||
|
'integrations/listAllSlackChannels'
|
||||||
|
);
|
||||||
|
availableChannels.value.sort((c1, c2) => c1.name - c2.name);
|
||||||
|
} catch {
|
||||||
|
t('INTEGRATION_SETTINGS.SLACK.FAILED_TO_FETCH_CHANNELS');
|
||||||
|
availableChannels.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateIntegration = async () => {
|
||||||
|
try {
|
||||||
|
await store.dispatch('integrations/updateSlack', {
|
||||||
|
referenceId: selectedChannelId.value,
|
||||||
|
});
|
||||||
|
useAlert(t('INTEGRATION_SETTINGS.SLACK.UPDATE_SUCCESS'));
|
||||||
|
} catch (error) {
|
||||||
|
useAlert(error.message || 'INTEGRATION_SETTINGS.SLACK.UPDATE_ERROR');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="px-6 py-4 mb-4 border border-yellow-200 rounded-md bg-yellow-50 dark:border-slate-700 dark:bg-slate-800"
|
class="px-6 py-4 mb-4 outline outline-n-container outline-1 bg-n-alpha-3 rounded-md shadow"
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0 mt-0.5">
|
<div class="flex-shrink-0">
|
||||||
<fluent-icon
|
<div class="i-lucide-bell text-xl text-n-amber-11 mt-1" />
|
||||||
icon="alert"
|
|
||||||
class="text-yellow-500 dark:text-yellow-400"
|
|
||||||
size="24"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<p
|
<p class="mb-1 text-base font-semibold text-n-slate-12">
|
||||||
class="mb-1 text-base font-semibold text-yellow-900 dark:text-yellow-500"
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
$t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.ATTENTION_REQUIRED')
|
$t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.ATTENTION_REQUIRED')
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-2 text-sm text-yellow-800 dark:text-yellow-600">
|
<div class="mt-2 text-sm text-n-slate-11 mb-3">
|
||||||
<p
|
<p v-dompurify-html="formattedErrorMessage" />
|
||||||
v-dompurify-html="
|
|
||||||
formatMessage(
|
|
||||||
useInstallationName(
|
|
||||||
errorDescription,
|
|
||||||
globalConfig.installationName
|
|
||||||
),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!hasConnectedAChannel" class="mt-2 ml-8">
|
<div v-if="!hasConnectedAChannel" class="mb-2 mt-1 ml-8">
|
||||||
<woot-submit-button
|
<Button
|
||||||
v-if="!availableChannels.length"
|
v-if="!availableChannels.length"
|
||||||
button-class="smooth small warning"
|
amber
|
||||||
:loading="uiFlags.isFetchingSlackChannels"
|
sm
|
||||||
:button-text="
|
:is-loading="uiFlags.isFetchingSlackChannels"
|
||||||
$t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.BUTTON_TEXT')
|
|
||||||
"
|
|
||||||
spinner-class="warning"
|
|
||||||
@click="fetchChannels"
|
@click="fetchChannels"
|
||||||
/>
|
>
|
||||||
|
{{ $t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.BUTTON_TEXT') }}
|
||||||
|
</Button>
|
||||||
<div v-else class="inline-flex">
|
<div v-else class="inline-flex">
|
||||||
<select
|
<select
|
||||||
v-model="selectedChannelId"
|
v-model="selectedChannelId"
|
||||||
@@ -120,13 +110,14 @@ export default {
|
|||||||
#{{ channel.name }}
|
#{{ channel.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<woot-submit-button
|
<Button
|
||||||
button-class="smooth small success"
|
teal
|
||||||
:button-text="$t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.UPDATE')"
|
sm
|
||||||
spinner-class="success"
|
:is-loading="uiFlags.isUpdatingSlack"
|
||||||
:loading="uiFlags.isUpdatingSlack"
|
|
||||||
@click="updateIntegration"
|
@click="updateIntegration"
|
||||||
/>
|
>
|
||||||
|
{{ $t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.UPDATE') }}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,41 +1,37 @@
|
|||||||
<script>
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||||
export default {
|
|
||||||
props: {
|
const props = defineProps({
|
||||||
selectedChannelName: {
|
selectedChannelName: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup() {
|
});
|
||||||
const { formatMessage } = useMessageFormatter();
|
|
||||||
return {
|
const { t } = useI18n();
|
||||||
formatMessage,
|
const { formatMessage } = useMessageFormatter();
|
||||||
};
|
|
||||||
},
|
const formattedHelpText = computed(() => {
|
||||||
};
|
return formatMessage(
|
||||||
|
t('INTEGRATION_SETTINGS.SLACK.HELP_TEXT.BODY', {
|
||||||
|
selectedChannelName: props.selectedChannelName,
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex-1 w-full p-6 bg-white rounded-md border border-slate-50 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200"
|
class="flex-1 w-full px-6 py-5 outline outline-n-container outline-1 bg-n-alpha-3 rounded-md shadow"
|
||||||
>
|
>
|
||||||
<div class="prose-lg max-w-5xl">
|
<div class="prose-lg max-w-5xl">
|
||||||
<h5 class="dark:text-slate-100">
|
<h5 class="text-n-slate-12 tracking-tight">
|
||||||
{{ $t('INTEGRATION_SETTINGS.SLACK.HELP_TEXT.TITLE') }}
|
{{ t('INTEGRATION_SETTINGS.SLACK.HELP_TEXT.TITLE') }}
|
||||||
</h5>
|
</h5>
|
||||||
<p>
|
<div v-dompurify-html="formattedHelpText" class="text-n-slate-11" />
|
||||||
<span
|
|
||||||
v-dompurify-html="
|
|
||||||
formatMessage(
|
|
||||||
$t('INTEGRATION_SETTINGS.SLACK.HELP_TEXT.BODY', {
|
|
||||||
selectedChannelName: selectedChannelName,
|
|
||||||
}),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="overflow-auto p-4 max-w-full my-auto flex flex-row flex-nowrap h-full bg-slate-25 dark:bg-slate-800"
|
class="overflow-auto p-4 max-w-full my-auto flex flex-row flex-nowrap h-full"
|
||||||
>
|
>
|
||||||
<woot-wizard class="hidden md:block w-1/4" :items="items" />
|
<woot-wizard class="hidden md:block w-1/4" :items="items" />
|
||||||
<router-view />
|
<router-view />
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="overflow-auto p-4 max-w-full my-auto flex flex-row flex-nowrap h-full bg-slate-25 dark:bg-slate-800"
|
class="overflow-auto p-4 max-w-full my-auto flex flex-row flex-nowrap h-full"
|
||||||
>
|
>
|
||||||
<woot-wizard class="hidden md:block w-1/4" :items="items" />
|
<woot-wizard class="hidden md:block w-1/4" :items="items" />
|
||||||
<router-view />
|
<router-view />
|
||||||
|
|||||||
Reference in New Issue
Block a user