mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 10:19:43 +00:00
Update
This commit is contained in:
1 parent
ef7bc8c90d
commit
3fcbea8d60
68 files changed
+3579
-834
No files matched your search
@@ -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() {
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/activity"
|
||||
element={
|
||||
<ProtectedRoute requiredPermission={PERMISSION_KEYS.VIEW_ACTIVITY}>
|
||||
<UserActivityPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* System */}
|
||||
<Route
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
Bot,
|
||||
Smartphone,
|
||||
X,
|
||||
Home,
|
||||
Activity,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
@@ -29,6 +31,14 @@ interface NavItem {
|
||||
requiredPermission?: PermissionKey;
|
||||
}
|
||||
|
||||
const dashboardNav: NavItem[] = [
|
||||
{
|
||||
label: 'Dashboard',
|
||||
href: '/',
|
||||
icon: <Home className="h-5 w-5" />,
|
||||
},
|
||||
];
|
||||
|
||||
const contentNav: NavItem[] = [
|
||||
{
|
||||
label: 'Categories',
|
||||
@@ -45,6 +55,12 @@ const usersNav: NavItem[] = [
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
requiredPermission: PERMISSION_KEYS.VIEW_USERS,
|
||||
},
|
||||
{
|
||||
label: 'User Activity',
|
||||
href: '/activity',
|
||||
icon: <Activity className="h-5 w-5" />,
|
||||
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 (
|
||||
<Link
|
||||
@@ -214,6 +232,7 @@ export function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex-1 space-y-6 overflow-auto px-3 py-2">
|
||||
<NavSection title="Dashboard" items={dashboardNav} />
|
||||
<NavSection title="Content" items={contentNav} />
|
||||
<NavSection title="Users" items={usersNav} />
|
||||
<NavSection title="System" items={systemNav} />
|
||||
@@ -310,6 +329,7 @@ export function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) {
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex-1 space-y-6 overflow-auto px-3 py-4">
|
||||
<NavSection title="Dashboard" items={dashboardNav} />
|
||||
<NavSection title="Content" items={contentNav} />
|
||||
<NavSection title="Users" items={usersNav} />
|
||||
<NavSection title="System" items={systemNav} />
|
||||
|
||||
@@ -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<PermissionKey, { label: string; descrip
|
||||
[PERMISSION_KEYS.VIEW_MEDIA]: { label: 'Can view media', description: 'View user photos and media', group: 'users' },
|
||||
[PERMISSION_KEYS.VIEW_MATCHES]: { label: 'Can view matches', description: 'View user matches and match details', group: 'users' },
|
||||
[PERMISSION_KEYS.VIEW_RESPONSES]: { label: 'Can view responses', description: 'View user question responses', group: 'users' },
|
||||
[PERMISSION_KEYS.VIEW_ACTIVITY]: { label: 'Can view activity', description: 'View user activity feed (responses, matches, messages, signups)', group: 'users' },
|
||||
[PERMISSION_KEYS.MANAGE_CODES]: { label: 'Can manage redemption codes', description: 'Create and view redemption codes', group: 'system' },
|
||||
[PERMISSION_KEYS.MANAGE_ADMINS]: { label: 'Can manage admins', description: 'Add/remove admin users and change permissions', group: 'system' },
|
||||
[PERMISSION_KEYS.MANAGE_AI_CONFIG]: { label: 'Can manage AI settings', description: 'Configure AI models, API keys, and council mode', group: 'system' },
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { Search, Crown, Users, ChevronRight, HardDrive } from 'lucide-react';
|
||||
import { Search, Crown, Users, ChevronRight, HardDrive, MailWarning } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
interface Profile {
|
||||
@@ -27,6 +27,8 @@ interface Profile {
|
||||
is_premium: boolean | null;
|
||||
couple_id: string | null;
|
||||
created_at: string | null;
|
||||
last_sign_in_at: string | null;
|
||||
email_confirmed_at: string | null;
|
||||
storage_bytes?: number;
|
||||
partner_is_premium?: boolean;
|
||||
}
|
||||
@@ -48,11 +50,9 @@ export function UsersPage() {
|
||||
const fetchProfiles = useCallback(async () => {
|
||||
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() {
|
||||
<TableHead>Couple</TableHead>
|
||||
<TableHead>Storage</TableHead>
|
||||
<TableHead>Joined</TableHead>
|
||||
<TableHead>Last Active</TableHead>
|
||||
<TableHead className="w-12"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredProfiles.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="text-center py-8 text-muted-foreground">
|
||||
<TableCell colSpan={8} className="text-center py-8 text-muted-foreground">
|
||||
{search ? 'No users found matching your search.' : 'No users yet.'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -224,7 +225,14 @@ export function UsersPage() {
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{profile.email || '—'}
|
||||
<div className="flex items-center gap-1.5">
|
||||
{profile.email || '—'}
|
||||
{profile.email && !profile.email_confirmed_at && (
|
||||
<span title="Email not verified">
|
||||
<MailWarning className="h-3.5 w-3.5 text-amber-500" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{profile.is_premium ? (
|
||||
@@ -268,6 +276,11 @@ export function UsersPage() {
|
||||
? format(new Date(profile.created_at), 'MMM d, yyyy')
|
||||
: '—'}
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground text-sm">
|
||||
{profile.last_sign_in_at
|
||||
? format(new Date(profile.last_sign_in_at), 'MMM d, yyyy h:mm a')
|
||||
: 'Never'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Link to={`/users/${profile.id}`}>
|
||||
<Button variant="ghost" size="icon">
|
||||
|
||||
Reference in new issue
Block a user