);
-}
+});
export function MainCenteredContainer({
children,
diff --git a/apps/webapp/app/components/navigation/EnvironmentBanner.tsx b/apps/webapp/app/components/navigation/EnvironmentBanner.tsx
deleted file mode 100644
index 2a34b9e434d..00000000000
--- a/apps/webapp/app/components/navigation/EnvironmentBanner.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import { ExclamationCircleIcon } from "@heroicons/react/20/solid";
-import { useLocation } from "@remix-run/react";
-import { AnimatePresence, motion } from "framer-motion";
-import { useEnvironment, useOptionalEnvironment } from "~/hooks/useEnvironment";
-import { useOptionalOrganization, useOrganization } from "~/hooks/useOrganizations";
-import { useOptionalProject, useProject } from "~/hooks/useProject";
-import { v3QueuesPath } from "~/utils/pathBuilder";
-import { environmentFullTitle } from "../environments/EnvironmentLabel";
-import { LinkButton } from "../primitives/Buttons";
-import { Icon } from "../primitives/Icon";
-import { Paragraph } from "../primitives/Paragraph";
-
-export function EnvironmentBanner() {
- const organization = useOptionalOrganization();
- const project = useOptionalProject();
- const environment = useOptionalEnvironment();
-
- const isPaused = organization && project && environment && environment.paused;
- const isArchived = organization && project && environment && environment.archivedAt;
-
- return (
-
- {isArchived ? : isPaused ? : null}
-
- );
-}
-
-function PausedBanner() {
- const organization = useOrganization();
- const project = useProject();
- const environment = useEnvironment();
-
- const location = useLocation();
- const hideButton = location.pathname.endsWith("/queues");
-
- return (
-
-
-
-
- {environmentFullTitle(environment)} environment paused. No new runs will be dequeued and
- executed.
-
-
- {hideButton ? null : (
-
-
- Manage
-
-
- )}
-
- );
-}
-
-function ArchivedBranchBanner() {
- const environment = useEnvironment();
-
- return (
-
-
-
-
- "{environment.branchName}" branch is archived and is read-only. No new runs will be
- dequeued and executed.
-
-
-
- );
-}
diff --git a/apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx b/apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
index e0ab1bf3b72..3573cec9564 100644
--- a/apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
+++ b/apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
@@ -20,7 +20,7 @@ import {
organizationTeamPath,
organizationVercelIntegrationPath,
rootPath,
- v3BillingAlertsPath,
+ v3BillingLimitsPath,
v3BillingPath,
v3PrivateConnectionsPath,
v3UsagePath,
@@ -109,12 +109,12 @@ export function OrganizationSettingsSideMenu({
/>
{showSelfServe ? (
) : null}
>
diff --git a/apps/webapp/app/components/primitives/AnimatedCallout.tsx b/apps/webapp/app/components/primitives/AnimatedCallout.tsx
new file mode 100644
index 00000000000..2391f893e99
--- /dev/null
+++ b/apps/webapp/app/components/primitives/AnimatedCallout.tsx
@@ -0,0 +1,94 @@
+import { useEffect, useRef, useState } from "react";
+import { Callout, type CalloutVariant } from "~/components/primitives/Callout";
+import { cn } from "~/utils/cn";
+
+const CALLOUT_ANIMATION_MS = 300;
+
+type AnimatedCalloutProps = {
+ show: boolean;
+ variant: CalloutVariant;
+ className?: string;
+ children: React.ReactNode;
+ /** When set, the callout auto-hides after this many milliseconds. */
+ autoHideMs?: number;
+ onAutoHide?: () => void;
+ onHidden?: () => void;
+};
+
+export function AnimatedCallout({
+ show,
+ variant,
+ className,
+ children,
+ autoHideMs,
+ onAutoHide,
+ onHidden,
+}: AnimatedCalloutProps) {
+ const [rendered, setRendered] = useState(show);
+ const [autoDismissed, setAutoDismissed] = useState(false);
+ const onAutoHideRef = useRef(onAutoHide);
+ const onHiddenRef = useRef(onHidden);
+
+ useEffect(() => {
+ onAutoHideRef.current = onAutoHide;
+ }, [onAutoHide]);
+
+ useEffect(() => {
+ onHiddenRef.current = onHidden;
+ }, [onHidden]);
+
+ const shouldShow = show && !autoDismissed;
+
+ useEffect(() => {
+ if (!show) {
+ setAutoDismissed(false);
+ }
+ }, [show]);
+
+ useEffect(() => {
+ if (shouldShow) {
+ setRendered(true);
+ return;
+ }
+
+ if (!rendered) {
+ return;
+ }
+
+ const hideTimer = window.setTimeout(() => {
+ setRendered(false);
+ onHiddenRef.current?.();
+ }, CALLOUT_ANIMATION_MS);
+
+ return () => window.clearTimeout(hideTimer);
+ }, [shouldShow, rendered]);
+
+ useEffect(() => {
+ if (!shouldShow || autoHideMs === undefined) {
+ return;
+ }
+
+ const closeTimer = window.setTimeout(() => {
+ setAutoDismissed(true);
+ onAutoHideRef.current?.();
+ }, autoHideMs);
+ return () => window.clearTimeout(closeTimer);
+ }, [shouldShow, autoHideMs]);
+
+ if (!rendered) {
+ return null;
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/webapp/app/components/primitives/PageHeader.tsx b/apps/webapp/app/components/primitives/PageHeader.tsx
index 1b5e3be5579..2c61988e3c6 100644
--- a/apps/webapp/app/components/primitives/PageHeader.tsx
+++ b/apps/webapp/app/components/primitives/PageHeader.tsx
@@ -1,13 +1,11 @@
import { Link, useNavigation } from "@remix-run/react";
import { type ReactNode } from "react";
import { QuestionMarkIcon } from "~/assets/icons/QuestionMarkIcon";
-import { useOptionalOrganization } from "~/hooks/useOrganizations";
-import { UpgradePrompt, useShowUpgradePrompt } from "../billing/UpgradePrompt";
+import { OrgBanner } from "../billing/OrgBanner";
import { BreadcrumbIcon } from "./BreadcrumbIcon";
import { Header2 } from "./Headers";
import { LoadingBarDivider } from "./LoadingBarDivider";
import { SimpleTooltip } from "./Tooltip";
-import { EnvironmentBanner } from "../navigation/EnvironmentBanner";
type WithChildren = {
children: React.ReactNode;
@@ -15,9 +13,6 @@ type WithChildren = {
};
export function NavBar({ children }: WithChildren) {
- const organization = useOptionalOrganization();
- const showUpgradePrompt = useShowUpgradePrompt(organization);
-
const navigation = useNavigation();
const isLoading = navigation.state === "loading" || navigation.state === "submitting";
@@ -27,7 +22,7 @@ export function NavBar({ children }: WithChildren) {
{children}
- {showUpgradePrompt.shouldShow && organization ?