@@ -7,6 +7,7 @@ import { useFeedConversion } from '../hooks/useFeedConversion';
77import { useStrategies } from '../hooks/useStrategies' ;
88
99const EMPTY_FEED_ERRORS = { url : '' , form : '' } ;
10+ const DEFAULT_FEED_CREATION = { enabled : true , access_token_required : true } ;
1011
1112function BrandLockup ( ) {
1213 return (
@@ -42,6 +43,7 @@ export function App() {
4243 const [ showTokenPrompt , setShowTokenPrompt ] = useState ( false ) ;
4344 const [ tokenDraft , setTokenDraft ] = useState ( '' ) ;
4445 const [ tokenError , setTokenError ] = useState ( '' ) ;
46+ const [ focusCreateComposerKey , setFocusCreateComposerKey ] = useState ( 0 ) ;
4547
4648 useEffect ( ( ) => {
4749 if ( typeof window === 'undefined' ) return ;
@@ -61,7 +63,7 @@ export function App() {
6163 if ( ! hasCurrentStrategy ) setFeedFormData ( ( prev ) => ( { ...prev , strategy : nextStrategy } ) ) ;
6264 } , [ strategies , feedFormData . strategy ] ) ;
6365
64- const feedCreation = metadata ?. instance . feed_creation ?? { enabled : true , access_token_required : true } ;
66+ const feedCreation = metadata ?. instance . feed_creation ?? DEFAULT_FEED_CREATION ;
6567
6668 const setFeedField = ( key : 'url' | 'strategy' , value : string ) => {
6769 setFeedFormData ( ( prev ) => ( { ...prev , [ key ] : value } ) ) ;
@@ -73,60 +75,68 @@ export function App() {
7375 } ;
7476
7577 const strategyHint = ( strategy : Strategy ) => {
76- if ( strategy . id === 'ssrf_filter' ) return 'Direct fetch for standard documents and static pages.' ;
77- if ( strategy . id === 'browserless' )
78- return 'Rendered browser pass for JavaScript-heavy pages, SPAs, and delayed content.' ;
78+ if ( strategy . id === 'ssrf_filter' ) return 'Start here for most pages.' ;
79+ if ( strategy . id === 'browserless' ) return 'Use this if the page loads content with JavaScript.' ;
7980 return strategy . name ;
8081 } ;
8182
82- const handleFeedSubmit = async ( event : Event ) => {
83- event . preventDefault ( ) ;
84- setFeedFieldErrors ( EMPTY_FEED_ERRORS ) ;
85-
83+ const attemptFeedCreation = async ( accessToken : string ) => {
8684 if ( ! feedFormData . url . trim ( ) ) {
8785 setFeedFieldErrors ( { ...EMPTY_FEED_ERRORS , url : 'Source URL is required.' } ) ;
88- return ;
86+ return false ;
8987 }
9088
9189 if ( ! feedCreation . enabled ) {
9290 setFeedFieldErrors ( {
9391 ...EMPTY_FEED_ERRORS ,
9492 form : 'Custom feed generation is disabled for this instance.' ,
9593 } ) ;
96- return ;
94+ return false ;
9795 }
9896
99- if ( feedCreation . access_token_required && ! hasToken ) {
97+ if ( feedCreation . access_token_required && ! accessToken ) {
10098 setShowTokenPrompt ( true ) ;
101- setTokenError ( 'Add an access token to create a custom feed .' ) ;
102- return ;
99+ setTokenError ( 'Paste an access token to keep going .' ) ;
100+ return false ;
103101 }
104102
105103 try {
106- await convertFeed ( feedFormData . url , feedFormData . strategy , token ?? '' ) ;
104+ await convertFeed ( feedFormData . url , feedFormData . strategy , accessToken ) ;
105+ setShowTokenPrompt ( false ) ;
106+ setTokenError ( '' ) ;
107+ return true ;
107108 } catch ( submitError ) {
108109 const message = submitError instanceof Error ? submitError . message : 'Unable to start feed generation.' ;
109110 if ( message . toLowerCase ( ) . includes ( 'url' ) ) {
110111 setFeedFieldErrors ( { ...EMPTY_FEED_ERRORS , url : message } ) ;
111112 } else {
112113 setFeedFieldErrors ( { ...EMPTY_FEED_ERRORS , form : message } ) ;
113114 }
115+ return false ;
114116 }
115117 } ;
116118
119+ const handleFeedSubmit = async ( event : Event ) => {
120+ event . preventDefault ( ) ;
121+ setFeedFieldErrors ( EMPTY_FEED_ERRORS ) ;
122+ await attemptFeedCreation ( token ?? '' ) ;
123+ } ;
124+
117125 const handleSaveToken = async ( ) => {
118126 try {
119- await saveToken ( tokenDraft ) ;
127+ const normalizedToken = tokenDraft . trim ( ) ;
128+ await saveToken ( normalizedToken ) ;
120129 setTokenError ( '' ) ;
121- setShowTokenPrompt ( false ) ;
122- setTokenDraft ( '' ) ;
130+ const created = await attemptFeedCreation ( normalizedToken ) ;
131+ if ( created ) setTokenDraft ( '' ) ;
123132 } catch ( error ) {
124133 setTokenError ( error instanceof Error ? error . message : 'Unable to save access token.' ) ;
125134 }
126135 } ;
127136
128137 const handleCreateAnother = ( ) => {
129138 clearResult ( ) ;
139+ setFocusCreateComposerKey ( ( current ) => current + 1 ) ;
130140 } ;
131141
132142 if ( metadataLoading || tokenLoading ) {
@@ -152,7 +162,7 @@ export function App() {
152162 </ div >
153163 { ! result && (
154164 < div class = "workspace-frame__titleblock" >
155- < h1 > Turn web pages into stable feeds .</ h1 >
165+ < h1 > Create a feed URL .</ h1 >
156166 </ div >
157167 ) }
158168 </ header >
@@ -167,43 +177,44 @@ export function App() {
167177 { result ? (
168178 < ResultDisplay result = { result } onCreateAnother = { handleCreateAnother } />
169179 ) : (
170- < CreateFeedPanel
171- feedFormData = { feedFormData }
172- feedFieldErrors = { feedFieldErrors }
173- conversionError = { conversionError }
174- isConverting = { isConverting }
175- strategies = { strategies }
176- strategiesLoading = { strategiesLoading }
177- strategiesError = { strategiesError }
178- feedCreationEnabled = { feedCreation . enabled }
179- accessTokenRequired = { feedCreation . access_token_required }
180- hasAccessToken = { hasToken }
181- tokenDraft = { tokenDraft }
182- tokenError = { tokenError }
183- showTokenPrompt = { showTokenPrompt }
184- onFeedSubmit = { handleFeedSubmit }
185- onFeedFieldChange = { setFeedField }
186- onTokenDraftChange = { ( value ) => {
187- setTokenDraft ( value ) ;
188- setTokenError ( '' ) ;
189- } }
190- onSaveToken = { handleSaveToken }
191- onCancelTokenPrompt = { ( ) => {
192- setShowTokenPrompt ( false ) ;
193- setTokenError ( '' ) ;
194- } }
195- strategyHint = { strategyHint }
196- />
197- ) }
198-
199- { ! result && (
200- < InstanceInfo
201- hasAccessToken = { hasToken }
202- onClearToken = { ( ) => {
203- clearToken ( ) ;
204- setShowTokenPrompt ( false ) ;
205- } }
206- />
180+ < div class = "workspace-grid" >
181+ < CreateFeedPanel
182+ focusComposerKey = { focusCreateComposerKey }
183+ feedFormData = { feedFormData }
184+ feedFieldErrors = { feedFieldErrors }
185+ conversionError = { conversionError }
186+ isConverting = { isConverting }
187+ strategies = { strategies }
188+ strategiesLoading = { strategiesLoading }
189+ strategiesError = { strategiesError }
190+ feedCreationEnabled = { feedCreation . enabled }
191+ accessTokenRequired = { feedCreation . access_token_required }
192+ hasAccessToken = { hasToken }
193+ tokenDraft = { tokenDraft }
194+ tokenError = { tokenError }
195+ showTokenPrompt = { showTokenPrompt }
196+ onFeedSubmit = { handleFeedSubmit }
197+ onFeedFieldChange = { setFeedField }
198+ onTokenDraftChange = { ( value ) => {
199+ setTokenDraft ( value ) ;
200+ setTokenError ( '' ) ;
201+ } }
202+ onSaveToken = { handleSaveToken }
203+ onCancelTokenPrompt = { ( ) => {
204+ setShowTokenPrompt ( false ) ;
205+ setTokenError ( '' ) ;
206+ } }
207+ strategyHint = { strategyHint }
208+ />
209+
210+ < InstanceInfo
211+ hasAccessToken = { hasToken }
212+ onClearToken = { ( ) => {
213+ clearToken ( ) ;
214+ setShowTokenPrompt ( false ) ;
215+ } }
216+ />
217+ </ div >
207218 ) }
208219 </ section >
209220 ) ;
0 commit comments