Merge branch 'develop' into feat/CW-7403
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { until } from '@vueuse/core';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store';
|
||||
@@ -49,7 +50,9 @@ const isVoiceEnabled = computed(
|
||||
const calls = computed(() => callHistoryStore.records);
|
||||
const meta = computed(() => callHistoryStore.meta);
|
||||
const isFetching = computed(() => callHistoryStore.uiFlags.isFetching);
|
||||
const inboxesUiFlags = useMapGetter('inboxes/getUIFlags');
|
||||
const accountUiFlags = useMapGetter('accounts/getUIFlags');
|
||||
|
||||
const isInitializing = ref(true);
|
||||
|
||||
// Filters are seeded from the URL so a shared link restores the same view.
|
||||
const activity = ref(
|
||||
@@ -98,20 +101,25 @@ const onPageChange = page => {
|
||||
fetchCalls();
|
||||
};
|
||||
|
||||
// inboxes/get flips isFetching true synchronously, so the spinner shows on the
|
||||
// first render and the setup CTA never flashes; hit the calls endpoint only
|
||||
// once inboxes confirm voice is on.
|
||||
store.dispatch('inboxes/get').then(() => {
|
||||
if (!isVoiceEnabled.value) return;
|
||||
// Only admins see the assignee filter, so only they need the agent list.
|
||||
if (isAdmin.value) store.dispatch('agents/get');
|
||||
fetchCalls();
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
store.dispatch('inboxes/get'),
|
||||
until(() => accountUiFlags.value.isFetchingItem).toBe(false),
|
||||
]);
|
||||
if (!isVoiceEnabled.value) return;
|
||||
// Only admins see the assignee filter, so only they need the agent list.
|
||||
if (isAdmin.value) store.dispatch('agents/get');
|
||||
await fetchCalls();
|
||||
} finally {
|
||||
isInitializing.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="inboxesUiFlags.isFetching"
|
||||
v-if="isInitializing"
|
||||
class="flex items-center justify-center w-full h-full bg-n-surface-1"
|
||||
>
|
||||
<Spinner :size="24" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, onUnmounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
@@ -27,19 +27,49 @@ const selectedRange = ref('this_month');
|
||||
|
||||
const assistantId = computed(() => route.params.assistantId);
|
||||
const stats = ref(null);
|
||||
const isFetching = ref(false);
|
||||
|
||||
// Increments on every fetch so a response (or retry) from a superseded
|
||||
// range/assistant can't clobber the latest request's state.
|
||||
let fetchToken = 0;
|
||||
let abortController = null;
|
||||
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const { data } = await CaptainAssistant.getStats({
|
||||
fetchToken += 1;
|
||||
const token = fetchToken;
|
||||
abortController?.abort();
|
||||
abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
stats.value = null;
|
||||
isFetching.value = true;
|
||||
|
||||
const requestStats = () =>
|
||||
CaptainAssistant.getStats({
|
||||
assistantId: assistantId.value,
|
||||
range: selectedRange.value,
|
||||
signal,
|
||||
});
|
||||
stats.value = data;
|
||||
|
||||
let data = null;
|
||||
try {
|
||||
({ data } = await requestStats());
|
||||
} catch {
|
||||
stats.value = null;
|
||||
// One silent retry before giving up, unless the request was aborted.
|
||||
try {
|
||||
if (token === fetchToken && !signal.aborted)
|
||||
({ data } = await requestStats());
|
||||
} catch {
|
||||
data = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (token !== fetchToken || signal.aborted) return;
|
||||
stats.value = data;
|
||||
isFetching.value = false;
|
||||
};
|
||||
|
||||
onUnmounted(() => abortController?.abort());
|
||||
|
||||
watch([selectedRange, assistantId], fetchStats, { immediate: true });
|
||||
|
||||
// `direction` says whether a rising trend is good ('up'), bad ('down'), or
|
||||
@@ -156,7 +186,7 @@ const closeDrilldown = () => {
|
||||
|
||||
<CoverageBanner :knowledge="stats?.knowledge" />
|
||||
|
||||
<WelcomeCard :range="selectedRange" />
|
||||
<WelcomeCard :range="selectedRange" :stats="stats" />
|
||||
|
||||
<div
|
||||
class="grid grid-cols-1 gap-px overflow-hidden border rounded-xl sm:grid-cols-2 lg:grid-cols-3 bg-n-weak border-n-weak"
|
||||
@@ -169,7 +199,8 @@ const closeDrilldown = () => {
|
||||
:trend="metric.trend"
|
||||
:hint="metric.hint"
|
||||
:trend-good="metric.trendGood"
|
||||
:clickable="canDrilldown && Boolean(metric.metric)"
|
||||
:loading="isFetching"
|
||||
:clickable="canDrilldown && Boolean(metric.metric) && !isFetching"
|
||||
@click="openDrilldown(metric)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
+1
-3
@@ -18,9 +18,7 @@ export function useChannelConfig() {
|
||||
// app id (not the 'none' sentinel) and the signup configuration id.
|
||||
whatsapp: () =>
|
||||
(!isOnChatwootCloud.value ||
|
||||
isCloudFeatureEnabled(
|
||||
FEATURE_FLAGS.WHATSAPP_EMBEDDED_SIGNUP_INBOX_CREATION
|
||||
)) &&
|
||||
isCloudFeatureEnabled(FEATURE_FLAGS.WHATSAPP_EMBEDDED_SIGNUP_FLOW)) &&
|
||||
Boolean(installationConfig.whatsappAppId) &&
|
||||
installationConfig.whatsappAppId !== 'none' &&
|
||||
Boolean(installationConfig.whatsappConfigurationId),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
@@ -14,7 +14,7 @@ const { accountId, currentAccount } = useAccount();
|
||||
|
||||
const globalConfig = useMapGetter('globalConfig/get');
|
||||
|
||||
const enabledFeatures = ref({});
|
||||
const enabledFeatures = computed(() => currentAccount.value?.features || {});
|
||||
|
||||
const hasTiktokConfigured = computed(() => {
|
||||
return window.chatwootConfig?.tiktokAppId;
|
||||
@@ -105,10 +105,6 @@ const channelList = computed(() => {
|
||||
return channels;
|
||||
});
|
||||
|
||||
const initializeEnabledFeatures = async () => {
|
||||
enabledFeatures.value = currentAccount.value.features;
|
||||
};
|
||||
|
||||
const initChannelAuth = channel => {
|
||||
const params = {
|
||||
sub_page: channel,
|
||||
@@ -116,10 +112,6 @@ const initChannelAuth = channel => {
|
||||
};
|
||||
router.push({ name: 'settings_inboxes_page_channel', params });
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initializeEnabledFeatures();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -390,6 +390,11 @@ export default {
|
||||
return (
|
||||
this.isAWhatsAppCloudChannel &&
|
||||
this.isEmbeddedSignupWhatsApp &&
|
||||
(!this.isOnChatwootCloud ||
|
||||
this.isFeatureEnabledonAccount(
|
||||
this.accountId,
|
||||
FEATURE_FLAGS.WHATSAPP_EMBEDDED_SIGNUP_FLOW
|
||||
)) &&
|
||||
this.inbox.reauthorization_required
|
||||
);
|
||||
},
|
||||
|
||||
@@ -42,9 +42,7 @@ const shouldShowWhatsappEmbeddedSignup = computed(() => {
|
||||
selectedProvider.value === PROVIDER_TYPES.WHATSAPP &&
|
||||
hasWhatsappAppId.value &&
|
||||
(!isOnChatwootCloud.value ||
|
||||
isCloudFeatureEnabled(
|
||||
FEATURE_FLAGS.WHATSAPP_EMBEDDED_SIGNUP_INBOX_CREATION
|
||||
))
|
||||
isCloudFeatureEnabled(FEATURE_FLAGS.WHATSAPP_EMBEDDED_SIGNUP_FLOW))
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
+5
-3
@@ -3,6 +3,7 @@ import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Avatar from 'next/avatar/Avatar.vue';
|
||||
import RadioCard from 'dashboard/components-next/radioCard/RadioCard.vue';
|
||||
import { useBranding } from 'shared/composables/useBranding';
|
||||
|
||||
const props = defineProps({
|
||||
senderNameType: {
|
||||
@@ -22,6 +23,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const { replaceInstallationName } = useBranding();
|
||||
|
||||
const senderNameKeyOptions = computed(() => [
|
||||
{
|
||||
@@ -30,7 +32,7 @@ const senderNameKeyOptions = computed(() => [
|
||||
content: t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.FRIENDLY.SUBTITLE'),
|
||||
preview: {
|
||||
senderName: 'Smith',
|
||||
businessName: 'Chatwoot',
|
||||
businessName: replaceInstallationName('Chatwoot'),
|
||||
email: '<support@yourbusiness.com>',
|
||||
},
|
||||
},
|
||||
@@ -40,7 +42,7 @@ const senderNameKeyOptions = computed(() => [
|
||||
content: t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.PROFESSIONAL.SUBTITLE'),
|
||||
preview: {
|
||||
senderName: '',
|
||||
businessName: 'Chatwoot',
|
||||
businessName: replaceInstallationName('Chatwoot'),
|
||||
email: '<support@yourbusiness.com>',
|
||||
},
|
||||
},
|
||||
@@ -51,7 +53,7 @@ const isKeyOptionFriendly = key => key === 'friendly';
|
||||
const userName = keyOption =>
|
||||
isKeyOptionFriendly(keyOption.key)
|
||||
? keyOption.preview.senderName
|
||||
: keyOption.preview.businessName;
|
||||
: props.businessName || keyOption.preview.businessName;
|
||||
|
||||
const toggleSenderNameType = key => {
|
||||
emit('update', key);
|
||||
|
||||
+4
-1
@@ -56,6 +56,7 @@ export default {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
|
||||
}),
|
||||
isEmbeddedSignupWhatsApp() {
|
||||
return this.inbox.provider_config?.source === 'embedded_signup';
|
||||
@@ -65,7 +66,9 @@ export default {
|
||||
this.isEmbeddedSignupWhatsApp &&
|
||||
this.isFeatureEnabledonAccount(
|
||||
this.accountId,
|
||||
FEATURE_FLAGS.WHATSAPP_RECONFIGURE
|
||||
this.isOnChatwootCloud
|
||||
? FEATURE_FLAGS.WHATSAPP_EMBEDDED_SIGNUP_FLOW
|
||||
: FEATURE_FLAGS.WHATSAPP_RECONFIGURE
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user