feat: add workspace management features including member invitations and role updates

- Implemented API endpoints for managing workspace members (GET, POST, PATCH, DELETE).
- Added workspace creation and retrieval functionalities.
- Enhanced project model to associate with workspaces.
- Updated project member roles and access control logic.
- Created sign-out page and updated authentication flow.
- Modified header to include navigation to workspaces.
- Updated Prisma schema to include workspace and member models.
- Seed script updated to create demo workspaces and associated members.
This commit is contained in:
Yusuf İpek
2026-02-07 07:41:12 +03:00
parent d90ce4e1f6
commit 6e95f667e3
28 changed files with 2891 additions and 138 deletions
+54 -11
View File
@@ -9,6 +9,8 @@ import {
Globe,
Lock,
UserPlus,
Users,
Building2,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
@@ -61,6 +63,7 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
const project = await db.project.findUnique({
where: { id: projectId },
include: {
workspace: { select: { id: true, name: true } },
owner: { select: { id: true, name: true } },
members: {
where: { userId: session?.user?.id || '' },
@@ -91,7 +94,29 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
const isMember = project.members.length > 0;
const isPublicOrLink = project.visibility !== 'PRIVATE';
if (!isOwner && !isMember && !isPublicOrLink) {
// Check workspace membership
let isWorkspaceMember = false;
let workspaceRole: string | null = null;
if (session?.user?.id) {
const wsMember = await db.workspaceMember.findUnique({
where: {
workspaceId_userId: {
workspaceId: project.workspaceId,
userId: session.user.id,
},
},
});
const ws = await db.workspace.findUnique({
where: { id: project.workspaceId },
select: { ownerId: true },
});
if (ws?.ownerId === session.user.id || wsMember) {
isWorkspaceMember = true;
workspaceRole = ws?.ownerId === session.user.id ? 'OWNER' : wsMember?.role || null;
}
}
if (!isOwner && !isMember && !isPublicOrLink && !isWorkspaceMember) {
redirect('/dashboard');
}
@@ -109,7 +134,7 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
};
});
const canEdit = isOwner || project.members[0]?.role === 'ADMIN' || project.members[0]?.role === 'EDITOR';
const canEdit = isOwner || project.members[0]?.role === 'ADMIN' || workspaceRole === 'OWNER' || workspaceRole === 'ADMIN';
return (
<div className="px-6 lg:px-8 py-8 w-full">
@@ -134,9 +159,19 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
{project.visibility.toLowerCase()}
</Badge>
</div>
{project.description && (
<p className="text-muted-foreground">{project.description}</p>
)}
<div className="flex items-center gap-2">
{project.workspace && (
<Link href={`/workspaces/${project.workspace.id}`}>
<Badge variant="secondary" className="flex items-center gap-1 hover:bg-accent transition-colors">
<Building2 className="h-3 w-3" />
{project.workspace.name}
</Badge>
</Link>
)}
{project.description && (
<span className="text-muted-foreground">{project.description}</span>
)}
</div>
</div>
<div className="flex items-center gap-2">
@@ -147,12 +182,20 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
</Link>
</Button>
{(isOwner || project.members[0]?.role === 'ADMIN') && (
<Button variant="outline" size="sm" asChild>
<Link href={`/projects/${projectId}/settings`}>
<Settings className="h-4 w-4 mr-2" />
Settings
</Link>
</Button>
<>
<Button variant="outline" size="sm" asChild>
<Link href={`/projects/${projectId}/members`}>
<Users className="h-4 w-4 mr-2" />
Members
</Link>
</Button>
<Button variant="outline" size="sm" asChild>
<Link href={`/projects/${projectId}/settings`}>
<Settings className="h-4 w-4 mr-2" />
Settings
</Link>
</Button>
</>
)}
{canEdit && (
<Button size="sm" asChild>