Make dashboard public and optional in sidebar

This commit is contained in:
lemon
2026-05-16 11:01:29 -07:00
parent 343085684e
commit d3fadeca09
4 changed files with 41 additions and 56 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ const REGION_COLORS = [
];
interface UseEventDashboardOptions {
/** Must be true for relay queries to fire. Pass isAdmin(user.pubkey). */
/** Must be true for relay queries to fire. */
enabled: boolean;
/** Current territorial view level. */
territorialLevel: TerritorialLevel;
+1 -1
View File
@@ -194,7 +194,7 @@ export const SIDEBAR_ITEMS: SidebarItemDef[] = [
{ id: "badges", label: "Badges", path: "/badges", icon: Award },
{ id: "communities", label: "Communities", path: "/communities", icon: Users },
{ id: "world", label: "World", path: "/world", icon: Earth },
{ id: "dashboard", label: "Dashboard", path: "/dashboard", icon: Activity, requiresAuth: true, requiresAdmin: true },
{ id: "dashboard", label: "Dashboard", path: "/dashboard", icon: Activity },
];
/** Set of all known sidebar item IDs for quick lookup. */
+29 -53
View File
@@ -1,14 +1,13 @@
import { useState } from 'react';
import { Activity, Lock, Shield, Radio, Settings } from 'lucide-react';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { Activity, PanelLeft, Radio, Settings, Trash2 } from 'lucide-react';
import { useLayoutOptions } from '@/contexts/LayoutContext';
import { isAdmin } from '@/lib/admins';
import { LoginArea } from '@/components/auth/LoginArea';
import { PageHeader } from '@/components/PageHeader';
import { Card, CardContent } from '@/components/ui/card';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { useFeedSettings } from '@/hooks/useFeedSettings';
import { useToast } from '@/hooks/useToast';
import { useEventDashboard } from '@/hooks/useEventDashboard';
import { KpiGrid } from '@/components/event-dashboard/KpiGrid';
@@ -22,14 +21,25 @@ import { ConfigDrawer } from '@/components/event-dashboard/ConfigDrawer';
import type { TerritorialLevel } from '@/components/event-dashboard/types';
/**
* Dashboard page — admin-only live monitoring dashboard.
* Phase 2: Connected to real relay data via useEventDashboard.
* Dashboard page — public live monitoring dashboard.
*/
export function EventDashboardPage() {
const { user } = useCurrentUser();
const userIsAdmin = !!user && isAdmin(user.pubkey);
const [territorialLevel, setTerritorialLevel] = useState<TerritorialLevel>('municipalities');
const [configOpen, setConfigOpen] = useState(false);
const { addToSidebar, removeFromSidebar, orderedItems } = useFeedSettings();
const { toast } = useToast();
const isInSidebar = orderedItems.includes('dashboard');
const handleToggleSidebar = () => {
if (isInSidebar) {
removeFromSidebar('dashboard');
toast({ title: 'Removed from sidebar' });
return;
}
addToSidebar('dashboard');
toast({ title: 'Added to sidebar' });
};
// Use wider layout — removes 600px cap but keeps sidebar shell
useLayoutOptions({ noMaxWidth: true, rightSidebar: null });
@@ -37,50 +47,7 @@ export function EventDashboardPage() {
const {
kpis, timeSeries, leaderboard, distribution, participants, activity,
status, isLoading, error,
} = useEventDashboard({ enabled: userIsAdmin, territorialLevel });
// Not logged in
if (!user) {
return (
<main>
<PageHeader title="Dashboard" icon={<Activity className="size-5" />} />
<div className="px-4 py-6 max-w-2xl mx-auto">
<div className="text-center space-y-6 py-12">
<div className="w-16 h-16 mx-auto rounded-full bg-primary/20 flex items-center justify-center">
<Shield className="h-8 w-8 text-primary" />
</div>
<div className="space-y-2">
<h3 className="font-semibold text-lg">Sign in required</h3>
<p className="text-muted-foreground">Log in with an authorized account to access the dashboard.</p>
</div>
<LoginArea className="justify-center" />
</div>
</div>
</main>
);
}
// Logged in but not admin
if (!userIsAdmin) {
return (
<main>
<PageHeader title="Dashboard" icon={<Activity className="size-5" />} />
<div className="px-4 py-6 max-w-2xl mx-auto">
<Card className="bg-gradient-to-br from-destructive/5 to-destructive/10 border-destructive/20">
<CardContent className="pt-6">
<div className="text-center space-y-4">
<div className="w-16 h-16 mx-auto rounded-full bg-gradient-to-br from-destructive/20 to-destructive/30 flex items-center justify-center">
<Lock className="h-8 w-8 text-destructive" />
</div>
<h3 className="font-semibold text-lg">Admin access required</h3>
<p className="text-muted-foreground">This dashboard is restricted to platform administrators.</p>
</div>
</CardContent>
</Card>
</div>
</main>
);
}
} = useEventDashboard({ enabled: true, territorialLevel });
// Error state
if (error && kpis.totalPosts === 0) {
@@ -120,12 +87,21 @@ export function EventDashboardPage() {
</Badge>
);
// Admin — show dashboard
return (
<main>
<PageHeader title="Dashboard" icon={<Activity className="size-5" />}>
<div className="flex items-center gap-2">
{statusBadge}
<Button
size="sm"
variant="outline"
className="h-8 gap-1.5"
onClick={handleToggleSidebar}
title={isInSidebar ? 'Remove from sidebar' : 'Add to sidebar'}
>
{isInSidebar ? <Trash2 className="size-4" /> : <PanelLeft className="size-4" />}
<span className="hidden sm:inline">{isInSidebar ? 'Remove' : 'Add to sidebar'}</span>
</Button>
<Button size="icon" variant="ghost" className="size-8" onClick={() => setConfigOpen(true)}>
<Settings className="size-4" />
</Button>
+10 -1
View File
@@ -1,6 +1,6 @@
import { useCallback, useMemo, useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { ArrowLeft, BarChart3, Globe, MessageCircle, MessageSquare, MoreHorizontal, Repeat2, Star, AlertTriangle, PanelLeft, Trash2 } from 'lucide-react';
import { Activity, ArrowLeft, BarChart3, Globe, MessageCircle, MessageSquare, MoreHorizontal, Repeat2, Star, AlertTriangle, PanelLeft, Trash2 } from 'lucide-react';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Skeleton } from '@/components/ui/skeleton';
import { Badge } from '@/components/ui/badge';
@@ -83,6 +83,7 @@ function ExternalActionBar({ content, onComment, commentCount }: ExternalActionB
// the bottom of the bar so the dropdown can dismiss without unmounting it
// mid-animation.
const countryCode = content.type === 'iso3166' ? content.code : null;
const showDashboardLink = countryCode?.toUpperCase() === 'VE';
const [statsOpen, setStatsOpen] = useState(false);
const handleAddToSidebar = useCallback(() => {
@@ -148,6 +149,14 @@ function ExternalActionBar({ content, onComment, commentCount }: ExternalActionB
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
{showDashboardLink && (
<DropdownMenuItem asChild>
<Link to="/dashboard" className="gap-3">
<Activity className="size-4" />
View Dashboard
</Link>
</DropdownMenuItem>
)}
{countryCode && (
<DropdownMenuItem onClick={() => setStatsOpen(true)} className="gap-3">
<BarChart3 className="size-4" />