Compare commits

...

1 Commits

Author SHA1 Message Date
de6aa9c768 feat(web): add teams API client (in progress)
Some checks failed
ci/woodpecker/push/web Pipeline failed
Hit rate limit mid-flight.
2026-02-28 12:48:30 -06:00

View File

@@ -1,14 +1,29 @@
/** import type {
* Teams API Client Team,
* Handles team-related API requests TeamMember,
*/ User,
WorkspaceMemberRole,
import type { Team, TeamMember, User } from "@mosaic/shared"; } from "@mosaic/shared";
import { TeamMemberRole } 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<User, "id" | "name" | "email" | "image">;
}
export interface TeamWithMembers extends Team { 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<User, "id" | "name" | "email" | "image">;
} }
export interface CreateTeamDto { export interface CreateTeamDto {
@@ -16,108 +31,81 @@ export interface CreateTeamDto {
description?: string; description?: string;
} }
export interface UpdateTeamDto {
name?: string;
description?: string;
}
export interface AddTeamMemberDto { export interface AddTeamMemberDto {
userId: string; userId: string;
role?: TeamMemberRole; role?: TeamMemberRole;
} }
/** type ApiPayload<T> = T | ApiResponse<T>;
* Fetch all teams for a workspace
*/ function isApiResponse<T>(payload: ApiPayload<T>): payload is ApiResponse<T> {
export async function fetchTeams(workspaceId: string): Promise<Team[]> { return typeof payload === "object" && payload !== null && "data" in payload;
const response = await apiGet<ApiResponse<Team[]>>(`/api/workspaces/${workspaceId}/teams`);
return response.data;
} }
/** function unwrapPayload<T>(payload: ApiPayload<T>): T {
* Fetch a single team with members return isApiResponse(payload) ? payload.data : payload;
*/ }
export async function fetchTeam(workspaceId: string, teamId: string): Promise<TeamWithMembers> {
const response = await apiGet<ApiResponse<TeamWithMembers>>( export function getTeamMemberCount(team: TeamWithMembers): number {
`/api/workspaces/${workspaceId}/teams/${teamId}` if (Array.isArray(team.members)) {
return team.members.length;
}
return team._count?.members ?? 0;
}
export async function fetchTeams(workspaceId: string): Promise<TeamWithMembers[]> {
const payload = await apiGet<ApiPayload<TeamWithMembers[]>>(
`/api/workspaces/${workspaceId}/teams`,
workspaceId
); );
return response.data; return unwrapPayload(payload);
} }
/** export async function createTeam(workspaceId: string, data: CreateTeamDto): Promise<TeamWithMembers> {
* Create a new team const payload = await apiPost<ApiPayload<TeamWithMembers>>(
*/ `/api/workspaces/${workspaceId}/teams`,
export async function createTeam(workspaceId: string, data: CreateTeamDto): Promise<Team> { data,
const response = await apiPost<ApiResponse<Team>>(`/api/workspaces/${workspaceId}/teams`, data); workspaceId
return response.data;
}
/**
* Update a team
*/
export async function updateTeam(
workspaceId: string,
teamId: string,
data: UpdateTeamDto
): Promise<Team> {
const response = await apiPatch<ApiResponse<Team>>(
`/api/workspaces/${workspaceId}/teams/${teamId}`,
data
); );
return response.data; return unwrapPayload(payload);
} }
/**
* Delete a team
*/
export async function deleteTeam(workspaceId: string, teamId: string): Promise<void> { export async function deleteTeam(workspaceId: string, teamId: string): Promise<void> {
await apiDelete(`/api/workspaces/${workspaceId}/teams/${teamId}`); await apiDelete<void>(`/api/workspaces/${workspaceId}/teams/${teamId}`, workspaceId);
} }
/**
* Add a member to a team
*/
export async function addTeamMember( export async function addTeamMember(
workspaceId: string, workspaceId: string,
teamId: string, teamId: string,
data: AddTeamMemberDto data: AddTeamMemberDto
): Promise<TeamMember> { ): Promise<TeamMemberWithUser> {
const response = await apiPost<ApiResponse<TeamMember>>( const payload = await apiPost<ApiPayload<TeamMemberWithUser>>(
`/api/workspaces/${workspaceId}/teams/${teamId}/members`, `/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( export async function removeTeamMember(
workspaceId: string, workspaceId: string,
teamId: string, teamId: string,
userId: string userId: string
): Promise<void> { ): Promise<void> {
await apiDelete(`/api/workspaces/${workspaceId}/teams/${teamId}/members/${userId}`); await apiDelete<void>(`/api/workspaces/${workspaceId}/teams/${teamId}/members/${userId}`, workspaceId);
} }
/** export async function fetchWorkspaceMembers(workspaceId: string): Promise<WorkspaceMemberWithUser[]> {
* Update a team member's role const payload = await apiGet<ApiPayload<WorkspaceMemberWithUser[]>>(
*/ `/api/workspaces/${workspaceId}/members`,
export async function updateTeamMemberRole( workspaceId
workspaceId: string,
teamId: string,
userId: string,
role: TeamMemberRole
): Promise<TeamMember> {
const response = await apiPatch<ApiResponse<TeamMember>>(
`/api/workspaces/${workspaceId}/teams/${teamId}/members/${userId}`,
{ role }
); );
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[] = [ export const mockTeams: Team[] = [
{ {
@@ -133,7 +121,7 @@ export const mockTeams: Team[] = [
id: "team-2", id: "team-2",
workspaceId: "workspace-1", workspaceId: "workspace-1",
name: "Design", name: "Design",
description: "UI/UX design team", description: "UI and UX design team",
metadata: {}, metadata: {},
createdAt: new Date("2026-01-22"), createdAt: new Date("2026-01-22"),
updatedAt: new Date("2026-01-22"), updatedAt: new Date("2026-01-22"),
@@ -149,24 +137,16 @@ export const mockTeams: Team[] = [
}, },
]; ];
/** const [defaultMockTeam] = mockTeams;
* Mock team with members for development if (!defaultMockTeam) {
*/ throw new Error("Mock team was not found");
const baseTeam = mockTeams[0];
if (!baseTeam) {
throw new Error("Mock team not found");
} }
export const mockTeamWithMembers: TeamWithMembers = { export const mockTeamWithMembers: TeamWithMembers = {
id: baseTeam.id, ...defaultMockTeam,
workspaceId: baseTeam.workspaceId,
name: baseTeam.name,
description: baseTeam.description,
metadata: baseTeam.metadata,
createdAt: baseTeam.createdAt,
updatedAt: baseTeam.updatedAt,
members: [ members: [
{ {
teamId: "team-1", teamId: defaultMockTeam.id,
userId: "user-1", userId: "user-1",
role: TeamMemberRole.OWNER, role: TeamMemberRole.OWNER,
joinedAt: new Date("2026-01-20"), joinedAt: new Date("2026-01-20"),
@@ -174,22 +154,11 @@ export const mockTeamWithMembers: TeamWithMembers = {
id: "user-1", id: "user-1",
email: "john@example.com", email: "john@example.com",
name: "John Doe", name: "John Doe",
emailVerified: true,
image: null, 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", userId: "user-2",
role: TeamMemberRole.MEMBER, role: TeamMemberRole.MEMBER,
joinedAt: new Date("2026-01-21"), joinedAt: new Date("2026-01-21"),
@@ -197,18 +166,7 @@ export const mockTeamWithMembers: TeamWithMembers = {
id: "user-2", id: "user-2",
email: "jane@example.com", email: "jane@example.com",
name: "Jane Smith", name: "Jane Smith",
emailVerified: true,
image: null, 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"),
}, },
}, },
], ],