1
+ "use client" ;
2
+
1
3
import { NotFound } from "@/app/[domain]/components/notFound" ;
2
4
import {
3
5
Breadcrumb ,
@@ -10,58 +12,108 @@ import {
10
12
import { ScrollArea } from "@/components/ui/scroll-area" ;
11
13
import { TabSwitcher } from "@/components/ui/tab-switcher" ;
12
14
import { Tabs , TabsContent } from "@/components/ui/tabs" ;
13
- import { getConnection , getLinkedRepos } from "@/data/connection" ;
14
15
import { ConnectionIcon } from "../components/connectionIcon" ;
15
16
import { Header } from "../../components/header" ;
16
17
import { ConfigSetting } from "./components/configSetting" ;
17
18
import { DeleteConnectionSetting } from "./components/deleteConnectionSetting" ;
18
19
import { DisplayNameSetting } from "./components/displayNameSetting" ;
19
20
import { RepoListItem } from "./components/repoListItem" ;
20
- import { getOrgFromDomain } from "@/data/org" ;
21
- import { PageNotFound } from "../../components/pageNotFound" ;
22
-
23
- interface ConnectionManagementPageProps {
24
- params : {
25
- id : string ;
26
- domain : string ;
27
- } ,
28
- searchParams : {
29
- tab ?: string ;
30
- }
31
- }
21
+ import { useParams , useSearchParams } from "next/navigation" ;
22
+ import { useEffect , useState } from "react" ;
23
+ import { Connection , Repo , Org } from "@sourcebot/db" ;
24
+ import { getConnectionInfoAction , getOrgFromDomainAction , flagConnectionForSync } from "@/actions" ;
25
+ import { isServiceError } from "@/lib/utils" ;
26
+ import { Button } from "@/components/ui/button" ;
27
+ import { ReloadIcon } from "@radix-ui/react-icons" ;
28
+ import { useToast } from "@/components/hooks/use-toast" ;
32
29
33
- export default async function ConnectionManagementPage ( {
34
- params,
35
- searchParams,
36
- } : ConnectionManagementPageProps ) {
37
- const org = await getOrgFromDomain ( params . domain ) ;
38
- if ( ! org ) {
39
- return < PageNotFound />
40
- }
30
+ export default function ConnectionManagementPage ( ) {
31
+ const params = useParams ( ) ;
32
+ const searchParams = useSearchParams ( ) ;
33
+ const { toast } = useToast ( ) ;
34
+ const [ org , setOrg ] = useState < Org | null > ( null ) ;
35
+ const [ connection , setConnection ] = useState < Connection | null > ( null ) ;
36
+ const [ linkedRepos , setLinkedRepos ] = useState < Repo [ ] > ( [ ] ) ;
37
+ const [ loading , setLoading ] = useState ( true ) ;
38
+ const [ error , setError ] = useState < string | null > ( null ) ;
41
39
42
- const connectionId = Number ( params . id ) ;
43
- if ( isNaN ( connectionId ) ) {
44
- return (
45
- < NotFound
46
- className = "flex w-full h-full items-center justify-center"
47
- message = "Connection not found"
48
- />
49
- )
40
+ useEffect ( ( ) => {
41
+ const loadData = async ( ) => {
42
+ try {
43
+ const orgResult = await getOrgFromDomainAction ( params . domain as string ) ;
44
+ if ( isServiceError ( orgResult ) ) {
45
+ setError ( orgResult . message ) ;
46
+ setLoading ( false ) ;
47
+ return ;
48
+ }
49
+ setOrg ( orgResult ) ;
50
+
51
+ const connectionId = Number ( params . id ) ;
52
+ if ( isNaN ( connectionId ) ) {
53
+ setError ( "Invalid connection ID" ) ;
54
+ setLoading ( false ) ;
55
+ return ;
56
+ }
57
+
58
+ const connectionInfoResult = await getConnectionInfoAction ( connectionId , params . domain as string ) ;
59
+ if ( isServiceError ( connectionInfoResult ) ) {
60
+ setError ( connectionInfoResult . message ) ;
61
+ setLoading ( false ) ;
62
+ return ;
63
+ }
64
+
65
+ connectionInfoResult . linkedRepos . sort ( ( a , b ) => {
66
+ // Helper function to get priority of indexing status
67
+ const getPriority = ( status : string ) => {
68
+ switch ( status ) {
69
+ case 'FAILED' : return 0 ;
70
+ case 'IN_INDEX_QUEUE' :
71
+ case 'INDEXING' : return 1 ;
72
+ case 'INDEXED' : return 2 ;
73
+ default : return 3 ;
74
+ }
75
+ } ;
76
+
77
+ const priorityA = getPriority ( a . repoIndexingStatus ) ;
78
+ const priorityB = getPriority ( b . repoIndexingStatus ) ;
79
+
80
+ // First sort by priority
81
+ if ( priorityA !== priorityB ) {
82
+ return priorityA - priorityB ;
83
+ }
84
+
85
+ // If same priority, sort by createdAt
86
+ return new Date ( a . createdAt ) . getTime ( ) - new Date ( b . createdAt ) . getTime ( ) ;
87
+ } ) ;
88
+
89
+ setConnection ( connectionInfoResult . connection ) ;
90
+ setLinkedRepos ( connectionInfoResult . linkedRepos ) ;
91
+ setLoading ( false ) ;
92
+ } catch ( err ) {
93
+ setError ( err instanceof Error ? err . message : "An error occurred while loading the connection. If the problem persists, please contact us at team@sourcebot.dev" ) ;
94
+ setLoading ( false ) ;
95
+ }
96
+ } ;
97
+
98
+ loadData ( ) ;
99
+ const intervalId = setInterval ( loadData , 1000 ) ;
100
+ return ( ) => clearInterval ( intervalId ) ;
101
+ } , [ params . domain , params . id ] ) ;
102
+
103
+ if ( loading ) {
104
+ return < div > Loading...</ div > ;
50
105
}
51
106
52
- const connection = await getConnection ( Number ( params . id ) , org . id ) ;
53
- if ( ! connection ) {
107
+ if ( error || ! org || ! connection ) {
54
108
return (
55
109
< NotFound
56
110
className = "flex w-full h-full items-center justify-center"
57
- message = "Connection not found"
111
+ message = { error || "Not found"}
58
112
/>
59
- )
113
+ ) ;
60
114
}
61
115
62
- const linkedRepos = await getLinkedRepos ( connectionId , org . id ) ;
63
-
64
- const currentTab = searchParams . tab || "overview" ;
116
+ const currentTab = searchParams . get ( "tab" ) || "overview" ;
65
117
66
118
return (
67
119
< Tabs
@@ -116,7 +168,30 @@ export default async function ConnectionManagementPage({
116
168
</ div >
117
169
< div className = "rounded-lg border border-border p-4 bg-background" >
118
170
< h2 className = "text-sm font-medium text-muted-foreground" > Status</ h2 >
119
- < p className = "mt-2 text-sm" > { connection . syncStatus } </ p >
171
+ < div className = "flex items-center gap-2" >
172
+ < p className = "mt-2 text-sm" > { connection . syncStatus } </ p >
173
+ { connection . syncStatus === "FAILED" && (
174
+ < Button
175
+ variant = "outline"
176
+ size = "sm"
177
+ className = "mt-2 rounded-full"
178
+ onClick = { async ( ) => {
179
+ const result = await flagConnectionForSync ( connection . id , params . domain as string ) ;
180
+ if ( isServiceError ( result ) ) {
181
+ toast ( {
182
+ description : `❌ Failed to flag connection for sync. Reason: ${ result . message } ` ,
183
+ } )
184
+ } else {
185
+ toast ( {
186
+ description : "✅ Connection flagged for sync." ,
187
+ } )
188
+ }
189
+ } }
190
+ >
191
+ < ReloadIcon className = "h-4 w-4" />
192
+ </ Button >
193
+ ) }
194
+ </ div >
120
195
</ div >
121
196
</ div >
122
197
</ div >
@@ -127,12 +202,12 @@ export default async function ConnectionManagementPage({
127
202
< div className = "flex flex-col gap-4" >
128
203
{ linkedRepos
129
204
. sort ( ( a , b ) => {
130
- const aIndexedAt = a . repo . indexedAt ?? new Date ( ) ;
131
- const bIndexedAt = b . repo . indexedAt ?? new Date ( ) ;
205
+ const aIndexedAt = a . indexedAt ?? new Date ( ) ;
206
+ const bIndexedAt = b . indexedAt ?? new Date ( ) ;
132
207
133
208
return bIndexedAt . getTime ( ) - aIndexedAt . getTime ( ) ;
134
209
} )
135
- . map ( ( { repo } ) => (
210
+ . map ( ( repo ) => (
136
211
< RepoListItem
137
212
key = { repo . id }
138
213
imageUrl = { repo . imageUrl ?? undefined }
@@ -162,6 +237,5 @@ export default async function ConnectionManagementPage({
162
237
/>
163
238
</ TabsContent >
164
239
</ Tabs >
165
-
166
- )
240
+ ) ;
167
241
}
0 commit comments