mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 20:18:08 +00:00
Fixes https://linear.app/chatwoot/issue/CW-4150/support-for-multiple-issues-linking-in-linear This PR significantly improves the Linear integration user experience by relocating the Linear integration from the conversation header to the contact panel and adding support for multiple issue linking per conversation. ### Key Changes - **Relocated Linear integration**: Moved from conversation header to contact panel for better organization and accessibility - **Multi-issue support**: Added ability to link/create multiple Linear issues for a single conversation - **Integration CTA**: Added a dedicated call-to-action section for users who haven't connected their Linear account yet - **UI/UX improvements**: Enhanced design consistency and user flow <details> <summary>Screenshots</summary> #### Multiple Issues Support  #### Integration CTA  --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: Pranav <pranavrajs@gmail.com>
151 lines
4.4 KiB
Vue
151 lines
4.4 KiB
Vue
<script setup>
|
|
import { computed, ref } from 'vue';
|
|
import { useRoute } from 'vue-router';
|
|
import { useStore } from 'vuex';
|
|
import { useElementSize } from '@vueuse/core';
|
|
import BackButton from '../BackButton.vue';
|
|
import InboxName from '../InboxName.vue';
|
|
import MoreActions from './MoreActions.vue';
|
|
import Thumbnail from '../Thumbnail.vue';
|
|
import SLACardLabel from './components/SLACardLabel.vue';
|
|
import wootConstants from 'dashboard/constants/globals';
|
|
import { conversationListPageURL } from 'dashboard/helper/URLHelper';
|
|
import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers';
|
|
import { useInbox } from 'dashboard/composables/useInbox';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const props = defineProps({
|
|
chat: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
showBackButton: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
});
|
|
|
|
const { t } = useI18n();
|
|
const store = useStore();
|
|
const route = useRoute();
|
|
const conversationHeader = ref(null);
|
|
const { width } = useElementSize(conversationHeader);
|
|
const { isAWebWidgetInbox } = useInbox();
|
|
|
|
const currentChat = computed(() => store.getters.getSelectedChat);
|
|
const accountId = computed(() => store.getters.getCurrentAccountId);
|
|
|
|
const chatMetadata = computed(() => props.chat.meta);
|
|
|
|
const backButtonUrl = computed(() => {
|
|
const {
|
|
params: { inbox_id: inboxId, label, teamId },
|
|
name,
|
|
} = route;
|
|
return conversationListPageURL({
|
|
accountId,
|
|
inboxId,
|
|
label,
|
|
teamId,
|
|
conversationType: name === 'conversation_mentions' ? 'mention' : '',
|
|
});
|
|
});
|
|
|
|
const isHMACVerified = computed(() => {
|
|
if (!isAWebWidgetInbox.value) {
|
|
return true;
|
|
}
|
|
return chatMetadata.value.hmac_verified;
|
|
});
|
|
|
|
const currentContact = computed(() =>
|
|
store.getters['contacts/getContact'](props.chat.meta.sender.id)
|
|
);
|
|
|
|
const isSnoozed = computed(
|
|
() => currentChat.value.status === wootConstants.STATUS_TYPE.SNOOZED
|
|
);
|
|
|
|
const snoozedDisplayText = computed(() => {
|
|
const { snoozed_until: snoozedUntil } = currentChat.value;
|
|
if (snoozedUntil) {
|
|
return `${t('CONVERSATION.HEADER.SNOOZED_UNTIL')} ${snoozedReopenTime(snoozedUntil)}`;
|
|
}
|
|
return t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY');
|
|
});
|
|
|
|
const inbox = computed(() => {
|
|
const { inbox_id: inboxId } = props.chat;
|
|
return store.getters['inboxes/getInbox'](inboxId);
|
|
});
|
|
|
|
const hasMultipleInboxes = computed(
|
|
() => store.getters['inboxes/getInboxes'].length > 1
|
|
);
|
|
|
|
const hasSlaPolicyId = computed(() => props.chat?.sla_policy_id);
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
ref="conversationHeader"
|
|
class="flex flex-col gap-3 items-center justify-between flex-1 w-full min-w-0 xl:flex-row px-3 py-2 border-b bg-n-background border-n-weak h-24 xl:h-12"
|
|
>
|
|
<div
|
|
class="flex items-center justify-start w-full xl:w-auto max-w-full min-w-0 xl:flex-1"
|
|
>
|
|
<BackButton
|
|
v-if="showBackButton"
|
|
:back-url="backButtonUrl"
|
|
class="ltr:mr-2 rtl:ml-2"
|
|
/>
|
|
<Thumbnail
|
|
:src="currentContact.thumbnail"
|
|
:username="currentContact.name"
|
|
:status="currentContact.availability_status"
|
|
size="32px"
|
|
class="flex-shrink-0"
|
|
/>
|
|
<div
|
|
class="flex flex-col items-start min-w-0 ml-2 overflow-hidden rtl:ml-0 rtl:mr-2"
|
|
>
|
|
<div class="flex flex-row items-center max-w-full gap-1 p-0 m-0">
|
|
<span
|
|
class="text-sm font-medium truncate leading-tight text-n-slate-12"
|
|
>
|
|
{{ currentContact.name }}
|
|
</span>
|
|
<fluent-icon
|
|
v-if="!isHMACVerified"
|
|
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
|
|
size="14"
|
|
class="text-n-amber-10 my-0 mx-0 min-w-[14px] flex-shrink-0"
|
|
icon="warning"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
class="flex items-center gap-2 overflow-hidden text-xs conversation--header--actions text-ellipsis whitespace-nowrap"
|
|
>
|
|
<InboxName v-if="hasMultipleInboxes" :inbox="inbox" class="!mx-0" />
|
|
<span v-if="isSnoozed" class="font-medium text-n-amber-10">
|
|
{{ snoozedDisplayText }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="flex flex-row items-center justify-start xl:justify-end flex-shrink-0 gap-2 w-full xl:w-auto header-actions-wrap"
|
|
>
|
|
<SLACardLabel
|
|
v-if="hasSlaPolicyId"
|
|
:chat="chat"
|
|
show-extended-info
|
|
:parent-width="width"
|
|
class="hidden md:flex"
|
|
/>
|
|
<MoreActions :conversation-id="currentChat.id" />
|
|
</div>
|
|
</div>
|
|
</template>
|