feat: ability to invite users to project#71
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR adds project invitation functionality to allow users to invite others to join projects via email. The implementation includes database schema changes, email notifications, and a complete UI workflow for sending and accepting invitations.
Key Changes:
- Database schema modifications to support project invitations with status tracking
- Email invitation system with React Email templates
- User interface for sending invitations and managing notifications
- Server actions for accepting/declining invitations
Reviewed Changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/common/src/db/schema/project.ts | Adds project invitation schema with status enum and relations |
| packages/common/drizzle/0010_tranquil_betty_brant.sql | Database migration for invitation table and enum |
| packages/app/src/lib/notifications.ts | Service for fetching user notifications |
| packages/app/src/emails/project-invitation.tsx | React Email template for invitation emails |
| packages/app/src/db/crud/project.ts | CRUD operations for project invitations |
| packages/app/src/components/team/add-team-member-form.tsx | Updated form to send invitations instead of direct team member addition |
| packages/app/src/components/common/sidebar/notifications-panel.tsx | Notifications panel for pending invitations |
| packages/app/src/actions/projects/sendInvitation.ts | Server action for sending project invitations |
| packages/app/src/actions/projects/acceptInvitation.ts | Server action for accepting invitations |
| packages/app/src/actions/projects/declineInvitation.ts | Server action for declining invitations |
| }) | ||
| .from(projectInvitation) | ||
| .leftJoin(user, eq(projectInvitation.invitedByUserId, user.id)) | ||
| .leftJoin(user, eq(projectInvitation.invitedUserId, user.id)) |
There was a problem hiding this comment.
This line creates a duplicate join alias for the user table. The second leftJoin should use a table alias to distinguish between invitedByUser and invitedUser. This will cause SQL ambiguity and incorrect results.
| .leftJoin(user, eq(projectInvitation.invitedUserId, user.id)) | |
| // Alias for the invitedUser join | |
| const invitedUserAlias = alias(user, "invited_user"); | |
| const invitations = await db | |
| .select({ | |
| invitation: projectInvitation, | |
| invitedByUser: user, | |
| invitedUser: invitedUserAlias, | |
| total: sql<number>`count(*) over()`, | |
| }) | |
| .from(projectInvitation) | |
| .leftJoin(user, eq(projectInvitation.invitedByUserId, user.id)) | |
| .leftJoin(invitedUserAlias, eq(projectInvitation.invitedUserId, invitedUserAlias.id)) |
| status: "ACCEPTED" | "DECLINED" | "EXPIRED", | ||
| userId?: string, | ||
| ): Promise<ProjectInvitation> { | ||
| const updateData: any = { status }; |
There was a problem hiding this comment.
Using any type defeats TypeScript's type safety. Define a proper type for the update data object to maintain type safety and catch potential errors at compile time.
| const updateData: any = { status }; | |
| const updateData: Partial<ProjectInvitation> = { status }; |
| project: project!, | ||
| }; | ||
| }), | ||
| ); |
There was a problem hiding this comment.
Using the non-null assertion operator (!) bypasses TypeScript's null checking without proper validation. Consider handling the case where project might be null/undefined more explicitly with proper error handling.
| ); | |
| const notificationsWithProjects = ( | |
| await Promise.all( | |
| pendingInvitations.map(async (invitation) => { | |
| const project = await readProjectById(invitation.projectId); | |
| if (!project) { | |
| // Optionally log or handle missing project here | |
| return null; | |
| } | |
| return { | |
| invitation, | |
| project, | |
| }; | |
| }), | |
| ) | |
| ).filter((item): item is InvitationWithProject => item !== null); |
| const showEmailOption = | ||
| isValidEmail && !hasExistingUsers && searchQuery.length > 0; | ||
|
|
||
| console.log(isValidEmail, hasExistingUsers, searchQuery.length); |
There was a problem hiding this comment.
Debug console.log statement should be removed before production deployment. Consider using a proper logging solution or removing this line entirely.
| console.log(isValidEmail, hasExistingUsers, searchQuery.length); |
No description provided.