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,2 @@
export * from "./permissions.decorator";
export * from "./workspace.decorator";

View File

@@ -0,0 +1,48 @@
import { SetMetadata } from "@nestjs/common";
/**
* Permission levels for workspace access control.
* These map to WorkspaceMemberRole from the database schema.
*/
export enum Permission {
/** Requires OWNER role - full control over workspace */
WORKSPACE_OWNER = "workspace:owner",
/** Requires ADMIN or OWNER role - administrative functions */
WORKSPACE_ADMIN = "workspace:admin",
/** Requires MEMBER, ADMIN, or OWNER role - standard access */
WORKSPACE_MEMBER = "workspace:member",
/** Any authenticated workspace member including GUEST */
WORKSPACE_ANY = "workspace:any",
}
export const PERMISSION_KEY = "permission";
/**
* Decorator to specify required permission level for a route.
* Use with PermissionGuard to enforce role-based access control.
*
* @param permission - The minimum permission level required
*
* @example
* ```typescript
* @RequirePermission(Permission.WORKSPACE_ADMIN)
* @Delete(':id')
* async deleteWorkspace(@Param('id') id: string) {
* // Only ADMIN or OWNER can execute this
* }
* ```
*
* @example
* ```typescript
* @RequirePermission(Permission.WORKSPACE_MEMBER)
* @Get()
* async getTasks() {
* // Any workspace member can execute this
* }
* ```
*/
export const RequirePermission = (permission: Permission) =>
SetMetadata(PERMISSION_KEY, permission);

View File

@@ -0,0 +1,40 @@
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
/**
* Decorator to extract workspace ID from the request.
* Must be used with WorkspaceGuard which validates and attaches the workspace.
*
* @example
* ```typescript
* @Get()
* @UseGuards(AuthGuard, WorkspaceGuard)
* async getTasks(@Workspace() workspaceId: string) {
* // workspaceId is validated and ready to use
* }
* ```
*/
export const Workspace = createParamDecorator(
(_data: unknown, ctx: ExecutionContext): string => {
const request = ctx.switchToHttp().getRequest();
return request.workspace?.id;
}
);
/**
* Decorator to extract full workspace context from the request.
*
* @example
* ```typescript
* @Get()
* @UseGuards(AuthGuard, WorkspaceGuard)
* async getTasks(@WorkspaceContext() workspace: { id: string }) {
* console.log(workspace.id);
* }
* ```
*/
export const WorkspaceContext = createParamDecorator(
(_data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.workspace;
}
);