@@ -25,6 +25,7 @@ import {
2525 TableRow ,
2626} from "~/components/primitives/Table" ;
2727import { InfoIconTooltip } from "~/components/primitives/Tooltip" ;
28+ import { PurchaseSchedulesModal } from "~/components/schedules/PurchaseSchedulesModal" ;
2829import { useAutoRevalidate } from "~/hooks/useAutoRevalidate" ;
2930import { useEnvironment } from "~/hooks/useEnvironment" ;
3031import { useShowSelfServe } from "~/hooks/useShowSelfServe" ;
@@ -39,6 +40,7 @@ import {
3940 type QuotaInfo ,
4041 type RateLimitInfo ,
4142} from "~/presenters/v3/LimitsPresenter.server" ;
43+ import { rbac } from "~/services/rbac.server" ;
4244import { requireUser } from "~/services/session.server" ;
4345import { hasWebhooksAccess } from "~/v3/webhooksAccess.server" ;
4446import { cn } from "~/utils/cn" ;
@@ -48,6 +50,7 @@ import {
4850 docsPath ,
4951 EnvironmentParamSchema ,
5052 organizationBillingPath ,
53+ v3SchedulesAddOnPath ,
5154} from "~/utils/pathBuilder" ;
5255import { limitsAgentPageContext } from "~/components/dashboard-agent/suggested-prompts" ;
5356import { WhenAgentUnavailable } from "~/components/dashboard-agent/WhenAgentUnavailable" ;
@@ -81,6 +84,12 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
8184 } ) ;
8285 }
8386
87+ const auth = await rbac . authenticateSession ( request , {
88+ userId,
89+ organizationId : project . organizationId ,
90+ } ) ;
91+ const canManageBilling = auth . ok && auth . ability . can ( "manage" , { type : "billing" } ) ;
92+
8493 const presenter = new LimitsPresenter ( ) ;
8594 const [ error , result ] = await tryCatch (
8695 presenter . call ( {
@@ -103,6 +112,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
103112
104113 return typedjson ( {
105114 ...result ,
115+ canManageBilling,
106116 autoReloadPollIntervalMs,
107117 } ) ;
108118} ;
@@ -174,6 +184,8 @@ export default function Page() {
174184 quotas = { data . quotas }
175185 isOnTopPlan = { data . isOnTopPlan }
176186 billingPath = { organizationBillingPath ( organization ) }
187+ schedulePurchase = { data . schedulePurchase }
188+ canManageBilling = { data . canManageBilling }
177189 />
178190
179191 { /* Features Section */ }
@@ -517,10 +529,14 @@ function QuotasSection({
517529 quotas,
518530 isOnTopPlan,
519531 billingPath,
532+ schedulePurchase,
533+ canManageBilling,
520534} : {
521535 quotas : LimitsResult [ "quotas" ] ;
522536 isOnTopPlan : boolean ;
523537 billingPath : string ;
538+ schedulePurchase : LimitsResult [ "schedulePurchase" ] ;
539+ canManageBilling : boolean ;
524540} ) {
525541 // Collect all quotas that should be shown
526542 const quotaRows : QuotaInfo [ ] = [ ] ;
@@ -584,6 +600,8 @@ function QuotasSection({
584600 isOnTopPlan = { isOnTopPlan }
585601 billingPath = { billingPath }
586602 showSelfServe = { showSelfServe }
603+ schedulePurchase = { schedulePurchase }
604+ canManageBilling = { canManageBilling }
587605 />
588606 ) ) }
589607 </ TableBody >
@@ -597,12 +615,18 @@ function QuotaRow({
597615 isOnTopPlan,
598616 billingPath,
599617 showSelfServe,
618+ schedulePurchase,
619+ canManageBilling,
600620} : {
601621 quota : QuotaInfo ;
602622 isOnTopPlan : boolean ;
603623 billingPath : string ;
604624 showSelfServe : boolean ;
625+ schedulePurchase : LimitsResult [ "schedulePurchase" ] ;
626+ canManageBilling : boolean ;
605627} ) {
628+ const organization = useOrganization ( ) ;
629+
606630 // For log retention and query period, we don't show current usage as it's a duration, not a count
607631 // For widgets per dashboard, the usage varies per dashboard so we don't show a single number
608632 const isDurationQuota =
@@ -675,6 +699,24 @@ function QuotaRow({
675699 ) ;
676700 }
677701
702+ // Same purchase flow as the scheduled task page
703+ if ( quota . name === "Schedules" && schedulePurchase ) {
704+ return (
705+ < div className = "flex justify-end" >
706+ < PurchaseSchedulesModal
707+ actionPath = { v3SchedulesAddOnPath ( organization ) }
708+ schedulePricing = { schedulePurchase . schedulePricing }
709+ extraSchedules = { schedulePurchase . extraSchedules }
710+ usedSchedules = { quota . currentUsage }
711+ maxQuota = { schedulePurchase . maxScheduleQuota }
712+ planScheduleLimit = { schedulePurchase . planScheduleLimit }
713+ canManageBilling = { canManageBilling }
714+ triggerButton = { < Button variant = "secondary/small" > Manage</ Button > }
715+ />
716+ </ div >
717+ ) ;
718+ }
719+
678720 if ( ! quota . isUpgradable ) {
679721 return null ;
680722 }
0 commit comments