fix(admin): use feature interest composite key

This commit is contained in:
jjbrunton committed 2026-09-05 17:02:07 +01:00
1 parent a6233d0175
commit 53a48ab6d5
3 files changed
+14 -7

No files matched your search

+7 -3
View File
@@ -1,16 +1,20 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { formatFeatureName, toRecentFeatureInterest } from './featureInterests'; import { featureInterestKey, formatFeatureName, toRecentFeatureInterest } from './featureInterests';
describe('toRecentFeatureInterest', () => { describe('toRecentFeatureInterest', () => {
it('preserves the standalone API feature field for dashboard rendering', () => { it('preserves the standalone API feature field for dashboard rendering', () => {
const interest = toRecentFeatureInterest({ const interest = toRecentFeatureInterest({
id: 'interest-1',
user_id: 'user-1', user_id: 'user-1',
feature: 'better_chat', feature: 'better_chat',
created_at: '2026-09-05T12:00:00.000Z', created_at: '2026-09-05T12:00:00.000Z',
}); });
expect(interest.feature).toBe('better_chat'); 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(formatFeatureName(interest.feature)).toBe('Better Chat');
expect(featureInterestKey(interest)).toBe('user-1:better_chat');
}); });
}); });
+4 -2
View File
@@ -1,5 +1,4 @@
export interface FeatureInterestRow { export interface FeatureInterestRow {
id: string;
user_id: string; user_id: string;
feature: string; feature: string;
created_at: string; created_at: string;
@@ -16,13 +15,16 @@ export interface RecentFeatureInterest extends FeatureInterestRow {
export function toRecentFeatureInterest(row: FeatureInterestRow): RecentFeatureInterest { export function toRecentFeatureInterest(row: FeatureInterestRow): RecentFeatureInterest {
return { return {
id: row.id,
user_id: row.user_id, user_id: row.user_id,
feature: row.feature, feature: row.feature,
created_at: row.created_at, created_at: row.created_at,
}; };
} }
export function featureInterestKey(row: Pick<FeatureInterestRow, 'user_id' | 'feature'>): string {
return `${row.user_id}:${row.feature}`;
}
export function formatFeatureName(feature: string): string { export function formatFeatureName(feature: string): string {
return feature return feature
.split('_') .split('_')
+3 -2
View File
@@ -12,6 +12,7 @@ import { useRealtimeSubscription } from '@/hooks/useRealtimeSubscription';
import { RealtimeStatusIndicator } from '@/components/RealtimeStatusIndicator'; import { RealtimeStatusIndicator } from '@/components/RealtimeStatusIndicator';
import { import {
formatFeatureName, formatFeatureName,
featureInterestKey,
toRecentFeatureInterest, toRecentFeatureInterest,
type FeatureInterestRow, type FeatureInterestRow,
type RecentFeatureInterest, type RecentFeatureInterest,
@@ -80,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()) ? 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<FeatureInterestRow>('feature_interests').select('id, user_id, feature, created_at').order('created_at', { ascending: false }).limit(10) ? adminData.from<FeatureInterestRow>('feature_interests').select('user_id, feature, created_at').order('created_at', { ascending: false }).limit(10)
: Promise.resolve({ data: [] }), : Promise.resolve({ data: [] }),
]); ]);
@@ -566,7 +567,7 @@ export function DashboardPage() {
.toUpperCase(); .toUpperCase();
return ( return (
<div key={item.id} className="flex items-center justify-between gap-4 rounded-xl bg-white/5 p-3"> <div key={featureInterestKey(item)} className="flex items-center justify-between gap-4 rounded-xl bg-white/5 p-3">
<div className="flex items-center gap-3 min-w-0"> <div className="flex items-center gap-3 min-w-0">
<Avatar className="h-8 w-8"> <Avatar className="h-8 w-8">
<AvatarImage src={item.profile?.avatar_url || undefined} /> <AvatarImage src={item.profile?.avatar_url || undefined} />