11"use client" ;
22
33import { useQueryState } from 'nuqs' ;
4- import { Button } from '@/components/ui/button' ;
5- import { Input } from '@/components/ui/input' ;
64import { useState , useEffect } from 'react' ;
75import { Card } from '@/components/ui/card' ;
86import Link from 'next/link' ;
9- import { listDataSources } from '@/lib/tinybird' ;
10- import { TOOLS , type AppGridItem } from '@/lib/constants' ;
7+ import { checkToolState } from '@/lib/tinybird' ;
8+ import { TOOLS , type AppGridItem , type ToolState } from '@/lib/constants' ;
119import TokenPrompt from '@/components/token-prompt' ;
1210
13- function AppCard ( { app, isInstalled, token } : { app : AppGridItem ; isInstalled : boolean ; token ?: string } ) {
14- return (
15- < Link
16- key = { app . id }
17- href = { `/${ app . id } ${ token ? `?token=${ token } ` : '' } ` }
18- >
19- < Card className = { `p-4 hover:bg-accent ${ isInstalled ? 'border-primary' : '' } ` } >
20- < div className = "flex items-center gap-4" >
21- < div className = "text-2xl" > { app . icon } </ div >
22- < div >
23- < h3 className = "font-semibold" > { app . name } </ h3 >
24- < p className = "text-sm text-muted-foreground" > { app . description } </ p >
25- </ div >
26- </ div >
27- </ Card >
28- </ Link >
29- ) ;
30- }
31-
3211export default function Home ( ) {
3312 const [ token ] = useQueryState ( 'token' ) ;
34- const [ installedApps , setInstalledApps ] = useState < string [ ] > ( [ ] ) ;
13+ const [ toolStates , setToolStates ] = useState < Record < string , ToolState > > ( { } ) ;
3514 const [ isLoading , setIsLoading ] = useState ( false ) ;
3615
3716 useEffect ( ( ) => {
38- async function fetchDataSources ( ) {
17+ async function fetchToolStates ( ) {
3918 if ( ! token ) return ;
4019 setIsLoading ( true ) ;
4120 try {
42- const sources = await listDataSources ( token ) ;
43- setInstalledApps ( sources . map ( source => source . name ) ) ;
21+ const states = await Promise . all (
22+ Object . values ( TOOLS ) . map ( async ( app ) => {
23+ const state = await checkToolState ( token , app . ds ) ;
24+ return [ app . id , state ] as const ;
25+ } )
26+ ) ;
27+ setToolStates ( Object . fromEntries ( states ) ) ;
4428 } catch ( error ) {
45- console . error ( 'Failed to fetch data sources :' , error ) ;
29+ console . error ( 'Failed to fetch tool states :' , error ) ;
4630 } finally {
4731 setIsLoading ( false ) ;
4832 }
4933 }
5034
51- fetchDataSources ( ) ;
35+ fetchToolStates ( ) ;
5236 } , [ token ] ) ;
5337
5438 return (
@@ -61,34 +45,55 @@ export default function Home() {
6145 ) }
6246 { token && ! isLoading && (
6347 < div className = "space-y-8" >
64- { installedApps . length > 0 && (
48+ { /* Configured Apps */ }
49+ { Object . values ( TOOLS ) . some ( app => toolStates [ app . id ] === 'configured' ) && (
50+ < div className = "space-y-4" >
51+ < h2 className = "text-lg font-semibold" > Configured Apps</ h2 >
52+ < div className = "grid gap-4 md:grid-cols-2 lg:grid-cols-3" >
53+ { Object . values ( TOOLS )
54+ . filter ( app => toolStates [ app . id ] === 'configured' )
55+ . map ( app => (
56+ < AppCard
57+ key = { app . id }
58+ app = { app }
59+ state = { toolStates [ app . id ] }
60+ token = { token }
61+ />
62+ ) ) }
63+ </ div >
64+ </ div >
65+ ) }
66+
67+ { /* Installed Apps */ }
68+ { Object . values ( TOOLS ) . some ( app => toolStates [ app . id ] === 'installed' ) && (
6569 < div className = "space-y-4" >
6670 < h2 className = "text-lg font-semibold" > Installed Apps</ h2 >
6771 < div className = "grid gap-4 md:grid-cols-2 lg:grid-cols-3" >
6872 { Object . values ( TOOLS )
69- . filter ( app => installedApps . includes ( app . ds ) )
73+ . filter ( app => toolStates [ app . id ] === 'installed' )
7074 . map ( app => (
7175 < AppCard
7276 key = { app . id }
7377 app = { app }
74- isInstalled = { true }
78+ state = { toolStates [ app . id ] }
7579 token = { token }
7680 />
7781 ) ) }
7882 </ div >
7983 </ div >
8084 ) }
8185
86+ { /* Available Apps */ }
8287 < div className = "space-y-4" >
8388 < h2 className = "text-lg font-semibold" > Available Apps</ h2 >
8489 < div className = "grid gap-4 md:grid-cols-2 lg:grid-cols-3" >
8590 { Object . values ( TOOLS )
86- . filter ( app => ! installedApps . includes ( app . ds ) )
91+ . filter ( app => ! toolStates [ app . id ] || toolStates [ app . id ] === 'available' )
8792 . map ( app => (
8893 < AppCard
8994 key = { app . id }
9095 app = { app }
91- isInstalled = { false }
96+ state = { toolStates [ app . id ] || 'available' }
9297 token = { token }
9398 />
9499 ) ) }
@@ -99,3 +104,39 @@ export default function Home() {
99104 </ div >
100105 ) ;
101106}
107+
108+ function AppCard ( {
109+ app,
110+ state,
111+ token
112+ } : {
113+ app : AppGridItem ;
114+ state : ToolState ;
115+ token ?: string ;
116+ } ) {
117+ const stateColors = {
118+ configured : 'border-green-500' ,
119+ installed : 'border-blue-500' ,
120+ available : ''
121+ } ;
122+
123+ return (
124+ < Link
125+ key = { app . id }
126+ href = { `/${ app . id } ${ token ? `?token=${ token } ` : '' } ` }
127+ >
128+ < Card className = { `p-4 hover:bg-accent ${ stateColors [ state ] } ` } >
129+ < div className = "flex items-center gap-4" >
130+ < div className = "text-2xl" > { app . icon } </ div >
131+ < div >
132+ < div className = "flex items-center gap-2" >
133+ < h3 className = "font-semibold" > { app . name } </ h3 >
134+ < span className = "text-xs text-muted-foreground" > ({ state } )</ span >
135+ </ div >
136+ < p className = "text-sm text-muted-foreground" > { app . description } </ p >
137+ </ div >
138+ </ div >
139+ </ Card >
140+ </ Link >
141+ ) ;
142+ }
0 commit comments