Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Premium" ADD COLUMN "pendingInvites" TEXT[];

-- CreateIndex
CREATE INDEX "Premium_pendingInvites_idx" ON "Premium"("pendingInvites");
4 changes: 4 additions & 0 deletions apps/web/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ model Premium {
users User[] @relation(name: "userPremium")
admins User[]

pendingInvites String[]

// lemon squeezy
lemonSqueezyRenewsAt DateTime?
lemonSqueezyCustomerId Int?
Expand All @@ -130,6 +132,8 @@ model Premium {
unsubscribeCredits Int?
aiMonth Int? // 1-12
aiCredits Int?

@@index([pendingInvites])
}

// not in use as it's only used for passwordless login
Expand Down
59 changes: 46 additions & 13 deletions apps/web/utils/actions/premium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export async function updateMultiAccountPremiumAction(
lemonSqueezySubscriptionItemId: true,
emailAccountsAccess: true,
admins: { select: { id: true } },
pendingInvites: true,
},
},
},
Expand All @@ -120,16 +121,16 @@ export async function updateMultiAccountPremiumAction(
const uniqueEmails = uniq(emails);
const users = await prisma.user.findMany({
where: { email: { in: uniqueEmails } },
select: { id: true, premium: true },
select: { id: true, premium: true, email: true },
});

const premium =
user.premium || (await createPremiumForUser(session.user.id));

const otherUsersToAdd = users.filter((u) => u.id !== session.user.id);
const otherUsers = users.filter((u) => u.id !== session.user.id);

// make sure that the users being added to this plan are not on higher tiers already
for (const userToAdd of otherUsersToAdd) {
for (const userToAdd of otherUsers) {
if (isOnHigherTier(userToAdd.premium?.tier, premium.tier)) {
return {
error:
Expand All @@ -138,7 +139,7 @@ export async function updateMultiAccountPremiumAction(
}
}

if ((premium.emailAccountsAccess || 0) < users.length) {
if ((premium.emailAccountsAccess || 0) < uniqueEmails.length) {
// TODO lifetime users
if (!premium.lemonSqueezySubscriptionItemId) {
return {
Expand All @@ -148,7 +149,7 @@ export async function updateMultiAccountPremiumAction(

await updateSubscriptionItemQuantity({
id: premium.lemonSqueezySubscriptionItemId,
quantity: otherUsersToAdd.length + 1,
quantity: uniqueEmails.length,
});
}

Expand All @@ -157,28 +158,60 @@ export async function updateMultiAccountPremiumAction(
await prisma.premium.deleteMany({
where: {
id: { not: premium.id },
users: { some: { id: { in: otherUsersToAdd.map((u) => u.id) } } },
users: { some: { id: { in: otherUsers.map((u) => u.id) } } },
},
});

// add users to plan
await prisma.premium.update({
where: { id: premium.id },
data: {
users: { connect: otherUsersToAdd.map((user) => ({ id: user.id })) },
users: { connect: otherUsers.map((user) => ({ id: user.id })) },
},
});

if (users.length < uniqueEmails.length) {
return {
warning:
"Not all users exist. Each account must sign up to Inbox Zero to share premium with it.",
};
}
// add users to pending invites
const nonExistingUsers = uniqueEmails.filter(
(email) => !users.some((u) => u.email === email),
);
await prisma.premium.update({
where: { id: premium.id },
data: {
pendingInvites: {
set: uniq([...(premium.pendingInvites || []), ...nonExistingUsers]),
},
},
});
},
);
}

export async function handlePendingPremiumInvite(user: { email: string }) {
// Check for pending invite
const premium = await prisma.premium.findFirst({
where: { pendingInvites: { has: user.email } },
select: {
id: true,
pendingInvites: true,
lemonSqueezySubscriptionItemId: true,
_count: { select: { users: true } },
},
});

if (premium?.lemonSqueezySubscriptionItemId) {
// Add user to premium and remove from pending invites
await prisma.premium.update({
where: { id: premium.id },
data: {
users: { connect: { email: user.email } },
pendingInvites: {
set: premium.pendingInvites.filter((email) => email !== user.email),
},
},
});
}
}

export const switchPremiumPlanAction = withActionInstrumentation(
"switchPremiumPlan",
async (premiumTier: PremiumTier) => {
Expand Down
7 changes: 7 additions & 0 deletions apps/web/utils/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import prisma from "@/utils/prisma";
import { env } from "@/env";
import { captureException } from "@/utils/error";
import { createScopedLogger } from "@/utils/logger";
import { handlePendingPremiumInvite } from "@/utils/actions/premium";

const logger = createScopedLogger("auth");

Expand Down Expand Up @@ -150,6 +151,12 @@ export const getAuthOptions: (options?: {
captureException(error, undefined, user.email);
}
}

if (isNewUser && user.email) {
logger.info("Handling pending premium invite", { email: user.email });
await handlePendingPremiumInvite({ email: user.email });
logger.info("Added user to premium from invite", { email: user.email });
}
},
},
pages: {
Expand Down
Loading