diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index a6d195d..dfea0bd 100755 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -15,6 +15,7 @@ import { AdminsPage } from '@/pages/admins/AdminsPage'; import { RedemptionCodesPage } from '@/pages/RedemptionCodesPage'; import { FeedbackPage } from '@/pages/FeedbackPage'; import { UsageInsightsPage } from '@/pages/UsageInsightsPage'; +import { UserActivityPage } from '@/pages/activity/UserActivityPage'; import { AiSettingsPage } from '@/pages/AiSettingsPage'; import { AppSettingsPage } from '@/pages/AppSettingsPage'; @@ -96,6 +97,14 @@ function App() { } /> + + + + } + /> {/* System */} , + }, +]; + const contentNav: NavItem[] = [ { label: 'Categories', @@ -45,6 +55,12 @@ const usersNav: NavItem[] = [ icon: , requiredPermission: PERMISSION_KEYS.VIEW_USERS, }, + { + label: 'User Activity', + href: '/activity', + icon: , + requiredPermission: PERMISSION_KEYS.VIEW_ACTIVITY, + }, { label: 'Usage Insights', href: '/usage-insights', @@ -112,7 +128,9 @@ export function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { const NavLink = ({ item }: { item: NavItem }) => { if (item.requiredPermission && !hasPermission(item.requiredPermission)) return null; - const isActive = location.pathname.startsWith(item.href); + const isActive = item.href === '/' + ? location.pathname === '/' + : location.pathname.startsWith(item.href); return ( + @@ -310,6 +329,7 @@ export function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { {/* Navigation */}
+ diff --git a/apps/admin/src/contexts/AuthContext.tsx b/apps/admin/src/contexts/AuthContext.tsx index b88ed78..656ff68 100644 --- a/apps/admin/src/contexts/AuthContext.tsx +++ b/apps/admin/src/contexts/AuthContext.tsx @@ -18,6 +18,7 @@ export const PERMISSION_KEYS = { VIEW_MEDIA: 'view_media', VIEW_MATCHES: 'view_matches', VIEW_RESPONSES: 'view_responses', + VIEW_ACTIVITY: 'view_activity', // System permissions MANAGE_CODES: 'manage_codes', MANAGE_ADMINS: 'manage_admins', @@ -38,6 +39,7 @@ export const PERMISSION_METADATA: Record { setLoading(true); try { - // Fetch profiles + // Fetch profiles with auth info (includes last_sign_in_at) const { data: profilesData, error } = await supabase - .from('profiles') - .select('*') - .order('created_at', { ascending: false }); + .rpc('get_profiles_with_auth_info'); if (error) throw error; @@ -197,13 +197,14 @@ export function UsersPage() { Couple Storage Joined + Last Active {filteredProfiles.length === 0 ? ( - + {search ? 'No users found matching your search.' : 'No users yet.'} @@ -224,7 +225,14 @@ export function UsersPage() { - {profile.email || '—'} +
+ {profile.email || '—'} + {profile.email && !profile.email_confirmed_at && ( + + + + )} +
{profile.is_premium ? ( @@ -268,6 +276,11 @@ export function UsersPage() { ? format(new Date(profile.created_at), 'MMM d, yyyy') : '—'} + + {profile.last_sign_in_at + ? format(new Date(profile.last_sign_in_at), 'MMM d, yyyy h:mm a') + : 'Never'} +