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:
@@ -8,8 +8,18 @@
|
||||
* @see docs/design/multi-tenant-rls.md for full documentation
|
||||
*/
|
||||
|
||||
import { prisma } from '@mosaic/database';
|
||||
import type { PrismaClient } from '@prisma/client';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
// Global prisma instance for standalone usage
|
||||
// Note: In NestJS controllers/services, inject PrismaService instead
|
||||
let prisma: PrismaClient | null = null;
|
||||
|
||||
function getPrismaInstance(): PrismaClient {
|
||||
if (!prisma) {
|
||||
prisma = new PrismaClient();
|
||||
}
|
||||
return prisma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current user ID for RLS policies.
|
||||
@@ -26,9 +36,10 @@ import type { PrismaClient } from '@prisma/client';
|
||||
*/
|
||||
export async function setCurrentUser(
|
||||
userId: string,
|
||||
client: PrismaClient = prisma
|
||||
client?: PrismaClient
|
||||
): Promise<void> {
|
||||
await client.$executeRaw`SET LOCAL app.current_user_id = ${userId}`;
|
||||
const prismaClient = client || getPrismaInstance();
|
||||
await prismaClient.$executeRaw`SET LOCAL app.current_user_id = ${userId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,9 +49,10 @@ export async function setCurrentUser(
|
||||
* @param client - Optional Prisma client (defaults to global prisma)
|
||||
*/
|
||||
export async function clearCurrentUser(
|
||||
client: PrismaClient = prisma
|
||||
client?: PrismaClient
|
||||
): Promise<void> {
|
||||
await client.$executeRaw`SET LOCAL app.current_user_id = NULL`;
|
||||
const prismaClient = client || getPrismaInstance();
|
||||
await prismaClient.$executeRaw`SET LOCAL app.current_user_id = NULL`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,10 +115,11 @@ export async function withUserContext<T>(
|
||||
*/
|
||||
export async function withUserTransaction<T>(
|
||||
userId: string,
|
||||
fn: (tx: PrismaClient) => Promise<T>
|
||||
fn: (tx: any) => Promise<T>
|
||||
): Promise<T> {
|
||||
return prisma.$transaction(async (tx) => {
|
||||
await setCurrentUser(userId, tx);
|
||||
const prismaClient = getPrismaInstance();
|
||||
return prismaClient.$transaction(async (tx) => {
|
||||
await setCurrentUser(userId, tx as PrismaClient);
|
||||
return fn(tx);
|
||||
});
|
||||
}
|
||||
@@ -155,8 +168,9 @@ export async function verifyWorkspaceAccess(
|
||||
userId: string,
|
||||
workspaceId: string
|
||||
): Promise<boolean> {
|
||||
const prismaClient = getPrismaInstance();
|
||||
return withUserContext(userId, async () => {
|
||||
const member = await prisma.workspaceMember.findUnique({
|
||||
const member = await prismaClient.workspaceMember.findUnique({
|
||||
where: {
|
||||
workspaceId_userId: {
|
||||
workspaceId,
|
||||
@@ -181,8 +195,9 @@ export async function verifyWorkspaceAccess(
|
||||
* ```
|
||||
*/
|
||||
export async function getUserWorkspaces(userId: string) {
|
||||
const prismaClient = getPrismaInstance();
|
||||
return withUserContext(userId, async () => {
|
||||
return prisma.workspace.findMany({
|
||||
return prismaClient.workspace.findMany({
|
||||
include: {
|
||||
members: {
|
||||
where: { userId },
|
||||
@@ -204,8 +219,9 @@ export async function isWorkspaceAdmin(
|
||||
userId: string,
|
||||
workspaceId: string
|
||||
): Promise<boolean> {
|
||||
const prismaClient = getPrismaInstance();
|
||||
return withUserContext(userId, async () => {
|
||||
const member = await prisma.workspaceMember.findUnique({
|
||||
const member = await prismaClient.workspaceMember.findUnique({
|
||||
where: {
|
||||
workspaceId_userId: {
|
||||
workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user