1- import { ChangeEvent , Dispatch , SetStateAction , useState } from "react" ;
1+ import {
2+ ChangeEvent ,
3+ Dispatch ,
4+ SetStateAction ,
5+ useState ,
6+ useEffect ,
7+ useRef ,
8+ } from "react" ;
29import { useParams } from "react-router-dom" ;
310import { useSetAtom } from "jotai" ;
411import {
@@ -21,6 +28,14 @@ type Props = {
2128 setAutoTriggerBuild : Dispatch < SetStateAction < boolean > > ;
2229} ;
2330
31+ // Utility function for formatting repository names with owner
32+ const getRepoNameWithOwner = (
33+ selectedOrg : string | null ,
34+ repo : Repo ,
35+ ) : string => {
36+ return selectedOrg ? `${ selectedOrg } /${ repo . name } ` : repo . nameWithOwner ;
37+ } ;
38+
2439function generateYamlTemplateUrl (
2540 org : string | null ,
2641 repo : string | undefined ,
@@ -67,55 +82,79 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
6782 const [ reposLoading , setReposLoading ] = useState < boolean > ( false ) ;
6883 const [ validRepo , setValidRepo ] = useState < boolean | null > ( null ) ;
6984 const [ validationError , setValidationError ] = useState < boolean > ( false ) ;
70- const [ missingYaml , setMissingYaml ] = useState < boolean > ( false ) ;
71- const [ defaultBranch , setDefaultBranch ] = useState < string | null > ( null ) ;
85+ const [ repoInputValue , setRepoInputValue ] = useState < string > ( "" ) ;
86+ const [ repoFetchError , setRepoFetchError ] = useState < string > ( "" ) ;
87+ const [ repoConnectError , setRepoConnectError ] = useState < string > ( "" ) ;
7288
73- const getRepoNameWithOwner = ( repo : Repo ) =>
74- selectedOrg ? `${ selectedOrg } /${ repo . name } ` : repo . nameWithOwner ;
89+ const [ defaultBranch , setDefaultBranch ] = useState < string | null > ( null ) ;
7590
76- const validateRepo = async ( repo : Repo | undefined ) => {
77- setMissingYaml ( false ) ;
91+ // Track autofill attempts to avoid unnecessary re-runs
92+ const autofillAttemptedRef = useRef < string | null > ( null ) ;
7893
94+ const validateRepoInternal = async ( repo : Repo | undefined ) => {
7995 if ( ! repo ) {
8096 return ;
8197 }
8298
8399 setValidating ( true ) ;
84100
85- const repoName = getRepoNameWithOwner ( repo ) ;
101+ const repoName = getRepoNameWithOwner ( selectedOrg , repo ) ;
86102
87- const response = await fetch (
88- `/api/${ snapId } /builds/validate-repo?repo=${ repoName } ` ,
89- ) ;
103+ try {
104+ const response = await fetch (
105+ `/api/${ snapId } /builds/validate-repo?repo=${ repoName } ` ,
106+ ) ;
90107
91- if ( ! response . ok ) {
92- setValidationError ( true ) ;
93- throw new Error ( "Not a valid repo" ) ;
94- }
108+ if ( ! response . ok ) {
109+ setValidationError ( true ) ;
110+ throw new Error ( "Not a valid repo" ) ;
111+ }
95112
96- const responseData = await response . json ( ) ;
113+ const responseData = await response . json ( ) ;
97114
98- if ( responseData . success ) {
99- setValidRepo ( true ) ;
100- setValidationMessage ( "" ) ;
101- setValidationError ( false ) ;
102- setMissingYaml ( false ) ;
103- } else {
104- const error = responseData . error ;
115+ if ( responseData . success ) {
116+ setValidRepo ( true ) ;
117+ setValidationMessage ( "" ) ;
118+ setValidationError ( false ) ;
119+ } else {
120+ const error = responseData . error ;
105121
106- if ( error . type === "MISSING_YAML_FILE" ) {
107- setMissingYaml ( true ) ;
108- setDefaultBranch ( responseData . data . default_branch ) ;
109- }
122+ if ( error . type === "MISSING_YAML_FILE" ) {
123+ setDefaultBranch ( responseData . data . default_branch ) ;
124+ }
110125
111- setValidationMessage ( responseData . error . message ) ;
126+ setValidationMessage ( responseData . error . message ) ;
127+ setValidationError ( true ) ;
128+ setValidRepo ( null ) ;
129+ }
130+ } catch {
112131 setValidationError ( true ) ;
113- setValidRepo ( null ) ;
132+ } finally {
133+ setValidating ( false ) ;
114134 }
115-
116- setValidating ( false ) ;
117135 } ;
118136
137+ // Autofill effect with proper dependencies
138+ useEffect ( ( ) => {
139+ const currentOrgKey = `${ selectedOrg || "user" } -${ snapId } ` ;
140+
141+ if (
142+ repos . length > 0 &&
143+ snapId &&
144+ ! repoInputValue &&
145+ autofillAttemptedRef . current !== currentOrgKey
146+ ) {
147+ const matchingRepo = repos . find ( ( repo : Repo ) => repo . name === snapId ) ;
148+ if ( matchingRepo ) {
149+ setRepoInputValue ( matchingRepo . name ) ;
150+ setSelectedRepo ( matchingRepo ) ;
151+ validateRepoInternal ( matchingRepo ) ;
152+ }
153+ // Mark autofill as attempted for this org/snap combination
154+ autofillAttemptedRef . current = currentOrgKey ;
155+ }
156+ } , [ repos , snapId , repoInputValue , selectedOrg ] ) ;
157+
119158 const getRepos = async ( org ?: string ) => {
120159 setReposLoading ( true ) ;
121160
@@ -125,17 +164,21 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
125164 apiUrl += `?org=${ org } ` ;
126165 }
127166
128- const response = await fetch ( apiUrl ) ;
129-
130- if ( ! response . ok ) {
131- throw Error ( "Unable to fetch repos" ) ;
132- }
133-
134- const responseData = await response . json ( ) ;
167+ try {
168+ const response = await fetch ( apiUrl ) ;
135169
136- setReposLoading ( false ) ;
170+ if ( ! response . ok ) {
171+ throw Error ( "Unable to fetch repos" ) ;
172+ }
137173
138- setRepos ( responseData ) ;
174+ const responseData = await response . json ( ) ;
175+ setRepos ( responseData ) ;
176+ } catch ( _error ) {
177+ setRepoFetchError ( "Failed to fetch repositories. Please try again." ) ;
178+ setRepos ( [ ] ) ;
179+ } finally {
180+ setReposLoading ( false ) ;
181+ }
139182 } ;
140183
141184 const getOrgs = ( ) : { label : string ; value : string } [ ] => {
@@ -166,9 +209,16 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
166209 const handleOrganizationChange = ( e : ChangeEvent ) => {
167210 const target = e . target as HTMLInputElement ;
168211
169- if ( target . value ) {
170- setReposLoading ( true ) ;
212+ setRepoInputValue ( "" ) ;
213+ setSelectedRepo ( undefined ) ;
214+ setValidRepo ( null ) ;
215+ setValidationError ( false ) ;
216+ setRepoFetchError ( "" ) ;
217+ setRepoConnectError ( "" ) ;
218+ setRepos ( [ ] ) ;
219+ autofillAttemptedRef . current = null ;
171220
221+ if ( target . value ) {
172222 const org = target . value ;
173223
174224 if ( org === githubData . github_user . login ) {
@@ -187,39 +237,53 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
187237 const target = e . target as HTMLInputElement ;
188238 const searchTerm = target . value ;
189239
240+ setRepoInputValue ( searchTerm ) ;
241+ setRepoFetchError ( "" ) ;
242+ setRepoConnectError ( "" ) ;
243+
190244 if ( ! searchTerm ) {
191245 setValidationError ( false ) ;
192- setValidRepo ( false ) ;
246+ setValidRepo ( null ) ;
247+ setSelectedRepo ( undefined ) ;
248+ return ;
193249 }
194250
195251 const selectedRepo = repos . find ( ( repo : Repo ) => repo . name === searchTerm ) ;
196252
197253 setSelectedRepo ( selectedRepo ) ;
198254
199255 if ( selectedRepo ) {
200- validateRepo ( selectedRepo ) ;
256+ validateRepoInternal ( selectedRepo ) ;
201257 }
202258 } ;
203259
204260 const connectRepo = async ( ) => {
205261 setBuilding ( true ) ;
262+ setRepoConnectError ( "" ) ;
206263 const formData = new FormData ( ) ;
207264 formData . set ( "csrf_token" , window . CSRF_TOKEN ) ;
208265 if ( selectedRepo ) {
209- const repoName = getRepoNameWithOwner ( selectedRepo ) ;
266+ const repoName = getRepoNameWithOwner ( selectedOrg , selectedRepo ) ;
210267 formData . set ( "github_repository" , repoName ) ;
211268 }
212269
213- const response = await fetch ( `/api/${ snapId } /builds` , {
214- method : "POST" ,
215- body : formData ,
216- } ) ;
270+ try {
271+ const response = await fetch ( `/api/${ snapId } /builds` , {
272+ method : "POST" ,
273+ body : formData ,
274+ } ) ;
217275
218- if ( response ) {
219- setAutoTriggerBuild ( true ) ;
276+ if ( response . ok ) {
277+ setAutoTriggerBuild ( true ) ;
278+ setRepoConnected ( true ) ;
279+ } else {
280+ setRepoConnectError ( "Failed to connect repository. Please try again." ) ;
281+ }
282+ } catch ( _error ) {
283+ setRepoConnectError ( "Failed to connect repository. Please try again." ) ;
284+ } finally {
285+ setBuilding ( false ) ;
220286 }
221-
222- setRepoConnected ( true ) ;
223287 } ;
224288
225289 return (
@@ -257,6 +321,7 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
257321 placeholder = "Search your repos"
258322 disabled = { selectedOrg === null || validating }
259323 className = "p-form-validation__input"
324+ value = { repoInputValue }
260325 onChange = { handleRepoChange }
261326 />
262327 { reposLoading ||
@@ -270,6 +335,11 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
270335 < option value = { repo . name } key = { repo . name } />
271336 ) ) }
272337 </ datalist >
338+ { repoFetchError && (
339+ < p className = "p-form-validation__message" role = "alert" >
340+ { repoFetchError }
341+ </ p >
342+ ) }
273343 </ Col >
274344 < Col size = { 2 } >
275345 < div style = { { display : "flex" , alignItems : "end" , height : "100%" } } >
@@ -289,7 +359,7 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
289359 className = "p-tooltip--btm-center"
290360 aria-describedby = "recheck-tooltip"
291361 onClick = { ( ) => {
292- validateRepo ( selectedRepo ) ;
362+ validateRepoInternal ( selectedRepo ) ;
293363 } }
294364 >
295365 < i className = "p-icon--restart" > </ i >
@@ -298,14 +368,14 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
298368 role = "tooltip"
299369 id = "recheck-tooltip"
300370 >
301- Re-check
371+ Check again
302372 </ span >
303373 </ Button >
304374 ) }
305375 </ div >
306376 </ Col >
307377 </ Row >
308- { missingYaml && (
378+ { validationError && (
309379 < div className = "u-fixed-width" >
310380 < p > { validationMessage } </ p >
311381 < p >
@@ -317,7 +387,7 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
317387 selectedOrg ,
318388 selectedRepo ?. name ,
319389 defaultBranch ,
320- snapId ,
390+ snapId || "" ,
321391 ) }
322392 >
323393 get started with a template
@@ -333,6 +403,15 @@ function RepoSelector({ githubData, setAutoTriggerBuild }: Props) {
333403 </ p >
334404 </ div >
335405 ) }
406+ { repoConnectError && (
407+ < Row >
408+ < Col size = { 12 } >
409+ < p className = "p-form-validation__message" role = "alert" >
410+ { repoConnectError }
411+ </ p >
412+ </ Col >
413+ </ Row >
414+ ) }
336415 </ Strip >
337416 ) ;
338417}
0 commit comments