-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Fix post sign up handling #1078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect } from "react"; | ||
| import { logOut } from "@/utils/user"; | ||
| import { Loading } from "@/components/Loading"; | ||
| import { BasicLayout } from "@/components/layouts/BasicLayout"; | ||
|
|
||
| export default function LogoutPage() { | ||
| useEffect(() => { | ||
| logOut("/login"); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <BasicLayout> | ||
| <Loading /> | ||
| </BasicLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| import { sso } from "@better-auth/sso"; | ||
| import { createContact as createLoopsContact } from "@inboxzero/loops"; | ||
| import { createContact as createResendContact } from "@inboxzero/resend"; | ||
| import type { Account, AuthContext, User } from "better-auth"; | ||
| import type { Account, AuthContext } from "better-auth"; | ||
| import { betterAuth } from "better-auth"; | ||
| import { prismaAdapter } from "better-auth/adapters/prisma"; | ||
| import { nextCookies } from "better-auth/next-js"; | ||
|
|
@@ -110,10 +110,22 @@ export const betterAuthConfig = betterAuth({ | |
| disableIdTokenSignIn: true, | ||
| }, | ||
| }, | ||
| events: { | ||
| signIn: handleSignIn, | ||
| }, | ||
| databaseHooks: { | ||
| user: { | ||
| create: { | ||
| after: async (user) => { | ||
| await postSignUp({ | ||
| id: user.id, | ||
| email: user.email, | ||
| name: user.name, | ||
| image: user.image, | ||
| }).catch((error) => { | ||
| logger.error("Error posting sign up", { error, user }); | ||
| captureException(error, { extra: { user } }); | ||
| }); | ||
| }, | ||
| }, | ||
| }, | ||
| account: { | ||
| create: { | ||
| after: async (account: Account) => { | ||
|
|
@@ -136,106 +148,114 @@ export const betterAuthConfig = betterAuth({ | |
| }, | ||
| }); | ||
|
|
||
| async function handleSignIn({ | ||
| user, | ||
| isNewUser, | ||
| async function postSignUp({ | ||
| id: userId, | ||
| email, | ||
| name, | ||
| image, | ||
| }: { | ||
| user: User; | ||
| isNewUser: boolean; | ||
| id: string; | ||
| email: string; | ||
| name?: string | null; | ||
| image?: string | null; | ||
| }) { | ||
| if (isNewUser && user.email) { | ||
| const loops = async () => { | ||
| const account = await prisma.account | ||
| .findFirst({ | ||
| where: { userId: user.id }, | ||
| select: { provider: true }, | ||
| }) | ||
| .catch((error) => { | ||
| logger.error("Error finding account", { | ||
| userId: user.id, | ||
| error, | ||
| }); | ||
| captureException(error, undefined, user.email); | ||
| const loops = async () => { | ||
| const account = await prisma.account | ||
| .findFirst({ | ||
| where: { userId }, | ||
| select: { provider: true }, | ||
| }) | ||
| .catch((error) => { | ||
| logger.error("Error finding account", { | ||
| userId, | ||
| error, | ||
| }); | ||
|
|
||
| await createLoopsContact( | ||
| user.email, | ||
| user.name?.split(" ")?.[0], | ||
| account?.provider, | ||
| ).catch((error) => { | ||
| const alreadyExists = | ||
| error instanceof Error && error.message.includes("409"); | ||
| if (!alreadyExists) { | ||
| logger.error("Error creating Loops contact", { | ||
| email: user.email, | ||
| error, | ||
| }); | ||
| captureException(error, undefined, user.email); | ||
| } | ||
| captureException(error, undefined, email); | ||
| }); | ||
| }; | ||
|
|
||
| const resend = createResendContact({ email: user.email }).catch((error) => { | ||
| logger.error("Error creating Resend contact", { | ||
| email: user.email, | ||
| error, | ||
| }); | ||
| captureException(error, undefined, user.email); | ||
| await createLoopsContact( | ||
| email, | ||
| name?.split(" ")?.[0], | ||
| account?.provider, | ||
| ).catch((error) => { | ||
| const alreadyExists = | ||
| error instanceof Error && error.message.includes("409"); | ||
| if (!alreadyExists) { | ||
| logger.error("Error creating Loops contact", { | ||
| email, | ||
| error, | ||
| }); | ||
| captureException(error, undefined, email); | ||
| } | ||
| }); | ||
| }; | ||
|
Comment on lines
+162
to
+191
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fragile error handling for duplicate contact detection. The error check on lines 181-183 uses Consider checking the HTTP status code directly if the error object provides it: - const alreadyExists =
- error instanceof Error && error.message.includes("409");
+ const alreadyExists =
+ (error as any)?.response?.status === 409 ||
+ (error instanceof Error && error.message.includes("409"));Alternatively, check the Loops API client documentation to see if it throws a specific error type for conflicts.
🤖 Prompt for AI Agents |
||
|
|
||
| const dub = trackDubSignUp(user).catch((error) => { | ||
| const resend = createResendContact({ email }).catch((error) => { | ||
| logger.error("Error creating Resend contact", { | ||
| email, | ||
| error, | ||
| }); | ||
| captureException(error, undefined, email); | ||
| }); | ||
|
|
||
| const dub = trackDubSignUp({ id: userId, email, name, image }).catch( | ||
| (error) => { | ||
| logger.error("Error tracking Dub sign up", { | ||
| email: user.email, | ||
| email, | ||
| error, | ||
| }); | ||
| captureException(error, undefined, user.email); | ||
| }); | ||
|
|
||
| await Promise.all([loops(), resend, dub]); | ||
| } | ||
|
|
||
| if (isNewUser && user.email && user.id) { | ||
| await Promise.all([ | ||
| handlePendingPremiumInvite({ email: user.email }), | ||
| handleReferralOnSignUp({ | ||
| userId: user.id, | ||
| email: user.email, | ||
| }), | ||
| ]); | ||
| } | ||
| captureException(error, undefined, email); | ||
| }, | ||
| ); | ||
|
|
||
| await Promise.all([ | ||
| loops(), | ||
| resend, | ||
| dub, | ||
| handlePendingPremiumInvite({ email }), | ||
| handleReferralOnSignUp({ userId, email }), | ||
| ]); | ||
| } | ||
|
|
||
| async function handlePendingPremiumInvite({ email }: { email: string }) { | ||
| logger.info("Handling pending premium invite", { email }); | ||
|
|
||
| // Check for pending invite | ||
| const premium = await prisma.premium.findFirst({ | ||
| where: { pendingInvites: { has: email } }, | ||
| select: { | ||
| id: true, | ||
| pendingInvites: true, | ||
| lemonSqueezySubscriptionItemId: true, | ||
| stripeSubscriptionId: true, | ||
| _count: { select: { users: true } }, | ||
| }, | ||
| }); | ||
| try { | ||
| logger.info("Handling pending premium invite", { email }); | ||
|
|
||
| // Check for pending invite | ||
| const premium = await prisma.premium.findFirst({ | ||
| where: { pendingInvites: { has: email } }, | ||
| select: { | ||
| id: true, | ||
| pendingInvites: true, | ||
| lemonSqueezySubscriptionItemId: true, | ||
| stripeSubscriptionId: true, | ||
| _count: { select: { users: true } }, | ||
| }, | ||
| }); | ||
|
|
||
| if ( | ||
| premium?.lemonSqueezySubscriptionItemId || | ||
| premium?.stripeSubscriptionId | ||
| ) { | ||
| // Add user to premium and remove from pending invites | ||
| await prisma.premium.update({ | ||
| where: { id: premium.id }, | ||
| data: { | ||
| users: { connect: { email } }, | ||
| pendingInvites: { | ||
| set: premium.pendingInvites.filter((e: string) => e !== email), | ||
| if ( | ||
| premium?.lemonSqueezySubscriptionItemId || | ||
| premium?.stripeSubscriptionId | ||
| ) { | ||
| // Add user to premium and remove from pending invites | ||
| await prisma.premium.update({ | ||
| where: { id: premium.id }, | ||
| data: { | ||
| users: { connect: { email } }, | ||
| pendingInvites: { | ||
| set: premium.pendingInvites.filter((e: string) => e !== email), | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| logger.info("Added user to premium from invite", { email }); | ||
| } | ||
| } catch (error) { | ||
| logger.error("Error handling pending premium invite", { error, email }); | ||
| captureException(error, { | ||
| extra: { email, location: "handlePendingPremiumInvite" }, | ||
| }); | ||
| } | ||
|
|
||
| logger.info("Added user to premium from invite", { email }); | ||
| } | ||
|
|
||
| export async function handleReferralOnSignUp({ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| v2.21.52 | ||
| v2.21.54 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
searchParams.forceis a string, soif (searchParams.force)treats "false" as truthy and redirects. Consider coercing to a boolean (e.g., compare to"true") before branching.