@@ -22,7 +22,10 @@ export function App() {
2222
2323 const [ showAuthForm , setShowAuthForm ] = useState ( false ) ;
2424 const [ authFormData , setAuthFormData ] = useState ( { username : '' , token : '' } ) ;
25+ const [ authFieldErrors , setAuthFieldErrors ] = useState ( { username : '' , token : '' , form : '' } ) ;
2526 const [ feedFormData , setFeedFormData ] = useState ( { url : '' , strategy : 'ssrf_filter' } ) ;
27+ const [ feedFieldErrors , setFeedFieldErrors ] = useState ( { url : '' , form : '' } ) ;
28+ const [ demoError , setDemoError ] = useState ( '' ) ;
2629
2730 useEffect ( ( ) => {
2831 if ( isAuthenticated ) {
@@ -39,21 +42,47 @@ export function App() {
3942 const handleAuthSubmit = async ( event ?: Event ) => {
4043 event ?. preventDefault ( ) ;
4144
42- if ( ! authFormData . username || ! authFormData . token ) return ;
45+ setAuthFieldErrors ( { username : '' , token : '' , form : '' } ) ;
46+
47+ if ( ! authFormData . username . trim ( ) ) {
48+ setAuthFieldErrors ( { username : 'Username is required.' , token : '' , form : '' } ) ;
49+ return ;
50+ }
51+ if ( ! authFormData . token . trim ( ) ) {
52+ setAuthFieldErrors ( { username : '' , token : 'Token is required.' , form : '' } ) ;
53+ return ;
54+ }
4355
4456 try {
4557 await login ( authFormData . username , authFormData . token ) ;
46- } catch ( error ) { }
58+ } catch ( error ) {
59+ setAuthFieldErrors ( {
60+ username : '' ,
61+ token : '' ,
62+ form : error instanceof Error ? error . message : 'Unable to authenticate. Please try again.' ,
63+ } ) ;
64+ }
4765 } ;
4866
4967 const handleFeedSubmit = async ( event : Event ) => {
5068 event . preventDefault ( ) ;
5169
52- if ( ! feedFormData . url ) return ;
70+ setFeedFieldErrors ( { url : '' , form : '' } ) ;
71+ if ( ! feedFormData . url . trim ( ) ) {
72+ setFeedFieldErrors ( { url : 'Website URL is required.' , form : '' } ) ;
73+ return ;
74+ }
5375
5476 try {
5577 await convertFeed ( feedFormData . url , feedFormData . strategy , token || '' ) ;
56- } catch ( error ) { }
78+ } catch ( error ) {
79+ const message = error instanceof Error ? error . message : 'Unable to start conversion.' ;
80+ if ( message . toLowerCase ( ) . includes ( 'url' ) ) {
81+ setFeedFieldErrors ( { url : message , form : '' } ) ;
82+ } else {
83+ setFeedFieldErrors ( { url : '' , form : message } ) ;
84+ }
85+ }
5786 } ;
5887
5988 const handleShowAuth = ( ) => {
@@ -67,10 +96,13 @@ export function App() {
6796 } ;
6897
6998 const handleDemoConversion = async ( url : string ) => {
99+ setDemoError ( '' ) ;
70100 try {
71101 const demoStrategy = strategies . length > 0 ? strategies [ 0 ] . id : 'ssrf_filter' ;
72102 await convertFeed ( url , demoStrategy , 'self-host-for-full-access' ) ;
73- } catch ( error ) { }
103+ } catch ( error ) {
104+ setDemoError ( error instanceof Error ? error . message : 'Demo conversion failed. Please try again.' ) ;
105+ }
74106 } ;
75107
76108 const showResultExperience = Boolean ( result ) ;
@@ -126,6 +158,11 @@ export function App() {
126158 < p class = "surface-header__hint" >
127159 Launch a demo conversion to see the results instantly. No sign-in required.
128160 </ p >
161+ { demoError && (
162+ < div class = "notice notice--error" role = "alert" >
163+ < p > { demoError } </ p >
164+ </ div >
165+ ) }
129166 </ header >
130167 < DemoButtons onConvert = { handleDemoConversion } />
131168 < QuickLogin onShowLogin = { handleShowAuth } />
@@ -136,10 +173,24 @@ export function App() {
136173 < section class = "surface" >
137174 < header class = "surface-header" >
138175 < h3 class = "surface-header__title" > 🔐 Sign in</ h3 >
139- < p class = "surface-header__hint" > Use your html2rss credentials to convert any website.</ p >
176+ < p class = "surface-header__hint" >
177+ Use your html2rss credentials to convert any website. Tokens are stored for this browser session.
178+ </ p >
179+ < p class = "surface-header__hint" >
180+ Need a token? Ask your html2rss-web admin or see the{ ' ' }
181+ < a href = "https://html2rss.github.io/" target = "_blank" rel = "noopener noreferrer" >
182+ official docs
183+ </ a >
184+ .
185+ </ p >
140186 </ header >
141187
142188 < form id = "auth-section" class = "form" onSubmit = { handleAuthSubmit } >
189+ { authFieldErrors . form && (
190+ < div class = "notice notice--error" role = "alert" >
191+ < p > { authFieldErrors . form } </ p >
192+ </ div >
193+ ) }
143194 < div class = "field" >
144195 < label for = "username" class = "label" data-required >
145196 Username
@@ -157,7 +208,9 @@ export function App() {
157208 setAuthFormData ( { ...authFormData , username : ( e . target as HTMLInputElement ) . value } )
158209 }
159210 />
160- < div class = "field-error" id = "username-error" > </ div >
211+ < div class = "field-error" id = "username-error" >
212+ { authFieldErrors . username }
213+ </ div >
161214 </ div >
162215
163216 < div class = "field" >
@@ -177,7 +230,9 @@ export function App() {
177230 setAuthFormData ( { ...authFormData , token : ( e . target as HTMLInputElement ) . value } )
178231 }
179232 />
180- < div class = "field-error" id = "token-error" > </ div >
233+ < div class = "field-error" id = "token-error" >
234+ { authFieldErrors . token }
235+ </ div >
181236 </ div >
182237
183238 < div class = "form-actions" >
@@ -231,7 +286,9 @@ export function App() {
231286 { isConverting ? 'Converting...' : 'Convert' }
232287 </ button >
233288 </ div >
234- < div class = "field-error" id = "url-error" > </ div >
289+ < div class = "field-error" id = "url-error" >
290+ { feedFieldErrors . url }
291+ </ div >
235292 </ div >
236293
237294 < fieldset class = "fieldset" >
@@ -280,13 +337,19 @@ export function App() {
280337 ) }
281338 </ fieldset >
282339 </ form >
340+ { feedFieldErrors . form && (
341+ < div class = "notice notice--error" role = "alert" >
342+ < p > { feedFieldErrors . form } </ p >
343+ </ div >
344+ ) }
283345 </ section >
284346 ) }
285347
286348 { ! showResultExperience && error && (
287349 < section class = "notice notice--error" >
288350 < h3 > Conversion error</ h3 >
289351 < p > { error } </ p >
352+ < p > Double-check your token if this keeps happening.</ p >
290353 < button type = "button" class = "btn btn--outline" onClick = { clearResult } >
291354 Close
292355 </ button >
0 commit comments