feat(web): add workspace management UI (M2 #12)

- Create workspace listing page at /settings/workspaces
  - List all user workspaces with role badges
  - Create new workspace functionality
  - Display member count per workspace

- Create workspace detail page at /settings/workspaces/[id]
  - Workspace settings (name, ID, created date)
  - Member management with role editing
  - Invite member functionality
  - Delete workspace (owner only)

- Add workspace components:
  - WorkspaceCard: Display workspace info with role badge
  - WorkspaceSettings: Edit workspace settings and delete
  - MemberList: Display and manage workspace members
  - InviteMember: Send invitations with role selection

- Add WorkspaceMemberWithUser type to shared package
- Follow existing app patterns for styling and structure
- Use mock data (ready for API integration)
This commit is contained in:
Jason Woltje
2026-01-29 16:59:26 -06:00
parent 287a0e2556
commit 5291fece26
43 changed files with 4152 additions and 99 deletions

View File

@@ -0,0 +1,196 @@
/**
* Teams API Client
* Handles team-related API requests
*/
import type { Team, TeamMember, User } from "@mosaic/shared";
import { TeamMemberRole } from "@mosaic/shared";
import { apiGet, apiPost, apiPatch, apiDelete, type ApiResponse } from "./client";
export interface TeamWithMembers extends Team {
members: (TeamMember & { user: User })[];
}
export interface CreateTeamDto {
name: string;
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<Team[]> {
const response = await apiGet<ApiResponse<Team[]>>(`/api/workspaces/${workspaceId}/teams`);
return response.data;
}
/**
* Fetch a single team with members
*/
export async function fetchTeam(workspaceId: string, teamId: string): Promise<TeamWithMembers> {
const response = await apiGet<ApiResponse<TeamWithMembers>>(
`/api/workspaces/${workspaceId}/teams/${teamId}`
);
return response.data;
}
/**
* Create a new team
*/
export async function createTeam(workspaceId: string, data: CreateTeamDto): Promise<Team> {
const response = await apiPost<ApiResponse<Team>>(
`/api/workspaces/${workspaceId}/teams`,
data
);
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;
}
/**
* Delete a team
*/
export async function deleteTeam(workspaceId: string, teamId: string): Promise<void> {
await apiDelete(`/api/workspaces/${workspaceId}/teams/${teamId}`);
}
/**
* Add a member to a team
*/
export async function addTeamMember(
workspaceId: string,
teamId: string,
data: AddTeamMemberDto
): Promise<TeamMember> {
const response = await apiPost<ApiResponse<TeamMember>>(
`/api/workspaces/${workspaceId}/teams/${teamId}/members`,
data
);
return response.data;
}
/**
* Remove a member from a team
*/
export async function removeTeamMember(
workspaceId: string,
teamId: string,
userId: string
): Promise<void> {
await apiDelete(`/api/workspaces/${workspaceId}/teams/${teamId}/members/${userId}`);
}
/**
* Update a team member's role
*/
export async function updateTeamMemberRole(
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;
}
/**
* Mock teams for development (until backend endpoints are ready)
*/
export const mockTeams: Team[] = [
{
id: "team-1",
workspaceId: "workspace-1",
name: "Engineering",
description: "Product development team",
metadata: {},
createdAt: new Date("2026-01-20"),
updatedAt: new Date("2026-01-20"),
},
{
id: "team-2",
workspaceId: "workspace-1",
name: "Design",
description: "UI/UX design team",
metadata: {},
createdAt: new Date("2026-01-22"),
updatedAt: new Date("2026-01-22"),
},
{
id: "team-3",
workspaceId: "workspace-1",
name: "Marketing",
description: null,
metadata: {},
createdAt: new Date("2026-01-25"),
updatedAt: new Date("2026-01-25"),
},
];
/**
* Mock team with members for development
*/
export const mockTeamWithMembers: TeamWithMembers = {
...mockTeams[0],
members: [
{
teamId: "team-1",
userId: "user-1",
role: TeamMemberRole.OWNER,
joinedAt: new Date("2026-01-20"),
user: {
id: "user-1",
email: "john@example.com",
name: "John Doe",
emailVerified: true,
image: null,
authProviderId: null,
preferences: {},
createdAt: new Date("2026-01-15"),
updatedAt: new Date("2026-01-15"),
},
},
{
teamId: "team-1",
userId: "user-2",
role: TeamMemberRole.MEMBER,
joinedAt: new Date("2026-01-21"),
user: {
id: "user-2",
email: "jane@example.com",
name: "Jane Smith",
emailVerified: true,
image: null,
authProviderId: null,
preferences: {},
createdAt: new Date("2026-01-16"),
updatedAt: new Date("2026-01-16"),
},
},
],
};