11import { conform , list , requestIntent , useFieldList , useForm } from "@conform-to/react" ;
22import { parse } from "@conform-to/zod" ;
33import { Form , useActionData , type MetaFunction } from "@remix-run/react" ;
4- import { json , type ActionFunction , type LoaderFunctionArgs } from "@remix-run/server-runtime" ;
4+ import { json } from "@remix-run/server-runtime" ;
55import { tryCatch } from "@trigger.dev/core" ;
66import { Fragment , useEffect , useRef , useState } from "react" ;
77import { redirect , typedjson , useTypedLoaderData } from "remix-typedjson" ;
@@ -25,11 +25,11 @@ import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/Page
2525import { Paragraph } from "~/components/primitives/Paragraph" ;
2626import { TextLink } from "~/components/primitives/TextLink" ;
2727import { InfoIconTooltip } from "~/components/primitives/Tooltip" ;
28- import { prisma } from "~/db.server" ;
28+ import { $replica , prisma } from "~/db.server" ;
2929import { featuresForRequest } from "~/features.server" ;
3030import { redirectWithErrorMessage , redirectWithSuccessMessage } from "~/models/message.server" ;
3131import { getBillingAlerts , setBillingAlert } from "~/services/platform.v3.server" ;
32- import { requireUserId } from "~/services/session.server " ;
32+ import { dashboardAction , dashboardLoader } from "~/services/routeBuilders/dashboardBuilder " ;
3333import { formatCurrency , formatNumber } from "~/utils/numberFormatter" ;
3434import {
3535 docsPath ,
@@ -47,39 +47,54 @@ export const meta: MetaFunction = () => {
4747 ] ;
4848} ;
4949
50- export async function loader ( { params, request } : LoaderFunctionArgs ) {
51- const userId = await requireUserId ( request ) ;
52- const { organizationSlug } = OrganizationParamsSchema . parse ( params ) ;
50+ async function resolveOrgIdFromSlug ( slug : string ) : Promise < string | null > {
51+ const org = await $replica . organization . findFirst ( { where : { slug } , select : { id : true } } ) ;
52+ return org ?. id ?? null ;
53+ }
5354
54- const { isManagedCloud } = featuresForRequest ( request ) ;
55- if ( ! isManagedCloud ) {
56- return redirect ( organizationPath ( { slug : organizationSlug } ) ) ;
57- }
55+ export const loader = dashboardLoader (
56+ {
57+ params : OrganizationParamsSchema ,
58+ context : async ( params ) => {
59+ const organizationId = await resolveOrgIdFromSlug ( params . organizationSlug ) ;
60+ return organizationId ? { organizationId } : { } ;
61+ } ,
62+ authorization : { action : "manage" , resource : { type : "billing" } } ,
63+ } ,
64+ async ( { params, request, user } ) => {
65+ const userId = user . id ;
66+ const { organizationSlug } = params ;
5867
59- const organization = await prisma . organization . findFirst ( {
60- where : { slug : organizationSlug , members : { some : { userId } } } ,
61- } ) ;
68+ const { isManagedCloud } = featuresForRequest ( request ) ;
69+ if ( ! isManagedCloud ) {
70+ return redirect ( organizationPath ( { slug : organizationSlug } ) ) ;
71+ }
6272
63- if ( ! organization ) {
64- throw new Response ( null , { status : 404 , statusText : "Organization not found" } ) ;
65- }
73+ const organization = await prisma . organization . findFirst ( {
74+ where : { slug : organizationSlug , members : { some : { userId } } } ,
75+ } ) ;
6676
67- const [ error , alerts ] = await tryCatch ( getBillingAlerts ( organization . id ) ) ;
68- if ( error ) {
69- throw new Response ( null , { status : 404 , statusText : `Billing alerts error: ${ error } ` } ) ;
70- }
77+ if ( ! organization ) {
78+ throw new Response ( null , { status : 404 , statusText : "Organization not found" } ) ;
79+ }
7180
72- if ( ! alerts ) {
73- throw new Response ( null , { status : 404 , statusText : "Billing alerts not found" } ) ;
74- }
81+ const [ error , alerts ] = await tryCatch ( getBillingAlerts ( organization . id ) ) ;
82+ if ( error ) {
83+ throw new Response ( null , { status : 404 , statusText : `Billing alerts error: ${ error } ` } ) ;
84+ }
7585
76- return typedjson ( {
77- alerts : {
78- ...alerts ,
79- amount : alerts . amount / 100 ,
80- } ,
81- } ) ;
82- }
86+ if ( ! alerts ) {
87+ throw new Response ( null , { status : 404 , statusText : "Billing alerts not found" } ) ;
88+ }
89+
90+ return typedjson ( {
91+ alerts : {
92+ ...alerts ,
93+ amount : alerts . amount / 100 ,
94+ } ,
95+ } ) ;
96+ }
97+ ) ;
8398
8499const schema = z . object ( {
85100 amount : z
@@ -104,61 +119,71 @@ const schema = z.object({
104119 } , z . coerce . number ( ) . array ( ) . nonempty ( "At least one alert level is required" ) ) ,
105120} ) ;
106121
107- export const action : ActionFunction = async ( { request, params } ) => {
108- const userId = await requireUserId ( request ) ;
109- const { organizationSlug } = OrganizationParamsSchema . parse ( params ) ;
122+ export const action = dashboardAction (
123+ {
124+ params : OrganizationParamsSchema ,
125+ context : async ( params ) => {
126+ const organizationId = await resolveOrgIdFromSlug ( params . organizationSlug ) ;
127+ return organizationId ? { organizationId } : { } ;
128+ } ,
129+ authorization : { action : "manage" , resource : { type : "billing" } } ,
130+ } ,
131+ async ( { request, params, user } ) => {
132+ const userId = user . id ;
133+ const { organizationSlug } = params ;
110134
111- const formData = await request . formData ( ) ;
112- const submission = parse ( formData , { schema } ) ;
135+ const formData = await request . formData ( ) ;
136+ const submission = parse ( formData , { schema } ) ;
113137
114- if ( ! submission . value || submission . intent !== "submit" ) {
115- return json ( submission ) ;
116- }
138+ if ( ! submission . value || submission . intent !== "submit" ) {
139+ return json ( submission ) ;
140+ }
117141
118- try {
119- const organization = await prisma . organization . findFirst ( {
120- where : { slug : organizationSlug , members : { some : { userId } } } ,
121- } ) ;
142+ try {
143+ const organization = await prisma . organization . findFirst ( {
144+ where : { slug : organizationSlug , members : { some : { userId } } } ,
145+ } ) ;
122146
123- if ( ! organization ) {
124- return redirectWithErrorMessage (
125- v3BillingAlertsPath ( { slug : organizationSlug } ) ,
126- request ,
127- "You are not authorized to update billing alerts"
128- ) ;
129- }
147+ if ( ! organization ) {
148+ return redirectWithErrorMessage (
149+ v3BillingAlertsPath ( { slug : organizationSlug } ) ,
150+ request ,
151+ "You are not authorized to update billing alerts"
152+ ) ;
153+ }
130154
131- const [ error , updatedAlert ] = await tryCatch (
132- setBillingAlert ( organization . id , {
133- ...submission . value ,
134- amount : submission . value . amount * 100 ,
135- } )
136- ) ;
137- if ( error ) {
138- return redirectWithErrorMessage (
139- v3BillingAlertsPath ( { slug : organizationSlug } ) ,
140- request ,
141- "Failed to update billing alert"
155+ const [ error , updatedAlert ] = await tryCatch (
156+ setBillingAlert ( organization . id , {
157+ ...submission . value ,
158+ amount : submission . value . amount * 100 ,
159+ } )
142160 ) ;
143- }
161+ if ( error ) {
162+ return redirectWithErrorMessage (
163+ v3BillingAlertsPath ( { slug : organizationSlug } ) ,
164+ request ,
165+ "Failed to update billing alert"
166+ ) ;
167+ }
168+
169+ if ( ! updatedAlert ) {
170+ return redirectWithErrorMessage (
171+ v3BillingAlertsPath ( { slug : organizationSlug } ) ,
172+ request ,
173+ "Failed to update billing alert"
174+ ) ;
175+ }
144176
145- if ( ! updatedAlert ) {
146- return redirectWithErrorMessage (
177+ return redirectWithSuccessMessage (
147178 v3BillingAlertsPath ( { slug : organizationSlug } ) ,
148179 request ,
149- "Failed to update billing alert"
180+ "Billing alert updated "
150181 ) ;
182+ } catch ( error : any ) {
183+ return json ( { errors : { body : error . message } } , { status : 400 } ) ;
151184 }
152-
153- return redirectWithSuccessMessage (
154- v3BillingAlertsPath ( { slug : organizationSlug } ) ,
155- request ,
156- "Billing alert updated"
157- ) ;
158- } catch ( error : any ) {
159- return json ( { errors : { body : error . message } } , { status : 400 } ) ;
160185 }
161- } ;
186+ ) ;
162187
163188export default function Page ( ) {
164189 const { alerts } = useTypedLoaderData < typeof loader > ( ) ;
0 commit comments