Skip to content

Commit c42f7f9

Browse files
committed
fix: add error handling for missing env vars and undefined query state
1 parent 36bf466 commit c42f7f9

2 files changed

Lines changed: 184 additions & 143 deletions

File tree

src/main.tsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ import { ConvexProviderWithAuthKit } from "@convex-dev/workos";
77
import App from "./App";
88
import "./index.css";
99

10-
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
10+
// Ensure VITE_CONVEX_URL is defined before creating client
11+
const convexUrl = import.meta.env.VITE_CONVEX_URL;
12+
if (!convexUrl) {
13+
console.error(
14+
"VITE_CONVEX_URL is not set. Please check your environment variables.",
15+
);
16+
}
17+
const convex = convexUrl ? new ConvexReactClient(convexUrl) : null;
1118

1219
// Handle redirect after OAuth callback
1320
// This is called by AuthKit after the authorization code exchange completes
@@ -20,11 +27,39 @@ const onRedirectCallback = () => {
2027
};
2128

2229
function Root() {
30+
// If Convex client failed to initialize, show setup error
31+
if (!convex) {
32+
return (
33+
<div className="min-h-screen bg-[#0E0E0E] flex items-center justify-center p-4">
34+
<div className="text-center max-w-md">
35+
<h1 className="text-xl font-medium text-zinc-100 mb-2">
36+
Setup Required
37+
</h1>
38+
<p className="text-sm text-zinc-400 mb-4">
39+
Missing VITE_CONVEX_URL environment variable. Please complete the
40+
setup.
41+
</p>
42+
<a
43+
href="https://github.com/waynesutton/opensync/blob/main/ONE-CLICK-DEPLOY.md"
44+
className="inline-flex items-center gap-1 text-sm text-yellow-500 hover:text-yellow-400 underline"
45+
target="_blank"
46+
rel="noopener noreferrer"
47+
>
48+
View setup guide
49+
</a>
50+
</div>
51+
</div>
52+
);
53+
}
54+
2355
return (
2456
<AuthKitProvider
2557
clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}
26-
redirectUri={import.meta.env.VITE_REDIRECT_URI || `${window.location.origin}/callback`}
27-
devMode={true} // Force localStorage tokens to avoid third-party cookie blocking in production
58+
redirectUri={
59+
import.meta.env.VITE_REDIRECT_URI ||
60+
`${window.location.origin}/callback`
61+
}
62+
devMode={true} // Force localStorage tokens to avoid third-party cookie blocking in production
2863
onRedirectCallback={onRedirectCallback}
2964
>
3065
<ConvexProviderWithAuthKit client={convex} useAuth={useAuth}>
@@ -39,5 +74,5 @@ function Root() {
3974
ReactDOM.createRoot(document.getElementById("root")!).render(
4075
<React.StrictMode>
4176
<Root />
42-
</React.StrictMode>
77+
</React.StrictMode>,
4378
);

src/pages/Login.tsx

Lines changed: 145 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -169,181 +169,187 @@ function getSourceDisplayName(source: string): string {
169169

170170
// Real-time platform leaderboard component (two boxes: Top Models, Top CLI)
171171
function PlatformLeaderboard({ isDark }: { isDark: boolean }) {
172+
// Skip query if Convex isn't ready (prevents crash on initialization)
172173
const platformStats = useQuery(api.analytics.publicPlatformStats);
173174

174175
// No spinner - Convex is real-time, data appears when ready
175-
// Show the container with "No data yet" if empty
176+
// Show the container with "No data yet" if empty or query not ready
176177
const topModels = platformStats?.topModels ?? [];
177178
const topSources = platformStats?.topSources ?? [];
178179

180+
// Don't render anything until query is ready (prevents hydration issues)
181+
if (platformStats === undefined) {
182+
return null;
183+
}
184+
179185
return (
180-
<div
181-
className={`mt-10 rounded-lg border p-5 ${
182-
isDark
183-
? "border-zinc-800 bg-[#161616]"
184-
: "border-[#e6e4e1] bg-[#f5f3f0]"
186+
<div
187+
className={`mt-10 rounded-lg border p-5 ${
188+
isDark
189+
? "border-zinc-800 bg-[#161616]"
190+
: "border-[#e6e4e1] bg-[#f5f3f0]"
191+
}`}
192+
>
193+
<h3
194+
className={`text-sm font-medium mb-5 flex items-center gap-2 ${
195+
isDark ? "text-zinc-300" : "text-[#1a1a1a]"
185196
}`}
186197
>
187-
<h3
188-
className={`text-sm font-medium mb-5 flex items-center gap-2 ${
189-
isDark ? "text-zinc-300" : "text-[#1a1a1a]"
190-
}`}
198+
<Zap
199+
className={`h-4 w-4 ${isDark ? "text-zinc-500" : "text-[#8b7355]"}`}
200+
/>
201+
Platform Stats
202+
<span
203+
className={`ml-auto text-[10px] font-normal ${isDark ? "text-zinc-600" : "text-[#8b7355]"}`}
191204
>
192-
<Zap
193-
className={`h-4 w-4 ${isDark ? "text-zinc-500" : "text-[#8b7355]"}`}
194-
/>
195-
Platform Stats
196-
<span
197-
className={`ml-auto text-[10px] font-normal ${isDark ? "text-zinc-600" : "text-[#8b7355]"}`}
198-
>
199-
real-time
200-
</span>
201-
</h3>
205+
real-time
206+
</span>
207+
</h3>
202208

203-
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
204-
{/* Top Models */}
205-
<div
206-
className={`rounded-md border p-4 ${
207-
isDark
208-
? "border-zinc-800 bg-[#0E0E0E]"
209-
: "border-[#e6e4e1] bg-[#faf8f5]"
210-
}`}
211-
>
212-
<div className="flex items-center justify-between mb-3">
213-
<div className="flex items-center gap-1.5">
214-
<Trophy
215-
className={`h-3 w-3 ${isDark ? "text-amber-500" : "text-amber-600"}`}
216-
/>
217-
<p
218-
className={`text-[10px] uppercase tracking-wider ${
219-
isDark ? "text-zinc-600" : "text-[#8b7355]"
220-
}`}
221-
>
222-
Top Models
223-
</p>
224-
</div>
225-
<span
209+
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
210+
{/* Top Models */}
211+
<div
212+
className={`rounded-md border p-4 ${
213+
isDark
214+
? "border-zinc-800 bg-[#0E0E0E]"
215+
: "border-[#e6e4e1] bg-[#faf8f5]"
216+
}`}
217+
>
218+
<div className="flex items-center justify-between mb-3">
219+
<div className="flex items-center gap-1.5">
220+
<Trophy
221+
className={`h-3 w-3 ${isDark ? "text-amber-500" : "text-amber-600"}`}
222+
/>
223+
<p
226224
className={`text-[10px] uppercase tracking-wider ${
227225
isDark ? "text-zinc-600" : "text-[#8b7355]"
228226
}`}
229227
>
230-
tokens
231-
</span>
228+
Top Models
229+
</p>
232230
</div>
233-
<div className="space-y-1.5">
234-
{topModels.length === 0 ? (
235-
<p
236-
className={`text-xs ${isDark ? "text-zinc-600" : "text-[#8b7355]"}`}
231+
<span
232+
className={`text-[10px] uppercase tracking-wider ${
233+
isDark ? "text-zinc-600" : "text-[#8b7355]"
234+
}`}
235+
>
236+
tokens
237+
</span>
238+
</div>
239+
<div className="space-y-1.5">
240+
{topModels.length === 0 ? (
241+
<p
242+
className={`text-xs ${isDark ? "text-zinc-600" : "text-[#8b7355]"}`}
243+
>
244+
No data yet
245+
</p>
246+
) : (
247+
topModels.map((item, index) => (
248+
<div
249+
key={item.model}
250+
className="flex items-center justify-between"
237251
>
238-
No data yet
239-
</p>
240-
) : (
241-
topModels.map((item, index) => (
242-
<div
243-
key={item.model}
244-
className="flex items-center justify-between"
252+
<span
253+
className={`text-xs truncate max-w-[140px] ${
254+
isDark ? "text-zinc-400" : "text-[#6b6b6b]"
255+
}`}
245256
>
246257
<span
247-
className={`text-xs truncate max-w-[140px] ${
248-
isDark ? "text-zinc-400" : "text-[#6b6b6b]"
258+
className={`mr-1.5 ${
259+
index === 0
260+
? isDark
261+
? "text-amber-500"
262+
: "text-amber-600"
263+
: isDark
264+
? "text-zinc-600"
265+
: "text-[#8b7355]"
249266
}`}
250267
>
251-
<span
252-
className={`mr-1.5 ${
253-
index === 0
254-
? isDark
255-
? "text-amber-500"
256-
: "text-amber-600"
257-
: isDark
258-
? "text-zinc-600"
259-
: "text-[#8b7355]"
260-
}`}
261-
>
262-
{index + 1}.
263-
</span>
264-
{item.model}
268+
{index + 1}.
265269
</span>
266-
<span
267-
className={`text-[10px] tabular-nums ${
268-
isDark ? "text-zinc-500" : "text-[#8b7355]"
269-
}`}
270-
>
271-
{formatNumber(item.totalTokens)}
272-
</span>
273-
</div>
274-
))
275-
)}
276-
</div>
270+
{item.model}
271+
</span>
272+
<span
273+
className={`text-[10px] tabular-nums ${
274+
isDark ? "text-zinc-500" : "text-[#8b7355]"
275+
}`}
276+
>
277+
{formatNumber(item.totalTokens)}
278+
</span>
279+
</div>
280+
))
281+
)}
277282
</div>
283+
</div>
278284

279-
{/* Top CLI (sources: opencode, claude-code, cursor, droid, codex, amp, etc.) */}
280-
<div
281-
className={`rounded-md border p-4 ${
282-
isDark
283-
? "border-zinc-800 bg-[#0E0E0E]"
284-
: "border-[#e6e4e1] bg-[#faf8f5]"
285-
}`}
286-
>
287-
<div className="flex items-center gap-1.5 mb-3">
288-
<Zap
289-
className={`h-3 w-3 ${isDark ? "text-blue-400" : "text-[#EB5601]"}`}
290-
/>
285+
{/* Top CLI (sources: opencode, claude-code, cursor, droid, codex, amp, etc.) */}
286+
<div
287+
className={`rounded-md border p-4 ${
288+
isDark
289+
? "border-zinc-800 bg-[#0E0E0E]"
290+
: "border-[#e6e4e1] bg-[#faf8f5]"
291+
}`}
292+
>
293+
<div className="flex items-center gap-1.5 mb-3">
294+
<Zap
295+
className={`h-3 w-3 ${isDark ? "text-blue-400" : "text-[#EB5601]"}`}
296+
/>
297+
<p
298+
className={`text-[10px] uppercase tracking-wider ${
299+
isDark ? "text-zinc-600" : "text-[#8b7355]"
300+
}`}
301+
>
302+
Top CLI
303+
</p>
304+
</div>
305+
<div className="space-y-1.5">
306+
{topSources.length === 0 ? (
291307
<p
292-
className={`text-[10px] uppercase tracking-wider ${
293-
isDark ? "text-zinc-600" : "text-[#8b7355]"
294-
}`}
308+
className={`text-xs ${isDark ? "text-zinc-600" : "text-[#8b7355]"}`}
295309
>
296-
Top CLI
310+
No data yet
297311
</p>
298-
</div>
299-
<div className="space-y-1.5">
300-
{topSources.length === 0 ? (
301-
<p
302-
className={`text-xs ${isDark ? "text-zinc-600" : "text-[#8b7355]"}`}
312+
) : (
313+
topSources.map((item, index) => (
314+
<div
315+
key={item.source}
316+
className="flex items-center justify-between"
303317
>
304-
No data yet
305-
</p>
306-
) : (
307-
topSources.map((item, index) => (
308-
<div
309-
key={item.source}
310-
className="flex items-center justify-between"
318+
<span
319+
className={`text-xs truncate max-w-[140px] ${
320+
isDark ? "text-zinc-400" : "text-[#6b6b6b]"
321+
}`}
311322
>
312323
<span
313-
className={`text-xs truncate max-w-[140px] ${
314-
isDark ? "text-zinc-400" : "text-[#6b6b6b]"
315-
}`}
316-
>
317-
<span
318-
className={`mr-1.5 ${
319-
index === 0
320-
? isDark
321-
? "text-blue-400"
322-
: "text-[#EB5601]"
323-
: isDark
324-
? "text-zinc-600"
325-
: "text-[#8b7355]"
326-
}`}
327-
>
328-
{index + 1}.
329-
</span>
330-
{getSourceDisplayName(item.source)}
331-
</span>
332-
<span
333-
className={`text-[10px] tabular-nums ${
334-
isDark ? "text-zinc-500" : "text-[#8b7355]"
324+
className={`mr-1.5 ${
325+
index === 0
326+
? isDark
327+
? "text-blue-400"
328+
: "text-[#EB5601]"
329+
: isDark
330+
? "text-zinc-600"
331+
: "text-[#8b7355]"
335332
}`}
336333
>
337-
{item.sessions} sessions
334+
{index + 1}.
338335
</span>
339-
</div>
340-
))
341-
)}
342-
</div>
336+
{getSourceDisplayName(item.source)}
337+
</span>
338+
<span
339+
className={`text-[10px] tabular-nums ${
340+
isDark ? "text-zinc-500" : "text-[#8b7355]"
341+
}`}
342+
>
343+
{item.sessions} sessions
344+
</span>
345+
</div>
346+
))
347+
)}
343348
</div>
344349
</div>
345350
</div>
346-
);
351+
</div>
352+
);
347353
}
348354

349355
// TEMP: Stats components moved to /stats page

0 commit comments

Comments
 (0)