import { useLocation } from "@remix-run/react"; import { DateTime } from "~/components/primitives/DateTime"; import { environmentFullTitle } from "~/components/environments/EnvironmentLabel"; import { AnimatedOrgBannerBar } from "~/components/billing/AnimatedOrgBannerBar"; import { OrgBannerKind, selectOrgBanner } from "~/components/billing/selectOrgBanner"; import { LinkButton } from "~/components/primitives/Buttons"; import { useEnvironment, useOptionalEnvironment } from "~/hooks/useEnvironment"; import { useOptionalOrganization, useOrganization, useBillingLimit, useCanManageBillingLimits, } from "~/hooks/useOrganizations"; import { useOptionalProject, useProject } from "~/hooks/useProject"; import { useShowSelfServe } from "~/hooks/useShowSelfServe"; import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route"; import { v3BillingLimitsPath, v3BillingPath, v3QueuesPath } from "~/utils/pathBuilder"; import { ENVIRONMENT_PAUSE_SOURCE_BILLING_LIMIT } from "~/utils/environmentPauseSource"; function getUpgradeResetDate(): Date { const nextMonth = new Date(); nextMonth.setUTCDate(1); nextMonth.setUTCHours(0, 0, 0, 0); nextMonth.setUTCMonth(nextMonth.getUTCMonth() + 1); return nextMonth; } export function OrgBanner() { const organization = useOptionalOrganization(); const project = useOptionalProject(); const environment = useOptionalEnvironment(); const billingLimit = useBillingLimit(); const currentPlan = useCurrentPlan(); const showSelfServe = useShowSelfServe(); const location = useLocation(); // Billing-limit pauses are surfaced by the dedicated limit banners (grace/rejected). // Don't also raise the generic "environment paused" warning for them — that would // alarm the user during the async resolve→ok window, when billing is already `ok` // but converge hasn't finished unpausing envs yet. Manual pauses still warn. const isPaused = !!( organization && project && environment && environment.paused && environment.pauseSource !== ENVIRONMENT_PAUSE_SOURCE_BILLING_LIMIT ); const isArchived = !!(organization && project && environment && environment.archivedAt); const bannerKind = selectOrgBanner({ billingLimit, hasExceededFreeTier: currentPlan?.v3Usage.hasExceededFreeTier === true, showEnvironmentWarning: isPaused || isArchived, showSelfServe, }); const hideQueuesButton = location.pathname.endsWith("/queues"); const hideBillingLimitBanner = location.pathname.endsWith("/settings/billing-limits"); switch (bannerKind) { case OrgBannerKind.LimitRejected: return hideBillingLimitBanner ? null : ; case OrgBannerKind.LimitGrace: return hideBillingLimitBanner ? null : ; case OrgBannerKind.NoLimitConfigured: return hideBillingLimitBanner ? null : ; case OrgBannerKind.Upgrade: return organization ? : null; case OrgBannerKind.EnvironmentWarning: return isArchived ? ( ) : ( ); default: return null; } } function LimitRejectedBanner() { const organization = useOrganization(); const showSelfServe = useShowSelfServe(); const canManageBillingLimits = useCanManageBillingLimits(); const canResolve = showSelfServe && canManageBillingLimits; return ( Resolve ) : undefined } > Billing limit exceeded — New triggers are currently blocked. {!canResolve ? " Contact your organization administrator to resolve this issue." : null} ); } function LimitGraceBanner() { const organization = useOrganization(); const billingLimit = useBillingLimit(); const showSelfServe = useShowSelfServe(); const canManageBillingLimits = useCanManageBillingLimits(); const canResolve = showSelfServe && canManageBillingLimits; const graceEndsAt = billingLimit?.isConfigured && billingLimit.limitState.status === "grace" ? billingLimit.limitState.graceEndsAt : null; return ( Resolve ) : undefined } > Billing limit reached — Queues have been paused. New runs will continue to queue until . {!canResolve ? " Contact your organization administrator to resolve this issue." : null} ); } function NoLimitConfiguredBanner() { const organization = useOrganization(); const canManageBillingLimits = useCanManageBillingLimits(); return ( Configure billing limit ) : undefined } > {canManageBillingLimits ? "Protect your organization from unexpected usage spikes." : "Billing limits are not configured for this organization. Contact an organization administrator to configure them."} ); } function UpgradeBanner() { const organization = useOrganization(); const plan = useCurrentPlan(); const freeCreditsDollars = (plan?.v3Subscription?.plan?.limits.includedUsage ?? 500) / 100; return ( Upgrade } > You have exceeded the monthly ${freeCreditsDollars} free credits. Existing runs will be queued and new runs won't be created until{" "} , or you upgrade. ); } function PausedEnvironmentBanner({ hideButton }: { hideButton: boolean }) { const organization = useOrganization(); const project = useProject(); const environment = useEnvironment(); return ( Manage ) } > {environmentFullTitle(environment)} environment paused. No new runs will be dequeued and executed. ); } function ArchivedEnvironmentBanner() { const environment = useEnvironment(); return ( "{environment.branchName}" branch is archived and is read-only. No new runs will be dequeued and executed. ); }