Skip to content

Commit d437da4

Browse files
author
Rajat
committed
Swapped next-auth with better-auth
1 parent d8a7788 commit d437da4

43 files changed

Lines changed: 1394 additions & 505 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/web/app/(with-contexts)/(with-layout)/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { auth } from "@/auth";
21
import { SessionProvider } from "next-auth/react";
32
import HomepageLayout from "./home-page-layout";
43
import { headers } from "next/headers";
@@ -13,7 +12,8 @@ export default async function Layout({
1312
const address = await getAddressFromHeaders(headers);
1413
const [siteInfo, session] = await Promise.all([
1514
getFullSiteSetup(address),
16-
auth(),
15+
// auth(),
16+
null,
1717
]);
1818

1919
if (!siteInfo) {

apps/web/app/(with-contexts)/(with-layout)/login/login-form.tsx

Lines changed: 121 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,15 @@ import {
1111
Input,
1212
Section,
1313
Text1,
14-
Text2,
1514
Link as PageLink,
1615
} from "@courselit/page-primitives";
17-
import { useContext, useState } from "react";
16+
import { useCallback, useContext, useEffect, useRef, useState } from "react";
1817
import { FormEvent } from "react";
19-
import { signIn } from "next-auth/react";
2018
import { Form, useToast } from "@courselit/components-library";
2119
import {
2220
BTN_LOGIN,
2321
BTN_LOGIN_GET_CODE,
24-
BTN_LOGIN_CODE_INTIMATION,
22+
LOGIN_CODE_INTIMATION_MESSAGE,
2523
LOGIN_NO_CODE,
2624
BTN_LOGIN_NO_CODE,
2725
LOGIN_FORM_LABEL,
@@ -37,6 +35,7 @@ import { checkPermission } from "@courselit/utils";
3735
import { Profile } from "@courselit/common-models";
3836
import { getUserProfile } from "../../helpers";
3937
import { ADMIN_PERMISSIONS } from "@ui-config/constants";
38+
import { authClient } from "@/lib/auth-client";
4039

4140
export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
4241
const { theme } = useContext(ThemeContext);
@@ -49,112 +48,82 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
4948
const serverConfig = useContext(ServerConfigContext);
5049
const { executeRecaptcha } = useRecaptcha();
5150
const address = useContext(AddressContext);
51+
const codeInputRef = useRef<HTMLInputElement>(null);
5252

53-
const requestCode = async function (e: FormEvent) {
54-
e.preventDefault();
55-
setLoading(true);
56-
setError("");
53+
const validateRecaptcha = useCallback(async (): Promise<boolean> => {
54+
if (!serverConfig.recaptchaSiteKey) {
55+
return true;
56+
}
5757

58-
if (serverConfig.recaptchaSiteKey) {
59-
if (!executeRecaptcha) {
60-
toast({
61-
title: TOAST_TITLE_ERROR,
62-
description:
63-
"reCAPTCHA service not available. Please try again later.",
64-
variant: "destructive",
65-
});
66-
setLoading(false);
67-
return;
68-
}
58+
if (!executeRecaptcha) {
59+
toast({
60+
title: TOAST_TITLE_ERROR,
61+
description:
62+
"reCAPTCHA service not available. Please try again later.",
63+
variant: "destructive",
64+
});
65+
setLoading(false);
66+
return false;
67+
}
6968

70-
const recaptchaToken = await executeRecaptcha("login_code_request");
71-
if (!recaptchaToken) {
72-
toast({
73-
title: TOAST_TITLE_ERROR,
74-
description:
75-
"reCAPTCHA validation failed. Please try again.",
76-
variant: "destructive",
77-
});
78-
setLoading(false);
79-
return;
80-
}
81-
try {
82-
const recaptchaVerificationResponse = await fetch(
83-
"/api/recaptcha",
84-
{
85-
method: "POST",
86-
headers: { "Content-Type": "application/json" },
87-
body: JSON.stringify({ token: recaptchaToken }),
88-
},
89-
);
69+
const recaptchaToken = await executeRecaptcha("login_code_request");
70+
if (!recaptchaToken) {
71+
toast({
72+
title: TOAST_TITLE_ERROR,
73+
description: "reCAPTCHA validation failed. Please try again.",
74+
variant: "destructive",
75+
});
76+
setLoading(false);
77+
return false;
78+
}
79+
try {
80+
const recaptchaVerificationResponse = await fetch(
81+
"/api/recaptcha",
82+
{
83+
method: "POST",
84+
headers: { "Content-Type": "application/json" },
85+
body: JSON.stringify({ token: recaptchaToken }),
86+
},
87+
);
9088

91-
const recaptchaData =
92-
await recaptchaVerificationResponse.json();
89+
const recaptchaData = await recaptchaVerificationResponse.json();
9390

94-
if (
95-
!recaptchaVerificationResponse.ok ||
96-
!recaptchaData.success ||
97-
(recaptchaData.score && recaptchaData.score < 0.5)
98-
) {
99-
toast({
100-
title: TOAST_TITLE_ERROR,
101-
description: `reCAPTCHA verification failed. ${recaptchaData.score ? `Score: ${recaptchaData.score.toFixed(2)}.` : ""} Please try again.`,
102-
variant: "destructive",
103-
});
104-
setLoading(false);
105-
return;
106-
}
107-
} catch (err) {
108-
console.error("Error during reCAPTCHA verification:", err);
91+
if (
92+
!recaptchaVerificationResponse.ok ||
93+
!recaptchaData.success ||
94+
(recaptchaData.score && recaptchaData.score < 0.5)
95+
) {
10996
toast({
11097
title: TOAST_TITLE_ERROR,
111-
description:
112-
"reCAPTCHA verification failed. Please try again.",
98+
description: `reCAPTCHA verification failed. ${recaptchaData.score ? `Score: ${recaptchaData.score.toFixed(2)}.` : ""} Please try again.`,
11399
variant: "destructive",
114100
});
115101
setLoading(false);
116-
return;
117-
}
118-
}
119-
120-
try {
121-
const url = `/api/auth/code/generate?email=${encodeURIComponent(
122-
email,
123-
)}`;
124-
const response = await fetch(url);
125-
const resp = await response.json();
126-
if (response.ok) {
127-
setShowCode(true);
128-
} else {
129-
toast({
130-
title: TOAST_TITLE_ERROR,
131-
description: resp.error || "Failed to request code.",
132-
variant: "destructive",
133-
});
102+
return false;
134103
}
135104
} catch (err) {
136-
console.error("Error during requestCode:", err);
137105
toast({
138106
title: TOAST_TITLE_ERROR,
139-
description: "An unexpected error occurred. Please try again.",
107+
description: "reCAPTCHA verification failed. Please try again.",
140108
variant: "destructive",
141109
});
142-
} finally {
143110
setLoading(false);
111+
return false;
144112
}
145-
};
113+
114+
return true;
115+
}, []);
146116

147117
const signInUser = async function (e: FormEvent) {
148118
e.preventDefault();
149119
try {
150120
setLoading(true);
151-
const response = await signIn("credentials", {
152-
email,
153-
code,
154-
redirect: false,
121+
const { data, error } = await authClient.signIn.emailOtp({
122+
email: email.trim().toLowerCase(),
123+
otp: code,
155124
});
156-
if (response?.error) {
157-
setError(`Can't sign you in at this time`);
125+
if (error) {
126+
setError(`Can't sign you in at this time: ${error.message}`);
158127
} else {
159128
window.location.href =
160129
redirectTo ||
@@ -178,6 +147,38 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
178147
}
179148
};
180149

150+
useEffect(() => {
151+
if (showCode) {
152+
codeInputRef.current?.focus();
153+
}
154+
}, [showCode]);
155+
156+
const requestCode = async function (e: FormEvent) {
157+
e.preventDefault();
158+
setLoading(true);
159+
setError("");
160+
161+
if (!validateRecaptcha()) {
162+
return;
163+
}
164+
165+
try {
166+
const { data, error } =
167+
await authClient.emailOtp.sendVerificationOtp({
168+
email: email.trim().toLowerCase(),
169+
type: "sign-in",
170+
});
171+
172+
if (error) {
173+
setError(error.message as any);
174+
} else {
175+
setShowCode(true);
176+
}
177+
} finally {
178+
setLoading(false);
179+
}
180+
};
181+
181182
return (
182183
<Section theme={theme.theme}>
183184
<div className="flex flex-col gap-4 min-h-[80vh]">
@@ -204,7 +205,7 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
204205
</Text1>
205206
<Form
206207
onSubmit={requestCode}
207-
className="flex flex-col gap-4"
208+
className="flex flex-col gap-4 w-full lg:w-[360px] mx-auto"
208209
>
209210
<Input
210211
type="email"
@@ -216,7 +217,12 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
216217
}
217218
theme={theme.theme}
218219
/>
219-
220+
<Button
221+
theme={theme.theme}
222+
disabled={loading}
223+
>
224+
{loading ? LOADING : BTN_LOGIN_GET_CODE}
225+
</Button>
220226
<Caption
221227
theme={theme.theme}
222228
className="text-center"
@@ -228,35 +234,17 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
228234
</span>
229235
</Link>
230236
</Caption>
231-
<div className="flex justify-center">
232-
{/* <FormSubmit
233-
text={
234-
loading
235-
? LOADING
236-
: BTN_LOGIN_GET_CODE
237-
}
238-
disabled={loading}
239-
/> */}
240-
<Button
241-
theme={theme.theme}
242-
disabled={loading}
243-
>
244-
{loading
245-
? LOADING
246-
: BTN_LOGIN_GET_CODE}
247-
</Button>
248-
</div>
249237
</Form>
250238
</div>
251239
)}
252240
{showCode && (
253241
<div>
254242
<Text1 theme={theme.theme} className="mb-4">
255-
{BTN_LOGIN_CODE_INTIMATION}{" "}
243+
{LOGIN_CODE_INTIMATION_MESSAGE}{" "}
256244
<strong>{email}</strong>
257245
</Text1>
258246
<Form
259-
className="flex flex-col gap-4 mb-4"
247+
className="flex flex-col gap-4 mb-4 w-full lg:w-[360px] mx-auto"
260248
onSubmit={signInUser}
261249
>
262250
<Input
@@ -268,34 +256,37 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) {
268256
setCode(e.target.value)
269257
}
270258
theme={theme.theme}
259+
ref={codeInputRef}
271260
/>
272-
<div className="flex justify-center">
273-
<Button
274-
theme={theme.theme}
275-
disabled={loading}
276-
>
277-
{loading ? LOADING : BTN_LOGIN}
278-
</Button>
279-
</div>
261+
<Button
262+
theme={theme.theme}
263+
disabled={loading}
264+
>
265+
{loading ? LOADING : BTN_LOGIN}
266+
</Button>
267+
{/* </div> */}
280268
</Form>
281269
<div className="flex justify-center items-center gap-1 text-sm">
282-
<Text2
270+
<Caption
283271
theme={theme.theme}
284-
className="text-slate-500"
272+
className="text-center flex items-center gap-1"
285273
>
286274
{LOGIN_NO_CODE}
287-
</Text2>
288-
<button
289-
onClick={requestCode}
290-
className="underline"
291-
disabled={loading}
292-
>
293-
<PageLink theme={theme.theme}>
294-
{loading
295-
? LOADING
296-
: BTN_LOGIN_NO_CODE}
297-
</PageLink>
298-
</button>
275+
<button
276+
onClick={requestCode}
277+
className="underline"
278+
disabled={loading}
279+
>
280+
<PageLink
281+
theme={theme.theme}
282+
className="text-xs"
283+
>
284+
{loading
285+
? LOADING
286+
: BTN_LOGIN_NO_CODE}
287+
</PageLink>
288+
</button>
289+
</Caption>
299290
</div>
300291
</div>
301292
)}

apps/web/app/(with-contexts)/(with-layout)/login/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { auth } from "@/auth";
22
import { redirect } from "next/navigation";
33
import LoginForm from "./login-form";
4+
import { headers } from "next/headers";
45

56
export default async function LoginPage({
67
searchParams,
78
}: {
89
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
910
}) {
10-
const session = await auth();
11+
const headersList = await headers();
12+
const session = await auth.api.getSession({
13+
headers: headersList,
14+
});
15+
1116
const redirectTo = (await searchParams).redirect as string | undefined;
1217

1318
if (session) {

0 commit comments

Comments
 (0)