@@ -2,24 +2,34 @@ import React, { useState } from 'react';
22import  type  {  FormEvent  }  from  'react' ; 
33
44interface  PromptModalProps  { 
5-   onSubmit : ( value : string ,  domain : string )  =>  void ; 
5+   onSubmit : ( value : string ,  domain : string ,   setError :  ( error :  string   |   null )   =>   void )  =>  void ; 
66  initialValue : string ; 
77  initialDomain : string ; 
8+   responseData : any ; 
9+   clientError : string  |  null ; 
10+   serverError : string  |  null ; 
811} 
912
10- function  PromptModal ( {  onSubmit,  initialValue,  initialDomain } : PromptModalProps )  { 
13+ function  PromptModal ( {  onSubmit,  initialValue,  initialDomain,  responseData ,  clientError ,  serverError  } : PromptModalProps )  { 
1114  const  [ value ,  setValue ]  =  useState ( initialValue ) ; 
1215  const  [ domain ,  setDomain ]  =  useState ( initialDomain ) ; 
16+   const  [ error ,  setError ]  =  useState < string  |  null > ( null ) ; 
17+   const  [ isSubmitting ,  setIsSubmitting ]  =  useState ( false ) ; 
1318
1419  const  handleSubmit  =  ( e : FormEvent < HTMLFormElement > )  =>  { 
1520    e . preventDefault ( ) ; 
16-     onSubmit ( value ,  domain ) ; 
21+     setError ( null ) ; 
22+     setIsSubmitting ( true ) ; 
23+     onSubmit ( value ,  domain ,  ( errorMessage )  =>  { 
24+       setError ( errorMessage ) ; 
25+       setIsSubmitting ( false ) ; 
26+     } ) ; 
1727  } ; 
1828
1929  return  ( 
20-     < div  className = "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center" > 
21-       < div  className = "bg-white dark:bg-[#343541] rounded-lg p-6  w-full max-w-md " > 
22-         < h2  className = "text-xl  font-semibold mb-4  text-black dark:text-white" > Enter Authentication Details</ h2 > 
30+     < div  className = "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 " > 
31+       < div  className = "bg-white dark:bg-[#343541] rounded-lg p-8  w-full max-w-2xl max-h-[90vh] overflow-y-auto " > 
32+         < h2  className = "text-2xl  font-semibold mb-6  text-black dark:text-white text-center " > Enter Authentication Details</ h2 > 
2333        < form  onSubmit = { handleSubmit }  className = "space-y-4" > 
2434          < div > 
2535            < label  htmlFor = "domain"  className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" > 
@@ -44,18 +54,52 @@ function PromptModal({ onSubmit, initialValue, initialDomain }: PromptModalProps
4454              type = "text" 
4555              value = { value } 
4656              onChange = { ( e )  =>  setValue ( e . target . value ) } 
47-               className = "w-full p-2 border border-gray-300 dark:border-gray-600 rounded mb-4  bg-white dark:bg-[#40414F] text-black dark:text-white" 
57+               className = "w-full p-2 border border-gray-300 dark:border-gray-600 rounded mb-2  bg-white dark:bg-[#40414F] text-black dark:text-white" 
4858              placeholder = "Enter consent prompt key..." 
4959              autoFocus 
5060              autoComplete = "off" 
5161            /> 
62+             { error  &&  ( 
63+               < div  className = "mb-4 p-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded text-sm" > 
64+                 < p  className = "text-red-700 dark:text-red-300" > { error } </ p > 
65+               </ div > 
66+             ) } 
5267          </ div > 
68+ 
69+           { /* Status Messages */ } 
70+           { clientError  &&  ( 
71+             < div  className = "mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg" > 
72+               < p  className = "font-semibold text-red-800 dark:text-red-400 mb-2" > Client Error:</ p > 
73+               < p  className = "text-red-700 dark:text-red-300" > { clientError } </ p > 
74+             </ div > 
75+           ) } 
76+ 
77+           { serverError  &&  ( 
78+             < div  className = "mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg" > 
79+               < p  className = "font-semibold text-red-800 dark:text-red-400 mb-2" > Server Error:</ p > 
80+               < p  className = "text-red-700 dark:text-red-300" > { serverError } </ p > 
81+             </ div > 
82+           ) } 
83+ 
84+ 
85+ 
86+           { responseData  &&  ( serverError  ||  clientError )  &&  ( 
87+             < div  className = "mb-4 p-4 bg-gray-800 text-gray-100 rounded-lg font-mono text-sm overflow-x-auto" > 
88+               < p  className = "font-semibold mb-2" > Response:</ p > 
89+               < pre  className = "whitespace-pre-wrap" > { JSON . stringify ( responseData ,  null ,  2 ) } </ pre > 
90+             </ div > 
91+           ) } 
92+ 
5393          < div  className = "flex justify-end gap-2" > 
5494            < button 
5595              type = "submit" 
56-               className = "px-4 py-2 bg-[#76b900] text-white rounded hover:bg-[#5a9100] focus:outline-none transition-colors duration-200" 
96+               disabled = { isSubmitting } 
97+               className = "px-4 py-2 bg-[#76b900] text-white rounded hover:bg-[#5a9100] focus:outline-none transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2" 
5798            > 
58-               Send Request
99+               { isSubmitting  &&  ( 
100+                 < div  className = "animate-spin rounded-full h-4 w-4 border-b-2 border-white" > </ div > 
101+               ) } 
102+               { isSubmitting  ? 'Processing...'  : 'Send Request' } 
59103            </ button > 
60104          </ div > 
61105        </ form > 
@@ -70,13 +114,37 @@ export default function AIQAuthPage() {
70114  const  [ error ,  setError ]  =  useState < string  |  null > ( null ) ; 
71115  const  [ responseError ,  setResponseError ]  =  useState < string  |  null > ( null ) ; 
72116  const  [ responseData ,  setResponseData ]  =  useState < any > ( null ) ; 
73-   const  [ modalKey ,  setModalKey ]  =  useState ( 0 ) ; 
74117
75-   const  handleSubmit  =  async  ( key : string ,  domain : string )  =>  { 
118+   const  [ authSuccess ,  setAuthSuccess ]  =  useState ( false ) ; 
119+   const  [ currentPopup ,  setCurrentPopup ]  =  useState < Window  |  null > ( null ) ; 
120+   const  [ authProviderName ,  setAuthProviderName ]  =  useState < string  |  null > ( null ) ; 
121+ 
122+   React . useEffect ( ( )  =>  { 
123+     const  handleMessage  =  ( event : MessageEvent )  =>  { 
124+       if  ( currentPopup  &&  ! currentPopup . closed )  { 
125+         try  { 
126+           currentPopup . document . title  =  `Authentication was successfully granted for authentication provider ${ authProviderName }  ` ; 
127+         }  catch  ( e )  { 
128+           console . log ( 'Could not update popup title due to cross-origin restrictions' ) ; 
129+         } 
130+       } 
131+       
132+       setShowPrompt ( false ) ; 
133+       setAuthSuccess ( true ) ; 
134+       setIsProcessing ( false ) ; 
135+     } ; 
136+ 
137+     window . addEventListener ( 'message' ,  handleMessage ) ; 
138+     return  ( )  =>  window . removeEventListener ( 'message' ,  handleMessage ) ; 
139+   } ,  [ currentPopup ,  authProviderName ] ) ; 
140+ 
141+   const  handleSubmit  =  async  ( key : string ,  domain : string ,  setModalError : ( error : string  |  null )  =>  void )  =>  { 
76142    setIsProcessing ( true ) ; 
77143    setError ( null ) ; 
78144    setResponseError ( null ) ; 
79145    setResponseData ( null ) ; 
146+     setAuthSuccess ( false ) ; 
147+     setAuthProviderName ( null ) ; 
80148
81149    try  { 
82150      const  response  =  await  fetch ( `${ domain }  /auth/prompt-uri` ,  { 
@@ -91,101 +159,97 @@ export default function AIQAuthPage() {
91159
92160      if  ( contentType  &&  contentType . includes ( 'application/json' ) )  { 
93161        const  data  =  await  response . json ( ) ; 
94-         setResponseData ( data ) ; 
95162
96163        if  ( ! response . ok )  { 
97-           const  errorMessage  =  data . message  ||  data . error  ||  `Error: ${ response . status }   - ${ response . statusText }  ` ; 
98-           setResponseError ( errorMessage ) ; 
164+           setResponseData ( data ) ; 
165+           const  errorMessage  =  data . detail  ||  data . message  ||  data . error  ||  `Error: ${ response . status }   - ${ response . statusText }  ` ; 
166+           setModalError ( errorMessage ) ; 
167+           setIsProcessing ( false ) ; 
99168          return ; 
100169        } 
101170
171+         setResponseData ( data ) ; 
172+ 
102173        if  ( data . redirect_url )  { 
103-           window . location . href  =  data . redirect_url ; 
174+           setAuthProviderName ( data . auth_provider_name ) ; 
175+           const  popup  =  window . open ( 
176+             data . redirect_url , 
177+             'auth-popup' , 
178+             'width=600,height=700,scrollbars=yes,resizable=yes' 
179+           ) ; 
180+           setCurrentPopup ( popup ) ; 
181+           setModalError ( null ) ; 
104182          return ; 
105183        } 
106184      } 
107185
108186      if  ( ! response . ok )  { 
109187        const  errorMessage  =  `Error: ${ response . status }   - ${ response . statusText }  ` ; 
110-         setResponseError ( errorMessage ) ; 
188+         setModalError ( errorMessage ) ; 
189+         setIsProcessing ( false ) ; 
111190        return ; 
112191      } 
113192
114-       setShowPrompt ( false ) ; 
115-       
116193    }  catch  ( error )  { 
117194      const  errorMessage  =  error  instanceof  Error  ? error . message  : 'An unexpected error occurred' ; 
118-       setError ( errorMessage ) ; 
195+       setModalError ( errorMessage ) ; 
119196    }  finally  { 
120197      setIsProcessing ( false ) ; 
121198    } 
122199  } ; 
123200
124201  return  ( 
125202    < div  className = "min-h-screen bg-gray-50 dark:bg-[#343541]" > 
126-       { showPrompt  &&  ! isProcessing  &&  ( 
127-         < PromptModal 
128-           key = { modalKey } 
129-           onSubmit = { handleSubmit } 
130-           initialValue = "" 
131-           initialDomain = "http://localhost:8000" 
132-         /> 
133-       ) } 
203+       { /* Green Banner */ } 
204+       < div  className = "bg-[#76b900] text-white py-4 px-6 text-center" > 
205+         < h1  className = "text-2xl font-bold" > NeMo-Agent-Toolkit</ h1 > 
206+       </ div > 
134207
135-       { isProcessing  &&  ( 
136-         < div  className = "fixed inset-0 bg-white dark:bg-[#343541] bg-opacity-75 dark:bg-opacity-75 flex items-center justify-center" > 
137-           < div  className = "text-center" > 
138-             < div  className = "animate-spin rounded-full h-12 w-12 border-b-2 border-[#76b900] mx-auto mb-4" > </ div > 
139-             < p  className = "text-gray-600 dark:text-gray-300" > Processing Request...</ p > 
140-           </ div > 
141-         </ div > 
142-       ) } 
143- 
144-       < div  className = "container mx-auto p-6" > 
145-         < h1  className = "text-2xl font-bold mb-4 text-black dark:text-white" > Agent IQ Authentication</ h1 > 
146-         
147-         { error  &&  ( 
148-           < div  className = "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-4" > 
149-             < p  className = "font-semibold text-red-800 dark:text-red-400" > Client Error:</ p > 
150-             < p  className = "text-red-700 dark:text-red-300" > { error } </ p > 
151-           </ div > 
152-         ) } 
153- 
154-         { responseError  &&  ( 
155-           < div  className = "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-4" > 
156-             < p  className = "font-semibold text-red-800 dark:text-red-400" > Server Error:</ p > 
157-             < p  className = "text-red-700 dark:text-red-300" > { responseError } </ p > 
158-           </ div > 
159-         ) } 
160- 
161-         { ! showPrompt  &&  ! isProcessing  &&  ! error  &&  ! responseError  &&  ( 
162-           < div  className = "bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4 mb-4" > 
163-             < p  className = "text-green-700 dark:text-green-300" > You are being redirected to your provider's consent screen to authorize access.</ p > 
164-           </ div > 
208+       < div  className = "flex items-center justify-center min-h-[calc(100vh-80px)]" > 
209+         { showPrompt  &&  ! authSuccess  &&  ( 
210+           < PromptModal 
211+             onSubmit = { handleSubmit } 
212+             initialValue = "" 
213+             initialDomain = "http://localhost:8000" 
214+             responseData = { responseData } 
215+             clientError = { error } 
216+             serverError = { responseError } 
217+           /> 
165218        ) } 
219+       </ div > 
166220
167-         { responseData  &&  ( responseError  ||  error )  &&  ( 
168-           < div  className = "bg-gray-800 text-gray-100 rounded-lg p-4 mb-4 font-mono text-sm overflow-x-auto" > 
169-             < p  className = "font-semibold mb-2" > Response:</ p > 
170-             < pre  className = "whitespace-pre-wrap" > { JSON . stringify ( responseData ,  null ,  2 ) } </ pre > 
221+       { authSuccess  &&  ( 
222+         < div  className = "fixed inset-0 z-[9999] bg-gray-50 dark:bg-[#343541] flex items-center justify-center" > 
223+           < div  className = "w-full max-w-md mx-auto p-6" > 
224+             < div  className = "bg-white dark:bg-[#343541] rounded-lg shadow-lg p-8 text-center border border-gray-200 dark:border-gray-600" > 
225+               < div  className = "mb-6" > 
226+                 < div  className = "w-16 h-16 bg-green-100 dark:bg-green-900/20 rounded-full flex items-center justify-center mx-auto mb-4" > 
227+                   < svg  className = "w-8 h-8 text-green-600 dark:text-green-400"  fill = "none"  stroke = "currentColor"  viewBox = "0 0 24 24" > 
228+                     < path  strokeLinecap = "round"  strokeLinejoin = "round"  strokeWidth = { 2 }  d = "M5 13l4 4L19 7"  /> 
229+                   </ svg > 
230+                 </ div > 
231+                 < h1  className = "text-2xl font-bold text-gray-900 dark:text-white mb-2" > 
232+                   Authentication was successfully granted for authentication provider { authProviderName } 
233+                 </ h1 > 
234+               </ div > 
235+               
236+               < button 
237+                 onClick = { ( )  =>  { 
238+                   setShowPrompt ( true ) ; 
239+                   setAuthSuccess ( false ) ; 
240+                   setError ( null ) ; 
241+                   setResponseError ( null ) ; 
242+                   setResponseData ( null ) ; 
243+                   setAuthProviderName ( null ) ; 
244+                 } } 
245+                 className = "w-full mt-3 px-4 py-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors duration-200" 
246+               > 
247+                 Authenticate Again
248+               </ button > 
249+             </ div > 
171250          </ div > 
172-         ) } 
173- 
174-         { ! showPrompt  &&  ! isProcessing  &&  ( 
175-           < button 
176-             onClick = { ( )  =>  { 
177-               setShowPrompt ( true ) ; 
178-               setError ( null ) ; 
179-               setResponseError ( null ) ; 
180-               setResponseData ( null ) ; 
181-               setModalKey ( prev  =>  prev  +  1 ) ; 
182-             } } 
183-             className = "px-4 py-2 bg-[#76b900] text-white rounded hover:bg-[#5a9100] focus:outline-none transition-colors duration-200" 
184-           > 
185-             Open Authentication Prompt
186-           </ button > 
187-         ) } 
188-       </ div > 
251+         </ div > 
252+       ) } 
189253    </ div > 
190254  ) ; 
191255}  
0 commit comments