finishing up ui enhancements

This commit is contained in:
Kumaraguru 2025-01-09 23:06:24 +00:00
parent 1146db13dc
commit acf3e90629
No known key found for this signature in database
GPG Key ID: 4E4555A84ECD28F7
5 changed files with 384 additions and 63 deletions

109
components/ui/dialog.jsx Normal file
View File

@ -0,0 +1,109 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-800 bg-neutral-900 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm text-white hover:text-[#7afbaf] ring-offset-neutral-900 transition-all hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2 disabled:pointer-events-none hover:bg-neutral-800/50 p-2">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight text-white",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-neutral-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

45
components/ui/tabs.jsx Normal file
View File

@ -0,0 +1,45 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg p-1 text-neutral-400",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-[#7afbaf] data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

73
package-lock.json generated
View File

@ -15,6 +15,7 @@
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@supabase/supabase-js": "^2.47.8",
"@tremor/react": "^3.18.6",
"class-variance-authority": "^0.7.1",
@ -27,7 +28,7 @@
"react": "^18",
"react-dom": "^18",
"recharts": "^2.15.0",
"tailwind-merge": "^2.5.5",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
@ -1097,6 +1098,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz",
"integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
@ -1358,6 +1360,37 @@
}
}
},
"node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
"integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-collection": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-select": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.4.tgz",
@ -1467,6 +1500,36 @@
}
}
},
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz",
"integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-roving-focus": "1.1.1",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
@ -2666,6 +2729,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
@ -6314,9 +6378,10 @@
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
},
"node_modules/tailwind-merge": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz",
"integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==",
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
"integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"

View File

@ -16,6 +16,7 @@
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@supabase/supabase-js": "^2.47.8",
"@tremor/react": "^3.18.6",
"class-variance-authority": "^0.7.1",
@ -28,7 +29,7 @@
"react": "^18",
"react-dom": "^18",
"recharts": "^2.15.0",
"tailwind-merge": "^2.5.5",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {

View File

@ -16,6 +16,8 @@ import {
Search,
ChevronLeft,
ChevronRight,
Globe,
Info,
} from "lucide-react";
import {
LineChart,
@ -26,6 +28,15 @@ import {
Tooltip,
ResponsiveContainer,
} from "recharts";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
// Initialize Supabase client
const supabase = createClient(
@ -204,7 +215,7 @@ export default function Dashboard() {
const totalPeerPages = getPageCount(displayPeerIds.length);
return (
<div className="min-h-screen bg-black text-white overflow-x-hidden">
<div className="min-h-screen bg-gradient-to-bl from-black to-[#222222] text-white overflow-x-hidden">
{/* Header */}
<motion.header
initial={{ opacity: 0, y: -20 }}
@ -214,7 +225,7 @@ export default function Dashboard() {
<div className="max-w-[2000px] mx-auto px-4 sm:px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<img src="/logo.svg" alt="Codex" className="w-10 h-10" />
<h1 className="text-lg sm:text-xl font-bold">Codex Metrics</h1>
<h1 className="text-lg sm:text-xl font-bold">Testnet Metrics</h1>
</div>
<div className="flex items-center gap-3">
<select
@ -240,6 +251,75 @@ export default function Dashboard() {
<RotateCw className={`w-5 h-5 ${loading ? 'animate-spin' : ''}`} />
<span className="sr-only">Refresh data</span>
</button>
<Dialog>
<DialogTrigger asChild>
<button
className="p-2 text-neutral-400 hover:text-[#7afbaf]
bg-neutral-900 border border-neutral-800 rounded-lg
hover:border-neutral-700 focus:border-[#7afbaf] focus:ring-1
focus:ring-[#7afbaf] transition-colors cursor-pointer outline-none"
>
<Info className="w-5 h-5" />
<span className="sr-only">Dashboard information</span>
</button>
</DialogTrigger>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle className="mb-6">Testnet Metrics</DialogTitle>
<div className="w-full h-40 bg-neutral-800 rounded-lg mb-6 animate-pulse" />
<DialogDescription className="pt-3">
The data displayed in this dashboard is collected from Codex nodes that use the{' '}
<a
href="https://github.com/codex-storage/cli"
target="_blank"
rel="noopener noreferrer"
className="text-[#7afbaf] hover:underline"
>
Codex CLI
</a>
{' '}for running a Codex alturistic node in the testnet. Users agree to a privacy
disclaimer before using the Codex CLI and the data collected will be used to
understand the testnet statistics and help troubleshooting users who face
difficulty in getting onboarded to Codex.
</DialogDescription>
<div className="mt-8 space-y-4 border-neutral-800 pt-4">
<div>
<h4 className="text-sm font-semibold text-white mb-2">Don't wish to provide data?</h4>
<p className="text-sm text-neutral-400">
You can still run a Codex node without providing any data. To do this, please follow the steps mentioned in the <a
href="https://docs.codex.storage/"
target="_blank"
rel="noopener noreferrer"
className="text-[#7afbaf] hover:underline"
>
Codex documentation
</a> which does not use the Codex CLI.
</p>
</div>
<div>
<h4 className="text-sm font-semibold text-white mb-2">Is there an incentive to run a Codex node?</h4>
<p className="text-sm text-neutral-400">
Codex is currently in testnet and it is not incentivized. However, in the future, Codex may be incentivized as per the roadmap. But please bear in mind that no incentives are promised for testnet node operators.
</p>
</div>
<div>
<h4 className="text-sm font-semibold text-white mb-2">I have a question or suggestion</h4>
<p className="text-sm text-neutral-400">
The best way to get in touch with us is to join the
<a
href="https://discord.gg/codex-storage"
target="_blank"
rel="noopener noreferrer"
className="text-[#7afbaf] hover:underline"
>
{" "}Codex discord
</a> and ask your question in the #support channel.
</p>
</div>
</div>
</DialogHeader>
</DialogContent>
</Dialog>
</div>
</div>
</motion.header>
@ -326,63 +406,84 @@ export default function Dashboard() {
className="bg-neutral-900 p-4 sm:p-6 rounded-xl h-[350px] lg:h-[450px] border border-neutral-800
hover:border-neutral-700 transition-colors"
>
<h3 className="text-neutral-400 mb-4 font-medium flex items-center gap-2">
<Activity className="w-5 h-5 opacity-60" />
Active Nodes Over Time
</h3>
{componentLoading.metrics ? (
<div className="h-[calc(100%-2rem)] flex items-center justify-center">
<ComponentSkeleton />
<Tabs defaultValue="nodes" className="h-full flex flex-col">
<div className="flex items-center justify-center mb-6">
<TabsList className="bg-neutral-800 border border-neutral-700">
<TabsTrigger value="nodes" className="data-[state=active]:bg-neutral-900">
<Activity className="w-4 h-4 mr-2" />
Active Nodes
</TabsTrigger>
<TabsTrigger value="geo" className="data-[state=active]:bg-neutral-900">
<Globe className="w-4 h-4 mr-2" />
Geographic Distribution
</TabsTrigger>
</TabsList>
</div>
) : metrics.length === 0 ? (
<div className="h-[calc(100%-2rem)] flex items-center justify-center">
<p className="text-neutral-400">No data available for the selected timeframe</p>
</div>
) : (
<div className="h-[calc(100%-2rem)]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={metrics}>
<CartesianGrid
strokeDasharray="3 3"
stroke="#333"
vertical={false}
/>
<XAxis
dataKey="date"
stroke="#666"
tickFormatter={(date) => format(new Date(date), "MMM d")}
fontSize={12}
tickMargin={10}
/>
<YAxis
stroke="#666"
fontSize={12}
tickMargin={10}
axisLine={false}
/>
<Tooltip
contentStyle={{
backgroundColor: "#1a1a1a",
border: "1px solid #333",
borderRadius: "8px",
fontFamily: "Inter",
fontSize: "12px",
padding: "12px",
}}
cursor={{ stroke: "#666" }}
/>
<Line
type="monotone"
dataKey="new_records_count"
stroke="#7afbaf"
strokeWidth={2}
dot={false}
activeDot={{ r: 6, fill: "#7afbaf" }}
/>
</LineChart>
</ResponsiveContainer>
</div>
)}
<TabsContent value="nodes" className="flex-1 mt-0">
{componentLoading.metrics ? (
<div className="h-full flex items-center justify-center">
<ComponentSkeleton />
</div>
) : metrics.length === 0 ? (
<div className="h-full flex items-center justify-center">
<p className="text-neutral-400">No data available for the selected timeframe</p>
</div>
) : (
<div className="h-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={metrics}>
<CartesianGrid
strokeDasharray="3 3"
stroke="#333"
vertical={false}
/>
<XAxis
dataKey="date"
stroke="#666"
tickFormatter={(date) => format(new Date(date), "MMM d")}
fontSize={12}
tickMargin={10}
/>
<YAxis
stroke="#666"
fontSize={12}
tickMargin={10}
axisLine={false}
/>
<Tooltip
contentStyle={{
backgroundColor: "#1a1a1a",
border: "1px solid #333",
borderRadius: "8px",
fontFamily: "var(--font-inter)",
fontSize: "12px",
padding: "12px",
}}
cursor={{ stroke: "#666" }}
formatter={(value) => [`${value} nodes`, 'Active Nodes']}
labelFormatter={(label) => format(new Date(label), "MMM d, yyyy")}
/>
<Line
type="monotone"
dataKey="new_records_count"
stroke="#7afbaf"
strokeWidth={2}
dot={false}
activeDot={{ r: 6, fill: "#7afbaf" }}
/>
</LineChart>
</ResponsiveContainer>
</div>
)}
</TabsContent>
<TabsContent value="geo" className="flex-1 mt-0">
<div className="h-full flex items-center justify-center">
<p className="text-neutral-400">Geographic distribution view coming soon</p>
</div>
</TabsContent>
</Tabs>
</motion.div>
</div>