@@ -23,9 +23,9 @@ export default function NewProject() {
23
23
const location = useLocation ( ) ;
24
24
const history = useHistory ( ) ;
25
25
const { teams } = useContext ( TeamsContext ) ;
26
- const { user } = useContext ( UserContext ) ;
26
+ const { user, setUser } = useContext ( UserContext ) ;
27
27
28
- const [ provider , setProvider ] = useState < string > ( "github.com" ) ;
28
+ const [ provider , setProvider ] = useState < string | undefined > ( ) ;
29
29
const [ reposInAccounts , setReposInAccounts ] = useState < ProviderRepository [ ] > ( [ ] ) ;
30
30
const [ repoSearchFilter , setRepoSearchFilter ] = useState < string > ( "" ) ;
31
31
const [ selectedAccount , setSelectedAccount ] = useState < string | undefined > ( undefined ) ;
@@ -37,6 +37,16 @@ export default function NewProject() {
37
37
const [ showNewTeam , setShowNewTeam ] = useState < boolean > ( false ) ;
38
38
const [ loaded , setLoaded ] = useState < boolean > ( false ) ;
39
39
40
+ useEffect ( ( ) => {
41
+ if ( user && provider === undefined ) {
42
+ if ( user . identities . find ( i => i . authProviderId === "Public-GitLab" ) ) {
43
+ setProvider ( "gitlab.com" ) ;
44
+ } else if ( user . identities . find ( i => i . authProviderId === "Public-GitHub" ) ) {
45
+ setProvider ( "github.com" ) ;
46
+ }
47
+ }
48
+ } , [ user ] ) ;
49
+
40
50
useEffect ( ( ) => {
41
51
const params = new URLSearchParams ( location . search ) ;
42
52
const teamParam = params . get ( "team" ) ;
@@ -45,16 +55,6 @@ export default function NewProject() {
45
55
const team = teams ?. find ( t => t . slug === teamParam ) ;
46
56
setSelectedTeamOrUser ( team ) ;
47
57
}
48
-
49
- ( async ( ) => {
50
- updateOrgsState ( ) ;
51
- const repos = await updateReposInAccounts ( ) ;
52
- const first = repos [ 0 ] ;
53
- if ( first ) {
54
- setSelectedAccount ( first . account ) ;
55
- }
56
- setLoaded ( true ) ;
57
- } ) ( ) ;
58
58
} , [ ] ) ;
59
59
60
60
useEffect ( ( ) => {
@@ -77,9 +77,27 @@ export default function NewProject() {
77
77
setRepoSearchFilter ( "" ) ;
78
78
} , [ selectedAccount ] ) ;
79
79
80
+ useEffect ( ( ) => {
81
+ if ( ! provider ) {
82
+ return ;
83
+ }
84
+ ( async ( ) => {
85
+ updateOrgsState ( ) ;
86
+ const repos = await updateReposInAccounts ( ) ;
87
+ const first = repos [ 0 ] ;
88
+ if ( first ) {
89
+ setSelectedAccount ( first . account ) ;
90
+ }
91
+ setLoaded ( true ) ;
92
+ } ) ( ) ;
93
+ } , [ provider ] ) ;
94
+
80
95
const isGitHub = ( ) => provider === "github.com" ;
81
96
82
97
const updateReposInAccounts = async ( installationId ?: string ) => {
98
+ if ( ! provider ) {
99
+ return [ ] ;
100
+ }
83
101
try {
84
102
const repos = await getGitpodService ( ) . server . getProviderRepositoriesForUser ( { provider, hints : { installationId } } ) ;
85
103
setReposInAccounts ( repos ) ;
@@ -96,7 +114,7 @@ export default function NewProject() {
96
114
}
97
115
98
116
const updateOrgsState = async ( ) => {
99
- if ( isGitHub ( ) ) {
117
+ if ( provider && isGitHub ( ) ) {
100
118
try {
101
119
const ghToken = await getToken ( provider ) ;
102
120
setNoOrgs ( ghToken ?. scopes . includes ( "read:org" ) !== true ) ;
@@ -130,6 +148,9 @@ export default function NewProject() {
130
148
}
131
149
132
150
const createProject = async ( teamOrUser : Team | User , selectedRepo : string ) => {
151
+ if ( ! provider ) {
152
+ return ;
153
+ }
133
154
const repo = reposInAccounts . find ( r => r . account === selectedAccount && r . name === selectedRepo ) ;
134
155
if ( ! repo ) {
135
156
console . error ( "No repo selected!" )
@@ -156,7 +177,6 @@ export default function NewProject() {
156
177
return splitted . shift ( ) && splitted . join ( "/" ) ;
157
178
}
158
179
159
- const reposToRender = Array . from ( reposInAccounts ) . filter ( r => r . account === selectedAccount && r . name . includes ( repoSearchFilter ) ) ;
160
180
const accounts = new Map < string , { avatarUrl : string } > ( ) ;
161
181
reposInAccounts . forEach ( r => { if ( ! accounts . has ( r . account ) ) accounts . set ( r . account , { avatarUrl : r . accountAvatarUrl } ) } ) ;
162
182
@@ -193,33 +213,40 @@ export default function NewProject() {
193
213
194
214
const renderSelectRepository = ( ) => {
195
215
216
+ const noReposAvailable = reposInAccounts . length === 0 ;
217
+ const filteredRepos = Array . from ( reposInAccounts ) . filter ( r => r . account === selectedAccount && r . name . includes ( repoSearchFilter ) ) ;
196
218
const icon = selectedAccount && accounts . get ( selectedAccount ) ?. avatarUrl ;
197
219
198
- const renderRepos = ( ) => ( < div className = "mt-10 border rounded-t-xl border-gray-100 flex-col" >
199
- < div className = "px-8 pt-8 flex flex-col space-y-2" >
200
- < ContextMenu classes = "w-full left-0 cursor-pointer" menuEntries = { getDropDownEntries ( accounts ) } >
201
- < div className = "w-full" >
202
- < img src = { icon } className = "rounded-full w-6 h-6 absolute top-1/4 left-4" />
203
- < input className = "w-full px-12 cursor-pointer font-semibold" readOnly type = "text" value = { selectedAccount || "" } > </ input >
204
- < img src = { CaretDown } title = "Select Account" className = "filter-grayscale absolute top-1/2 right-3" />
205
- </ div >
206
- </ ContextMenu >
207
- < div className = "w-full relative " >
208
- < img src = { search } title = "Search" className = "filter-grayscale absolute top-1/3 left-3" />
209
- < input className = "w-96 pl-10 border-0" type = "text" placeholder = "Search Repositories" value = { repoSearchFilter }
210
- onChange = { ( e ) => setRepoSearchFilter ( e . target . value ) } > </ input >
220
+ const renderRepos = ( ) => ( < >
221
+ < div className = { `mt-10 border rounded-xl border-gray-100 flex-col` } >
222
+ < div className = "px-8 pt-8 flex flex-col space-y-2" >
223
+ < ContextMenu classes = "w-full left-0 cursor-pointer" menuEntries = { getDropDownEntries ( accounts ) } >
224
+ < div className = "w-full" >
225
+ { icon && (
226
+ < img src = { icon } className = "rounded-full w-6 h-6 absolute top-1/4 left-4" />
227
+ ) }
228
+ < input className = "w-full px-12 cursor-pointer font-semibold" readOnly type = "text" value = { selectedAccount || "" } > </ input >
229
+ < img src = { CaretDown } title = "Select Account" className = "filter-grayscale absolute top-1/2 right-3" />
230
+ </ div >
231
+ </ ContextMenu >
232
+ { filteredRepos . length > 0 && (
233
+ < div className = "w-full relative " >
234
+ < img src = { search } title = "Search" className = "filter-grayscale absolute top-1/3 left-3" />
235
+ < input className = "w-96 pl-10 border-0" type = "text" placeholder = "Search Repositories" value = { repoSearchFilter }
236
+ onChange = { ( e ) => setRepoSearchFilter ( e . target . value ) } > </ input >
237
+ </ div >
238
+ ) }
211
239
</ div >
212
- </ div >
213
- < div className = "p-6 flex-col" >
214
- { reposToRender . length > 0 && (
215
- < div className = "overscroll-contain max-h-80 overflow-y-auto pr-2" >
216
- { reposToRender . map ( r => (
217
- < div key = { `repo-${ r . name } ` } className = "flex p-3 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light transition ease-in-out group" >
218
-
219
- < div className = "flex-grow" >
220
- < div className = "text-base text-gray-900 dark:text-gray-50 font-medium rounded-xl whitespace-nowrap" > { toSimpleName ( r . name ) } </ div >
221
- < p > Updated { moment ( r . updatedAt ) . fromNow ( ) } </ p >
222
- </ div >
240
+ < div className = "p-6 flex-col" >
241
+ { filteredRepos . length > 0 && (
242
+ < div className = "overscroll-contain max-h-80 overflow-y-auto pr-2" >
243
+ { filteredRepos . map ( ( r , index ) => (
244
+ < div key = { `repo-${ index } -${ r . account } -${ r . name } ` } className = "flex p-3 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light transition ease-in-out group" >
245
+
246
+ < div className = "flex-grow" >
247
+ < div className = "text-base text-gray-900 dark:text-gray-50 font-medium rounded-xl whitespace-nowrap" > { toSimpleName ( r . name ) } </ div >
248
+ < p > Updated { moment ( r . updatedAt ) . fromNow ( ) } </ p >
249
+ </ div >
223
250
< div className = "flex justify-end" >
224
251
< div className = "h-full my-auto flex self-center opacity-0 group-hover:opacity-100" >
225
252
{ ! r . inUse ? (
@@ -229,54 +256,73 @@ export default function NewProject() {
229
256
) }
230
257
</ div >
231
258
</ div >
259
+ </ div >
260
+ ) ) }
261
+ </ div >
262
+ ) }
263
+ { ! noReposAvailable && filteredRepos . length === 0 && (
264
+ < p className = "text-center" > No Results</ p >
265
+ ) }
266
+ { loaded && noReposAvailable && isGitHub ( ) && ( < div >
267
+ < div className = "px-12 py-16 text-center text-gray-500 bg-gray-50 dark:bg-gray-800 rounded-xl" >
268
+ < img src = { NoAccess } title = "No Access" className = "m-auto mb-4" />
269
+ < h3 className = "mb-2 text-gray-600 dark:text-gray-400" >
270
+ No Access
271
+ </ h3 >
272
+ < span className = "dark:text-gray-500" >
273
+ Authorize GitHub (github.com) or select a different account.
274
+ </ span >
275
+ < br />
276
+ < button className = "mt-6" onClick = { ( ) => reconfigure ( ) } > Authorize</ button >
277
+ </ div >
278
+ </ div > ) }
279
+ </ div >
280
+
281
+ </ div >
282
+ { isGitHub ( ) && (
283
+ < div className = "pt-3" >
284
+ < div className = "text-gray-500 text-center w-96 mx-8" >
285
+ Repository not found? < a href = "javascript:void(0)" onClick = { e => reconfigure ( ) } className = "text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600" > Reconfigure</ a >
286
+ </ div >
287
+ { isGitHub ( ) && noOrgs && (
288
+ < div className = "text-gray-500 mx-auto text-center" >
289
+ Missing organizations? < a href = "javascript:void(0)" onClick = { e => grantReadOrgPermissions ( ) } className = "text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600" > Grant permissions</ a >
232
290
</ div >
233
- ) ) }
291
+ ) }
234
292
</ div >
235
- ) }
236
- { reposToRender . length === 0 && (
237
- < p className = "text-center " > not found</ p >
238
- ) }
239
- </ div >
240
-
241
- < div className = "px-3 pt-3 bg-gray-100" >
242
- < div className = "text-gray-500 text-center" >
243
- Repository not found? < a href = "javascript:void(0)" onClick = { e => reconfigure ( ) } className = "text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600" > Reconfigure</ a >
244
- </ div >
245
- { isGitHub ( ) && noOrgs && (
246
- < div className = "text-gray-500 mx-auto text-center" >
247
- Missing organizations? < a href = "javascript:void(0)" onClick = { e => grantReadOrgPermissions ( ) } className = "text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600" > Grant permissions</ a >
293
+ ) }
294
+ </ >
295
+ ) ;
296
+
297
+ const renderEmptyState = ( ) => ( < div >
298
+ < div className = "mt-8 border rounded-xl border-gray-100 dark:border-gray-700 flex-col" >
299
+ < div >
300
+ < div className = "px-12 py-16 text-center text-gray-500 bg-gray-50 dark:bg-gray-800 rounded-xl w-96 h-h96 flex items-center justify-center" >
301
+ < h3 className = "mb-2 text-gray-400 dark:text-gray-600 animate-pulse" >
302
+ Loading ...
303
+ </ h3 >
248
304
</ div >
249
- ) }
250
- </ div >
251
- < div className = "h-3 border rounded-b-xl border-gray-100 bg-gray-100" > </ div >
252
- </ div > ) ;
253
-
254
- const renderEmptyState = ( ) => ( < div className = "mt-8 border rounded-xl border-gray-100 dark:border-gray-700 flex-col" >
255
- < div >
256
- < div className = "px-12 py-16 text-center text-gray-500 bg-gray-50 dark:bg-gray-800 rounded-xl" >
257
- < img src = { NoAccess } title = "No Access" className = "m-auto mb-4" />
258
- < h3 className = "mb-2 text-gray-600 dark:text-gray-400" >
259
- No Access
260
- </ h3 >
261
- < span className = "dark:text-gray-500" >
262
- Authorize GitHub (github.com) or select a different account.
263
- </ span >
264
- < br />
265
- < button className = "mt-6" onClick = { ( ) => reconfigure ( ) } > Authorize Provider</ button >
266
305
</ div >
267
306
</ div >
268
307
</ div > )
269
308
270
- const empty = reposInAccounts . length === 0 ;
271
-
272
- const onGitProviderSeleted = ( host : string ) => {
309
+ const onGitProviderSeleted = async ( host : string , updateUser ?: boolean ) => {
310
+ if ( updateUser ) {
311
+ setUser ( await getGitpodService ( ) . server . getLoggedInUser ( ) ) ;
312
+ }
273
313
setShowGitProviders ( false ) ;
274
314
setProvider ( host ) ;
275
315
}
276
316
277
- return ( < >
278
- { ( loaded && empty ) ? renderEmptyState ( ) : ( showGitProviders ? ( < GitProviders onHostSelected = { onGitProviderSeleted } /> ) : renderRepos ( ) ) }
279
- </ > )
317
+ if ( ! loaded ) {
318
+ return renderEmptyState ( ) ;
319
+ }
320
+
321
+ if ( showGitProviders ) {
322
+ return ( < GitProviders onHostSelected = { onGitProviderSeleted } /> ) ;
323
+ }
324
+
325
+ return renderRepos ( ) ;
280
326
} ;
281
327
282
328
const renderSelectTeam = ( ) => {
@@ -322,7 +368,7 @@ export default function NewProject() {
322
368
323
369
return ( < div className = "flex flex-col w-96 mt-24 mx-auto items-center" >
324
370
< h1 > New Project</ h1 >
325
- < p className = "text-gray-500 text-center text-base" > Select a git repository.</ p >
371
+ < p className = "text-gray-500 text-center text-base" > Select a git repository on < strong > { provider } </ strong > .</ p >
326
372
327
373
{ ! selectedRepo && renderSelectRepository ( ) }
328
374
@@ -335,7 +381,7 @@ export default function NewProject() {
335
381
}
336
382
337
383
function GitProviders ( props : {
338
- onHostSelected : ( host : string ) => void
384
+ onHostSelected : ( host : string , updateUser ?: boolean ) => void
339
385
} ) {
340
386
const [ authProviders , setAuthProviders ] = useState < AuthProviderInfo [ ] > ( [ ] ) ;
341
387
@@ -355,8 +401,8 @@ function GitProviders(props: {
355
401
await openAuthorizeWindow ( {
356
402
host : ap . host ,
357
403
scopes : ap . requirements ?. default ,
358
- onSuccess : ( ) => {
359
- props . onHostSelected ( ap . host ) ;
404
+ onSuccess : async ( ) => {
405
+ props . onHostSelected ( ap . host , true ) ;
360
406
} ,
361
407
onError : ( error ) => {
362
408
console . log ( error ) ;
0 commit comments