add pending approvals count to sidebar

This commit is contained in:
miloschwartz
2026-01-19 21:25:28 -08:00
parent f143d2e214
commit 0b8068e13d
6 changed files with 229 additions and 20 deletions

View File

@@ -46,6 +46,7 @@ export interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
disabled?: boolean;
onItemClick?: () => void;
isCollapsed?: boolean;
notificationCounts?: Record<string, number | undefined>;
}
type CollapsibleNavItemProps = {
@@ -59,6 +60,7 @@ type CollapsibleNavItemProps = {
t: (key: string) => string;
build: string;
isUnlocked: () => boolean;
getNotificationCount: (item: SidebarNavItem) => number | undefined;
};
function CollapsibleNavItem({
@@ -71,8 +73,10 @@ function CollapsibleNavItem({
renderNavItem,
t,
build,
isUnlocked
isUnlocked,
getNotificationCount
}: CollapsibleNavItemProps) {
const notificationCount = getNotificationCount(item);
const storageKey = `pangolin-sidebar-expanded-${item.title}`;
// Get initial state from localStorage or use isChildActive
@@ -139,6 +143,14 @@ function CollapsibleNavItem({
)}
</div>
<div className="flex items-center gap-1.5 flex-shrink-0 ml-2">
{notificationCount !== undefined &&
notificationCount > 0 && (
<Badge
variant="secondary"
>
{notificationCount > 99 ? "99+" : notificationCount}
</Badge>
)}
{build === "enterprise" &&
item.showEE &&
!isUnlocked() && (
@@ -177,6 +189,7 @@ export function SidebarNav({
disabled = false,
onItemClick,
isCollapsed = false,
notificationCounts,
...props
}: SidebarNavProps) {
const pathname = usePathname();
@@ -191,6 +204,11 @@ export function SidebarNav({
const { user } = useUserContext();
const t = useTranslations();
function getNotificationCount(item: SidebarNavItem): number | undefined {
if (!notificationCounts) return undefined;
return notificationCounts[item.title];
}
function hydrateHref(val?: string): string | undefined {
if (!val) return undefined;
return val
@@ -247,16 +265,19 @@ export function SidebarNav({
t={t}
build={build}
isUnlocked={isUnlocked}
getNotificationCount={getNotificationCount}
/>
);
}
const notificationCount = getNotificationCount(item);
// Regular item without nested items
const itemContent = hydratedHref ? (
<Link
href={isDisabled ? "#" : hydratedHref}
className={cn(
"flex items-center rounded-md transition-colors",
"flex items-center rounded-md transition-colors relative",
isCollapsed
? "px-2 py-2 justify-center"
: level === 0
@@ -297,18 +318,40 @@ export function SidebarNav({
</span>
)}
</div>
{build === "enterprise" &&
item.showEE &&
!isUnlocked() && (
<Badge
variant="outlinePrimary"
className="flex-shrink-0"
>
{t("licenseBadge")}
</Badge>
)}
<div className="flex items-center gap-1.5 flex-shrink-0">
{notificationCount !== undefined &&
notificationCount > 0 && (
<Badge
variant="secondary"
>
{notificationCount > 99
? "99+"
: notificationCount}
</Badge>
)}
{build === "enterprise" &&
item.showEE &&
!isUnlocked() && (
<Badge
variant="outlinePrimary"
className="flex-shrink-0"
>
{t("licenseBadge")}
</Badge>
)}
</div>
</>
)}
{isCollapsed &&
notificationCount !== undefined &&
notificationCount > 0 && (
<Badge
variant="default"
className="absolute -top-1 -right-1 h-5 min-w-5 px-1.5 flex items-center justify-center text-xs bg-primary text-primary-foreground"
>
{notificationCount > 99 ? "99+" : notificationCount}
</Badge>
)}
</Link>
) : (
<div
@@ -332,14 +375,27 @@ export function SidebarNav({
</span>
)}
</div>
{build === "enterprise" && item.showEE && !isUnlocked() && (
<Badge
variant="outlinePrimary"
className="flex-shrink-0 ml-2"
>
{t("licenseBadge")}
</Badge>
)}
<div className="flex items-center gap-1.5 flex-shrink-0 ml-2">
{notificationCount !== undefined &&
notificationCount > 0 && (
<Badge
variant="default"
className="flex-shrink-0 bg-primary text-primary-foreground"
>
{notificationCount > 99
? "99+"
: notificationCount}
</Badge>
)}
{build === "enterprise" && item.showEE && !isUnlocked() && (
<Badge
variant="outlinePrimary"
className="flex-shrink-0"
>
{t("licenseBadge")}
</Badge>
)}
</div>
</div>
);