* 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/web (3 of 19 files). Show all
Unified · Split
| @@ -3,12 +3,13 @@ import { FetchHttpClient } from "@effect/platform"; · 2 unmodified lines | ||
| 3 | 3 | import { addGroup } from "@executor/api"; |
| 4 | 4 | import { getBaseUrl } from "@executor/react/api/base-url"; |
| 5 | 5 | import { CloudAuthApi } from "../auth/api"; |
| 6 | +import { TeamApi } from "../team/api"; | |
| 6 | 7 | |
| 7 | 8 | // --------------------------------------------------------------------------- |
| 8 | −// Cloud API client — core API + cloud auth | |
| 9 | +// Cloud API client — core API + cloud auth + team | |
| 9 | 10 | // --------------------------------------------------------------------------- |
| 10 | 11 | |
| 11 | −const CloudApi = addGroup(CloudAuthApi); | |
| 12 | +const CloudApi = addGroup(CloudAuthApi).add(TeamApi); | |
| 12 | 13 | |
| 13 | 14 | class CloudApiClient extends AtomHttpApi.Tag<CloudApiClient>()("CloudApiClient", { |
| 14 | 15 | api: CloudApi, |
| @@ -138,6 +138,7 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show · 137 unmodified lines | ||
| 138 | 138 | const isHome = props.pathname === "/"; |
| 139 | 139 | const isSecrets = props.pathname === "/secrets"; |
| 140 | 140 | const isBilling = props.pathname === "/billing" || props.pathname.startsWith("/billing/"); |
| 141 | + const isTeam = props.pathname === "/team"; | |
| 141 | 142 | |
| 142 | 143 | return ( |
| 143 | 144 | <> |
| @@ -152,6 +153,7 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show · 8 unmodified lines | ||
| 152 | 153 | <nav className="flex flex-1 flex-col overflow-y-auto p-2"> |
| 153 | 154 | <NavItem to="/" label="Sources" active={isHome} onNavigate={props.onNavigate} /> |
| 154 | 155 | <NavItem to="/secrets" label="Secrets" active={isSecrets} onNavigate={props.onNavigate} /> |
| 156 | + <NavItem to="/team" label="Team" active={isTeam} onNavigate={props.onNavigate} /> | |
| 155 | 157 | <NavItem to="/billing" label="Billing" active={isBilling} onNavigate={props.onNavigate} /> |
| 156 | 158 | |
| 157 | 159 | <div className="mt-5 mb-1 px-2.5 text-[10px] font-medium uppercase tracking-widest text-muted-foreground/50"> |
| @@ -0,0 +1,15 @@ | ||
| 1 | +import { CloudApiClient } from "./client"; | |
| 2 | + | |
| 3 | +export const teamMembersAtom = CloudApiClient.query("team", "listMembers", { | |
| 4 | + timeToLive: "30 seconds", | |
| 5 | +}); | |
| 6 | + | |
| 7 | +export const teamRolesAtom = CloudApiClient.query("team", "listRoles", { | |
| 8 | + timeToLive: "5 minutes", | |
| 9 | +}); | |
| 10 | + | |
| 11 | +export const inviteMember = CloudApiClient.mutation("team", "invite"); | |
| 12 | + | |
| 13 | +export const removeMember = CloudApiClient.mutation("team", "removeMember"); | |
| 14 | + | |
| 15 | +export const updateMemberRole = CloudApiClient.mutation("team", "updateMemberRole"); | |