Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions app/api/projects/[id]/collaborators/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { getProfileByEmail, addProjectCollaborator, getProjectCollaborators, rem
// GET /api/projects/[id]/collaborators - List collaborators for a project
export async function GET(
request: Request,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const projectId = params.id
const { id: projectId } = await params
const supabase = createClient()

// Check if user is authenticated
Expand Down Expand Up @@ -68,19 +68,40 @@ export async function GET(
// POST /api/projects/[id]/collaborators - Add a collaborator to a project
export async function POST(
request: Request,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const projectId = params.id
const { id: projectId } = await params
const { email, role = "viewer" } = await request.json()

if (!email) {
// Validate and trim email
if (!email || typeof email !== 'string') {
return NextResponse.json(
{ error: "Email is required" },
{ status: 400 }
)
}

const trimmedEmail = email.trim()

// Basic email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(trimmedEmail)) {
return NextResponse.json(
{ error: "Invalid email format" },
{ status: 400 }
)
}

// Validate role
const validRoles = ['owner', 'admin', 'editor', 'viewer']
if (!validRoles.includes(role)) {
return NextResponse.json(
{ error: "Invalid role. Must be one of: owner, admin, editor, viewer" },
{ status: 400 }
)
}

const supabase = createClient()

// Check if user is authenticated
Expand Down Expand Up @@ -126,7 +147,7 @@ export async function POST(
}

// Find the user to add
const userProfile = await getProfileByEmail(email)
const userProfile = await getProfileByEmail(trimmedEmail)

if (!userProfile) {
return NextResponse.json(
Expand Down Expand Up @@ -179,10 +200,10 @@ export async function POST(
// DELETE /api/projects/[id]/collaborators - Remove a collaborator from a project
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const projectId = params.id
const { id: projectId } = await params
const { searchParams } = new URL(request.url)
const userIdToRemove = searchParams.get("userId")

Expand Down
15 changes: 14 additions & 1 deletion app/dashboard/collaborators/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,16 @@ export default function CollaboratorsPage() {

// Build collaborator map using API
const collabMap = new Map<string, CollaboratorInfo>()
const failedProjects: string[] = []

for (const project of userProjects) {
try {
// Fetch collaborators from API
const response = await fetch(`/api/projects/${project.id}/collaborators`)
if (!response.ok) continue
if (!response.ok) {
failedProjects.push(project.name)
continue
}

const { collaborators: projectCollabs } = await response.json()

Expand All @@ -93,9 +97,16 @@ export default function CollaboratorsPage() {
})
} catch (err) {
console.warn('Error loading collaborators for project:', project.id, err)
failedProjects.push(project.name)
}
}

// Show warning if some projects failed to load
if (failedProjects.length > 0) {
console.warn(`Failed to load collaborators for ${failedProjects.length} project(s): ${failedProjects.join(', ')}`)
alert(`⚠️ Warning: Could not load collaborators for some projects:\n${failedProjects.join('\n')}\n\nThe displayed data may be incomplete.`)
}

// Convert map to array and sort by project count
const collabArray = Array.from(collabMap.values()).sort((a, b) => b.projectCount - a.projectCount)
setCollaborators(collabArray)
Expand Down Expand Up @@ -138,6 +149,7 @@ export default function CollaboratorsPage() {

// Reload collaborators
loadCollaborators()
alert('Collaborator removed from all projects.')
} catch (err) {
console.error('Error removing collaborator from all projects:', err)
alert('Failed to remove collaborator. Please try again.')
Expand Down Expand Up @@ -169,6 +181,7 @@ export default function CollaboratorsPage() {

// Reload collaborators
loadCollaborators()
alert('Collaborator removed from project.')
} catch (err) {
console.error('Error removing collaborator from project:', err)
alert('Failed to remove collaborator. Please try again.')
Expand Down
56 changes: 34 additions & 22 deletions app/dashboard/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ export default function ProjectsPage() {
if (!response.ok) {
setError(data.error || "Failed to add collaborator")
alert(data.error || "Failed to add collaborator. Please try again.")
setLoading(false)
return
}

Expand All @@ -277,18 +278,23 @@ export default function ProjectsPage() {

// Update selected project
const collaboratorsResponse = await fetch(`/api/projects/${selectedProject.id}/collaborators`)
const collaboratorsData = await collaboratorsResponse.json()

if (collaboratorsResponse.ok) {
const collaboratorEmails = collaboratorsData.collaborators
.map((c: any) => c.profiles?.email)
.filter((email: string | undefined): email is string => Boolean(email))

setSelectedProject({
...selectedProject,
collaborators: collaboratorEmails
})
if (!collaboratorsResponse.ok) {
console.warn('Failed to reload collaborators after adding')
setLoading(false)
return
}

const collaboratorsData = await collaboratorsResponse.json()

const collaboratorEmails = collaboratorsData.collaborators
.map((c: any) => c.profiles?.email)
.filter((email: string | undefined): email is string => Boolean(email))

setSelectedProject({
...selectedProject,
collaborators: collaboratorEmails
})
} catch (err) {
console.error("Error adding collaborator:", err)
setError("Failed to add collaborator. Please try again.")
Expand All @@ -308,6 +314,7 @@ export default function ProjectsPage() {
const userProfile = await getProfileByEmail(email)
if (!userProfile) {
alert("User not found.")
setLoading(false)
return
}

Expand All @@ -316,10 +323,10 @@ export default function ProjectsPage() {
method: 'DELETE'
})

const data = await response.json()

if (!response.ok) {
const data = await response.json()
alert(data.error || "Failed to remove collaborator")
setLoading(false)
return
}

Expand All @@ -328,18 +335,23 @@ export default function ProjectsPage() {

// Update selected project
const collaboratorsResponse = await fetch(`/api/projects/${selectedProject.id}/collaborators`)
const collaboratorsData = await collaboratorsResponse.json()

if (collaboratorsResponse.ok) {
const collaboratorEmails = collaboratorsData.collaborators
.map((c: any) => c.profiles?.email)
.filter((email: string | undefined): email is string => Boolean(email))

setSelectedProject({
...selectedProject,
collaborators: collaboratorEmails
})
if (!collaboratorsResponse.ok) {
console.warn('Failed to reload collaborators after removal')
setLoading(false)
return
}

const collaboratorsData = await collaboratorsResponse.json()

const collaboratorEmails = collaboratorsData.collaborators
.map((c: any) => c.profiles?.email)
.filter((email: string | undefined): email is string => Boolean(email))

setSelectedProject({
...selectedProject,
collaborators: collaboratorEmails
})
} catch (err) {
console.error("Error removing collaborator:", err)
setError("Failed to remove collaborator. Please try again.")
Expand Down