chore: remove the legacy notifications page (#15426)

This commit is contained in:
Sivin Varghese
2026-08-13 11:49:44 +05:30
committed by GitHub
parent 046c6c754f
commit 590e17dfb7
16 changed files with 1 additions and 481 deletions

View File

@@ -1,40 +0,0 @@
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useMapGetter } from 'dashboard/composables/store';
const emit = defineEmits(['openNotificationPanel']);
const notificationMetadata = useMapGetter('notifications/getMeta');
const route = useRoute();
const unreadCount = computed(() => {
if (!notificationMetadata.value.unreadCount) {
return '';
}
return notificationMetadata.value.unreadCount < 100
? `${notificationMetadata.value.unreadCount}`
: '99+';
});
function openNotificationPanel() {
if (route.name !== 'notifications_index') {
emit('openNotificationPanel');
}
}
</script>
<template>
<button
class="size-8 rounded-lg hover:bg-n-alpha-1 flex-shrink-0 grid place-content-center relative"
@click="openNotificationPanel"
>
<span class="i-lucide-bell size-4" />
<span
v-if="unreadCount"
class="min-h-2 min-w-2 p-0.5 px-1 bg-n-ruby-9 rounded-lg absolute -top-1 -right-1.5 grid place-items-center text-[9px] leading-none text-n-ruby-3"
>
{{ unreadCount }}
</span>
</button>
</template>

View File

@@ -7,7 +7,6 @@ import { BUS_EVENTS } from 'shared/constants/busEvents';
import {
isAConversationRoute,
isAInboxViewRoute,
isNotificationRoute,
} from 'dashboard/helper/routeHelpers';
import { useEventListener } from '@vueuse/core';
@@ -47,9 +46,7 @@ const closeNotification = () => {
const isInAnyOfTheRoutes = routeName => {
return (
isAConversationRoute(routeName, true) ||
isAInboxViewRoute(routeName, true) ||
isNotificationRoute(routeName, true)
isAConversationRoute(routeName, true) || isAInboxViewRoute(routeName, true)
);
};

View File

@@ -4,7 +4,6 @@ import { useMapGetter } from 'dashboard/composables/store';
import { useRouter } from 'vue-router';
import { usePolicy } from 'dashboard/composables/usePolicy';
import {
ICON_BELL,
ICON_BLOCKS,
ICON_BOT,
ICON_BRIEFCASE,
@@ -262,13 +261,6 @@ const GO_TO_COMMANDS = [
icon: ICON_USER_PEN,
routeName: 'profile_settings_index',
},
{
id: 'open_notifications',
title: 'COMMAND_BAR.COMMANDS.GO_TO_NOTIFICATIONS',
section: SECTION_SETTINGS,
icon: ICON_BELL,
routeName: 'notifications_index',
},
];
export function useGoToCommandHotKeys(isPaywalled = ref(false)) {

View File

@@ -19,9 +19,6 @@ export const ACCOUNT_EVENTS = Object.freeze({
ADDED_A_CUSTOM_ATTRIBUTE: 'Added a custom attribute',
ADDED_AN_INBOX: 'Added an inbox',
OPEN_MESSAGE_CONTEXT_MENU: 'Opened message context menu',
OPENED_NOTIFICATIONS: 'Opened notifications',
MARK_AS_READ_NOTIFICATIONS: 'Marked notifications as read',
OPEN_CONVERSATION_VIA_NOTIFICATION: 'Opened conversation via notification',
});
export const LABEL_EVENTS = Object.freeze({

View File

@@ -4,7 +4,6 @@ import { differenceInSeconds } from 'date-fns';
import {
isAConversationRoute,
isAInboxViewRoute,
isNotificationRoute,
} from 'dashboard/helper/routeHelpers';
const MAX_DISCONNECT_SECONDS = 10800;
@@ -122,8 +121,6 @@ class ReconnectService {
await this.fetchNotificationsOnReconnect(
this.store.getters['notifications/getNotificationFilters']
);
} else if (isNotificationRoute(currentRoute)) {
await this.fetchNotificationsOnReconnect();
}
};

View File

@@ -148,9 +148,6 @@ export const isAInboxViewRoute = (routeName, includeBase = false) => {
return routeNames.includes(routeName);
};
export const isNotificationRoute = routeName =>
routeName === 'notifications_index';
export const isUpgradePageBypassRoute = routeName =>
[
'billing_settings_index',

View File

@@ -4,7 +4,6 @@ import { differenceInSeconds } from 'date-fns';
import {
isAConversationRoute,
isAInboxViewRoute,
isNotificationRoute,
} from 'dashboard/helper/routeHelpers';
import ReconnectService from 'dashboard/helper/ReconnectService';
@@ -23,7 +22,6 @@ vi.mock('date-fns', () => ({
vi.mock('dashboard/helper/routeHelpers', () => ({
isAConversationRoute: vi.fn(),
isAInboxViewRoute: vi.fn(),
isNotificationRoute: vi.fn(),
}));
const storeMock = {
@@ -296,13 +294,6 @@ describe('ReconnectService', () => {
await reconnectService.handleRouteSpecificFetch();
expect(spy).toHaveBeenCalled();
});
it('should fetch notifications if current route is a notification route', async () => {
isNotificationRoute.mockReturnValue(true);
const spy = vi.spyOn(reconnectService, 'fetchNotificationsOnReconnect');
await reconnectService.handleRouteSpecificFetch();
expect(spy).toHaveBeenCalled();
});
});
describe('setConversationLastMessageId', () => {

View File

@@ -139,37 +139,6 @@
"SELECT": "Select"
}
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Notifications",
"MARK_ALL_DONE": "Mark All Done",
"DELETE_TITLE": "deleted",
"UNREAD_NOTIFICATION": {
"TITLE": "Unread Notifications",
"ALL_NOTIFICATIONS": "View all notifications",
"LOADING_UNREAD_MESSAGE": "Loading unread notifications...",
"EMPTY_MESSAGE": "You have no unread notifications"
},
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
"TABLE_HEADER": [
"Name",
"Phone Number",
"Conversations",
"Last Contacted"
]
},
"TYPE_LABEL": {
"conversation_creation": "New conversation",
"conversation_assignment": "Conversation Assigned",
"assigned_conversation_new_message": "New Message",
"participating_conversation_new_message": "New Message",
"conversation_mention": "Mention",
"sla_missed_first_response": "SLA Missed",
"sla_missed_next_response": "SLA Missed",
"sla_missed_resolution": "SLA Missed"
}
},
"NETWORK": {
"NOTIFICATION": {
"OFFLINE": "Offline",
@@ -232,7 +201,6 @@
"GO_TO_SETTINGS_BILLING": "Go to Billing",
"GO_TO_SETTINGS_ACCOUNT": "Go to Account Settings",
"GO_TO_SETTINGS_PROFILE": "Go to Profile Settings",
"GO_TO_NOTIFICATIONS": "Go to Notifications",
"ADD_LABELS_TO_CONVERSATION": "Add label to the conversation",
"ASSIGN_AN_AGENT": "Assign an agent",
"AI_ASSIST": "AI Assist",

View File

@@ -4,7 +4,6 @@ import { routes as searchRoutes } from '../../modules/search/search.routes';
import { routes as callRoutes } from './calls/routes';
import { routes as contactRoutes } from './contacts/routes';
import { routes as companyRoutes } from './companies/routes';
import { routes as notificationRoutes } from './notifications/routes';
import { routes as inboxRoutes } from './inbox/routes';
import { frontendURL } from '../../helper/URLHelper';
import helpcenterRoutes from './helpcenter/helpcenter.routes';
@@ -30,7 +29,6 @@ export default {
...contactRoutes,
...companyRoutes,
...searchRoutes,
...notificationRoutes,
...helpcenterRoutes.routes,
...campaignsRoutes.routes,
],

View File

@@ -1,211 +0,0 @@
<script>
import Avatar from 'next/avatar/Avatar.vue';
import Spinner from 'shared/components/Spinner.vue';
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
import { dynamicTime } from 'shared/helpers/timeHelper';
import { mapGetters } from 'vuex';
import NextButton from 'dashboard/components-next/button/Button.vue';
export default {
components: {
Avatar,
Spinner,
EmptyState,
NextButton,
},
props: {
notifications: {
type: Array,
default: () => [],
},
isLoading: {
type: Boolean,
default: false,
},
isUpdating: {
type: Boolean,
default: false,
},
onClickNotification: {
type: Function,
default: () => {},
},
onMarkAllDoneClick: {
type: Function,
default: () => {},
},
},
computed: {
...mapGetters({
notificationMetadata: 'notifications/getMeta',
}),
showEmptyResult() {
return !this.isLoading && this.notifications.length === 0;
},
},
methods: {
dynamicTime,
},
};
</script>
<template>
<section
class="flex-grow flex-shrink h-full px-4 py-8 overflow-hidden bg-n-background"
>
<div class="flex w-full items-center justify-between gap-2 mb-4">
<h6 class="text-xl font-medium text-n-slate-12">
{{ $t('NOTIFICATIONS_PAGE.HEADER') }}
</h6>
<NextButton
v-if="notificationMetadata.unreadCount"
type="submit"
sm
:label="$t('NOTIFICATIONS_PAGE.MARK_ALL_DONE')"
:is-loading="isUpdating"
@click="onMarkAllDoneClick"
/>
</div>
<table class="notifications-table overflow-auto">
<tbody v-show="!isLoading">
<tr
v-for="notificationItem in notifications"
:key="notificationItem.id"
:class="{
'is-unread': notificationItem.read_at === null,
}"
class="border-b border-n-weak"
@click="() => onClickNotification(notificationItem)"
>
<td class="p-2.5 text-n-slate-12">
<div
class="overflow-hidden flex-view notification-contant--wrap whitespace-nowrap text-ellipsis"
>
<h5 class="notification--title">
{{
`#${
notificationItem.primary_actor
? notificationItem.primary_actor.id
: $t(`NOTIFICATIONS_PAGE.DELETE_TITLE`)
}`
}}
</h5>
<span
class="overflow-hidden notification--message-title whitespace-nowrap text-ellipsis"
>
{{ notificationItem.push_message_title }}
</span>
</div>
</td>
<td class="text-right">
<span class="notification--type">
{{
$t(
`NOTIFICATIONS_PAGE.TYPE_LABEL.${notificationItem.notification_type}`
)
}}
</span>
</td>
<td class="thumbnail--column">
<Avatar
v-if="notificationItem.primary_actor.meta.assignee"
:src="notificationItem.primary_actor.meta.assignee.thumbnail"
:size="28"
:name="notificationItem.primary_actor.meta.assignee.name"
rounded-full
/>
</td>
<td>
<div class="text-right timestamp--column ltr:mr-2 rtl:ml-2">
<span class="notification--created-at">
{{ dynamicTime(notificationItem.last_activity_at) }}
</span>
</div>
</td>
<td>
<div
v-if="!notificationItem.read_at"
class="notification--unread-indicator"
/>
</td>
</tr>
</tbody>
</table>
<EmptyState
v-if="showEmptyResult"
:title="$t('NOTIFICATIONS_PAGE.LIST.404')"
/>
<div v-if="isLoading" class="notifications--loader">
<Spinner />
<span>{{ $t('NOTIFICATIONS_PAGE.LIST.LOADING_MESSAGE') }}</span>
</div>
</section>
</template>
<style lang="scss" scoped>
.notification--title {
@apply text-sm m-0 text-n-slate-12;
}
.notifications-table {
> tbody {
> tr {
@apply cursor-pointer;
&:hover {
@apply bg-n-slate-3;
}
&.is-active {
@apply bg-n-slate-4 dark:bg-n-slate-6;
}
> td {
&.conversation-count-item {
@apply pl-6 rtl:pl-0 rtl:pr-6;
}
}
&:last-child {
@apply border-b-0;
}
}
}
}
.is-unread {
@apply font-semibold;
}
.notifications--loader {
@apply text-base flex items-center justify-center p-10;
}
.notification--unread-indicator {
@apply w-2.5 h-2.5 rounded-full bg-n-brand;
}
.notification--created-at {
@apply text-n-slate-11 text-xs;
}
.notification--type {
@apply text-xs;
}
.thumbnail--column {
@apply w-[3.25rem];
}
.timestamp--column {
@apply min-w-[9.125rem] text-right;
}
.notification-contant--wrap {
@apply flex-col max-w-[31.25rem];
}
.notification--message-title {
@apply text-n-slate-12;
}
</style>

View File

@@ -1,79 +0,0 @@
<script>
import { mapGetters } from 'vuex';
import { useTrack } from 'dashboard/composables';
import TableFooter from 'dashboard/components/widgets/TableFooter.vue';
import NotificationTable from './NotificationTable.vue';
import { ACCOUNT_EVENTS } from '../../../../helper/AnalyticsHelper/events';
export default {
components: {
NotificationTable,
TableFooter,
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
meta: 'notifications/getMeta',
records: 'notifications/getNotifications',
uiFlags: 'notifications/getUIFlags',
}),
},
mounted() {
this.$store.dispatch('notifications/get', { page: 1 });
},
methods: {
onPageChange(page) {
window.history.pushState({}, null, `${this.$route.path}?page=${page}`);
this.$store.dispatch('notifications/get', { page });
},
openConversation(notification) {
const {
primary_actor_id: primaryActorId,
primary_actor_type: primaryActorType,
primary_actor: { id: conversationId },
notification_type: notificationType,
} = notification;
useTrack(ACCOUNT_EVENTS.OPEN_CONVERSATION_VIA_NOTIFICATION, {
notificationType,
});
this.$store.dispatch('notifications/read', {
id: notification.id,
primaryActorId,
primaryActorType,
unreadCount: this.meta.unreadCount,
});
this.$router.push(
`/app/accounts/${this.accountId}/conversations/${conversationId}`
);
},
onMarkAllDoneClick() {
useTrack(ACCOUNT_EVENTS.MARK_AS_READ_NOTIFICATIONS);
this.$store.dispatch('notifications/readAll');
},
},
};
</script>
<template>
<div class="h-full overflow-y-auto">
<div class="flex flex-col h-full">
<NotificationTable
:notifications="records"
:is-loading="uiFlags.isFetching"
:is-updating="uiFlags.isUpdating"
:on-click-notification="openConversation"
:on-mark-all-done-click="onMarkAllDoneClick"
/>
<TableFooter
class="border-t border-n-weak"
:current-page="Number(meta.currentPage)"
:total-count="meta.count"
:page-size="15"
@page-change="onPageChange"
/>
</div>
</div>
</template>

View File

@@ -1,21 +0,0 @@
/* eslint arrow-body-style: 0 */
import { frontendURL } from '../../../helper/URLHelper';
import SettingsWrapper from '../settings/SettingsWrapper.vue';
import NotificationsView from './components/NotificationsView.vue';
export const routes = [
{
path: frontendURL('accounts/:accountId/notifications'),
component: SettingsWrapper,
children: [
{
path: '',
name: 'notifications_index',
component: NotificationsView,
meta: {
permissions: ['administrator', 'agent', 'custom_role'],
},
},
],
},
];

View File

@@ -2,22 +2,6 @@ import types from '../../mutation-types';
import NotificationsAPI from '../../../api/notifications';
export const actions = {
get: async ({ commit }, { page = 1 } = {}) => {
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: true });
try {
const {
data: {
data: { payload, meta },
},
} = await NotificationsAPI.get({ page });
commit(types.CLEAR_NOTIFICATIONS);
commit(types.SET_NOTIFICATIONS, payload);
commit(types.SET_NOTIFICATIONS_META, meta);
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: false });
} catch (error) {
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: false });
}
},
index: async ({ commit }, { page = 1, status, type, sortOrder } = {}) => {
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: true });
try {

View File

@@ -2,9 +2,6 @@ import { sortComparator } from './helpers';
import camelcaseKeys from 'camelcase-keys';
export const getters = {
getNotifications($state) {
return Object.values($state.records).sort((n1, n2) => n2.id - n1.id);
},
getFilteredNotifications: $state => filters => {
const sortOrder = filters.sortOrder === 'desc' ? 'newest' : 'oldest';
const sortedNotifications = Object.values($state.records).sort((n1, n2) =>

View File

@@ -6,38 +6,6 @@ global.axios = axios;
vi.mock('axios');
describe('#actions', () => {
describe('#get', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({
data: {
data: {
payload: [{ id: 1 }],
meta: { count: 3, current_page: 1, unread_count: 2 },
},
},
});
await actions.get({ commit });
expect(commit.mock.calls).toEqual([
[types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: true }],
[types.CLEAR_NOTIFICATIONS],
[types.SET_NOTIFICATIONS, [{ id: 1 }]],
[
types.SET_NOTIFICATIONS_META,
{ count: 3, current_page: 1, unread_count: 2 },
],
[types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.get.mockRejectedValue({ message: 'Incorrect header' });
await actions.get({ commit });
expect(commit.mock.calls).toEqual([
[types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: true }],
[types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: false }],
]);
});
});
describe('#index', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({

View File

@@ -1,21 +1,6 @@
import { getters } from '../../notifications/getters';
describe('#getters', () => {
it('getNotifications', () => {
const state = {
records: {
1: { id: 1 },
2: { id: 2 },
3: { id: 3 },
},
};
expect(getters.getNotifications(state)).toEqual([
{ id: 3 },
{ id: 2 },
{ id: 1 },
]);
});
it('getFilteredNotifications', () => {
const state = {
records: {