diff --git a/apps/admin/src/lib/featureInterests.test.ts b/apps/admin/src/lib/featureInterests.test.ts new file mode 100644 index 0000000..f962c77 --- /dev/null +++ b/apps/admin/src/lib/featureInterests.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest'; +import { featureInterestKey, formatFeatureName, toRecentFeatureInterest } from './featureInterests'; + +describe('toRecentFeatureInterest', () => { + it('preserves the standalone API feature field for dashboard rendering', () => { + const interest = toRecentFeatureInterest({ + user_id: 'user-1', + feature: 'better_chat', + created_at: '2026-09-05T12:00:00.000Z', + }); + + expect(interest).toEqual({ + user_id: 'user-1', + feature: 'better_chat', + created_at: '2026-09-05T12:00:00.000Z', + }); + expect(formatFeatureName(interest.feature)).toBe('Better Chat'); + expect(featureInterestKey(interest)).toBe('user-1:better_chat'); + }); +}); diff --git a/apps/admin/src/lib/featureInterests.ts b/apps/admin/src/lib/featureInterests.ts new file mode 100644 index 0000000..4e73ecd --- /dev/null +++ b/apps/admin/src/lib/featureInterests.ts @@ -0,0 +1,34 @@ +export interface FeatureInterestRow { + user_id: string; + feature: string; + created_at: string; +} + +export interface RecentFeatureInterest extends FeatureInterestRow { + profile?: { + id: string; + name: string | null; + email: string | null; + avatar_url: string | null; + }; +} + +export function toRecentFeatureInterest(row: FeatureInterestRow): RecentFeatureInterest { + return { + user_id: row.user_id, + feature: row.feature, + created_at: row.created_at, + }; +} + +export function featureInterestKey(row: Pick): string { + return `${row.user_id}:${row.feature}`; +} + +export function formatFeatureName(feature: string): string { + return feature + .split('_') + .filter(Boolean) + .map(part => part.charAt(0).toUpperCase() + part.slice(1)) + .join(' '); +} diff --git a/apps/admin/src/pages/DashboardPage.tsx b/apps/admin/src/pages/DashboardPage.tsx index 857757e..28b2b41 100644 --- a/apps/admin/src/pages/DashboardPage.tsx +++ b/apps/admin/src/pages/DashboardPage.tsx @@ -10,36 +10,22 @@ import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis, Tooltip } from 'recha import { format, subDays, startOfDay, endOfDay, formatDistanceToNow } from 'date-fns'; import { useRealtimeSubscription } from '@/hooks/useRealtimeSubscription'; import { RealtimeStatusIndicator } from '@/components/RealtimeStatusIndicator'; +import { + formatFeatureName, + featureInterestKey, + toRecentFeatureInterest, + type FeatureInterestRow, + type RecentFeatureInterest, +} from '@/lib/featureInterests'; type FeatureInterestChartPoint = { date: string; count: number }; -type RecentFeatureInterest = { - id: string; - user_id: string; - feature_name: string; - created_at: string; - profile?: { - id: string; - name: string | null; - email: string | null; - avatar_url: string | null; - }; -}; - type FeatureInterestCountsRow = { feature_name: string; opt_in_count: number; opt_in_count_last_7_days: number; }; -function formatFeatureName(featureName: string) { - return featureName - .split('_') - .filter(Boolean) - .map(part => part.charAt(0).toUpperCase() + part.slice(1)) - .join(' '); -} - export function DashboardPage() { const { hasPermission } = useAuth(); const canViewUsers = hasPermission(PERMISSION_KEYS.VIEW_USERS); @@ -95,7 +81,7 @@ export function DashboardPage() { ? adminData.from('feature_interests').select('created_at, user_id').gte('created_at', startDate.toISOString()).lte('created_at', endDate.toISOString()) : Promise.resolve({ data: [] }), canViewUsers - ? adminData.from('feature_interests').select('id, user_id, feature_name, created_at').order('created_at', { ascending: false }).limit(10) + ? adminData.from('feature_interests').select('user_id, feature, created_at').order('created_at', { ascending: false }).limit(10) : Promise.resolve({ data: [] }), ]); @@ -158,12 +144,7 @@ export function DashboardPage() { })) ); - let recentActivity: RecentFeatureInterest[] = (recentInterests ?? []).map(item => ({ - id: item.id, - user_id: item.user_id, - feature_name: item.feature_name, - created_at: item.created_at, - })); + let recentActivity: RecentFeatureInterest[] = (recentInterests ?? []).map(toRecentFeatureInterest); if (canViewUsers && recentActivity.length > 0) { const userIds = Array.from(new Set(recentActivity.map(item => item.user_id))); @@ -579,14 +560,14 @@ export function DashboardPage() { if (item.profile?.name && item.profile.email) { subtitleParts.push(item.profile.email); } - subtitleParts.push(formatFeatureName(item.feature_name)); + subtitleParts.push(formatFeatureName(item.feature)); const fallbackChar = (item.profile?.name || item.profile?.email || 'U') .charAt(0) .toUpperCase(); return ( -
+