sourcebot/packages/web/src/app/[domain]/settings/layout.tsx
2025-11-02 16:11:48 -08:00

155 lines
5 KiB
TypeScript

import React from "react"
import { Metadata } from "next"
import { SidebarNav, SidebarNavItem } from "./components/sidebar-nav"
import { NavigationMenu } from "../components/navigationMenu"
import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe";
import { redirect } from "next/navigation";
import { auth } from "@/auth";
import { isServiceError } from "@/lib/utils";
import { getConnectionStats, getMe, getOrgAccountRequests } from "@/actions";
import { ServiceErrorException } from "@/lib/serviceError";
import { getOrgFromDomain } from "@/data/org";
import { OrgRole } from "@prisma/client";
import { env } from "@/env.mjs";
import { hasEntitlement } from "@sourcebot/shared";
interface LayoutProps {
children: React.ReactNode;
params: Promise<{ domain: string }>;
}
export const metadata: Metadata = {
title: "Settings",
}
export default async function SettingsLayout(
props: LayoutProps
) {
const params = await props.params;
const {
domain
} = params;
const {
children
} = props;
const session = await auth();
if (!session) {
return redirect(`/${domain}`);
}
const org = await getOrgFromDomain(domain);
if (!org) {
throw new Error("Organization not found");
}
const me = await getMe();
if (isServiceError(me)) {
throw new ServiceErrorException(me);
}
const userRoleInOrg = me.memberships.find((membership) => membership.id === org.id)?.role;
if (!userRoleInOrg) {
throw new Error("User role not found");
}
let numJoinRequests: number | undefined;
if (userRoleInOrg === OrgRole.OWNER) {
const requests = await getOrgAccountRequests(domain);
if (isServiceError(requests)) {
throw new ServiceErrorException(requests);
}
numJoinRequests = requests.length;
}
const connectionStats = await getConnectionStats();
if (isServiceError(connectionStats)) {
throw new ServiceErrorException(connectionStats);
}
const hasPermissionSyncingEntitlement = await hasEntitlement("permission-syncing");
const sidebarNavItems: SidebarNavItem[] = [
{
title: "General",
href: `/${domain}/settings`,
},
...(IS_BILLING_ENABLED ? [
{
title: "Billing",
href: `/${domain}/settings/billing`,
}
] : []),
...(userRoleInOrg === OrgRole.OWNER ? [
{
title: "Access",
href: `/${domain}/settings/access`,
}
] : []),
...(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>
),
href: `/${domain}/settings/members`,
}] : []),
...(userRoleInOrg === OrgRole.OWNER ? [
{
title: "Connections",
href: `/${domain}/settings/connections`,
hrefRegex: `/${domain}/settings/connections(/[^/]+)?$`,
isNotificationDotVisible: connectionStats.numberOfConnectionsWithFirstTimeSyncJobsInProgress > 0,
}
] : []),
{
title: "API Keys",
href: `/${domain}/settings/apiKeys`,
},
{
title: "Analytics",
href: `/${domain}/settings/analytics`,
},
...(hasPermissionSyncingEntitlement ? [
{
title: "Linked Accounts",
href: `/${domain}/settings/permission-syncing`,
}
] : []),
...(env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
{
title: "License",
href: `/${domain}/settings/license`,
}
] : []),
]
return (
<div className="min-h-screen flex flex-col">
<NavigationMenu domain={domain} />
<main className="flex-grow flex justify-center p-4 bg-backgroundSecondary relative">
<div className="w-full max-w-6xl rounded-lg p-6">
<div className="container mx-auto">
<div className="mb-16">
<h1 className="text-3xl font-semibold">Settings</h1>
</div>
<div className="flex flex-row gap-10">
<aside className="lg:w-48">
<SidebarNav items={sidebarNavItems} />
</aside>
<div className="w-full rounded-lg">{children}</div>
</div>
</div>
</div>
</main>
</div>
)
}