Skip to content

Commit 59c43f5

Browse files
committed
fix: vercel integration
1 parent 98eb819 commit 59c43f5

File tree

3 files changed

+99
-74
lines changed

3 files changed

+99
-74
lines changed

apps/dashboard/app/(auth)/login/page.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,33 @@ function LoginPage() {
2727
const [showPassword, setShowPassword] = useState(false);
2828
const [lastUsed, setLastUsed] = useState<string | null>(null);
2929

30-
const callbackUrl = searchParams.get('callback') || '/websites';
30+
const defaultCallbackUrl = searchParams.get('callback') || '/websites';
31+
const prefilledEmail = searchParams.get('email');
3132

3233
useEffect(() => {
3334
setLastUsed(localStorage.getItem('lastUsedLogin'));
34-
}, []);
35+
if (prefilledEmail) {
36+
setEmail(prefilledEmail);
37+
}
38+
}, [prefilledEmail]);
39+
40+
const handlePostAuthCallback = () => {
41+
const callbackUrl = searchParams.get('callback');
42+
if (callbackUrl) {
43+
router.push(callbackUrl);
44+
}
45+
};
3546

3647
const handleGoogleLogin = () => {
3748
setIsLoading(true);
3849
signIn.social({
3950
provider: 'google',
40-
callbackURL: callbackUrl,
51+
callbackURL: defaultCallbackUrl,
4152
newUserCallbackURL: '/onboarding',
4253
fetchOptions: {
4354
onSuccess: () => {
4455
localStorage.setItem('lastUsedLogin', 'google');
56+
handlePostAuthCallback();
4557
},
4658
onError: () => {
4759
setIsLoading(false);
@@ -55,11 +67,12 @@ function LoginPage() {
5567
setIsLoading(true);
5668
signIn.social({
5769
provider: 'github',
58-
callbackURL: callbackUrl,
70+
callbackURL: defaultCallbackUrl,
5971
newUserCallbackURL: '/onboarding',
6072
fetchOptions: {
6173
onSuccess: () => {
6274
localStorage.setItem('lastUsedLogin', 'github');
75+
handlePostAuthCallback();
6376
},
6477
onError: () => {
6578
setIsLoading(false);
@@ -81,10 +94,11 @@ function LoginPage() {
8194
await signIn.email({
8295
email,
8396
password,
84-
callbackURL: callbackUrl,
97+
callbackURL: defaultCallbackUrl,
8598
fetchOptions: {
8699
onSuccess: () => {
87100
localStorage.setItem('lastUsedLogin', 'email');
101+
handlePostAuthCallback();
88102
},
89103
onError: (error) => {
90104
setIsLoading(false);

apps/dashboard/app/(auth)/register/page.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ function RegisterPageContent() {
3333
const router = useRouter();
3434
const searchParams = useSearchParams();
3535
const selectedPlan = searchParams.get('plan');
36+
const callbackUrl = searchParams.get('callback');
37+
const prefilledEmail = searchParams.get('email');
38+
const prefilledName = searchParams.get('name');
3639
const [isLoading, setIsLoading] = useState(false);
3740
const [formData, setFormData] = useState({
38-
name: '',
39-
email: '',
41+
name: prefilledName || '',
42+
email: prefilledEmail || '',
4043
password: '',
4144
confirmPassword: '',
4245
});
@@ -53,6 +56,12 @@ function RegisterPageContent() {
5356
setFormData((prev) => ({ ...prev, [name]: value }));
5457
};
5558

59+
const handlePostAuthCallback = () => {
60+
if (callbackUrl) {
61+
router.push(callbackUrl);
62+
}
63+
};
64+
5665
const handleSubmit = async (e: React.FormEvent) => {
5766
e.preventDefault();
5867

@@ -83,6 +92,13 @@ function RegisterPageContent() {
8392
if (authToken) {
8493
localStorage.setItem('authToken', authToken);
8594
}
95+
96+
if (callbackUrl) {
97+
toast.success('Account created! Completing integration...');
98+
handlePostAuthCallback();
99+
return;
100+
}
101+
86102
toast.success(
87103
'Account created! Please check your email to verify your account.'
88104
);
@@ -130,20 +146,22 @@ function RegisterPageContent() {
130146

131147
await authClient.signIn.social({
132148
provider,
149+
callbackURL: callbackUrl || '/websites',
133150
fetchOptions: {
134151
onSuccess: (ctx) => {
135152
const authToken = ctx.response.headers.get('set-auth-token');
136153
if (authToken) {
137154
localStorage.setItem('authToken', authToken);
138155
}
156+
139157
toast.success('Registration successful!');
140158

141159
// Redirect to billing with plan selection if plan was specified
142160
if (selectedPlan) {
143161
localStorage.setItem('pendingPlanSelection', selectedPlan);
144162
router.push(`/billing?tab=plans&plan=${selectedPlan}`);
145163
} else {
146-
router.push('/home');
164+
router.push('/websites');
147165
}
148166
},
149167
onError: () => {
@@ -549,7 +567,11 @@ function RegisterPageContent() {
549567
Already have an account?{' '}
550568
<Link
551569
className="font-medium text-primary hover:text-primary/80"
552-
href="/login"
570+
href={
571+
callbackUrl
572+
? `/login?email=${encodeURIComponent(formData.email)}&callback=${encodeURIComponent(callbackUrl)}`
573+
: '/login'
574+
}
553575
>
554576
Sign in
555577
</Link>

apps/dashboard/app/api/integrations/vercel/callback/route.ts

Lines changed: 54 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1-
import { db, eq, user, account, sql, and } from '@databuddy/db';
2-
import { NextRequest, NextResponse } from 'next/server';
3-
import { randomUUID } from 'crypto';
1+
import { randomUUID } from 'node:crypto';
2+
import { account, and, db, eq, user } from '@databuddy/db';
3+
import { type NextRequest, NextResponse } from 'next/server';
4+
5+
interface VercelTokenResponse {
6+
access_token: string;
7+
token_type: string;
8+
installation_id: string;
9+
}
10+
11+
interface VercelUserInfo {
12+
id: string;
13+
email: string;
14+
name?: string;
15+
avatar?: string;
16+
}
417

518
export async function GET(request: NextRequest) {
619
try {
@@ -40,7 +53,7 @@ export async function GET(request: NextRequest) {
4053
);
4154
}
4255

43-
const tokens = await tokenResponse.json();
56+
const tokens: VercelTokenResponse = await tokenResponse.json();
4457

4558
const userResponse = await fetch('https://api.vercel.com/v2/user', {
4659
headers: {
@@ -55,9 +68,9 @@ export async function GET(request: NextRequest) {
5568
}
5669

5770
const userResponse_json = await userResponse.json();
58-
const userInfo = userResponse_json.user;
71+
const userInfo: VercelUserInfo = userResponse_json.user;
5972

60-
if (!userInfo.email || !userInfo.id) {
73+
if (!(userInfo.email && userInfo.id)) {
6174
return NextResponse.redirect(
6275
`${process.env.BETTER_AUTH_URL}/auth/error?error=invalid_user_info`
6376
);
@@ -67,82 +80,58 @@ export async function GET(request: NextRequest) {
6780
where: eq(user.email, userInfo.email),
6881
});
6982

70-
let userId: string;
83+
if (!existingUser) {
84+
// Create the callback URL that will complete the integration after auth
85+
const callbackUrl = new URL(request.url);
86+
const completeIntegrationUrl = `${process.env.BETTER_AUTH_URL}${callbackUrl.pathname}${callbackUrl.search}`;
7187

72-
if (existingUser) {
73-
userId = existingUser.id;
74-
} else {
75-
const newUserId = randomUUID();
76-
const now = new Date().toISOString();
77-
78-
await db.execute(sql`
79-
INSERT INTO "user" (
80-
id, name, email, email_verified, image, created_at, updated_at
81-
) VALUES (
82-
${newUserId},
83-
${userInfo.name || userInfo.email},
84-
${userInfo.email},
85-
${true},
86-
${userInfo.avatar || null},
87-
${now},
88-
${now}
89-
)
90-
`);
91-
92-
userId = newUserId;
88+
return NextResponse.redirect(
89+
`${process.env.BETTER_AUTH_URL}/register?email=${encodeURIComponent(userInfo.email)}&name=${encodeURIComponent(userInfo.name || '')}&callback=${encodeURIComponent(completeIntegrationUrl)}`
90+
);
9391
}
9492

93+
const userId = existingUser.id;
94+
9595
const existingAccount = await db.query.account.findFirst({
9696
where: and(eq(account.userId, userId), eq(account.providerId, 'vercel')),
9797
});
9898

99-
const now = new Date();
100-
const accountData = {
101-
id: existingAccount?.id || randomUUID(),
102-
accountId: userInfo.id,
103-
providerId: 'vercel',
104-
userId: userId,
105-
accessToken: tokens.access_token,
106-
scope: JSON.stringify({
107-
configurationId,
108-
teamId,
109-
installationId: tokens.installation_id,
110-
tokenType: tokens.token_type,
111-
}),
112-
createdAt: existingAccount?.createdAt || now.toISOString(),
113-
updatedAt: now.toISOString(),
114-
};
99+
const now = new Date().toISOString();
100+
const scopeData = JSON.stringify({
101+
configurationId,
102+
teamId,
103+
installationId: tokens.installation_id,
104+
tokenType: tokens.token_type,
105+
});
115106

116107
if (existingAccount) {
117-
await db.execute(sql`
118-
UPDATE "account" SET
119-
access_token = ${accountData.accessToken},
120-
scope = ${accountData.scope},
121-
updated_at = ${accountData.updatedAt}
122-
WHERE id = ${existingAccount.id}
123-
`);
108+
await db
109+
.update(account)
110+
.set({
111+
accessToken: tokens.access_token,
112+
scope: scopeData,
113+
updatedAt: now,
114+
})
115+
.where(eq(account.id, existingAccount.id));
124116
} else {
125-
await db.execute(sql`
126-
INSERT INTO "account" (
127-
id, account_id, provider_id, user_id, access_token, scope, created_at, updated_at
128-
) VALUES (
129-
${accountData.id},
130-
${accountData.accountId},
131-
${accountData.providerId},
132-
${accountData.userId},
133-
${accountData.accessToken},
134-
${accountData.scope},
135-
${accountData.createdAt},
136-
${accountData.updatedAt}
137-
)
138-
`);
117+
await db.insert(account).values({
118+
id: randomUUID(),
119+
accountId: userInfo.id,
120+
providerId: 'vercel',
121+
userId,
122+
accessToken: tokens.access_token,
123+
scope: scopeData,
124+
createdAt: now,
125+
updatedAt: now,
126+
});
139127
}
140128

141129
const redirectUrl =
142130
next || `${process.env.BETTER_AUTH_URL}/dashboard?vercel_integrated=true`;
143131

144132
return NextResponse.redirect(redirectUrl);
145133
} catch (error) {
134+
console.error('Vercel OAuth callback error:', error);
146135
return NextResponse.redirect(
147136
`${process.env.BETTER_AUTH_URL}/auth/error?error=internal_error`
148137
);

0 commit comments

Comments
 (0)