Skip to content

Commit 4a4a8fd

Browse files
committed
install/config on count
1 parent b61fd96 commit 4a4a8fd

File tree

4 files changed

+112
-43
lines changed

4 files changed

+112
-43
lines changed

apps/web/src/app/[id]/page.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
import { useQueryState } from 'nuqs';
44
import React, { useEffect, useState } from 'react';
5-
import { listDataSources } from '@/lib/tinybird';
6-
import { TOOL_IMPORTS, type ToolId, TOOLS } from '@/lib/constants';
5+
import { checkToolState } from '@/lib/tinybird';
6+
import { TOOL_IMPORTS, type ToolId, TOOLS, type ToolState } from '@/lib/constants';
77

88
export default function AppPage({ params }: { params: Promise<{ id: string }> }) {
99
const [token, setToken] = useQueryState('token');
10-
const [isInstalled, setIsInstalled] = useState(false);
10+
const [toolState, setToolState] = useState<ToolState>('available');
1111
const { id } = React.use(params) as { id: ToolId };
1212

1313
useEffect(() => {
1414
async function checkInstallation() {
1515
if (!token) return;
16-
const sources = await listDataSources(token);
17-
setIsInstalled(sources.some(source => source.name === TOOLS[id].ds));
16+
const state = await checkToolState(token, TOOLS[id].ds);
17+
setToolState(state);
1818
}
1919
checkInstallation();
2020
}, [token, id]);
@@ -27,12 +27,16 @@ export default function AppPage({ params }: { params: Promise<{ id: string }> })
2727
return <div>Tool not implemented</div>;
2828
}
2929

30-
const Component = isInstalled ? tool_comps.Dashboard : tool_comps.Readme;
30+
// Only show Dashboard if tool is configured or installed
31+
const Component = toolState === 'available' ? tool_comps.Readme : tool_comps.Dashboard;
3132

3233
return (
3334
<div className="container py-6">
3435
<div className="flex flex-col gap-6">
35-
<h1 className="text-2xl font-bold">{TOOLS[id].name}</h1>
36+
<div className="flex items-center gap-2">
37+
<h1 className="text-2xl font-bold">{TOOLS[id].name}</h1>
38+
<span className="text-sm text-muted-foreground">({toolState})</span>
39+
</div>
3640
<Component />
3741
</div>
3842
</div>

apps/web/src/app/page.tsx

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,38 @@
11
"use client";
22

33
import { useQueryState } from 'nuqs';
4-
import { Button } from '@/components/ui/button';
5-
import { Input } from '@/components/ui/input';
64
import { useState, useEffect } from 'react';
75
import { Card } from '@/components/ui/card';
86
import 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';
119
import 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-
3211
export 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+
}

apps/web/src/lib/constants.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import dynamic from 'next/dynamic';
22

3-
export type AppGridItem = {
3+
export type ToolState = 'available' | 'installed' | 'configured';
4+
5+
export interface AppGridItem {
46
id: string;
57
ds: string;
68
name: string;
79
description: string;
810
icon: string;
911
}
1012

11-
1213
export const TOOLS: Record<string, AppGridItem> = {
1314
clerk: {
1415
id: 'clerk',

apps/web/src/lib/tinybird.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { type ToolState } from './constants';
2+
13
export interface TinybirdDataSource {
24
name: string;
35
description?: string;
@@ -40,6 +42,27 @@ export async function query(token: string, sql: string): Promise<QueryResult> {
4042
return data;
4143
}
4244

45+
export async function checkToolState(token: string, datasource: string): Promise<ToolState> {
46+
try {
47+
// First check if data source exists
48+
const sources = await listDataSources(token);
49+
const exists = sources.some(source => source.name === datasource);
50+
51+
if (!exists) {
52+
return 'available';
53+
}
54+
55+
// Check if there's any data
56+
const result = await query(token, `SELECT count() as count FROM ${datasource} FORMAT JSON`);
57+
const hasData = result.data[0]?.count > 0;
58+
59+
return hasData ? 'configured' : 'installed';
60+
} catch (error) {
61+
console.error('Failed to check tool state:', error);
62+
return 'available';
63+
}
64+
}
65+
4366
export async function pipe<T = QueryResult>(
4467
token: string,
4568
pipeName: string,

0 commit comments

Comments
 (0)