diff --git a/app/api/projects/[id]/collaborators/route.ts b/app/api/projects/[id]/collaborators/route.ts index 5764015..ba6d362 100644 --- a/app/api/projects/[id]/collaborators/route.ts +++ b/app/api/projects/[id]/collaborators/route.ts @@ -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 @@ -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 @@ -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( @@ -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") diff --git a/app/dashboard/collaborators/page.tsx b/app/dashboard/collaborators/page.tsx index c2e657a..531b303 100644 --- a/app/dashboard/collaborators/page.tsx +++ b/app/dashboard/collaborators/page.tsx @@ -61,12 +61,16 @@ export default function CollaboratorsPage() { // Build collaborator map using API const collabMap = new Map() + 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() @@ -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) @@ -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.') @@ -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.') diff --git a/app/dashboard/projects/page.tsx b/app/dashboard/projects/page.tsx index be69392..99550cb 100644 --- a/app/dashboard/projects/page.tsx +++ b/app/dashboard/projects/page.tsx @@ -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 } @@ -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.") @@ -308,6 +314,7 @@ export default function ProjectsPage() { const userProfile = await getProfileByEmail(email) if (!userProfile) { alert("User not found.") + setLoading(false) return } @@ -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 } @@ -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.")