Skip to content

Commit bba9349

Browse files
committed
auth0, clerk, basic dash
1 parent 5f62174 commit bba9349

File tree

17 files changed

+710
-101
lines changed

17 files changed

+710
-101
lines changed

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

Lines changed: 34 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -3,116 +3,55 @@
33
import { useQueryState } from 'nuqs';
44
import React, { useEffect, useState } from 'react';
55
import { Card } from '@/components/ui/card';
6-
import { listDataSources, query } from '@/lib/tinybird';
7-
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
8-
import Link from 'next/link';
9-
10-
const MOCK_DATA = [
11-
{ name: 'Jan', value: 400 },
12-
{ name: 'Feb', value: 300 },
13-
{ name: 'Mar', value: 600 },
14-
{ name: 'Apr', value: 800 },
15-
{ name: 'May', value: 700 }
16-
];
17-
18-
const INSTALLATION_GUIDES = {
19-
clerk: `# Installing Clerk Integration
20-
21-
1. Sign up for a Clerk account
22-
2. Create a new application
23-
3. Configure your endpoints
24-
4. Add the following to your Tinybird pipes...`,
25-
26-
resend: `# Installing Resend Integration
27-
28-
1. Create a Resend account
29-
2. Generate an API key
30-
3. Set up your email domain
31-
4. Configure the data source...`,
32-
33-
auth0: `# Installing Auth0 Integration
34-
35-
1. Set up an Auth0 account
36-
2. Create a new application
37-
3. Configure your callbacks
38-
4. Set up the event tracking...`
39-
};
6+
import { listDataSources } from '@/lib/tinybird';
7+
import dynamic from 'next/dynamic';
8+
9+
const TOOLS = {
10+
clerk: {
11+
name: 'Clerk',
12+
Dashboard: dynamic(() => import('@/components/tools/clerk/dashboard')),
13+
Readme: dynamic(() => import('@/components/tools/clerk/readme')),
14+
},
15+
resend: {
16+
name: 'Resend',
17+
Dashboard: dynamic(() => import('@/components/tools/resend/dashboard')),
18+
Readme: dynamic(() => import('@/components/tools/resend/readme')),
19+
},
20+
auth0: {
21+
name: 'Auth0',
22+
Dashboard: dynamic(() => import('@/components/tools/auth0/dashboard')),
23+
Readme: dynamic(() => import('@/components/tools/auth0/readme')),
24+
},
25+
} as const;
26+
27+
type ToolId = keyof typeof TOOLS;
4028

4129
export default function AppPage({ params }: { params: Promise<{ id: string }> }) {
4230
const [token, setToken] = useQueryState('token');
4331
const [isInstalled, setIsInstalled] = useState(false);
44-
const [totalCount, setTotalCount] = useState<number | null>(null);
45-
const { id } = React.use(params);
32+
const { id } = React.use(params) as { id: ToolId };
4633

4734
useEffect(() => {
4835
async function checkInstallation() {
4936
if (!token) return;
50-
51-
try {
52-
const sources = await listDataSources(token);
53-
setIsInstalled(sources.some(s => s.name === id));
54-
55-
if (sources.some(s => s.name === id)) {
56-
const result = await query(token, `select count() as total from ${id} format json`);
57-
setTotalCount(result.data[0]?.total || 0);
58-
}
59-
} catch (error) {
60-
console.error('Failed to check installation:', error);
61-
}
37+
const sources = await listDataSources(token);
38+
setIsInstalled(sources.some(source => source.name.startsWith(id)));
6239
}
63-
6440
checkInstallation();
6541
}, [token, id]);
6642

67-
if (!isInstalled) {
68-
return (
69-
<div className="container mx-auto p-6">
70-
<h1 className="text-3xl font-bold mb-2">Analytics for {id}</h1>
71-
<Link
72-
href={token ? `/?token=${token}` : '/'}
73-
className="text-sm text-gray-500 hover:text-gray-700 mb-8 inline-block"
74-
>
75-
← Back to Apps
76-
</Link>
77-
<Card className="p-6">
78-
<div className="prose">
79-
<pre className="p-4 bg-gray-100 rounded">
80-
{INSTALLATION_GUIDES[id as keyof typeof INSTALLATION_GUIDES] || 'Installation guide coming soon...'}
81-
</pre>
82-
</div>
83-
</Card>
84-
</div>
85-
);
43+
const tool = TOOLS[id];
44+
if (!tool) {
45+
return <div>Tool not found</div>;
8646
}
8747

48+
const Component = isInstalled ? tool.Dashboard : tool.Readme;
49+
8850
return (
89-
<div className="container mx-auto p-6">
90-
<h1 className="text-3xl font-bold mb-2">Analytics for {id}</h1>
91-
<Link
92-
href={token ? `/?token=${token}` : '/'}
93-
className="text-sm text-gray-500 hover:text-gray-700 mb-8 inline-block"
94-
>
95-
← Back to Apps
96-
</Link>
97-
<div className="space-y-6">
98-
<Card className="p-6">
99-
<h2 className="text-xl font-semibold mb-4">Total Records</h2>
100-
<p className="text-4xl font-bold">{totalCount?.toLocaleString() || '...'}</p>
101-
</Card>
102-
<Card className="p-6">
103-
<h2 className="text-xl font-semibold mb-4">Usage Over Time</h2>
104-
<div className="h-[400px]">
105-
<ResponsiveContainer width="100%" height="100%">
106-
<LineChart data={MOCK_DATA}>
107-
<CartesianGrid strokeDasharray="3 3" />
108-
<XAxis dataKey="name" />
109-
<YAxis />
110-
<Tooltip />
111-
<Line type="monotone" dataKey="value" stroke="#8884d8" />
112-
</LineChart>
113-
</ResponsiveContainer>
114-
</div>
115-
</Card>
51+
<div className="container py-6">
52+
<div className="flex flex-col gap-6">
53+
<h1 className="text-2xl font-bold">{tool.name}</h1>
54+
<Component />
11655
</div>
11756
</div>
11857
);

apps/web/src/app/api/query/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export async function GET(request: Request) {
1212
const url = new URL('https://api.tinybird.co/v0/sql');
1313
url.searchParams.set('q', query);
1414

15-
console.log(url);
1615
const response = await fetch(url.toString(), {
1716
method: 'GET',
1817
headers: {
@@ -22,5 +21,6 @@ export async function GET(request: Request) {
2221
});
2322

2423
const data = await response.json();
24+
console.log(data);
2525
return NextResponse.json(data);
2626
}

apps/web/src/app/globals.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,21 @@ body {
7070
@apply bg-background text-foreground;
7171
}
7272
}
73+
74+
@layer base {
75+
:root {
76+
--chart-1: 12 76% 61%;
77+
--chart-2: 173 58% 39%;
78+
--chart-3: 197 37% 24%;
79+
--chart-4: 43 74% 66%;
80+
--chart-5: 27 87% 67%;
81+
}
82+
83+
.dark {
84+
--chart-1: 220 70% 50%;
85+
--chart-2: 160 60% 45%;
86+
--chart-3: 30 80% 55%;
87+
--chart-4: 280 65% 60%;
88+
--chart-5: 340 75% 55%;
89+
}
90+
}

apps/web/src/app/layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ export default function RootLayout({
2828
<body
2929
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
3030
>
31-
<NuqsAdapter>{children}</NuqsAdapter>
31+
<NuqsAdapter>
32+
<div className="container mx-auto p-6">
33+
{children}
34+
</div>
35+
</NuqsAdapter>
3236
</body>
3337
</html>
3438
);

apps/web/src/app/page.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { listDataSources } from '@/lib/tinybird';
1010

1111
interface AppGridItem {
1212
id: string;
13+
ds: string;
1314
name: string;
1415
description: string;
1516
icon: string;
@@ -18,18 +19,21 @@ interface AppGridItem {
1819
const KNOWN_APPS: AppGridItem[] = [
1920
{
2021
id: 'clerk',
22+
ds: 'clerk',
2123
name: 'Clerk',
2224
description: 'Authentication and user management',
2325
icon: '🔐'
2426
},
2527
{
2628
id: 'resend',
29+
ds: 'resend',
2730
name: 'Resend',
2831
description: 'Email delivery service',
2932
icon: '✉️'
3033
},
3134
{
3235
id: 'auth0',
36+
ds: 'auth0_logs',
3337
name: 'Auth0',
3438
description: 'Identity platform',
3539
icon: '🔑'
@@ -48,7 +52,6 @@ export default function Home() {
4852

4953
setIsLoading(true);
5054
try {
51-
console.log(token);
5255
const sources = await listDataSources(token);
5356
const installed = sources.map(s => s.name);
5457
setInstalledApps(installed);
@@ -86,7 +89,7 @@ export default function Home() {
8689
}
8790

8891
return (
89-
<div className="container mx-auto p-6">
92+
<>
9093
<h1 className="text-3xl font-bold mb-8">Your Apps</h1>
9194
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
9295
{KNOWN_APPS.map((app) => (
@@ -101,7 +104,7 @@ export default function Home() {
101104
<h2 className="text-xl font-semibold">{app.name}</h2>
102105
<p className="text-gray-500">{app.description}</p>
103106
<div className="mt-2">
104-
{installedApps.includes(app.id) ? (
107+
{installedApps.includes(app.ds) ? (
105108
<span className="text-green-500 text-sm">Installed</span>
106109
) : (
107110
<span className="text-gray-400 text-sm">Not installed</span>
@@ -113,6 +116,6 @@ export default function Home() {
113116
</Link>
114117
))}
115118
</div>
116-
</div>
119+
</>
117120
);
118121
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"use client"
2+
3+
import { useQueryState } from 'nuqs'
4+
import { useEffect, useState } from 'react'
5+
import Link from 'next/link'
6+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
7+
import { query } from '@/lib/tinybird'
8+
import MetricCard from './metric'
9+
10+
export default function Auth0Dashboard() {
11+
const [token] = useQueryState('token')
12+
const [totalUsers, setTotalUsers] = useState<number>(0)
13+
const [activeUsers, setActiveUsers] = useState<number>(0)
14+
const [conversionRate, setConversionRate] = useState<number>(0)
15+
16+
useEffect(() => {
17+
async function fetchMetrics() {
18+
if (!token) return
19+
20+
try {
21+
const [totalResult, activeResult] = await Promise.all([
22+
query(token, "SELECT count() as total FROM auth0_logs WHERE type = 'ss' FORMAT JSON"),
23+
query(token, "SELECT count(DISTINCT user_id) as active FROM auth0_logs where type == 's' and date >= now() - interval 30 days FORMAT JSON")
24+
])
25+
26+
const total = totalResult.data[0]?.total || 0
27+
const active = activeResult.data[0]?.active || 0
28+
29+
setTotalUsers(total)
30+
setActiveUsers(active)
31+
setConversionRate(total > 0 ? Math.round((active / total) * 100) : 0)
32+
} catch (error) {
33+
console.error('Failed to fetch metrics:', error)
34+
}
35+
}
36+
37+
fetchMetrics()
38+
}, [token])
39+
40+
return (
41+
<div className="space-y-8">
42+
<div>
43+
<h1 className="text-2xl font-bold">Auth0 Analytics</h1>
44+
<Link
45+
href={token ? `/?token=${token}` : '/'}
46+
className="text-sm text-muted-foreground hover:text-primary"
47+
>
48+
← Back to Apps
49+
</Link>
50+
</div>
51+
52+
{/* Metrics Row */}
53+
<div className="grid gap-4 md:grid-cols-3">
54+
<MetricCard
55+
title="Total Users"
56+
value={totalUsers.toLocaleString()}
57+
description="Total registered users"
58+
/>
59+
<MetricCard
60+
title="Active Users (30d)"
61+
value={activeUsers.toLocaleString()}
62+
description="Users active in the last 30 days"
63+
/>
64+
<MetricCard
65+
title="Conversion Rate"
66+
value={`${conversionRate}%`}
67+
description="Active users / Total users"
68+
/>
69+
</div>
70+
71+
{/* Charts Grid */}
72+
<div className="grid gap-4 md:grid-cols-2">
73+
<Card className="col-span-1">
74+
<CardHeader>
75+
<CardTitle>User Growth</CardTitle>
76+
</CardHeader>
77+
<CardContent className="h-[300px]">
78+
{/* User Growth Chart will go here */}
79+
</CardContent>
80+
</Card>
81+
<Card className="col-span-1">
82+
<CardHeader>
83+
<CardTitle>Daily Active Users</CardTitle>
84+
</CardHeader>
85+
<CardContent className="h-[300px]">
86+
{/* DAU Chart will go here */}
87+
</CardContent>
88+
</Card>
89+
<Card className="col-span-1">
90+
<CardHeader>
91+
<CardTitle>User Actions</CardTitle>
92+
</CardHeader>
93+
<CardContent className="h-[300px]">
94+
{/* User Actions Chart will go here */}
95+
</CardContent>
96+
</Card>
97+
<Card className="col-span-1">
98+
<CardHeader>
99+
<CardTitle>Session Duration</CardTitle>
100+
</CardHeader>
101+
<CardContent className="h-[300px]">
102+
{/* Session Duration Chart will go here */}
103+
</CardContent>
104+
</Card>
105+
</div>
106+
</div>
107+
)
108+
}

0 commit comments

Comments
 (0)