1+ "use client"
2+
13import { cn } from "@/lib/utils"
24import { Button } from "@/components/ui/button"
35import { Card , CardContent } from "@/components/ui/card"
46import { Field , FieldGroup , FieldLabel } from "@/components/ui/field"
57import { Input } from "@/components/ui/input"
8+ import { toast } from "sonner"
69import Image from "next/image"
10+ import { useState , useRef } from "react"
711
812interface LoginFormProps extends React . ComponentProps < 'div' > {
913 clientId : string
@@ -16,12 +20,85 @@ interface LoginFormProps extends React.ComponentProps<'div'> {
1620export function LoginForm ( { className, clientId, redirectUri, state, error, errorDescription, ...props } : LoginFormProps ) {
1721 const basePath = process . env . NEXT_PUBLIC_BASE_PATH || '' ;
1822 const formAction = `${ basePath } /api/oauth/authorize` ;
23+ const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
24+ const timeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
25+ const toastId = useRef < string | number | null > ( null ) ;
26+
27+ const handleSubmit = async ( e : React . FormEvent < HTMLFormElement > ) => {
28+ e . preventDefault ( ) ;
29+
30+ if ( isSubmitting ) return ;
31+
32+ setIsSubmitting ( true ) ;
33+ timeoutRef . current = setTimeout ( ( ) => {
34+ toastId . current = toast . loading ( "Check your phone for an MFA prompt!" , {
35+ duration : Infinity ,
36+ } ) ;
37+ } , 2000 ) ;
38+
39+ try {
40+ const formData = new FormData ( e . currentTarget ) ;
41+
42+ const response = await fetch ( formAction , {
43+ method : 'POST' ,
44+ body : formData
45+ } ) ;
46+
47+ if ( timeoutRef . current ) {
48+ clearTimeout ( timeoutRef . current ) ;
49+ timeoutRef . current = null ;
50+ }
51+
52+ // Dismiss the MFA toast if it was shown
53+ if ( toastId . current ) {
54+ toast . dismiss ( toastId . current ) ;
55+ toastId . current = null ;
56+ }
57+
58+ if ( response . redirected ) {
59+ // Follow the redirect
60+ window . location . href = response . url ;
61+ } else if ( ! response . ok ) {
62+ // Handle error response
63+ const responseText = await response . text ( ) ;
64+ const errorMatch = responseText . match ( / e r r o r = ( [ ^ & ] + ) / ) ;
65+ const descMatch = responseText . match ( / e r r o r _ d e s c r i p t i o n = ( [ ^ & ] + ) / ) ;
66+
67+ if ( errorMatch ) {
68+ toast . error ( "Authentication failed" , {
69+ description : descMatch ? decodeURIComponent ( descMatch [ 1 ] ) : "Please check your credentials"
70+ } ) ;
71+ } else {
72+ toast . error ( "Authentication failed" , {
73+ description : "Please check your credentials and try again"
74+ } ) ;
75+ }
76+ }
77+ } catch ( err ) {
78+ // Clear timeout on error
79+ if ( timeoutRef . current ) {
80+ clearTimeout ( timeoutRef . current ) ;
81+ timeoutRef . current = null ;
82+ }
83+
84+ if ( toastId . current ) {
85+ toast . dismiss ( toastId . current ) ;
86+ toastId . current = null ;
87+ }
88+
89+ toast . error ( "Connection failed" , {
90+ description : "Unable to connect to the authentication server"
91+ } ) ;
92+ } finally {
93+ setIsSubmitting ( false ) ;
94+ }
95+ } ;
1996
2097 return (
2198 < div className = { cn ( "flex flex-col gap-6" , className ) } { ...props } >
2299 < Card className = "overflow-hidden p-0" >
23100 < CardContent className = "grid p-0 md:grid-cols-2" >
24- < form className = "p-6 md:p-8" method = "post" action = { formAction } >
101+ < form className = "p-6 md:p-8" onSubmit = { handleSubmit } >
25102 < FieldGroup >
26103 < div className = "flex flex-col items-center gap-2 text-center" >
27104 < h1 className = "text-2xl font-bold" > Welcome back</ h1 >
@@ -53,7 +130,9 @@ export function LoginForm({ className, clientId, redirectUri, state, error, erro
53130 { redirectUri && ( < input type = "hidden" name = "redirect_uri" value = { redirectUri } /> ) }
54131 { state && ( < input type = "hidden" name = "state" value = { state } /> ) }
55132 < Field >
56- < Button type = "submit" > Login</ Button >
133+ < Button type = "submit" disabled = { isSubmitting } >
134+ { isSubmitting ? "Authenticating..." : "Login" }
135+ </ Button >
57136 </ Field >
58137 </ FieldGroup >
59138 </ form >
0 commit comments