@@ -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" ;
1817import { FormEvent } from "react" ;
19- import { signIn } from "next-auth/react" ;
2018import { Form , useToast } from "@courselit/components-library" ;
2119import {
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";
3735import { Profile } from "@courselit/common-models" ;
3836import { getUserProfile } from "../../helpers" ;
3937import { ADMIN_PERMISSIONS } from "@ui-config/constants" ;
38+ import { authClient } from "@/lib/auth-client" ;
4039
4140export 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 ) }
0 commit comments