From d2dde0a84495c17ee11d97cb3ec037c7e0a42fd6 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 1 May 2025 17:35:55 +0100 Subject: [PATCH 01/14] formatDateTimeISO() fix (#2010) * Fix for the DateTime ISO function * Fix implementation and added unit tests * Preserve milliseconds properly --- .../app/components/primitives/DateTime.tsx | 40 +++++++++++++- apps/webapp/test/components/DateTime.test.ts | 54 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 apps/webapp/test/components/DateTime.test.ts diff --git a/apps/webapp/app/components/primitives/DateTime.tsx b/apps/webapp/app/components/primitives/DateTime.tsx index 11a7ab990..7d75bc735 100644 --- a/apps/webapp/app/components/primitives/DateTime.tsx +++ b/apps/webapp/app/components/primitives/DateTime.tsx @@ -98,7 +98,45 @@ export function formatDateTime( } export function formatDateTimeISO(date: Date, timeZone: string): string { - return new Date(date.toLocaleString("en-US", { timeZone })).toISOString(); + // Special handling for UTC + if (timeZone === "UTC") { + return date.toISOString(); + } + + // Get the date parts in the target timezone + const dateFormatter = new Intl.DateTimeFormat("en-US", { + timeZone, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }); + + // Get the timezone offset for this specific date + const timeZoneFormatter = new Intl.DateTimeFormat("en-US", { + timeZone, + timeZoneName: "longOffset", + }); + + const dateParts = Object.fromEntries( + dateFormatter.formatToParts(date).map(({ type, value }) => [type, value]) + ); + + const timeZoneParts = timeZoneFormatter.formatToParts(date); + const offset = + timeZoneParts.find((part) => part.type === "timeZoneName")?.value.replace("GMT", "") || + "+00:00"; + + // Format: YYYY-MM-DDThh:mm:ss.sss±hh:mm + return ( + `${dateParts.year}-${dateParts.month}-${dateParts.day}T` + + `${dateParts.hour}:${dateParts.minute}:${dateParts.second}.${String( + date.getMilliseconds() + ).padStart(3, "0")}${offset}` + ); } // New component that only shows date when it changes diff --git a/apps/webapp/test/components/DateTime.test.ts b/apps/webapp/test/components/DateTime.test.ts new file mode 100644 index 000000000..103f416eb --- /dev/null +++ b/apps/webapp/test/components/DateTime.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect } from "vitest"; +import { formatDateTimeISO } from "~/components/primitives/DateTime"; + +describe("formatDateTimeISO", () => { + it("should format UTC dates with Z suffix", () => { + const date = new Date("2025-04-29T14:01:19.000Z"); + const result = formatDateTimeISO(date, "UTC"); + expect(result).toBe("2025-04-29T14:01:19.000Z"); + }); + + describe("British Time (Europe/London)", () => { + it("should format with +01:00 during BST (summer)", () => { + // BST - British Summer Time (last Sunday in March to last Sunday in October) + const summerDate = new Date("2025-07-15T14:01:19.000Z"); + const result = formatDateTimeISO(summerDate, "Europe/London"); + expect(result).toBe("2025-07-15T15:01:19.000+01:00"); + }); + + it("should format with +00:00 during GMT (winter)", () => { + // GMT - Greenwich Mean Time (winter) + const winterDate = new Date("2025-01-15T14:01:19.000Z"); + const result = formatDateTimeISO(winterDate, "Europe/London"); + expect(result).toBe("2025-01-15T14:01:19.000+00:00"); + }); + }); + + describe("US Pacific Time (America/Los_Angeles)", () => { + it("should format with -07:00 during PDT (summer)", () => { + // PDT - Pacific Daylight Time (second Sunday in March to first Sunday in November) + const summerDate = new Date("2025-07-15T14:01:19.000Z"); + const result = formatDateTimeISO(summerDate, "America/Los_Angeles"); + expect(result).toBe("2025-07-15T07:01:19.000-07:00"); + }); + + it("should format with -08:00 during PST (winter)", () => { + // PST - Pacific Standard Time (winter) + const winterDate = new Date("2025-01-15T14:01:19.000Z"); + const result = formatDateTimeISO(winterDate, "America/Los_Angeles"); + expect(result).toBe("2025-01-15T06:01:19.000-08:00"); + }); + }); + + it("should preserve milliseconds", () => { + const date = new Date("2025-04-29T14:01:19.123Z"); + const result = formatDateTimeISO(date, "UTC"); + expect(result).toBe("2025-04-29T14:01:19.123Z"); + }); + + it("should preserve milliseconds, not UTC", () => { + const date = new Date("2025-04-29T14:01:19.123Z"); + const result = formatDateTimeISO(date, "Europe/London"); + expect(result).toBe("2025-04-29T15:01:19.123+01:00"); + }); +}); From 0fe85eaec427a061b8d9958ff692bbcc2a8ff685 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Thu, 1 May 2025 17:40:54 +0100 Subject: [PATCH 02/14] Highlight queues when concurrency limit reached and new table col (#2008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Combines the Running and Concurrency limit cols into 1 * Display a badge when a queue is at the concurrency limit * Colors the Running/Limit column text amber if the concurrency limit is hit * Turns the “Running” big number amber and shows “At concurrency limit” text * BigNumber now handles big values using formatNumber and formatNumberCompact Also includes some responsive improvements to make sure things wrap when it gets tight * Adds a new col for showing how the queue is limited * Reinstates a threshold for making very big numbers compact * Added border to make the search bar not float --- .../app/components/metrics/BigNumber.tsx | 34 ++- .../components/primitives/AnimatedNumber.tsx | 2 +- .../route.tsx | 232 ++++++++++++------ 3 files changed, 178 insertions(+), 90 deletions(-) diff --git a/apps/webapp/app/components/metrics/BigNumber.tsx b/apps/webapp/app/components/metrics/BigNumber.tsx index 2097ba928..7c4441be3 100644 --- a/apps/webapp/app/components/metrics/BigNumber.tsx +++ b/apps/webapp/app/components/metrics/BigNumber.tsx @@ -1,7 +1,10 @@ import { type ReactNode } from "react"; -import { AnimatedNumber } from "../primitives/AnimatedNumber"; -import { Spinner } from "../primitives/Spinner"; import { cn } from "~/utils/cn"; +import { formatNumber, formatNumberCompact } from "~/utils/numberFormatter"; +import { Header3 } from "../primitives/Headers"; +import { Spinner } from "../primitives/Spinner"; +import { SimpleTooltip } from "../primitives/Tooltip"; +import { AnimatedNumber } from "../primitives/AnimatedNumber"; interface BigNumberProps { title: ReactNode; @@ -13,6 +16,7 @@ interface BigNumberProps { accessory?: ReactNode; suffix?: string; suffixClassName?: string; + compactThreshold?: number; } export function BigNumber({ @@ -25,25 +29,39 @@ export function BigNumber({ accessory, animate = false, loading = false, + compactThreshold, }: BigNumberProps) { const v = value ?? defaultValue; + + const shouldCompact = + typeof compactThreshold === "number" && v !== undefined && v >= compactThreshold; + return ( -
-
-
{title}
+
+
+ {title} {accessory &&
{accessory}
}
{loading ? ( ) : v !== undefined ? ( -
- {animate ? : v} +
+ {shouldCompact ? ( + : formatNumberCompact(v)} + content={formatNumber(v)} + /> + ) : animate ? ( + + ) : ( + formatNumber(v) + )} {suffix &&
{suffix}
}
) : ( diff --git a/apps/webapp/app/components/primitives/AnimatedNumber.tsx b/apps/webapp/app/components/primitives/AnimatedNumber.tsx index a58567050..f2f309a52 100644 --- a/apps/webapp/app/components/primitives/AnimatedNumber.tsx +++ b/apps/webapp/app/components/primitives/AnimatedNumber.tsx @@ -1,4 +1,4 @@ -import { motion, useSpring, useTransform, useMotionValue, animate } from "framer-motion"; +import { animate, motion, useMotionValue, useTransform } from "framer-motion"; import { useEffect } from "react"; export function AnimatedNumber({ value }: { value: number }) { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx index 00ec1336a..c9820c726 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx @@ -69,6 +69,7 @@ import { docsPath, EnvironmentParamSchema, v3BillingPath } from "~/utils/pathBui import { PauseEnvironmentService } from "~/v3/services/pauseEnvironment.server"; import { PauseQueueService } from "~/v3/services/pauseQueue.server"; import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route"; +import { Header3 } from "~/components/primitives/Headers"; import { Input } from "~/components/primitives/Input"; import { useThrottle } from "~/hooks/useThrottle"; @@ -257,13 +258,30 @@ export default function Page() { suffix={env.paused && environment.queued > 0 ? "paused" : undefined} animate accessory={} - valueClassName={env.paused ? "text-amber-500" : undefined} + valueClassName={env.paused ? "text-warning" : undefined} + compactThreshold={1000000} + /> + - Name Queued - Running + Running/limit + +
+ Environment + + This queue is limited by your environment's concurrency limit of{" "} + {environment.concurrencyLimit}. + +
+
+ User + + This queue is limited by a concurrency limit set in your code. + +
+
+ } + > + Limited by + Release on waitpoint - Concurrency limit Pause/resume @@ -342,80 +389,103 @@ export default function Page() { {queues.length > 0 ? ( - queues.map((queue) => ( - - - - {queue.type === "task" ? ( - - } - content={`This queue was automatically created from your "${queue.name}" task`} - /> - ) : ( - - } - content={`This is a custom queue you added in your code.`} - /> + queues.map((queue) => { + const limit = queue.concurrencyLimit ?? environment.concurrencyLimit; + const isAtLimit = queue.running === limit; + return ( + + + + {queue.type === "task" ? ( + + } + content={`This queue was automatically created from your "${queue.name}" task`} + /> + ) : ( + + } + content={`This is a custom queue you added in your code.`} + /> + )} + + {queue.name} + + {queue.paused ? ( + + Paused + + ) : null} + {isAtLimit ? ( + + At concurrency limit + + ) : null} + + + + {queue.queued} + + - {queue.name} + > + {queue.running}/ + + {limit} - {queue.paused ? ( - - Paused - - ) : null} - - - - {queue.queued} - - - {queue.running} - - - {queue.releaseConcurrencyOnWaitpoint ? "Yes" : "No"} - - - {queue.concurrencyLimit ?? ( - - Max ({environment.concurrencyLimit}) - - )} - - } - hiddenButtons={!queue.paused && } - /> - - )) + + + {queue.concurrencyLimit ? "User" : "Environment"} + + + {queue.releaseConcurrencyOnWaitpoint ? "Yes" : "No"} + + + } + hiddenButtons={ + !queue.paused && + } + /> + + ); + }) ) : ( @@ -503,7 +573,7 @@ function EnvironmentPauseResumeButton({ type="button" variant="secondary/small" LeadingIcon={env.paused ? PlayIcon : PauseIcon} - leadingIconClassName={env.paused ? "text-success" : "text-amber-500"} + leadingIconClassName={env.paused ? "text-success" : "text-warning"} > {env.paused ? "Resume..." : "Pause environment..."} @@ -512,8 +582,8 @@ function EnvironmentPauseResumeButton({ {env.paused - ? `Resume processing runs in ${environmentFullTitle(env)}.` - : `Pause processing runs in ${environmentFullTitle(env)}.`} + ? `Resume processing runs in ${environmentFullTitle(env)}` + : `Pause processing runs in ${environmentFullTitle(env)}`} @@ -582,7 +652,7 @@ function QueuePauseResumeButton({ type="button" variant="tertiary/small" LeadingIcon={queue.paused ? PlayIcon : PauseIcon} - leadingIconClassName={queue.paused ? "text-success" : "text-amber-500"} + leadingIconClassName={queue.paused ? "text-success" : "text-warning"} > {queue.paused ? "Resume..." : "Pause..."} @@ -703,7 +773,7 @@ export function QueueFilters() { const search = searchParams.get("query") ?? ""; return ( -
+
Date: Thu, 1 May 2025 17:44:17 +0100 Subject: [PATCH 03/14] Code blocks have an optional text-wrap toggle (#2009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Code blocks have an optional text-wrap button * Wrap “words”, not “all” * wrapping is default false * Change the wording in the tooltip --- .../app/assets/icons/TextInlineIcon.tsx | 41 ++++++++ apps/webapp/app/assets/icons/TextWrapIcon.tsx | 34 +++++++ apps/webapp/app/components/code/CodeBlock.tsx | 96 +++++++++++++------ .../app/components/runs/v3/PacketDisplay.tsx | 1 + 4 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 apps/webapp/app/assets/icons/TextInlineIcon.tsx create mode 100644 apps/webapp/app/assets/icons/TextWrapIcon.tsx diff --git a/apps/webapp/app/assets/icons/TextInlineIcon.tsx b/apps/webapp/app/assets/icons/TextInlineIcon.tsx new file mode 100644 index 000000000..538d9768d --- /dev/null +++ b/apps/webapp/app/assets/icons/TextInlineIcon.tsx @@ -0,0 +1,41 @@ +export function TextInlineIcon({ className }: { className?: string }) { + return ( + + + + + + + + ); +} diff --git a/apps/webapp/app/assets/icons/TextWrapIcon.tsx b/apps/webapp/app/assets/icons/TextWrapIcon.tsx new file mode 100644 index 000000000..ac37867e8 --- /dev/null +++ b/apps/webapp/app/assets/icons/TextWrapIcon.tsx @@ -0,0 +1,34 @@ +export function TextWrapIcon({ className }: { className?: string }) { + return ( + + + + + + + ); +} diff --git a/apps/webapp/app/components/code/CodeBlock.tsx b/apps/webapp/app/components/code/CodeBlock.tsx index eb133105b..ca6200a18 100644 --- a/apps/webapp/app/components/code/CodeBlock.tsx +++ b/apps/webapp/app/components/code/CodeBlock.tsx @@ -3,11 +3,13 @@ import { Clipboard, ClipboardCheck } from "lucide-react"; import type { Language, PrismTheme } from "prism-react-renderer"; import { Highlight, Prism } from "prism-react-renderer"; import { forwardRef, ReactNode, useCallback, useEffect, useState } from "react"; +import { TextWrapIcon } from "~/assets/icons/TextWrapIcon"; import { cn } from "~/utils/cn"; import { Button } from "../primitives/Buttons"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../primitives/Dialog"; import { Paragraph } from "../primitives/Paragraph"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; +import { TextInlineIcon } from "~/assets/icons/TextInlineIcon"; //This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx //it didn't support highlighting lines by dimming the rest of the code, or animations on the highlighting @@ -31,6 +33,9 @@ type CodeBlockProps = { /** Show copy to clipboard button */ showCopyButton?: boolean; + /** Show text wrapping button */ + showTextWrapping?: boolean; + /** Display line numbers */ showLineNumbers?: boolean; @@ -183,6 +188,7 @@ export const CodeBlock = forwardRef( ( { showCopyButton = true, + showTextWrapping = false, showLineNumbers = true, showOpenInModal = true, highlightedRanges, @@ -202,6 +208,7 @@ export const CodeBlock = forwardRef( const [copied, setCopied] = useState(false); const [modalCopied, setModalCopied] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); + const [isWrapped, setIsWrapped] = useState(false); const onCopied = useCallback( (event: React.MouseEvent) => { @@ -263,6 +270,25 @@ export const CodeBlock = forwardRef( showChrome ? "right-1.5 top-1.5" : "top-2.5" )} > + {showTextWrapping && ( + + + setIsWrapped(!isWrapped)} + className="transition-colors focus-custom hover:cursor-pointer hover:text-text-bright" + > + {isWrapped ? ( + + ) : ( + + )} + + + {isWrapped ? "Unwrap" : "Wrap"} + + + + )} {showCopyButton && ( @@ -311,16 +337,27 @@ export const CodeBlock = forwardRef( maxLineWidth={maxLineWidth} className="px-2 py-3" preClassName="text-xs" + isWrapped={isWrapped} /> ) : (
-
+              
                 {code}
               
@@ -355,6 +392,7 @@ export const CodeBlock = forwardRef( maxLineWidth={maxLineWidth} className="min-h-full" preClassName="text-sm" + isWrapped={isWrapped} /> ) : (
{ - // This ensures the language definitions are loaded Promise.all([ //@ts-ignore import("prismjs/components/prism-json"), @@ -434,16 +473,23 @@ function HighlightCode({ ]).then(() => setIsLoaded(true)); }, []); + const containerClasses = cn( + "px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600", + !isWrapped && "overflow-x-auto", + isWrapped && "overflow-y-auto", + className + ); + + const preClasses = cn( + "relative mr-2 font-mono leading-relaxed", + preClassName, + isWrapped && "[&_span]:whitespace-pre-wrap [&_span]:break-words" + ); + if (!isLoaded) { return ( -
-
{code}
+
+
{code}
); } @@ -457,22 +503,8 @@ function HighlightCode({ getLineProps, getTokenProps, }) => ( -
-
+        
+
             {tokens
               .map((line, index) => {
                 if (index === tokens.length - 1 && line.length === 1 && line[0].content === "\n") {
@@ -495,7 +527,8 @@ function HighlightCode({
                     {...lineProps}
                     className={cn(
                       "flex w-full justify-start transition-opacity duration-500",
-                      lineProps.className
+                      lineProps.className,
+                      isWrapped && "flex-wrap"
                     )}
                     style={{
                       opacity: shouldDim ? dimAmount : undefined,
@@ -504,9 +537,10 @@ function HighlightCode({
                   >
                     {showLineNumbers && (
                       
); } From 9c3aeb3efbdbd4cb3f6195cbdf6052cbe7a512b8 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Thu, 1 May 2025 19:22:56 +0100 Subject: [PATCH 04/14] Adds friendly formatting for max duration values (#2011) --- .../route.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx index 58e5ce907..d0fa15bf5 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx @@ -647,7 +647,12 @@ function RunBody({ Max duration - {run.maxDurationInSeconds ? `${run.maxDurationInSeconds}s` : "–"} + {run.maxDurationInSeconds + ? `${run.maxDurationInSeconds}s (${formatDurationMilliseconds( + run.maxDurationInSeconds * 1000, + { style: "short" } + )})` + : "–"} From 2448242be3054f41843e35e0b6c57a03e47e246d Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Thu, 1 May 2025 19:24:03 +0100 Subject: [PATCH 05/14] Small improvement to the loading animation so it moves left to right only (#2012) --- .../app/components/primitives/LoadingBarDivider.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/webapp/app/components/primitives/LoadingBarDivider.tsx b/apps/webapp/app/components/primitives/LoadingBarDivider.tsx index f227f9e73..d35067ea6 100644 --- a/apps/webapp/app/components/primitives/LoadingBarDivider.tsx +++ b/apps/webapp/app/components/primitives/LoadingBarDivider.tsx @@ -7,7 +7,7 @@ type LoadingBarDividerProps = { export function LoadingBarDivider({ isLoading }: LoadingBarDividerProps) { return ( -
+
); @@ -21,11 +21,9 @@ export function AnimationDivider({ isLoading }: LoadingBarDividerProps) { if (isPresent) { const enterAnimation = async () => { await animate( - [ - [scope.current, { width: "30%", left: "35%" }, { duration: 1, ease: "easeIn" }], - [scope.current, { width: 0, left: "100%" }, { duration: 1, ease: "easeOut" }], - ], - { repeat: Infinity, repeatType: "reverse" } + scope.current, + { left: ["-100%", "100%"], width: "100%" }, + { duration: 2, ease: "easeOut", repeat: Infinity } ); }; enterAnimation(); From 4456184f37999173e22d75d8ab2353f0c58ddd93 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Fri, 2 May 2025 10:47:57 +0100 Subject: [PATCH 06/14] Fix task and schedule icons squishing (#2017) --- apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx b/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx index 23948508b..0dfc5f10e 100644 --- a/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx +++ b/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx @@ -12,10 +12,12 @@ export function TaskTriggerSourceIcon({ }) { switch (source) { case "STANDARD": { - return ; + return ; } case "SCHEDULED": { - return ; + return ( + + ); } } } From 73f363f9d547a4d61f5df672c899da88ce66cc1c Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Fri, 2 May 2025 10:50:22 +0100 Subject: [PATCH 07/14] Changes the spinner to white now we have purple primary buttons (#2014) --- .../route.tsx | 4 +++- .../routes/resources.orgs.$organizationSlug.select-plan.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx index c9820c726..24446a7ee 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx @@ -610,7 +610,9 @@ function EnvironmentPauseResumeButton({ type="submit" disabled={isLoading} variant={env.paused ? "primary/medium" : "danger/medium"} - LeadingIcon={isLoading ? : env.paused ? PlayIcon : PauseIcon} + LeadingIcon={ + isLoading ? : env.paused ? PlayIcon : PauseIcon + } shortcut={{ modifiers: ["mod"], key: "enter" }} > {env.paused ? "Resume environment" : "Pause environment"} diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx index 1ac03e1ac..1d96293d0 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx @@ -678,7 +678,7 @@ export function TierPro({