Files
2026-07-13 13:32:57 +08:00

249 lines
9.0 KiB
TypeScript

import { ArrowLeftIcon, LinkIcon } from "@heroicons/react/24/solid";
import { BellIcon } from "~/assets/icons/BellIcon";
import { CreditCardIcon } from "~/assets/icons/CreditCardIcon";
import { PadlockIcon } from "~/assets/icons/PadlockIcon";
import { UsageIcon } from "~/assets/icons/UsageIcon";
import { RolesIcon } from "~/assets/icons/RolesIcon";
import { SlackIcon } from "~/assets/icons/SlackIcon";
import { SlidersIcon } from "~/assets/icons/SlidersIcon";
import { UserGroupIcon } from "~/assets/icons/UserGroupIcon";
import { VercelLogo } from "~/components/integrations/VercelLogo";
import { useFeatureFlags } from "~/hooks/useFeatureFlags";
import { useFeatures } from "~/hooks/useFeatures";
import { type MatchedOrganization } from "~/hooks/useOrganizations";
import { cn } from "~/utils/cn";
import {
organizationRolesPath,
organizationSettingsPath,
organizationSlackIntegrationPath,
organizationSsoPath,
organizationTeamPath,
organizationVercelIntegrationPath,
rootPath,
v3BillingLimitsPath,
v3BillingPath,
v3PrivateConnectionsPath,
v3UsagePath,
} from "~/utils/pathBuilder";
import { LinkButton } from "../primitives/Buttons";
import { HelpAndFeedback } from "./HelpAndFeedbackPopover";
import { SideMenuHeader } from "./SideMenuHeader";
import { SideMenuItem } from "./SideMenuItem";
import { useShowSelfServe } from "~/hooks/useShowSelfServe";
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
import { Paragraph } from "../primitives/Paragraph";
import { Badge } from "../primitives/Badge";
import { useHasAdminAccess } from "~/hooks/useUser";
import { AskAI } from "../AskAI";
export type BuildInfo = {
appVersion: string | undefined;
packageVersion: string;
buildTimestampSeconds: string | undefined;
gitSha: string | undefined;
gitRefName: string | undefined;
};
export function OrganizationSettingsSideMenu({
organization,
buildInfo,
isUsingPlugin,
isSsoUsingPlugin,
}: {
organization: MatchedOrganization;
buildInfo: BuildInfo;
isUsingPlugin: boolean;
isSsoUsingPlugin: boolean;
}) {
const { isManagedCloud } = useFeatures();
const featureFlags = useFeatureFlags();
const currentPlan = useCurrentPlan();
const showSelfServe = useShowSelfServe();
const isAdmin = useHasAdminAccess();
const showBuildInfo = isAdmin || !isManagedCloud;
return (
<div
className={cn(
"grid h-full grid-rows-[2.5rem_auto_2.5rem] overflow-hidden border-r border-grid-bright bg-background-bright transition"
)}
>
<div className={cn("flex items-center justify-between p-1 transition")}>
<LinkButton
variant="minimal/medium"
LeadingIcon={ArrowLeftIcon}
to={rootPath()}
fullWidth
textAlignLeft
>
<span className="text-text-bright">Back to app</span>
</LinkButton>
</div>
<div className="mb-6 flex grow flex-col gap-4 overflow-y-auto px-1 pt-2 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
<div className="flex flex-col">
<div className="mb-1">
<SideMenuHeader title="Organization" />
</div>
{isManagedCloud && (
<>
<SideMenuItem
name="Usage"
icon={UsageIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={v3UsagePath(organization)}
data-action="usage"
/>
<SideMenuItem
name="Billing"
icon={CreditCardIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={v3BillingPath(organization)}
data-action="billing"
badge={
currentPlan?.v3Subscription?.isPaying ? (
<Badge variant="extra-small">{currentPlan?.v3Subscription?.plan?.title}</Badge>
) : undefined
}
/>
{showSelfServe ? (
<SideMenuItem
name="Billing limits"
icon={BellIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={v3BillingLimitsPath(organization)}
data-action="billing-limits"
/>
) : null}
</>
)}
<SideMenuItem
name="Team"
icon={UserGroupIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={organizationTeamPath(organization)}
data-action="team"
/>
{featureFlags.hasPrivateConnections && (
<SideMenuItem
name="Private Connections"
icon={LinkIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={v3PrivateConnectionsPath(organization)}
data-action="private-connections"
/>
)}
{isUsingPlugin && (
<SideMenuItem
name="Roles"
icon={RolesIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={organizationRolesPath(organization)}
data-action="roles"
/>
)}
{isSsoUsingPlugin && (
<SideMenuItem
name="SSO & Directory Sync"
icon={PadlockIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={organizationSsoPath(organization)}
data-action="sso"
badge={
currentPlan?.v3Subscription?.plan?.code === "enterprise" ? undefined : (
<Badge variant="extra-small">Enterprise</Badge>
)
}
/>
)}
<SideMenuItem
name="Settings"
icon={SlidersIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={organizationSettingsPath(organization)}
data-action="settings"
/>
</div>
<div className="flex flex-col">
<div className="mb-1">
<SideMenuHeader title="Integrations" />
</div>
<SideMenuItem
name="Vercel"
icon={VercelLogo}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
iconClassName="size-4 ml-[0.15rem]"
to={organizationVercelIntegrationPath(organization)}
data-action="integrations"
/>
<SideMenuItem
name="Slack"
icon={SlackIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
iconClassName="size-4 ml-0.5"
to={organizationSlackIntegrationPath(organization)}
data-action="integrations"
/>
</div>
<div className="flex flex-col gap-1">
<SideMenuHeader title="App version" />
<Paragraph variant="extra-small" className="px-2 text-text-dimmed">
{buildInfo.appVersion || `v${buildInfo.packageVersion}`}
</Paragraph>
</div>
{showBuildInfo && buildInfo.buildTimestampSeconds && (
<div className="flex flex-col gap-1">
<SideMenuHeader title="Build timestamp" />
<Paragraph variant="extra-small" className="px-2 text-text-dimmed">
{new Date(Number(buildInfo.buildTimestampSeconds) * 1000).toISOString()}
</Paragraph>
</div>
)}
{showBuildInfo && buildInfo.gitRefName && (
<div className="flex flex-col gap-1">
<SideMenuHeader title="Git ref" />
<Paragraph variant="extra-small" className="px-2 text-text-dimmed">
<a
href={`https://github.com/triggerdotdev/trigger.dev/tree/${buildInfo.gitRefName}`}
target="_blank"
rel="noopener noreferrer"
className="transition hover:text-text-bright"
>
{buildInfo.gitRefName}
</a>
</Paragraph>
</div>
)}
{showBuildInfo && buildInfo.gitSha && (
<div className="flex flex-col gap-1">
<SideMenuHeader title="Git sha" />
<Paragraph variant="extra-small" className="px-2 text-text-dimmed">
<a
href={`https://github.com/triggerdotdev/trigger.dev/commit/${buildInfo.gitSha}`}
target="_blank"
rel="noopener noreferrer"
className="transition hover:text-text-bright"
>
{buildInfo.gitSha.slice(0, 9)}
</a>
</Paragraph>
</div>
)}
</div>
<div className="flex w-full items-center justify-between border-t border-grid-bright p-1">
<HelpAndFeedback organizationId={organization.id} />
<AskAI />
</div>
</div>
);
}