chore: Update settings page to match the new design colors (#11072)

This commit is contained in:
Pranav
2025-03-12 18:11:42 -07:00
committed by GitHub
parent 7e54b13a8b
commit 2024b9e90d
19 changed files with 512 additions and 567 deletions

View File

@@ -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>

View File

@@ -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
class="flex items-center mb-0 text-2xl text-slate-900 dark:text-slate-100"
> >
<h1 class="flex items-center mb-0 text-2xl text-n-slate-12">
<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">

View File

@@ -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,6 +25,7 @@ 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"
> >
<div class="max-w-6xl mx-auto w-full h-full">
<SettingsHeader <SettingsHeader
button-route="new" button-route="new"
:icon="icon" :icon="icon"
@@ -35,11 +36,13 @@ const showNewButton = computed(
:show-new-button="showNewButton" :show-new-button="showNewButton"
:show-sidemenu-icon="showSidemenuIcon" :show-sidemenu-icon="showSidemenuIcon"
/> />
<router-view v-slot="{ Component }">
<keep-alive v-if="keepAlive"> <router-view v-slot="{ Component }" class="px-5">
<component :is="Component" /> <component :is="Component" v-if="!keepAlive" :key="$route.fullPath" />
<keep-alive v-else>
<component :is="Component" :key="$route.fullPath" />
</keep-alive> </keep-alive>
<component :is="Component" v-else />
</router-view> </router-view>
</div> </div>
</div>
</template> </template>

View File

@@ -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,10 +167,18 @@ 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">
<BaseSettingsHeader :title="$t('GENERAL_SETTINGS.TITLE')">
<template #actions>
<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"> <form v-if="!uiFlags.isFetchingItem" @submit.prevent="updateAccount">
<div <div
class="flex flex-row p-4 border-b border-slate-25 dark:border-slate-800" class="flex flex-row border-b border-slate-25 dark:border-slate-800"
> >
<div <div
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0" class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0"
@@ -247,13 +261,13 @@ export default {
</label> </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 <div class="flex flex-row">
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0" <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"> <h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
{{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.TITLE') }} {{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.TITLE') }}
</h4> </h4>
@@ -278,14 +292,5 @@ export default {
<div>{{ `Build ${globalConfig.gitSha}` }}</div> <div>{{ `Build ${globalConfig.gitSha}` }}</div>
</div> </div>
</div> </div>
<woot-submit-button
class="button nice success button--fixed-top"
:button-text="$t('GENERAL_SETTINGS.SUBMIT')"
:loading="isUpdating"
/>
</form>
<woot-loading-state v-if="uiFlags.isFetchingItem" />
</div> </div>
</template> </template>

View File

@@ -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: '',

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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"

View File

@@ -1,12 +1,12 @@
<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],
props: {
integrationId: { integrationId: {
type: [String, Number], type: [String, Number],
required: true, required: true,
@@ -17,62 +17,60 @@ export default {
integrationAction: { type: String, default: '' }, integrationAction: { type: String, default: '' },
actionButtonText: { type: String, default: '' }, actionButtonText: { type: String, default: '' },
deleteConfirmationText: { type: Object, default: () => ({}) }, deleteConfirmationText: { type: Object, default: () => ({}) },
}, });
data() {
return { const store = useStore();
showDeleteConfirmationPopup: false, const router = useRouter();
};
}, const showDeleteConfirmationPopup = ref(false);
computed: {
...mapGetters({ const accountId = computed(() => store.getters.getCurrentAccountId);
accountId: 'getCurrentAccountId', const globalConfig = computed(() => store.getters['globalConfig/get']);
globalConfig: 'globalConfig/get',
}), const openDeletePopup = () => {
}, showDeleteConfirmationPopup.value = true;
methods: { };
frontendURL,
openDeletePopup() { const closeDeletePopup = () => {
this.showDeleteConfirmationPopup = true; showDeleteConfirmationPopup.value = false;
}, };
closeDeletePopup() {
this.showDeleteConfirmationPopup = false; const deleteIntegration = async () => {
},
confirmDeletion() {
this.closeDeletePopup();
this.deleteIntegration(this.deleteIntegration);
this.$router.push({ name: 'settings_integrations' });
},
async deleteIntegration() {
try { try {
await this.$store.dispatch( await store.dispatch('integrations/deleteIntegration', props.integrationId);
'integrations/deleteIntegration', useAlert('INTEGRATION_SETTINGS.DELETE.API.SUCCESS_MESSAGE');
this.integrationId
);
useAlert(this.$t('INTEGRATION_SETTINGS.DELETE.API.SUCCESS_MESSAGE'));
} catch (error) { } catch (error) {
useAlert( useAlert('INTEGRATION_SETTINGS.WEBHOOK.DELETE.API.ERROR_MESSAGE');
this.$t('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">
<div class="flex h-16 w-16 items-center justify-center">
<img <img
:src="`/dashboard/images/integrations/${integrationId}.png`" :src="`/dashboard/images/integrations/${integrationId}.png`"
class="w-16 h-16 p-2 mr-4" 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,

View File

@@ -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"

View File

@@ -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>

View File

@@ -35,14 +35,8 @@ 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">
<div>
<div
v-if="integrationLoaded && !uiFlags.isCreatingLinear"
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 <Integration
:integration-id="integration.id" :integration-id="integration.id"
:integration-logo="integration.logo" :integration-logo="integration.logo"
@@ -60,7 +54,4 @@ onMounted(() => {
<Spinner size="" color-scheme="primary" /> <Spinner size="" color-scheme="primary" />
</div> </div>
</div> </div>
</div>
</div>
</div>
</template> </template>

View File

@@ -47,14 +47,8 @@ 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">
<div>
<div
v-if="integrationLoaded"
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 <Integration
:integration-id="integration.id" :integration-id="integration.id"
:integration-logo="integration.logo" :integration-logo="integration.logo"
@@ -64,14 +58,8 @@ export default {
:integration-action="integrationAction()" :integration-action="integrationAction()"
/> />
</div> </div>
<div <div v-if="integration.enabled">
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 /> <IntegrationHelpText />
</div> </div>
</div> </div>
</div>
</div>
</div>
</template> </template>

View File

@@ -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: {
const props = defineProps({
integrationId: { integrationId: {
type: String, type: String,
required: true, required: true,
}, },
}, });
emits: ['add', 'delete'],
setup(props) { defineEmits(['add', 'delete']);
const { integration, hasConnectedHooks } = useIntegrationHook(
const { integration, hasConnectedHooks } = useIntegrationHook(
props.integrationId props.integrationId
); );
return { integration, hasConnectedHooks };
},
};
</script> </script>
<template> <template>
<div class="flex-shrink flex-grow overflow-auto p-4">
<div class="flex flex-col">
<div <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" class="outline outline-n-container outline-1 bg-n-alpha-3 rounded-md shadow flex-grow overflow-auto p-4"
> >
<div class="flex"> <div class="flex items-center justify-center">
<div class="flex h-[6.25rem] w-[6.25rem]"> <div class="flex h-16 w-16 items-center justify-center">
<img <img
:src="`/dashboard/images/integrations/${integration.id}.png`" :src="`/dashboard/images/integrations/${integrationId}.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/${integration.id}-dark.png`" :src="`/dashboard/images/integrations/${integrationId}-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>
<div class="flex flex-col justify-center m-0 mx-4 flex-1"> <div class="flex flex-col justify-center m-0 mx-4 flex-1">
<h3 <h3 class="mb-1 text-xl font-medium text-n-slate-12">
class="text-xl font-medium mb-1 text-slate-800 dark:text-slate-100"
>
{{ integration.name }} {{ integration.name }}
</h3> </h3>
<p class="text-slate-700 dark:text-slate-200"> <p class="text-n-slate-11 text-sm leading-6">
{{ integration.description }} {{ integration.description }}
</p> </p>
</div> </div>
<div class="flex justify-center items-center mb-0 w-[15%]"> <div class="flex justify-center items-center mb-0 w-[15%]">
<div v-if="hasConnectedHooks"> <div v-if="hasConnectedHooks">
<div @click="$emit('delete', integration.hooks[0])"> <div @click="$emit('delete', integration.hooks[0])">
<woot-button class="nice alert"> <Button
{{ $t('INTEGRATION_APPS.DISCONNECT.BUTTON_TEXT') }} ruby
</woot-button> faded
:label="$t('INTEGRATION_APPS.DISCONNECT.BUTTON_TEXT')"
/>
</div> </div>
</div> </div>
<div v-else> <div v-else>
<woot-button class="button nice" @click="$emit('add')"> <Button
{{ $t('INTEGRATION_APPS.CONNECT.BUTTON_TEXT') }} blue
</woot-button> faded
</div> :label="$t('INTEGRATION_APPS.CONNECT.BUTTON_TEXT')"
</div> @click="$emit('add')"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,86 +1,84 @@
<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: { const props = defineProps({
Spinner,
Integration,
SelectChannelWarning,
SlackIntegrationHelpText,
},
mixins: [globalConfigMixin],
props: {
code: { type: String, default: '' }, code: { type: String, default: '' },
}, });
data() {
return { integrationLoaded: false }; const store = useStore();
}, const router = useRouter();
computed: { const route = useRoute();
integration() { const { t } = useI18n();
return this.$store.getters['integrations/getIntegration']('slack');
}, const integrationLoaded = ref(false);
areHooksAvailable() {
const { hooks = [] } = this.integration || {}; const integration = computed(() => {
return store.getters['integrations/getIntegration']('slack');
});
const areHooksAvailable = computed(() => {
const { hooks = [] } = integration.value || {};
return !!hooks.length; return !!hooks.length;
}, });
hook() {
const { hooks = [] } = this.integration || {}; const hook = computed(() => {
const [hook] = hooks; const { hooks = [] } = integration.value || {};
return hook || {}; const [firstHook] = hooks;
}, return firstHook || {};
isIntegrationHookEnabled() { });
return this.hook.status || false;
}, const isIntegrationHookEnabled = computed(() => {
hasConnectedAChannel() { return hook.value.status || false;
return !!this.hook.reference_id; });
},
selectedChannelName() { const hasConnectedAChannel = computed(() => {
if (this.hook.status) { return !!hook.value.reference_id;
const { settings: { channel_name: channelName = '' } = {} } = this.hook; });
const selectedChannelName = computed(() => {
if (hook.value.status) {
const { settings: { channel_name: channelName = '' } = {} } = hook.value;
return channelName || 'customer-conversations'; return channelName || 'customer-conversations';
} }
return this.$t('INTEGRATION_SETTINGS.SLACK.HELP_TEXT.SELECTED'); return t('INTEGRATION_SETTINGS.SLACK.HELP_TEXT.SELECTED');
}, });
...mapGetters({
uiFlags: 'integrations/getUIFlags',
}),
integrationAction() { const uiFlags = computed(() => store.getters['integrations/getUIFlags']);
if (this.integration.enabled) {
const integrationAction = computed(() => {
if (integration.value.enabled) {
return 'disconnect'; return 'disconnect';
} }
return this.integration.action; return integration.value.action;
}, });
},
mounted() { const intializeSlackIntegration = async () => {
this.intializeSlackIntegration(); await store.dispatch('integrations/get', 'slack');
}, if (props.code) {
methods: { await store.dispatch('integrations/connectSlack', props.code);
async intializeSlackIntegration() {
await this.$store.dispatch('integrations/get', 'slack');
if (this.code) {
await this.$store.dispatch('integrations/connectSlack', this.code);
// Clear the query param `code` from the URL as the // Clear the query param `code` from the URL as the
// subsequent reloads would result in an error // subsequent reloads would result in an error
this.$router.replace(this.$route.path); router.replace(route.path);
} }
this.integrationLoaded = true; 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
class="p-4 bg-white border-b border-solid rounded-sm dark:bg-slate-800 border-slate-75 dark:border-slate-700/50"
> >
<Integration <Integration
:integration-id="integration.id" :integration-id="integration.id"
@@ -95,8 +93,7 @@ export default {
message: $t('INTEGRATION_SETTINGS.SLACK.DELETE_CONFIRMATION.MESSAGE'), message: $t('INTEGRATION_SETTINGS.SLACK.DELETE_CONFIRMATION.MESSAGE'),
}" }"
/> />
</div> <div v-if="areHooksAvailable" class="flex-1">
<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"

View File

@@ -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],
props: {
hasConnectedAChannel: { hasConnectedAChannel: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
}, });
setup() {
const { formatMessage } = useMessageFormatter(); const store = useStore();
return { const { t } = useI18n();
formatMessage,
}; const { formatMessage } = useMessageFormatter();
},
data() { const selectedChannelId = ref('');
return { selectedChannelId: '', availableChannels: [] }; const availableChannels = ref([]);
},
computed: { const uiFlags = computed(() => store.getters['integrations/getUIFlags']);
...mapGetters({
globalConfig: 'globalConfig/get', const errorDescription = computed(() => {
uiFlags: 'integrations/getUIFlags', return !props.hasConnectedAChannel
}), ? t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.DESCRIPTION')
errorDescription() { : t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.EXPIRED');
return !this.hasConnectedAChannel });
? this.$t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.DESCRIPTION') const globalConfig = computed(() => store.getters['globalConfig/get']);
: this.$t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.EXPIRED');
}, const formattedErrorMessage = computed(() => {
}, return formatMessage(
methods: { useInstallationName(
async fetchChannels() { errorDescription.value,
globalConfig.value.installationName
),
false
);
});
const fetchChannels = async () => {
try { try {
this.availableChannels = await this.$store.dispatch( availableChannels.value = await store.dispatch(
'integrations/listAllSlackChannels' 'integrations/listAllSlackChannels'
); );
this.availableChannels.sort((c1, c2) => c1.name - c2.name); availableChannels.value.sort((c1, c2) => c1.name - c2.name);
} catch { } catch {
this.$t('INTEGRATION_SETTINGS.SLACK.FAILED_TO_FETCH_CHANNELS'); t('INTEGRATION_SETTINGS.SLACK.FAILED_TO_FETCH_CHANNELS');
this.availableChannels = []; availableChannels.value = [];
} }
}, };
async updateIntegration() {
const updateIntegration = async () => {
try { try {
await this.$store.dispatch('integrations/updateSlack', { await store.dispatch('integrations/updateSlack', {
referenceId: this.selectedChannelId, referenceId: selectedChannelId.value,
}); });
useAlert(this.$t('INTEGRATION_SETTINGS.SLACK.UPDATE_SUCCESS')); useAlert(t('INTEGRATION_SETTINGS.SLACK.UPDATE_SUCCESS'));
} catch (error) { } catch (error) {
useAlert(error.message || 'INTEGRATION_SETTINGS.SLACK.UPDATE_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>

View File

@@ -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(); const { t } = useI18n();
return { const { formatMessage } = useMessageFormatter();
formatMessage,
}; 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>

View File

@@ -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 />

View File

@@ -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 />