* feat(cloud): add team management page with WorkOS org members Custom team page using WorkOS API for listing members, inviting, removing, and changing roles. Admin-gated write operations with server-side role checks. Uses Effect API layer pattern with typed endpoints and effect-atom for client-side state. Also fixes Sonner toaster to use system theme detection instead of next-themes, removes next-themes dependency. * style: format * chore: remove unused widget token endpoint and getWidgetToken * fix: remove any types from team handler tests * style: format * fix: add @types/node to storage-postgres for tsgo compat * fix: resolve merge conflict in test file * style: format * fix: resolve tsgo typecheck errors in billing and test files * fix: add explicit Plan types for tsgo compatibility * style: format * fix: cast empty array for tsgo type inference
Showing the changes to apps/cloud/src/api.ts (1 of 19 files). Show all
Unified · Split
| @@ -46,6 +46,8 @@ import { · 45 unmodified lines | ||
| 46 | 46 | import { WorkOSAuth } from "./auth/workos"; |
| 47 | 47 | import { DbService } from "./services/db"; |
| 48 | 48 | import { createOrgExecutor } from "./services/executor"; |
| 49 | +import { TeamOrgApi } from "./team/compose"; | |
| 50 | +import { TeamHandlers } from "./team/handlers"; | |
| 49 | 51 | import { server } from "./env"; |
| 50 | 52 | |
| 51 | 53 | // --------------------------------------------------------------------------- |
| @@ -90,6 +92,11 @@ const NonProtectedApiLive = HttpApiBuilder.api(NonProtectedApi).pipe( · 38 unmodified lines | ||
| 90 | 92 | Layer.provideMerge(SessionAuthLive), |
| 91 | 93 | ); |
| 92 | 94 | |
| 95 | +const TeamApiLive = HttpApiBuilder.api(TeamOrgApi).pipe( | |
| 96 | + Layer.provide(TeamHandlers), | |
| 97 | + Layer.provideMerge(OrgAuthLive), | |
| 98 | +); | |
| 99 | + | |
| 93 | 100 | // --------------------------------------------------------------------------- |
| 94 | 101 | // Public auth web handler |
| 95 | 102 | // --------------------------------------------------------------------------- |
| @@ -113,6 +120,12 @@ const createNonProtectedHandler = () => · 17 unmodified lines | ||
| 113 | 120 | { middleware: HttpMiddleware.logger }, |
| 114 | 121 | ); |
| 115 | 122 | |
| 123 | +const createTeamHandler = () => | |
| 124 | + HttpApiBuilder.toWebHandler( | |
| 125 | + TeamApiLive.pipe(Layer.provideMerge(SharedServices), Layer.provideMerge(RouterConfig)), | |
| 126 | + { middleware: HttpMiddleware.logger }, | |
| 127 | + ); | |
| 128 | + | |
| 116 | 129 | // --------------------------------------------------------------------------- |
| 117 | 130 | // Protected handler — must be built per-request because the executor varies |
| 118 | 131 | // --------------------------------------------------------------------------- |
| @@ -158,6 +171,7 @@ const buildProtectedHandler = ( · 39 unmodified lines | ||
| 158 | 171 | |
| 159 | 172 | const isAuthPath = (pathname: string): boolean => pathname.startsWith("/auth/"); |
| 160 | 173 | const isAutumnPath = (pathname: string): boolean => pathname.startsWith("/autumn/"); |
| 174 | +const isTeamPath = (pathname: string): boolean => pathname.startsWith("/team/"); | |
| 161 | 175 | const isExecutionPath = (pathname: string): boolean => |
| 162 | 176 | pathname === "/executions" || /^\/executions\/[^/]+\/resume$/.test(pathname); |
| 163 | 177 | |
| @@ -241,9 +255,22 @@ const handleAutumnRequest = async (request: Request): Promise<Response> => { · 77 unmodified lines | ||
| 241 | 255 | ); |
| 242 | 256 | }; |
| 243 | 257 | |
| 258 | +// --------------------------------------------------------------------------- | |
| 259 | +// Widget token endpoint — returns a WorkOS widget token for the session user | |
| 260 | +// --------------------------------------------------------------------------- | |
| 261 | + | |
| 244 | 262 | export const handleApiRequest = async (request: Request): Promise<Response> => { |
| 245 | 263 | const pathname = new URL(request.url).pathname; |
| 246 | 264 | |
| 265 | + if (isTeamPath(pathname)) { | |
| 266 | + const handler = createTeamHandler(); | |
| 267 | + try { | |
| 268 | + return await handler.handler(request); | |
| 269 | + } finally { | |
| 270 | + await handler.dispose(); | |
| 271 | + } | |
| 272 | + } | |
| 273 | + | |
| 247 | 274 | if (isAutumnPath(pathname)) { |
| 248 | 275 | return handleAutumnRequest(request); |
| 249 | 276 | } |