Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions packages/docs/audits/hono-to-tanstack-migration.md

Large diffs are not rendered by default.

53 changes: 38 additions & 15 deletions packages/web/src/components/admin/AnalyticsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ import {
HomeIcon,
DollarSignIcon,
} from 'lucide-react';
import { parseResponse } from 'hono/client';
import { api } from '@/lib/rpc';
import { queryKeys } from '@/lib/queryKeys';

async function fetchStats<T>(path: string): Promise<T> {
const res = await fetch(path, { credentials: 'include' });
const data = (await res.json()) as T;
if (!res.ok) throw data;
return data;
}
import { LineChart, BarChart, DoughnutChart } from '@/components/admin/charts';
import { AdminBox } from '@/components/admin/ui';

Expand Down Expand Up @@ -49,13 +54,35 @@ export function AnalyticsSection() {
const [signupDays, setSignupDays] = useState(30);
const [webhookDays, setWebhookDays] = useState(7);

interface DailyCountSeries {
data: { date: string; count: number }[];
total: number;
days: number;
}
interface WebhookSeries {
data: { date: string; success: number; failed: number; pending: number }[];
totals: { success: number; failed: number; pending: number };
days: number;
}
interface SubscriptionStats {
active: number;
trialing: number;
pastDue: number;
canceled: number;
hasMore: boolean;
}
interface RevenueStats {
data: { month: string; label: string; revenue: number }[];
total: number;
currency: string;
months: number;
}

const signupQuery = useQuery({
queryKey: [...queryKeys.admin.stats, 'signups', signupDays],
queryFn: async () => {
try {
return await parseResponse(
api.api.admin.stats.signups.$get({ query: { days: signupDays.toString() } }),
);
return await fetchStats<DailyCountSeries>(`/api/admin/stats/signups?days=${signupDays}`);
} catch (err) {
console.warn('Failed to fetch signups stats:', (err as Error).message);
return null;
Expand All @@ -68,8 +95,8 @@ export function AnalyticsSection() {
queryKey: [...queryKeys.admin.stats, 'organizations', signupDays],
queryFn: async () => {
try {
return await parseResponse(
api.api.admin.stats.organizations.$get({ query: { days: signupDays.toString() } }),
return await fetchStats<DailyCountSeries>(
`/api/admin/stats/organizations?days=${signupDays}`,
);
} catch (err) {
console.warn('Failed to fetch organizations stats:', (err as Error).message);
Expand All @@ -83,9 +110,7 @@ export function AnalyticsSection() {
queryKey: [...queryKeys.admin.stats, 'projects', signupDays],
queryFn: async () => {
try {
return await parseResponse(
api.api.admin.stats.projects.$get({ query: { days: signupDays.toString() } }),
);
return await fetchStats<DailyCountSeries>(`/api/admin/stats/projects?days=${signupDays}`);
} catch (err) {
console.warn('Failed to fetch projects stats:', (err as Error).message);
return null;
Expand All @@ -98,9 +123,7 @@ export function AnalyticsSection() {
queryKey: [...queryKeys.admin.stats, 'webhooks', webhookDays],
queryFn: async () => {
try {
return await parseResponse(
api.api.admin.stats.webhooks.$get({ query: { days: webhookDays.toString() } }),
);
return await fetchStats<WebhookSeries>(`/api/admin/stats/webhooks?days=${webhookDays}`);
} catch (err) {
console.warn('Failed to fetch webhooks stats:', (err as Error).message);
return null;
Expand All @@ -113,7 +136,7 @@ export function AnalyticsSection() {
queryKey: [...queryKeys.admin.stats, 'subscriptions'],
queryFn: async () => {
try {
return await parseResponse(api.api.admin.stats.subscriptions.$get());
return await fetchStats<SubscriptionStats>('/api/admin/stats/subscriptions');
} catch (err) {
console.warn('Failed to fetch subscriptions stats:', (err as Error).message);
return null;
Expand All @@ -126,7 +149,7 @@ export function AnalyticsSection() {
queryKey: [...queryKeys.admin.stats, 'revenue'],
queryFn: async () => {
try {
return await parseResponse(api.api.admin.stats.revenue.$get({ query: { months: '6' } }));
return await fetchStats<RevenueStats>('/api/admin/stats/revenue?months=6');
} catch (err) {
console.warn('Failed to fetch revenue stats:', (err as Error).message);
return null;
Expand Down
137 changes: 84 additions & 53 deletions packages/web/src/hooks/useAdminQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
*/

import { useQuery } from '@tanstack/react-query';
import { parseResponse } from 'hono/client';
import { api } from '@/lib/rpc';
import { queryKeys } from '@/lib/queryKeys';
import {
fetchOrgs,
Expand All @@ -25,7 +23,12 @@ const ADMIN_QUERY_CONFIG = {
export function useAdminStats() {
return useQuery({
queryKey: queryKeys.admin.stats,
queryFn: () => parseResponse(api.api.admin.stats.$get()),
queryFn: async () => {
const res = await fetch('/api/admin/stats', { credentials: 'include' });
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
...ADMIN_QUERY_CONFIG,
});
}
Expand All @@ -36,13 +39,13 @@ export function useAdminUsers(params: { page?: number; limit?: number; search?:
const search = params.search ?? '';
return useQuery({
queryKey: queryKeys.admin.users(page, limit, search),
queryFn: () => {
const query: Record<string, string> = {
page: page.toString(),
limit: limit.toString(),
};
if (search) query.search = search;
return parseResponse(api.api.admin.users.$get({ query }));
queryFn: async () => {
const qs = new URLSearchParams({ page: page.toString(), limit: limit.toString() });
if (search) qs.set('search', search);
const res = await fetch(`/api/admin/users?${qs.toString()}`, { credentials: 'include' });
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
...ADMIN_QUERY_CONFIG,
});
Expand All @@ -51,8 +54,14 @@ export function useAdminUsers(params: { page?: number; limit?: number; search?:
export function useAdminUserDetails(userId: string | null | undefined) {
return useQuery({
queryKey: queryKeys.admin.userDetails(userId),
queryFn: () =>
parseResponse(api.api.admin.users[':userId'].$get({ param: { userId: userId! } })),
queryFn: async () => {
const res = await fetch(`/api/admin/users/${encodeURIComponent(userId!)}`, {
credentials: 'include',
});
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
enabled: !!userId,
...ADMIN_QUERY_CONFIG,
});
Expand All @@ -67,14 +76,16 @@ export function useAdminProjects(
const orgId = params.orgId ?? '';
return useQuery({
queryKey: queryKeys.admin.projects(page, limit, search, orgId),
queryFn: () => {
const query: Record<string, string> = {
page: page.toString(),
limit: limit.toString(),
};
if (search) query.search = search;
if (orgId) query.orgId = orgId;
return parseResponse(api.api.admin.projects.$get({ query }));
queryFn: async () => {
const qs = new URLSearchParams({ page: page.toString(), limit: limit.toString() });
if (search) qs.set('search', search);
if (orgId) qs.set('orgId', orgId);
const res = await fetch(`/api/admin/projects?${qs.toString()}`, {
credentials: 'include',
});
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
...ADMIN_QUERY_CONFIG,
});
Expand All @@ -83,10 +94,14 @@ export function useAdminProjects(
export function useAdminProjectDetails(projectId: string | null | undefined) {
return useQuery({
queryKey: queryKeys.admin.projectDetails(projectId),
queryFn: () =>
parseResponse(
api.api.admin.projects[':projectId'].$get({ param: { projectId: projectId! } }),
),
queryFn: async () => {
const res = await fetch(`/api/admin/projects/${encodeURIComponent(projectId!)}`, {
credentials: 'include',
});
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
enabled: !!projectId,
...ADMIN_QUERY_CONFIG,
});
Expand All @@ -95,12 +110,14 @@ export function useAdminProjectDetails(projectId: string | null | undefined) {
export function useAdminProjectDocStats(projectId: string | null | undefined) {
return useQuery({
queryKey: queryKeys.admin.projectDocStats(projectId),
queryFn: () =>
parseResponse(
api.api.admin.projects[':projectId']['doc-stats'].$get({
param: { projectId: projectId! },
}),
),
queryFn: async () => {
const res = await fetch(`/api/admin/projects/${encodeURIComponent(projectId!)}/doc-stats`, {
credentials: 'include',
});
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
enabled: !!projectId,
...ADMIN_QUERY_CONFIG,
});
Expand All @@ -115,12 +132,17 @@ export function useStorageDocuments(
const search = params.search ?? '';
return useQuery({
queryKey: queryKeys.admin.storageDocuments(cursor, limit, prefix, search),
queryFn: () => {
const query: Record<string, string> = { limit: limit.toString() };
if (cursor) query.cursor = cursor;
if (prefix) query.prefix = prefix;
if (search) query.search = search;
return parseResponse(api.api.admin.storage.documents.$get({ query }));
queryFn: async () => {
const params = new URLSearchParams({ limit: limit.toString() });
if (cursor) params.set('cursor', cursor);
if (prefix) params.set('prefix', prefix);
if (search) params.set('search', search);
const res = await fetch(`/api/admin/storage/documents?${params.toString()}`, {
credentials: 'include',
});
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
...ADMIN_QUERY_CONFIG,
});
Expand Down Expand Up @@ -210,20 +232,28 @@ export function useAdminOrgBillingReconcile(
export function useAdminDatabaseTables() {
return useQuery({
queryKey: queryKeys.admin.databaseTables,
queryFn: () => parseResponse(api.api.admin.database.tables.$get()),
queryFn: async () => {
const res = await fetch('/api/admin/database/tables', { credentials: 'include' });
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
...ADMIN_QUERY_CONFIG,
});
}

export function useAdminTableSchema(tableName: string | null | undefined) {
return useQuery({
queryKey: queryKeys.admin.tableSchema(tableName),
queryFn: () =>
parseResponse(
api.api.admin.database.tables[':tableName'].schema.$get({
param: { tableName: tableName! },
}),
),
queryFn: async () => {
const res = await fetch(
`/api/admin/database/tables/${encodeURIComponent(tableName!)}/schema`,
{ credentials: 'include' },
);
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
enabled: !!tableName,
staleTime: 1000 * 60 * 10,
gcTime: 1000 * 60 * 30,
Expand Down Expand Up @@ -259,23 +289,24 @@ export function useAdminTableRows(
filterBy,
filterValue,
),
queryFn: () => {
const query: Record<string, string> = {
queryFn: async () => {
const qs = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
orderBy,
order,
};
});
if (filterBy && filterValue) {
query.filterBy = filterBy;
query.filterValue = filterValue;
qs.set('filterBy', filterBy);
qs.set('filterValue', filterValue);
}
return parseResponse(
api.api.admin.database.tables[':tableName'].rows.$get({
param: { tableName: tableName! },
query,
}),
const res = await fetch(
`/api/admin/database/tables/${encodeURIComponent(tableName!)}/rows?${qs.toString()}`,
{ credentials: 'include' },
);
const data = (await res.json()) as Record<string, unknown>;
if (!res.ok) throw data;
return data;
},
enabled: !!tableName,
...ADMIN_QUERY_CONFIG,
Expand Down
Loading