diff --git a/app/relays/page.tsx b/app/relays/page.tsx new file mode 100644 index 0000000..d99ba17 --- /dev/null +++ b/app/relays/page.tsx @@ -0,0 +1,186 @@ +"use client"; + +import { useNostr } from "nostr-react"; +import { useEffect, useState } from "react"; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { CheckCircle2, XCircle, AlertCircle, SignalHigh, Clock } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Button } from "@/components/ui/button"; + +export default function RelaysPage() { + const { connectedRelays } = useNostr(); + const [relayStatus, setRelayStatus] = useState<{ [url: string]: 'connected' | 'connecting' | 'disconnected' | 'error' }>({}); + const [loading, setLoading] = useState(true); + + useEffect(() => { + document.title = `Relays | LUMINA`; + + if (connectedRelays) { + const status: { [url: string]: 'connected' | 'connecting' | 'disconnected' | 'error' } = {}; + + // Get status of each relay + connectedRelays.forEach(relay => { + if (relay.status === 1) { + status[relay.url] = 'connected'; + } else if (relay.status === 0) { + status[relay.url] = 'connecting'; + } else { + status[relay.url] = 'disconnected'; + } + }); + + setRelayStatus(status); + setLoading(false); + } + }, [connectedRelays]); + + // Function to get the appropriate status icon + const getStatusIcon = (status: string) => { + switch (status) { + case 'connected': + return ; + case 'connecting': + return ; + case 'disconnected': + return ; + case 'error': + return ; + default: + return null; + } + }; + + // Function to get the appropriate status badge + const getStatusBadge = (status: string) => { + switch (status) { + case 'connected': + return Connected; + case 'connecting': + return Connecting; + case 'disconnected': + return Disconnected; + case 'error': + return Error; + default: + return null; + } + }; + + return ( +
+

Nostr Relays

+ + + + List View + Card View + + + + + + Connected Relays + Current active relay connections ({Object.keys(relayStatus).length}) + + + + {loading ? ( +
+ + + +
+ ) : ( +
+ {Object.entries(relayStatus).map(([url, status]) => ( +
+
+ {getStatusIcon(status)} +
+

{url}

+
+
+
+ {getStatusBadge(status)} +
+
+ ))} +
+ )} +
+
+ + {/*
+ Active subscriptions: {activeSubscriptions?.length || 0} +
*/} + +
+
+
+ + + {loading ? ( +
+ + + +
+ ) : ( +
+ {Object.entries(relayStatus).map(([url, status]) => ( + + + + {getStatusIcon(status)} + + {url.replace(/^wss:\/\//, '')} + + + + +
+
+ + + {status === 'connected' ? 'Ready for events' : 'Not receiving events'} + +
+ {getStatusBadge(status)} +
+
+
+ ))} +
+ )} +
+
+ +
+ + + About Nostr Relays + + +

+ Relays are servers that receive, store, and forward Nostr events. They act as the infrastructure + that makes the decentralized social network possible. You can connect to multiple relays to + increase the reach and resilience of your posts and profile. +

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 09a103c..c987388 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/headerComponents/AvatarDropdown.tsx b/components/headerComponents/AvatarDropdown.tsx index c75ae49..cad8cf8 100644 --- a/components/headerComponents/AvatarDropdown.tsx +++ b/components/headerComponents/AvatarDropdown.tsx @@ -39,6 +39,11 @@ export function AvatarDropdown() { Profile + + + Relays + + Settings diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..f000e3e --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/package-lock.json b/package-lock.json index bf54b46..6590e7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.1.6", - "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", @@ -2698,6 +2698,25 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.1", "license": "MIT", @@ -2765,6 +2784,25 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.0.1", "license": "MIT", @@ -2964,6 +3002,25 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-navigation-menu": { "version": "1.1.4", "license": "MIT", @@ -3097,6 +3154,25 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", @@ -3636,15 +3712,31 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.0.2", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" + "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -3936,6 +4028,25 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.1", "license": "MIT", diff --git a/package.json b/package.json index ea04868..de6ea2f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.1.6", - "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5",