1- import { MetaFunction } from "@remix-run/react" ;
1+ import { PlusIcon } from "@heroicons/react/20/solid" ;
2+ import { type MetaFunction } from "@remix-run/react" ;
23import { type ActionFunctionArgs , type LoaderFunctionArgs } from "@remix-run/server-runtime" ;
34import { tryCatch } from "@trigger.dev/core" ;
45import { typedjson , useTypedLoaderData } from "remix-typedjson" ;
56import { z } from "zod" ;
67import { AdminDebugTooltip } from "~/components/admin/debugTooltip" ;
78import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel" ;
89import {
9- MainCenteredContainer ,
1010 MainHorizontallyCenteredContainer ,
1111 PageBody ,
1212 PageContainer ,
1313} from "~/components/layout/AppLayout" ;
14- import { Header2 } from "~/components/primitives/Headers" ;
15- import { InputGroup } from "~/components/primitives/InputGroup" ;
16- import { Label } from "~/components/primitives/Label" ;
14+ import { Button , LinkButton } from "~/components/primitives/Buttons" ;
15+ import { Header2 , Header3 } from "~/components/primitives/Headers" ;
1716import { NavBar , PageAccessories , PageTitle } from "~/components/primitives/PageHeader" ;
1817import { Paragraph } from "~/components/primitives/Paragraph" ;
1918import * as Property from "~/components/primitives/PropertyTable" ;
@@ -25,20 +24,20 @@ import {
2524 TableHeaderCell ,
2625 TableRow ,
2726} from "~/components/primitives/Table" ;
27+ import { InfoIconTooltip } from "~/components/primitives/Tooltip" ;
28+ import { useFeatures } from "~/hooks/useFeatures" ;
2829import { useOrganization } from "~/hooks/useOrganizations" ;
2930import { redirectWithErrorMessage , redirectWithSuccessMessage } from "~/models/message.server" ;
3031import { findProjectBySlug } from "~/models/project.server" ;
3132import {
32- EnvironmentWithConcurrency ,
33+ type ConcurrencyResult ,
34+ type EnvironmentWithConcurrency ,
3335 ManageConcurrencyPresenter ,
3436} from "~/presenters/v3/ManageConcurrencyPresenter.server" ;
3537import { requireUser , requireUserId } from "~/services/session.server" ;
36- import { cn } from "~/utils/cn" ;
3738import { EnvironmentParamSchema , regionsPath , v3BillingPath } from "~/utils/pathBuilder" ;
3839import { SetDefaultRegionService } from "~/v3/services/setDefaultRegion.server" ;
3940import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route" ;
40- import { useFeatures } from "~/hooks/useFeatures" ;
41- import { LinkButton } from "~/components/primitives/Buttons" ;
4241
4342export const meta : MetaFunction = ( ) => {
4443 return [
@@ -123,8 +122,13 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
123122} ;
124123
125124export default function Page ( ) {
126- const { canAddConcurrency, environments } = useTypedLoaderData < typeof loader > ( ) ;
127- const organization = useOrganization ( ) ;
125+ const {
126+ canAddConcurrency,
127+ extraConcurrency,
128+ extraAllocatedConcurrency,
129+ extraUnallocatedConcurrency,
130+ environments,
131+ } = useTypedLoaderData < typeof loader > ( ) ;
128132
129133 return (
130134 < PageContainer >
@@ -149,18 +153,13 @@ export default function Page() {
149153 < PageBody scrollable = { false } >
150154 < MainHorizontallyCenteredContainer >
151155 { canAddConcurrency ? (
152- < div >
153- < div className = "mb-3 border-b border-grid-dimmed pb-1" >
154- < Header2 > Manage your concurrency</ Header2 >
155- </ div >
156- < div className = "flex flex-col gap-6" >
157- < InputGroup fullWidth >
158- < div className = "flex w-full items-center justify-between" >
159- < Label > Secret key</ Label >
160- </ div >
161- </ InputGroup >
162- </ div >
163- </ div >
156+ < Upgradable
157+ canAddConcurrency = { canAddConcurrency }
158+ extraConcurrency = { extraConcurrency }
159+ extraAllocatedConcurrency = { extraAllocatedConcurrency }
160+ extraUnallocatedConcurrency = { extraUnallocatedConcurrency }
161+ environments = { environments }
162+ />
164163 ) : (
165164 < NotUpgradable environments = { environments } />
166165 ) }
@@ -170,6 +169,103 @@ export default function Page() {
170169 ) ;
171170}
172171
172+ function Upgradable ( {
173+ canAddConcurrency,
174+ extraConcurrency,
175+ extraAllocatedConcurrency,
176+ extraUnallocatedConcurrency,
177+ environments,
178+ } : ConcurrencyResult ) {
179+ const organization = useOrganization ( ) ;
180+
181+ return (
182+ < div className = "flex flex-col gap-3" >
183+ < div className = "border-b border-grid-dimmed pb-1" >
184+ < Header2 > Your concurrency</ Header2 >
185+ </ div >
186+ < Paragraph variant = "small" >
187+ Concurrency limits determine how many runs you can execute at the same time. You can add
188+ extra concurrency to your organization which you can allocate to environments in your
189+ projects.
190+ </ Paragraph >
191+ < div className = "mt-3 flex flex-col gap-6" >
192+ < div className = "flex flex-col gap-2" >
193+ < div className = "flex items-center first-letter:pb-1" >
194+ < Header3 className = "grow" > Extra concurrency</ Header3 >
195+ < Button variant = "primary/small" LeadingIcon = { PlusIcon } >
196+ Purchase extra concurrency...
197+ </ Button >
198+ </ div >
199+ < Table >
200+ < TableHeader >
201+ < TableRow >
202+ < TableHeaderCell className = "pl-0" > Extra concurrency purchased</ TableHeaderCell >
203+ < TableHeaderCell alignment = "right" className = "text-text-bright" >
204+ { extraConcurrency }
205+ </ TableHeaderCell >
206+ </ TableRow >
207+ </ TableHeader >
208+ < TableBody >
209+ < TableRow >
210+ < TableCell > Allocated concurrency</ TableCell >
211+ < TableCell alignment = "right" className = "text-text-bright" >
212+ { extraAllocatedConcurrency }
213+ </ TableCell >
214+ </ TableRow >
215+ < TableRow >
216+ < TableCell > Unallocated concurrency</ TableCell >
217+ < TableCell
218+ alignment = "right"
219+ className = { extraUnallocatedConcurrency > 0 ? "text-success" : "text-text-bright" }
220+ >
221+ { extraUnallocatedConcurrency }
222+ </ TableCell >
223+ </ TableRow >
224+ </ TableBody >
225+ </ Table >
226+ </ div >
227+ < div className = "flex flex-col gap-2" >
228+ < div className = "flex items-center pb-1" >
229+ < Header3 className = "grow" > Concurrency allocation</ Header3 >
230+ </ div >
231+ < Table >
232+ < TableHeader >
233+ < TableRow >
234+ < TableHeaderCell className = "pl-0" > Environment</ TableHeaderCell >
235+ < TableHeaderCell alignment = "right" >
236+ < span className = "flex items-center gap-x-1" >
237+ Included{ " " }
238+ < InfoIconTooltip content = "This is the included concurrency based on your plan." />
239+ </ span >
240+ </ TableHeaderCell >
241+ < TableHeaderCell alignment = "right" > Extra concurrency</ TableHeaderCell >
242+ < TableHeaderCell alignment = "right" > Total</ TableHeaderCell >
243+ </ TableRow >
244+ </ TableHeader >
245+ < TableBody >
246+ { environments . map ( ( environment ) => (
247+ < TableRow key = { environment . id } >
248+ < TableCell className = "pl-0" >
249+ < EnvironmentCombo environment = { environment } />
250+ </ TableCell >
251+ < TableCell alignment = "right" > { environment . planConcurrencyLimit } </ TableCell >
252+ < TableCell alignment = "right" className = "text-text-bright" >
253+ { Math . max (
254+ 0 ,
255+ environment . maximumConcurrencyLimit - environment . planConcurrencyLimit
256+ ) }
257+ </ TableCell >
258+ < TableCell alignment = "right" > { environment . maximumConcurrencyLimit } </ TableCell >
259+ </ TableRow >
260+ ) ) }
261+ </ TableBody >
262+ </ Table >
263+ </ div >
264+ </ div >
265+ </ div >
266+ ) ;
267+ }
268+
173269function NotUpgradable ( { environments } : { environments : EnvironmentWithConcurrency [ ] } ) {
174270 const { isManagedCloud } = useFeatures ( ) ;
175271 const plan = useCurrentPlan ( ) ;
0 commit comments