diff --git a/apps/webapp/app/models/member.server.ts b/apps/webapp/app/models/member.server.ts index 86ae5d371d..82af3d01b9 100644 --- a/apps/webapp/app/models/member.server.ts +++ b/apps/webapp/app/models/member.server.ts @@ -147,12 +147,19 @@ export async function getUsersInvites({ email }: { email: string }) { }); } -export async function acceptInvite({ userId, inviteId }: { userId: string; inviteId: string }) { +export async function acceptInvite({ + user, + inviteId, +}: { + user: { id: string; email: string }; + inviteId: string; +}) { return await prisma.$transaction(async (tx) => { // 1. Delete the invite and get the invite details const invite = await tx.orgMemberInvite.delete({ where: { id: inviteId, + email: user.email, }, include: { organization: { @@ -167,7 +174,7 @@ export async function acceptInvite({ userId, inviteId }: { userId: string; invit const member = await tx.orgMember.create({ data: { organizationId: invite.organizationId, - userId, + userId: user.id, role: invite.role, }, }); @@ -187,7 +194,7 @@ export async function acceptInvite({ userId, inviteId }: { userId: string; invit // 4. Check for other invites const remainingInvites = await tx.orgMemberInvite.findMany({ where: { - email: invite.email, + email: user.email, }, }); @@ -195,28 +202,29 @@ export async function acceptInvite({ userId, inviteId }: { userId: string; invit }); } -export async function declineInvite({ userId, inviteId }: { userId: string; inviteId: string }) { +export async function declineInvite({ + user, + inviteId, +}: { + user: { id: string; email: string }; + inviteId: string; +}) { return await prisma.$transaction(async (tx) => { //1. delete invite const declinedInvite = await prisma.orgMemberInvite.delete({ where: { id: inviteId, + email: user.email, }, include: { organization: true, }, }); - //2. get email - const user = await prisma.user.findUnique({ - where: { id: userId }, - select: { email: true }, - }); - - //3. check for other invites + //2. check for other invites const remainingInvites = await prisma.orgMemberInvite.findMany({ where: { - email: user!.email, + email: user.email, }, }); @@ -224,10 +232,11 @@ export async function declineInvite({ userId, inviteId }: { userId: string; invi }); } -export async function resendInvite({ inviteId }: { inviteId: string }) { +export async function resendInvite({ inviteId, userId }: { inviteId: string; userId: string }) { return await prisma.orgMemberInvite.update({ where: { id: inviteId, + inviterId: userId, }, data: { updatedAt: new Date(), @@ -241,26 +250,27 @@ export async function resendInvite({ inviteId }: { inviteId: string }) { export async function revokeInvite({ userId, - slug, + orgSlug, inviteId, }: { userId: string; - slug: string; + orgSlug: string; inviteId: string; }) { - const org = await prisma.organization.findFirst({ - where: { slug, members: { some: { userId } } }, - }); - - if (!org) { - throw new Error("User does not have access to this organization"); - } - const invite = await prisma.orgMemberInvite.delete({ + const invite = await prisma.orgMemberInvite.findFirst({ where: { id: inviteId, - organizationId: org.id, + organization: { + slug: orgSlug, + members: { + some: { + userId, + }, + }, + }, }, select: { + id: true, email: true, organization: true, }, @@ -270,5 +280,11 @@ export async function revokeInvite({ throw new Error("Invite not found"); } + await prisma.orgMemberInvite.delete({ + where: { + id: invite.id, + }, + }); + return { email: invite.email, organization: invite.organization }; } diff --git a/apps/webapp/app/routes/invite-accept.tsx b/apps/webapp/app/routes/invite-accept.tsx index 57c1ddff06..592384b951 100644 --- a/apps/webapp/app/routes/invite-accept.tsx +++ b/apps/webapp/app/routes/invite-accept.tsx @@ -18,6 +18,12 @@ export async function loader({ request }: LoaderFunctionArgs) { ); } + if (!user) { + return redirectWithSuccessMessage("/", request, "Please log in to accept the invite.", { + ephemeral: false, + }); + } + const invite = await getInviteFromToken({ token }); if (!invite) { return redirectWithErrorMessage( @@ -28,12 +34,6 @@ export async function loader({ request }: LoaderFunctionArgs) { ); } - if (!user) { - return redirectWithSuccessMessage("/", request, "Please log in to accept the invite.", { - ephemeral: false, - }); - } - if (invite.email !== user.email) { return redirectWithErrorMessage( "/", diff --git a/apps/webapp/app/routes/invite-resend.tsx b/apps/webapp/app/routes/invite-resend.tsx index 9d5ee08abe..dc66e89851 100644 --- a/apps/webapp/app/routes/invite-resend.tsx +++ b/apps/webapp/app/routes/invite-resend.tsx @@ -1,5 +1,5 @@ import { parse } from "@conform-to/zod"; -import { ActionFunction, json } from "@remix-run/server-runtime"; +import { type ActionFunction, json } from "@remix-run/server-runtime"; import { env } from "process"; import { z } from "zod"; import { resendInvite } from "~/models/member.server"; @@ -25,6 +25,7 @@ export const action: ActionFunction = async ({ request }) => { try { const invite = await resendInvite({ inviteId: submission.value.inviteId, + userId, }); try { diff --git a/apps/webapp/app/routes/invite-revoke.tsx b/apps/webapp/app/routes/invite-revoke.tsx index b066a08ba3..cd499e58dc 100644 --- a/apps/webapp/app/routes/invite-revoke.tsx +++ b/apps/webapp/app/routes/invite-revoke.tsx @@ -1,5 +1,5 @@ import { parse } from "@conform-to/zod"; -import { ActionFunction, json } from "@remix-run/server-runtime"; +import { type ActionFunction, json } from "@remix-run/server-runtime"; import { z } from "zod"; import { revokeInvite } from "~/models/member.server"; import { redirectWithSuccessMessage } from "~/models/message.server"; @@ -24,7 +24,7 @@ export const action: ActionFunction = async ({ request }) => { try { const { email, organization } = await revokeInvite({ userId, - slug: submission.value.slug, + orgSlug: submission.value.slug, inviteId: submission.value.inviteId, }); diff --git a/apps/webapp/app/routes/invites.tsx b/apps/webapp/app/routes/invites.tsx index c4bd0057ec..11998a4676 100644 --- a/apps/webapp/app/routes/invites.tsx +++ b/apps/webapp/app/routes/invites.tsx @@ -1,6 +1,6 @@ import { conform, useForm } from "@conform-to/react"; import { parse } from "@conform-to/zod"; -import { ActionFunction, LoaderFunctionArgs, json, redirect } from "@remix-run/node"; +import { type ActionFunction, type LoaderFunctionArgs, json, redirect } from "@remix-run/node"; import { Form, useActionData } from "@remix-run/react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; @@ -36,7 +36,7 @@ const schema = z.object({ }); export const action: ActionFunction = async ({ request }) => { - const userId = await requireUserId(request); + const user = await requireUser(request); const formData = await request.formData(); const submission = parse(formData, { schema }); @@ -49,7 +49,7 @@ export const action: ActionFunction = async ({ request }) => { if (submission.intent === "accept") { const { remainingInvites, organization } = await acceptInvite({ inviteId: submission.value.inviteId, - userId, + user: { id: user.id, email: user.email }, }); if (remainingInvites.length === 0) { @@ -64,7 +64,7 @@ export const action: ActionFunction = async ({ request }) => { } else if (submission.intent === "decline") { const { remainingInvites, organization } = await declineInvite({ inviteId: submission.value.inviteId, - userId, + user: { id: user.id, email: user.email }, }); if (remainingInvites.length === 0) { return redirectWithSuccessMessage(