|
1 | 1 | "use client"; |
2 | 2 |
|
3 | 3 | import { useQueryState } from 'nuqs'; |
4 | | -import { useState, useEffect, Suspense } from 'react'; |
5 | | -import { Card } from '@/components/ui/card'; |
6 | | -import Link from 'next/link'; |
7 | | -import { checkToolState, InvalidTokenError } from '@/lib/tinybird'; |
8 | | -import { TOOLS, type AppGridItem, type ToolState } from '@/lib/constants'; |
| 4 | +import { Suspense } from 'react'; |
9 | 5 | import TokenPrompt from '@/components/token-prompt'; |
10 | | -import { SectionHeader } from '@/components/section-header'; |
11 | 6 |
|
12 | 7 | function HomeContent() { |
13 | | - const [token, setToken] = useQueryState('token'); |
14 | | - const [toolStates, setToolStates] = useState<Record<string, ToolState>>({}); |
15 | | - const [isLoading, setIsLoading] = useState(false); |
16 | | - const [error, setError] = useState<string>(); |
17 | | - const [isValidToken, setIsValidToken] = useState(false); |
| 8 | + const [token] = useQueryState('token'); |
18 | 9 |
|
19 | | - useEffect(() => { |
20 | | - async function fetchToolStates() { |
21 | | - if (!token) { |
22 | | - setIsValidToken(false); |
23 | | - return; |
24 | | - } |
25 | | - setIsLoading(true); |
26 | | - setError(undefined); |
27 | | - try { |
28 | | - const states = await Promise.all( |
29 | | - Object.values(TOOLS).map(async (app) => { |
30 | | - const state = await checkToolState(token, app.ds); |
31 | | - return [app.id, state] as const; |
32 | | - }) |
33 | | - ); |
34 | | - setToolStates(Object.fromEntries(states)); |
35 | | - setIsValidToken(true); |
36 | | - } catch (error) { |
37 | | - if (error instanceof InvalidTokenError) { |
38 | | - setError('Invalid token'); |
39 | | - setToken(null); |
40 | | - setIsValidToken(false); |
41 | | - } else { |
42 | | - console.error('Failed to fetch tool states:', error); |
43 | | - setError('Failed to fetch tool states'); |
44 | | - setIsValidToken(false); |
45 | | - } |
46 | | - } finally { |
47 | | - setIsLoading(false); |
48 | | - } |
49 | | - } |
50 | | - |
51 | | - fetchToolStates(); |
52 | | - }, [token, setToken]); |
53 | | - |
54 | | - if (!token || !isValidToken) { |
55 | | - return ( |
56 | | - <div className="container py-6"> |
57 | | - <TokenPrompt error={error} /> |
58 | | - </div> |
59 | | - ); |
| 10 | + if (!token) { |
| 11 | + return <TokenPrompt />; |
60 | 12 | } |
61 | 13 |
|
62 | 14 | return ( |
63 | | - <div className="container py-6"> |
64 | | - {isLoading ? ( |
65 | | - <div className="flex items-center justify-center"> |
66 | | - <p className="text-lg font-semibold">Loading...</p> |
67 | | - </div> |
68 | | - ) : ( |
69 | | - <div className="space-y-8"> |
70 | | - {/* Configured Apps */} |
71 | | - {Object.values(TOOLS).some(app => toolStates[app.id] === 'configured') && ( |
72 | | - <div className="space-y-4"> |
73 | | - <SectionHeader |
74 | | - title="Configured Apps" |
75 | | - tooltip="These apps are fully set up and have data. They're ready to use!" |
76 | | - /> |
77 | | - <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> |
78 | | - {Object.values(TOOLS) |
79 | | - .filter(app => toolStates[app.id] === 'configured') |
80 | | - .map(app => ( |
81 | | - <AppCard |
82 | | - key={app.id} |
83 | | - app={app} |
84 | | - state={toolStates[app.id]} |
85 | | - token={token} |
86 | | - /> |
87 | | - ))} |
88 | | - </div> |
89 | | - </div> |
90 | | - )} |
91 | | - |
92 | | - {/* Installed Apps */} |
93 | | - {Object.values(TOOLS).some(app => toolStates[app.id] === 'installed') && ( |
94 | | - <div className="space-y-4"> |
95 | | - <SectionHeader |
96 | | - title="Installed Apps" |
97 | | - tooltip="Your Tinybird Workspace has the Data Sources installed, but you're not receiving data. Click an app to learn how to add data." |
98 | | - /> |
99 | | - <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> |
100 | | - {Object.values(TOOLS) |
101 | | - .filter(app => toolStates[app.id] === 'installed') |
102 | | - .map(app => ( |
103 | | - <AppCard |
104 | | - key={app.id} |
105 | | - app={app} |
106 | | - state={toolStates[app.id]} |
107 | | - token={token} |
108 | | - /> |
109 | | - ))} |
110 | | - </div> |
111 | | - </div> |
112 | | - )} |
113 | | - |
114 | | - {/* Available Apps */} |
115 | | - <div className="space-y-4"> |
116 | | - <SectionHeader |
117 | | - title="Available Apps" |
118 | | - tooltip="Your Tinybird Workspace doesn't have the Data Sources installed yet. Click an app to learn how to install it." |
119 | | - /> |
120 | | - <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> |
121 | | - {Object.values(TOOLS) |
122 | | - .filter(app => !toolStates[app.id] || toolStates[app.id] === 'available') |
123 | | - .map(app => ( |
124 | | - <AppCard |
125 | | - key={app.id} |
126 | | - app={app} |
127 | | - state={toolStates[app.id] || 'available'} |
128 | | - token={token} |
129 | | - /> |
130 | | - ))} |
131 | | - </div> |
132 | | - </div> |
133 | | - </div> |
134 | | - )} |
| 15 | + <div className="py-6"> |
| 16 | + <div className="space-y-4"> |
| 17 | + <h1 className="text-2xl font-bold">Welcome to Tinynest</h1> |
| 18 | + <p className="text-muted-foreground"> |
| 19 | + Select an app from the sidebar to get started. Apps are organized by their status: |
| 20 | + </p> |
| 21 | + <ul className="list-disc list-inside space-y-2 text-muted-foreground"> |
| 22 | + <li><strong>Configured Apps</strong> - These apps are fully set up and have data. They're ready to use!</li> |
| 23 | + <li><strong>Installed Apps</strong> - Your Tinybird Workspace has the Data Sources installed, but you're not receiving data.</li> |
| 24 | + <li><strong>Available Apps</strong> - Your Tinybird Workspace doesn't have the Data Sources installed yet.</li> |
| 25 | + </ul> |
| 26 | + </div> |
135 | 27 | </div> |
136 | 28 | ); |
137 | 29 | } |
138 | 30 |
|
139 | | -function AppCard({ |
140 | | - app, |
141 | | - state, |
142 | | - token |
143 | | -}: { |
144 | | - app: AppGridItem; |
145 | | - state: ToolState; |
146 | | - token?: string; |
147 | | -}) { |
148 | | - const stateColors = { |
149 | | - configured: 'border-green-500', |
150 | | - installed: 'border-blue-500', |
151 | | - available: '' |
152 | | - }; |
153 | | - |
154 | | - return ( |
155 | | - <Link |
156 | | - key={app.id} |
157 | | - href={`/${app.id}${token ? `?token=${token}` : ''}`} |
158 | | - > |
159 | | - <Card className={`p-4 hover:bg-accent ${stateColors[state]}`}> |
160 | | - <div className="flex items-center gap-4"> |
161 | | - <div className="text-2xl">{app.icon}</div> |
162 | | - <div> |
163 | | - <div className="flex items-center gap-2"> |
164 | | - <h3 className="font-semibold">{app.name}</h3> |
165 | | - <span className="text-xs text-muted-foreground">({state})</span> |
166 | | - </div> |
167 | | - <p className="text-sm text-muted-foreground">{app.description}</p> |
168 | | - </div> |
169 | | - </div> |
170 | | - </Card> |
171 | | - </Link> |
172 | | - ); |
173 | | -} |
174 | | - |
175 | 31 | export default function Home() { |
176 | 32 | return ( |
177 | 33 | <Suspense> |
|
0 commit comments