fix(admin): render standalone feature interests

This commit is contained in:
jjbrunton committed 2026-09-05 16:57:57 +01:00
1 parent c776859183
commit a6233d0175
3 files changed
+57 -29

No files matched your search

@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { formatFeatureName, toRecentFeatureInterest } from './featureInterests';
describe('toRecentFeatureInterest', () => {
it('preserves the standalone API feature field for dashboard rendering', () => {
const interest = toRecentFeatureInterest({
id: 'interest-1',
user_id: 'user-1',
feature: 'better_chat',
created_at: '2026-09-05T12:00:00.000Z',
});
expect(interest.feature).toBe('better_chat');
expect(formatFeatureName(interest.feature)).toBe('Better Chat');
});
});
+32
View File
@@ -0,0 +1,32 @@
export interface FeatureInterestRow {
id: string;
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 {
id: row.id,
user_id: row.user_id,
feature: row.feature,
created_at: row.created_at,
};
}
export function formatFeatureName(feature: string): string {
return feature
.split('_')
.filter(Boolean)
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
+9 -29
View File
@@ -10,36 +10,21 @@ import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis, Tooltip } from 'recha
import { format, subDays, startOfDay, endOfDay, formatDistanceToNow } from 'date-fns'; import { format, subDays, startOfDay, endOfDay, formatDistanceToNow } from 'date-fns';
import { useRealtimeSubscription } from '@/hooks/useRealtimeSubscription'; import { useRealtimeSubscription } from '@/hooks/useRealtimeSubscription';
import { RealtimeStatusIndicator } from '@/components/RealtimeStatusIndicator'; import { RealtimeStatusIndicator } from '@/components/RealtimeStatusIndicator';
import {
formatFeatureName,
toRecentFeatureInterest,
type FeatureInterestRow,
type RecentFeatureInterest,
} from '@/lib/featureInterests';
type FeatureInterestChartPoint = { date: string; count: number }; 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 = { type FeatureInterestCountsRow = {
feature_name: string; feature_name: string;
opt_in_count: number; opt_in_count: number;
opt_in_count_last_7_days: 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() { export function DashboardPage() {
const { hasPermission } = useAuth(); const { hasPermission } = useAuth();
const canViewUsers = hasPermission(PERMISSION_KEYS.VIEW_USERS); const canViewUsers = hasPermission(PERMISSION_KEYS.VIEW_USERS);
@@ -95,7 +80,7 @@ export function DashboardPage() {
? adminData.from('feature_interests').select('created_at, user_id').gte('created_at', startDate.toISOString()).lte('created_at', endDate.toISOString()) ? adminData.from('feature_interests').select('created_at, user_id').gte('created_at', startDate.toISOString()).lte('created_at', endDate.toISOString())
: Promise.resolve({ data: [] }), : Promise.resolve({ data: [] }),
canViewUsers canViewUsers
? adminData.from('feature_interests').select('id, user_id, feature_name, created_at').order('created_at', { ascending: false }).limit(10) ? adminData.from<FeatureInterestRow>('feature_interests').select('id, user_id, feature, created_at').order('created_at', { ascending: false }).limit(10)
: Promise.resolve({ data: [] }), : Promise.resolve({ data: [] }),
]); ]);
@@ -158,12 +143,7 @@ export function DashboardPage() {
})) }))
); );
let recentActivity: RecentFeatureInterest[] = (recentInterests ?? []).map(item => ({ let recentActivity: RecentFeatureInterest[] = (recentInterests ?? []).map(toRecentFeatureInterest);
id: item.id,
user_id: item.user_id,
feature_name: item.feature_name,
created_at: item.created_at,
}));
if (canViewUsers && recentActivity.length > 0) { if (canViewUsers && recentActivity.length > 0) {
const userIds = Array.from(new Set(recentActivity.map(item => item.user_id))); const userIds = Array.from(new Set(recentActivity.map(item => item.user_id)));
@@ -579,7 +559,7 @@ export function DashboardPage() {
if (item.profile?.name && item.profile.email) { if (item.profile?.name && item.profile.email) {
subtitleParts.push(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') const fallbackChar = (item.profile?.name || item.profile?.email || 'U')
.charAt(0) .charAt(0)