Merge branch 'develop' into fix/next-message-bubbles

This commit is contained in:
Sivin Varghese
2025-02-26 14:52:32 +05:30
committed by GitHub
58 changed files with 1083 additions and 476 deletions
@@ -50,6 +50,7 @@ const { t } = useI18n();
</div>
<ComboBox
id="inbox"
use-api-results
:model-value="primaryContactId"
:options="primaryContactList"
:empty-state="
@@ -1,12 +1,12 @@
<script setup>
import { computed } from 'vue';
import { useAccount } from 'dashboard/composables/useAccount';
import { usePolicy } from 'dashboard/composables/usePolicy';
import Button from 'dashboard/components-next/button/Button.vue';
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import Policy from 'dashboard/components/policy.vue';
const { featureFlag } = defineProps({
const props = defineProps({
currentPage: {
type: Number,
default: 1,
@@ -50,10 +50,10 @@ const { featureFlag } = defineProps({
});
const emit = defineEmits(['click', 'close', 'update:currentPage']);
const { isCloudFeatureEnabled } = useAccount();
const { shouldShowPaywall } = usePolicy();
const showPaywall = computed(() => {
return !isCloudFeatureEnabled(featureFlag);
return shouldShowPaywall(props.featureFlag);
});
const handleButtonClick = () => {
@@ -97,7 +97,7 @@ const handlePageChange = event => {
</header>
<main class="flex-1 px-6 overflow-y-auto xl:px-0">
<div class="w-full max-w-[960px] mx-auto py-4">
<slot name="controls" />
<slot v-if="!showPaywall" name="controls" />
<div
v-if="isFetching"
class="flex items-center justify-center py-10 text-n-slate-11"
@@ -13,34 +13,14 @@ const props = defineProps({
validator: value =>
value.every(option => 'value' in option && 'label' in option),
},
placeholder: {
type: String,
default: '',
},
modelValue: {
type: [String, Number],
default: '',
},
disabled: {
type: Boolean,
default: false,
},
searchPlaceholder: {
type: String,
default: '',
},
emptyState: {
type: String,
default: '',
},
message: {
type: String,
default: '',
},
hasError: {
type: Boolean,
default: false,
},
placeholder: { type: String, default: '' },
modelValue: { type: [String, Number], default: '' },
disabled: { type: Boolean, default: false },
searchPlaceholder: { type: String, default: '' },
emptyState: { type: String, default: '' },
message: { type: String, default: '' },
hasError: { type: Boolean, default: false },
useApiResults: { type: Boolean, default: false }, // useApiResults prop to determine if search is handled by API
});
const emit = defineEmits(['update:modelValue', 'search']);
@@ -54,6 +34,12 @@ const dropdownRef = ref(null);
const comboboxRef = ref(null);
const filteredOptions = computed(() => {
// For API search, don't filter options locally
if (props.useApiResults && search.value) {
return props.options;
}
// For local search, filter options based on search term
const searchTerm = search.value.toLowerCase();
return props.options.filter(option =>
option.label.toLowerCase().includes(searchTerm)
@@ -240,24 +240,20 @@ const menuItems = computed(() => {
name: 'Captain',
icon: 'i-woot-captain',
label: t('SIDEBAR.CAPTAIN'),
showOnlyOnCloud: true,
children: [
{
name: 'Assistants',
label: t('SIDEBAR.CAPTAIN_ASSISTANTS'),
showOnlyOnCloud: true,
to: accountScopedRoute('captain_assistants_index'),
},
{
name: 'Documents',
label: t('SIDEBAR.CAPTAIN_DOCUMENTS'),
showOnlyOnCloud: true,
to: accountScopedRoute('captain_documents_index'),
},
{
name: 'Responses',
label: t('SIDEBAR.CAPTAIN_RESPONSES'),
showOnlyOnCloud: true,
to: accountScopedRoute('captain_responses_index'),
},
],
@@ -509,7 +505,6 @@ const menuItems = computed(() => {
name: 'Settings Billing',
label: t('SIDEBAR.BILLING'),
icon: 'i-lucide-credit-card',
showOnlyOnCloud: true,
to: accountScopedRoute('billing_settings_index'),
},
],
@@ -24,7 +24,6 @@ const {
resolvePath,
resolvePermissions,
resolveFeatureFlag,
isOnChatwootCloud,
isAllowed,
} = useSidebarContext();
@@ -43,7 +42,6 @@ const hasChildren = computed(
const accessibleItems = computed(() => {
if (!hasChildren.value) return [];
return props.children.filter(child => {
if (child.showOnlyOnCloud && !isOnChatwootCloud.value) return false;
// If a item has no link, it means it's just a subgroup header
// So we don't need to check for permissions here, because there's nothing to
// access here anyway
@@ -166,7 +164,7 @@ onMounted(async () => {
:active-child="activeChild"
/>
<SidebarGroupLeaf
v-else
v-else-if="isAllowed(child.to)"
v-show="isExpanded || activeChild?.name === child.name"
v-bind="child"
:active="activeChild?.name === child.name"
@@ -9,18 +9,10 @@ const props = defineProps({
to: { type: [String, Object], required: true },
icon: { type: [String, Object], default: null },
active: { type: Boolean, default: false },
showOnlyOnCloud: { type: Boolean, default: false },
component: { type: Function, default: null },
});
const { resolvePermissions, resolveFeatureFlag, isOnChatwootCloud } =
useSidebarContext();
const allowedToShow = computed(() => {
if (props.showOnlyOnCloud && !isOnChatwootCloud.value) return false;
return true;
});
const { resolvePermissions, resolveFeatureFlag } = useSidebarContext();
const shouldRenderComponent = computed(() => {
return typeof props.component === 'function' || isVNode(props.component);
@@ -30,7 +22,6 @@ const shouldRenderComponent = computed(() => {
<!-- eslint-disable-next-line vue/no-root-v-if -->
<template>
<Policy
v-if="allowedToShow"
:permissions="resolvePermissions(to)"
:feature-flag="resolveFeatureFlag(to)"
as="li"
@@ -13,6 +13,7 @@ import {
DropdownSeparator,
DropdownItem,
} from 'next/dropdown-menu/base';
import CustomBrandPolicyWrapper from '../../components/CustomBrandPolicyWrapper.vue';
const emit = defineEmits(['close', 'openKeyShortcutModal']);
@@ -43,6 +44,7 @@ const menuItems = computed(() => {
return [
{
show: showChatSupport.value,
showOnCustomBrandedInstance: false,
label: t('SIDEBAR_ITEMS.CONTACT_SUPPORT'),
icon: 'i-lucide-life-buoy',
click: () => {
@@ -51,6 +53,7 @@ const menuItems = computed(() => {
},
{
show: true,
showOnCustomBrandedInstance: true,
label: t('SIDEBAR_ITEMS.KEYBOARD_SHORTCUTS'),
icon: 'i-lucide-keyboard',
click: () => {
@@ -59,12 +62,14 @@ const menuItems = computed(() => {
},
{
show: true,
showOnCustomBrandedInstance: true,
label: t('SIDEBAR_ITEMS.PROFILE_SETTINGS'),
icon: 'i-lucide-user-pen',
link: { name: 'profile_settings_index' },
},
{
show: true,
showOnCustomBrandedInstance: true,
label: t('SIDEBAR_ITEMS.APPEARANCE'),
icon: 'i-lucide-palette',
click: () => {
@@ -74,6 +79,7 @@ const menuItems = computed(() => {
},
{
show: true,
showOnCustomBrandedInstance: false,
label: t('SIDEBAR_ITEMS.DOCS'),
icon: 'i-lucide-book',
link: 'https://www.chatwoot.com/hc/user-guide/en',
@@ -82,6 +88,7 @@ const menuItems = computed(() => {
},
{
show: currentUser.value.type === 'SuperAdmin',
showOnCustomBrandedInstance: true,
label: t('SIDEBAR_ITEMS.SUPER_ADMIN_CONSOLE'),
icon: 'i-lucide-castle',
link: '/super_admin',
@@ -90,6 +97,7 @@ const menuItems = computed(() => {
},
{
show: true,
showOnCustomBrandedInstance: true,
label: t('SIDEBAR_ITEMS.LOGOUT'),
icon: 'i-lucide-power',
click: Auth.logout,
@@ -136,7 +144,11 @@ const allowedMenuItems = computed(() => {
<SidebarProfileMenuStatus />
<DropdownSeparator />
<template v-for="item in allowedMenuItems" :key="item.label">
<DropdownItem v-if="item.show" v-bind="item" />
<CustomBrandPolicyWrapper
:show-on-custom-branded-instance="item.showOnCustomBrandedInstance"
>
<DropdownItem v-if="item.show" v-bind="item" />
</CustomBrandPolicyWrapper>
</template>
</DropdownBody>
</DropdownContainer>
@@ -14,12 +14,11 @@ const props = defineProps({
activeChild: { type: Object, default: undefined },
});
const { isAllowed, isOnChatwootCloud } = useSidebarContext();
const { isAllowed } = useSidebarContext();
const scrollableContainer = ref(null);
const accessibleItems = computed(() =>
props.children.filter(child => {
if (child.showOnlyOnCloud && !isOnChatwootCloud.value) return false;
return child.to && isAllowed(child.to);
})
);
@@ -1,5 +1,4 @@
import { inject, provide } from 'vue';
import { useMapGetter } from 'dashboard/composables/store';
import { usePolicy } from 'dashboard/composables/usePolicy';
import { useRouter } from 'vue-router';
@@ -12,9 +11,8 @@ export function useSidebarContext() {
}
const router = useRouter();
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
const { checkFeatureAllowed, checkPermissions } = usePolicy();
const { shouldShow } = usePolicy();
const resolvePath = to => {
if (to) return router.resolve(to)?.path || '/';
@@ -31,11 +29,17 @@ export function useSidebarContext() {
return '';
};
const resolveInstallationType = to => {
if (to) return router.resolve(to)?.meta?.installationTypes || [];
return [];
};
const isAllowed = to => {
const permissions = resolvePermissions(to);
const featureFlag = resolveFeatureFlag(to);
const installationType = resolveInstallationType(to);
return checkPermissions(permissions) && checkFeatureAllowed(featureFlag);
return shouldShow(featureFlag, permissions, installationType);
};
return {
@@ -44,7 +48,6 @@ export function useSidebarContext() {
resolvePermissions,
resolveFeatureFlag,
isAllowed,
isOnChatwootCloud,
};
}