Compare commits

...
Author SHA1 Message Date
Muhsin 8801a7b09a feat(inboxes): show connection action indicators 2026-07-16 15:53:28 +04:00
5 changed files with 124 additions and 7 deletions
@@ -22,11 +22,21 @@ const props = defineProps({
type: [Number, String],
default: 0,
},
manualMigrationRecommended: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['reviewManualMigration']);
const reauthorizationRequired = computed(() => {
return props.inbox.reauthorization_required;
});
const showManualMigrationRecommendation = computed(() => {
return props.manualMigrationRecommended && !reauthorizationRequired.value;
});
</script>
<template>
@@ -42,4 +52,14 @@ const reauthorizationRequired = computed(() => {
>
<Icon icon="i-woot-alert" class="size-3 text-n-ruby-9" />
</div>
<button
v-else-if="showManualMigrationRecommendation"
v-tooltip.top-end="$t('SIDEBAR.WHATSAPP_MANUAL_MIGRATION')"
type="button"
:aria-label="$t('SIDEBAR.WHATSAPP_MANUAL_MIGRATION')"
class="grid place-content-center size-5 bg-n-blue-5/60 rounded-full hover:bg-n-blue-5 focus-visible:bg-n-blue-5 focus-visible:outline-none"
@click.stop.prevent="emit('reviewManualMigration')"
>
<Icon icon="i-lucide-info" class="size-3 text-n-blue-9" />
</button>
</template>
@@ -6,6 +6,8 @@ import { useKbd } from 'dashboard/composables/utils/useKbd';
import { useMapGetter } from 'dashboard/composables/store';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useAdmin } from 'dashboard/composables/useAdmin';
import { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts';
import { vOnClickOutside } from '@vueuse/components';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
@@ -43,6 +45,8 @@ const emit = defineEmits([
]);
const { accountScopedRoute, isOnChatwootCloud } = useAccount();
const { isAdmin } = useAdmin();
const router = useRouter();
const store = useStore();
const searchShortcut = useKbd([`$mod`, 'k']);
const { t } = useI18n();
@@ -92,6 +96,32 @@ const hasDataImport = computed(() => {
);
});
const hasWhatsAppManualTransfer = computed(() => {
return isFeatureEnabledonAccount.value(
accountId.value,
FEATURE_FLAGS.WHATSAPP_MANUAL_TRANSFER
);
});
const isWhatsAppManualMigrationRecommended = inbox => {
return (
isAdmin.value &&
hasWhatsAppManualTransfer.value &&
inbox.channel_type === 'Channel::Whatsapp' &&
inbox.provider === 'whatsapp_cloud' &&
inbox.provider_config?.source === 'embedded_signup' &&
!inbox.reauthorization_required
);
};
const reviewWhatsAppManualMigration = inboxId => {
router.push(
accountScopedRoute('settings_inbox_show', {
inboxId,
})
);
};
const fetchConversationUnreadCounts = ([currentAccountId, isEnabled]) => {
if (!currentAccountId) return;
@@ -458,6 +488,10 @@ const menuItems = computed(() => {
active: leafProps.active,
inbox,
badgeCount: leafProps.badgeCount,
manualMigrationRecommended:
isWhatsAppManualMigrationRecommended(inbox),
onReviewManualMigration: () =>
reviewWhatsAppManualMigration(inbox.id),
}),
})),
},
@@ -11,7 +11,9 @@
"WHATSAPP_REGISTRATION_INCOMPLETE": "Your WhatsApp Business registration isnt complete. Please check your display name status in Meta Business Manager before reconnecting.",
"COMPLETE_REGISTRATION": "Complete Registration",
"LIST": {
"404": "There are no inboxes attached to this account."
"404": "There are no inboxes attached to this account.",
"REAUTHORIZATION_REQUIRED": "Reauthorization required",
"MANUAL_SETUP_RECOMMENDED": "Manual setup recommended"
},
"CREATE_FLOW": {
"CHANNEL": {
@@ -380,6 +380,7 @@
"BETA": "Beta",
"REPORTS_OVERVIEW": "Overview",
"REAUTHORIZE": "Your inbox connection has expired, please reconnect\n to continue receiving and sending messages",
"WHATSAPP_MANUAL_MIGRATION": "Manual setup recommended. Reconnect this WhatsApp inbox with your own Meta app.",
"HELP_CENTER": {
"TITLE": "Help Center",
"ARTICLES": "Articles",
@@ -15,6 +15,9 @@ import {
import ChannelName from './components/ChannelName.vue';
import ChannelIcon from 'next/icon/ChannelIcon.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import Label from 'dashboard/components-next/label/Label.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
const getters = useStoreGetters();
const store = useStore();
@@ -26,6 +29,27 @@ const selectedInbox = ref({});
const searchQuery = ref('');
const inboxes = useMapGetter('inboxes/getInboxes');
const accountId = useMapGetter('getCurrentAccountId');
const isFeatureEnabledonAccount = useMapGetter(
'accounts/isFeatureEnabledonAccount'
);
const hasWhatsAppManualTransfer = computed(() => {
return isFeatureEnabledonAccount.value(
accountId.value,
FEATURE_FLAGS.WHATSAPP_MANUAL_TRANSFER
);
});
const isWhatsAppManualMigrationRecommended = inbox => {
return (
hasWhatsAppManualTransfer.value &&
inbox.channel_type === 'Channel::Whatsapp' &&
inbox.provider === 'whatsapp_cloud' &&
inbox.provider_config?.source === 'embedded_signup' &&
!inbox.reauthorization_required
);
};
onActivated(() => {
store.dispatch('inboxes/get');
@@ -146,12 +170,48 @@ const openDelete = inbox => {
<span class="block text-heading-3 text-n-slate-12 capitalize">
{{ inbox.name }}
</span>
<ChannelName
:channel-type="inbox.channel_type"
:medium="inbox.medium"
:voice-enabled="inbox.voice_enabled"
class="text-body-main text-n-slate-11"
/>
<div class="flex flex-wrap items-center gap-2">
<ChannelName
:channel-type="inbox.channel_type"
:medium="inbox.medium"
:voice-enabled="inbox.voice_enabled"
class="text-body-main text-n-slate-11"
/>
<router-link
v-if="inbox.reauthorization_required"
:to="{
name: 'settings_inbox_show',
params: { inboxId: inbox.id },
}"
>
<Label
:label="$t('INBOX_MGMT.LIST.REAUTHORIZATION_REQUIRED')"
color="ruby"
compact
>
<template #icon>
<Icon icon="i-lucide-triangle-alert" class="size-3.5" />
</template>
</Label>
</router-link>
<router-link
v-else-if="isWhatsAppManualMigrationRecommended(inbox)"
:to="{
name: 'settings_inbox_show',
params: { inboxId: inbox.id },
}"
>
<Label
:label="$t('INBOX_MGMT.LIST.MANUAL_SETUP_RECOMMENDED')"
color="blue"
compact
>
<template #icon>
<Icon icon="i-lucide-info" class="size-3.5" />
</template>
</Label>
</router-link>
</div>
</div>
</div>
<div class="flex gap-3 justify-end">