From 2f99abbb9e59870760967e55b8c3fb5f12d6e246 Mon Sep 17 00:00:00 2001 From: rameen Date: Sun, 21 Jun 2026 13:26:55 +0330 Subject: [PATCH] front end feature to showing pending connection request in sidebar. --- .../app/(dashboard)/organizations/page.tsx | 2 + frontend/src/components/ui/shared/Sidebar.tsx | 14 +++++- frontend/src/lib/api/organization.ts | 5 ++ .../lib/hooks/usePendingConnectionsCount.ts | 48 +++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/hooks/usePendingConnectionsCount.ts diff --git a/frontend/src/app/(dashboard)/organizations/page.tsx b/frontend/src/app/(dashboard)/organizations/page.tsx index b3c9ddb..df889a0 100644 --- a/frontend/src/app/(dashboard)/organizations/page.tsx +++ b/frontend/src/app/(dashboard)/organizations/page.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'; import { useToast } from '@/lib/hooks/useToast'; import { Check, Trash2, UserPlus, X } from 'lucide-react'; import { useAuth } from '@/lib/hooks/useAuth'; +import { notifyPendingConnectionsChanged } from '@/lib/hooks/usePendingConnectionsCount'; import { useOrganizationInviteLinkCopy } from '@/lib/hooks/useOrganizationInviteLinkCopy'; import { organizationApi, @@ -246,6 +247,7 @@ export default function OrganizationsPage() { toast.showSuccess( action === 'ACCEPT' ? 'Connection request accepted.' : 'Connection request declined.', ); + notifyPendingConnectionsChanged(); await loadList(); } catch (e) { toast.showError(formatApiMessage(e)); diff --git a/frontend/src/components/ui/shared/Sidebar.tsx b/frontend/src/components/ui/shared/Sidebar.tsx index 80bc7f8..7738326 100644 --- a/frontend/src/components/ui/shared/Sidebar.tsx +++ b/frontend/src/components/ui/shared/Sidebar.tsx @@ -13,6 +13,7 @@ import { CreditCard, } from 'lucide-react'; import { useAuth } from '@/lib/hooks/useAuth'; +import { usePendingConnectionsCount } from '@/lib/hooks/usePendingConnectionsCount'; import { canAccessAppointmentsSection, canViewTab } from '@/components/shared/permissions'; import { counterpartOrganizationType, @@ -32,6 +33,7 @@ const menu = [ function Sidebar() { const pathname = usePathname(); const { currentOrganization } = useAuth(); + const pendingConnectionsCount = usePendingConnectionsCount(); const counterpartLabel = currentOrganization?.type === 'LAB' ? 'Clinics' : 'Labs'; const organizationsTabIcon = organizationTypeIcon( counterpartOrganizationType(currentOrganization?.type), @@ -75,6 +77,8 @@ function Sidebar() { {visibleMenu.map((item) => { const Icon = item.icon; const isActive = pathname === item.path; + const showPendingBadge = + item.path === '/organizations' && pendingConnectionsCount > 0; return ( - {item.name} + {item.name} + {showPendingBadge && ( + + {pendingConnectionsCount} + + )} ); })} diff --git a/frontend/src/lib/api/organization.ts b/frontend/src/lib/api/organization.ts index 7c940ce..0e54cd8 100644 --- a/frontend/src/lib/api/organization.ts +++ b/frontend/src/lib/api/organization.ts @@ -46,6 +46,11 @@ export const organizationApi = { return response.data; }, + pendingIncomingCount: async (): Promise<{ success: boolean; data: { count: number } }> => { + const response = await apiClient.get('/organizations/connections/pending-count'); + return response.data; + }, + listInvitations: async (): Promise<{ success: boolean; data: { items: OrganizationInvitationHistoryItemDto[] }; diff --git a/frontend/src/lib/hooks/usePendingConnectionsCount.ts b/frontend/src/lib/hooks/usePendingConnectionsCount.ts new file mode 100644 index 0000000..4be203b --- /dev/null +++ b/frontend/src/lib/hooks/usePendingConnectionsCount.ts @@ -0,0 +1,48 @@ +'use client'; + +import { useCallback, useEffect, useState } from 'react'; +import { usePathname } from 'next/navigation'; +import { canViewTab } from '@/components/shared/permissions'; +import { organizationApi } from '@/lib/api/organization'; +import { useAuth } from '@/lib/hooks/useAuth'; + +/** Tell the sidebar badge to refetch after accept/decline on the organizations page. */ +export function notifyPendingConnectionsChanged() { + window.dispatchEvent(new Event('pending-connections-changed')); +} + +/** + * Sidebar-only: fetches GET /organizations/connections/pending-count. + * Independent from the organizations tab list API. + */ +export function usePendingConnectionsCount(): number { + const pathname = usePathname(); + const { currentOrganization } = useAuth(); + const [count, setCount] = useState(0); + + const fetchCount = useCallback(async () => { + if (!currentOrganization?.id || !canViewTab(currentOrganization, 'TAB_ORGANIZATIONS_READ')) { + setCount(0); + return; + } + + try { + const res = await organizationApi.pendingIncomingCount(); + setCount(res.data.count); + } catch { + setCount(0); + } + }, [currentOrganization]); + + useEffect(() => { + void fetchCount(); + }, [fetchCount, pathname]); + + useEffect(() => { + const onChanged = () => void fetchCount(); + window.addEventListener('pending-connections-changed', onChanged); + return () => window.removeEventListener('pending-connections-changed', onChanged); + }, [fetchCount]); + + return count; +}