@@ -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