From de6aa9c76846ed4324a105304d509869b0683d86 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sat, 28 Feb 2026 12:48:30 -0600 Subject: [PATCH] feat(web): add teams API client (in progress) Hit rate limit mid-flight. --- apps/web/src/lib/api/teams.ts | 184 +++++++++++++--------------------- 1 file changed, 71 insertions(+), 113 deletions(-) diff --git a/apps/web/src/lib/api/teams.ts b/apps/web/src/lib/api/teams.ts index 18e4dc8..7910a07 100644 --- a/apps/web/src/lib/api/teams.ts +++ b/apps/web/src/lib/api/teams.ts @@ -1,14 +1,29 @@ -/** - * Teams API Client - * Handles team-related API requests - */ - -import type { Team, TeamMember, User } from "@mosaic/shared"; +import type { + Team, + TeamMember, + User, + WorkspaceMemberRole, +} from "@mosaic/shared"; import { TeamMemberRole } from "@mosaic/shared"; -import { apiGet, apiPost, apiPatch, apiDelete, type ApiResponse } from "./client"; +import { apiDelete, apiGet, apiPost, type ApiResponse } from "./client"; + +export interface TeamMemberWithUser extends TeamMember { + user: Pick; +} export interface TeamWithMembers extends Team { - members: (TeamMember & { user: User })[]; + members?: TeamMemberWithUser[]; + _count?: { + members: number; + }; +} + +export interface WorkspaceMemberWithUser { + workspaceId: string; + userId: string; + role: WorkspaceMemberRole; + joinedAt: string | Date; + user: Pick; } export interface CreateTeamDto { @@ -16,108 +31,81 @@ export interface CreateTeamDto { description?: string; } -export interface UpdateTeamDto { - name?: string; - description?: string; -} - export interface AddTeamMemberDto { userId: string; role?: TeamMemberRole; } -/** - * Fetch all teams for a workspace - */ -export async function fetchTeams(workspaceId: string): Promise { - const response = await apiGet>(`/api/workspaces/${workspaceId}/teams`); - return response.data; +type ApiPayload = T | ApiResponse; + +function isApiResponse(payload: ApiPayload): payload is ApiResponse { + return typeof payload === "object" && payload !== null && "data" in payload; } -/** - * Fetch a single team with members - */ -export async function fetchTeam(workspaceId: string, teamId: string): Promise { - const response = await apiGet>( - `/api/workspaces/${workspaceId}/teams/${teamId}` +function unwrapPayload(payload: ApiPayload): T { + return isApiResponse(payload) ? payload.data : payload; +} + +export function getTeamMemberCount(team: TeamWithMembers): number { + if (Array.isArray(team.members)) { + return team.members.length; + } + + return team._count?.members ?? 0; +} + +export async function fetchTeams(workspaceId: string): Promise { + const payload = await apiGet>( + `/api/workspaces/${workspaceId}/teams`, + workspaceId ); - return response.data; + return unwrapPayload(payload); } -/** - * Create a new team - */ -export async function createTeam(workspaceId: string, data: CreateTeamDto): Promise { - const response = await apiPost>(`/api/workspaces/${workspaceId}/teams`, data); - return response.data; -} - -/** - * Update a team - */ -export async function updateTeam( - workspaceId: string, - teamId: string, - data: UpdateTeamDto -): Promise { - const response = await apiPatch>( - `/api/workspaces/${workspaceId}/teams/${teamId}`, - data +export async function createTeam(workspaceId: string, data: CreateTeamDto): Promise { + const payload = await apiPost>( + `/api/workspaces/${workspaceId}/teams`, + data, + workspaceId ); - return response.data; + return unwrapPayload(payload); } -/** - * Delete a team - */ export async function deleteTeam(workspaceId: string, teamId: string): Promise { - await apiDelete(`/api/workspaces/${workspaceId}/teams/${teamId}`); + await apiDelete(`/api/workspaces/${workspaceId}/teams/${teamId}`, workspaceId); } -/** - * Add a member to a team - */ export async function addTeamMember( workspaceId: string, teamId: string, data: AddTeamMemberDto -): Promise { - const response = await apiPost>( +): Promise { + const payload = await apiPost>( `/api/workspaces/${workspaceId}/teams/${teamId}/members`, - data + data, + workspaceId ); - return response.data; + return unwrapPayload(payload); } -/** - * Remove a member from a team - */ export async function removeTeamMember( workspaceId: string, teamId: string, userId: string ): Promise { - await apiDelete(`/api/workspaces/${workspaceId}/teams/${teamId}/members/${userId}`); + await apiDelete(`/api/workspaces/${workspaceId}/teams/${teamId}/members/${userId}`, workspaceId); } -/** - * Update a team member's role - */ -export async function updateTeamMemberRole( - workspaceId: string, - teamId: string, - userId: string, - role: TeamMemberRole -): Promise { - const response = await apiPatch>( - `/api/workspaces/${workspaceId}/teams/${teamId}/members/${userId}`, - { role } +export async function fetchWorkspaceMembers(workspaceId: string): Promise { + const payload = await apiGet>( + `/api/workspaces/${workspaceId}/members`, + workspaceId ); - return response.data; + return unwrapPayload(payload); } /** - * Mock teams for development (until backend endpoints are ready) + * Mock teams for development in legacy routes under /app/settings. */ export const mockTeams: Team[] = [ { @@ -133,7 +121,7 @@ export const mockTeams: Team[] = [ id: "team-2", workspaceId: "workspace-1", name: "Design", - description: "UI/UX design team", + description: "UI and UX design team", metadata: {}, createdAt: new Date("2026-01-22"), updatedAt: new Date("2026-01-22"), @@ -149,24 +137,16 @@ export const mockTeams: Team[] = [ }, ]; -/** - * Mock team with members for development - */ -const baseTeam = mockTeams[0]; -if (!baseTeam) { - throw new Error("Mock team not found"); +const [defaultMockTeam] = mockTeams; +if (!defaultMockTeam) { + throw new Error("Mock team was not found"); } + export const mockTeamWithMembers: TeamWithMembers = { - id: baseTeam.id, - workspaceId: baseTeam.workspaceId, - name: baseTeam.name, - description: baseTeam.description, - metadata: baseTeam.metadata, - createdAt: baseTeam.createdAt, - updatedAt: baseTeam.updatedAt, + ...defaultMockTeam, members: [ { - teamId: "team-1", + teamId: defaultMockTeam.id, userId: "user-1", role: TeamMemberRole.OWNER, joinedAt: new Date("2026-01-20"), @@ -174,22 +154,11 @@ export const mockTeamWithMembers: TeamWithMembers = { id: "user-1", email: "john@example.com", name: "John Doe", - emailVerified: true, image: null, - authProviderId: null, - preferences: {}, - deactivatedAt: null, - isLocalAuth: false, - passwordHash: null, - invitedBy: null, - invitationToken: null, - invitedAt: null, - createdAt: new Date("2026-01-15"), - updatedAt: new Date("2026-01-15"), }, }, { - teamId: "team-1", + teamId: defaultMockTeam.id, userId: "user-2", role: TeamMemberRole.MEMBER, joinedAt: new Date("2026-01-21"), @@ -197,18 +166,7 @@ export const mockTeamWithMembers: TeamWithMembers = { id: "user-2", email: "jane@example.com", name: "Jane Smith", - emailVerified: true, image: null, - authProviderId: null, - preferences: {}, - deactivatedAt: null, - isLocalAuth: false, - passwordHash: null, - invitedBy: null, - invitationToken: null, - invitedAt: null, - createdAt: new Date("2026-01-16"), - updatedAt: new Date("2026-01-16"), }, }, ],