chore(web): Add count to members / requests / invites tabs in settings (#621)
Some checks failed
Update Roadmap Released / update (push) Has been cancelled
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Has been cancelled
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Has been cancelled
Publish to ghcr / merge (push) Has been cancelled

This commit is contained in:
Brendan Kellam 2025-11-18 10:41:40 -08:00 committed by GitHub
parent 831197980c
commit 97dd54d48f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 39 deletions

View file

@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Added counts to members, requets, and invites tabs in the members settings. [#621](https://github.com/sourcebot-dev/sourcebot/pull/621)
### Fixed
- Fixed spurious infinite loads with explore panel, file tree, and file search command. [#617](https://github.com/sourcebot-dev/sourcebot/pull/617)
- Wipe search context on init if entitlement no longer exists [#618](https://github.com/sourcebot-dev/sourcebot/pull/618)

View file

@ -1,4 +1,4 @@
import { getConnectionStats, getRepos, getReposStats } from "@/actions";
import { getConnectionStats, getCurrentUserRole, getOrgAccountRequests, getRepos, getReposStats } from "@/actions";
import { SourcebotLogo } from "@/app/components/sourcebotLogo";
import { auth } from "@/auth";
import { Button } from "@/components/ui/button";
@ -10,7 +10,7 @@ import { env } from "@sourcebot/shared";
import { ServiceErrorException } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { DiscordLogoIcon, GitHubLogoIcon } from "@radix-ui/react-icons";
import { RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db";
import { OrgRole, RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db";
import Link from "next/link";
import { redirect } from "next/navigation";
import { OrgSelector } from "../orgSelector";
@ -39,11 +39,32 @@ export const NavigationMenu = async ({
throw new ServiceErrorException(repoStats);
}
const connectionStats = isAuthenticated ? await getConnectionStats() : null;
if (isServiceError(connectionStats)) {
throw new ServiceErrorException(connectionStats);
const role = isAuthenticated ? await getCurrentUserRole(domain) : null;
if (isServiceError(role)) {
throw new ServiceErrorException(role);
}
const stats = await (async () => {
if (!isAuthenticated || role !== OrgRole.OWNER) {
return null;
}
const joinRequests = await getOrgAccountRequests(domain);
if (isServiceError(joinRequests)) {
throw new ServiceErrorException(joinRequests);
}
const connectionStats = await getConnectionStats();
if (isServiceError(connectionStats)) {
throw new ServiceErrorException(connectionStats);
}
return {
numJoinRequests: joinRequests.length,
connectionStats,
};
})();
const sampleRepos = await getRepos({
where: {
jobs: {
@ -100,9 +121,10 @@ export const NavigationMenu = async ({
numberOfRepos={numberOfRepos}
isReposButtonNotificationDotVisible={numberOfReposWithFirstTimeIndexingJobsInProgress > 0}
isSettingsButtonNotificationDotVisible={
connectionStats ?
connectionStats.numberOfConnectionsWithFirstTimeSyncJobsInProgress > 0 :
false
stats ? (
stats.connectionStats.numberOfConnectionsWithFirstTimeSyncJobsInProgress > 0 ||
stats.numJoinRequests > 0
) : false
}
isAuthenticated={isAuthenticated}
/>

View file

@ -69,7 +69,7 @@ export default async function SettingsLayout(
throw new ServiceErrorException(connectionStats);
}
const hasPermissionSyncingEntitlement = await hasEntitlement("permission-syncing");
const hasPermissionSyncingEntitlement = hasEntitlement("permission-syncing");
const sidebarNavItems: SidebarNavItem[] = [
{
@ -89,16 +89,8 @@ export default async function SettingsLayout(
}
] : []),
...(userRoleInOrg === OrgRole.OWNER ? [{
title: (
<div className="flex items-center gap-2">
Members
{numJoinRequests !== undefined && numJoinRequests > 0 && (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-medium text-primary-foreground">
{numJoinRequests}
</span>
)}
</div>
),
title:"Members",
isNotificationDotVisible: numJoinRequests !== undefined && numJoinRequests > 0,
href: `/${domain}/settings/members`,
}] : []),
...(userRoleInOrg === OrgRole.OWNER ? [

View file

@ -13,6 +13,8 @@ import { getSeats, SOURCEBOT_UNLIMITED_SEATS } from "@sourcebot/shared";
import { RequestsList } from "./components/requestsList";
import { OrgRole } from "@prisma/client";
import { redirect } from "next/navigation";
import { NotificationDot } from "../../components/notificationDot";
import { Badge } from "@/components/ui/badge";
interface MembersSettingsPageProps {
params: Promise<{
@ -106,32 +108,45 @@ export default async function MembersSettingsPage(props: MembersSettingsPageProp
<TabSwitcher
className="h-auto p-0 bg-transparent"
tabs={[
{ label: "Team Members", value: "members" },
...(userRoleInOrg === OrgRole.OWNER ? [
{
label: (
<div className="flex items-center gap-2">
Pending Requests
{requests.length > 0 && (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-medium text-primary-foreground">
{requests.length}
</span>
)}
Team Members
<Badge variant="secondary" className="px-1.5 relative">
{members.length}
</Badge>
</div>
),
value: "requests"
value: "members"
},
{
label: (
<div className="flex items-center gap-2">
Pending Invites
{invites.length > 0 && (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-medium text-primary-foreground">
{invites.length}
</span>
)}
</div>
),
...(userRoleInOrg === OrgRole.OWNER ? [
{
label: (
<div className="flex items-center gap-2">
{requests.length > 0 && (
<NotificationDot />
)}
Pending Requests
{requests.length > 0 && (
<Badge variant="secondary" className="px-1.5 relative">
{requests.length}
</Badge>
)}
</div>
),
value: "requests"
},
{
label: (
<div className="flex items-center gap-2">
Pending Invites
{invites.length > 0 && (
<Badge variant="secondary" className="px-1.5 relative">
{invites.length}
</Badge>
)}
</div>
),
value: "invites"
},
] : []),