From 0492427582a74d79ba2d98ab430223f5310f36f8 Mon Sep 17 00:00:00 2001 From: highperfocused Date: Sun, 4 Jan 2026 20:40:46 +0100 Subject: [PATCH] fix: add DashboardExport page and integrate into routing and sidebar navigation --- src/AppRouter.tsx | 2 + src/components/navigation/AppSidebar.tsx | 7 +- src/pages/DashboardExport.tsx | 251 +++++++++++++++++++++++ 3 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 src/pages/DashboardExport.tsx diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 3877196..e3bb12e 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -5,6 +5,7 @@ import Index from "./pages/Index"; import { Explore } from "./pages/Explore"; import { Dashboard } from "./pages/Dashboard"; import { DashboardEvents } from "./pages/DashboardEvents"; +import { DashboardExport } from "./pages/DashboardExport"; import { NIP19Page } from "./pages/NIP19Page"; import { Terms } from "./pages/Terms"; import { Privacy } from "./pages/Privacy"; @@ -19,6 +20,7 @@ export function AppRouter() { } /> } /> } /> + } /> } /> } /> {/* NIP-19 route for npub1, note1, naddr1, nevent1, nprofile1 */} diff --git a/src/components/navigation/AppSidebar.tsx b/src/components/navigation/AppSidebar.tsx index a4aee59..79481d4 100644 --- a/src/components/navigation/AppSidebar.tsx +++ b/src/components/navigation/AppSidebar.tsx @@ -1,4 +1,4 @@ -import { Home, LayoutDashboard, FileText } from 'lucide-react'; +import { Home, LayoutDashboard, FileText, Download } from 'lucide-react'; import { Link, useLocation } from 'react-router-dom'; import { Sidebar, @@ -29,6 +29,11 @@ const navigationItems = [ url: '/dashboard/events', icon: FileText, }, + { + title: 'Export Following', + url: '/dashboard/export', + icon: Download, + }, ]; export function AppSidebar() { diff --git a/src/pages/DashboardExport.tsx b/src/pages/DashboardExport.tsx new file mode 100644 index 0000000..0255a8c --- /dev/null +++ b/src/pages/DashboardExport.tsx @@ -0,0 +1,251 @@ +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'; +import { AppSidebar } from '@/components/navigation/AppSidebar'; +import { useCurrentUser } from '@/hooks/useCurrentUser'; +import { useNostr } from '@nostrify/react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Button } from '@/components/ui/button'; +import { InfoIcon, Download, Users, Calendar } from 'lucide-react'; +import { Skeleton } from '@/components/ui/skeleton'; + +export function DashboardExport() { + const { user } = useCurrentUser(); + const { nostr } = useNostr(); + const [isExporting, setIsExporting] = useState(false); + + // Fetch the user's contact list (kind 3 event) + const { data: contactListEvent, isLoading } = useQuery({ + queryKey: ['contact-list', user?.pubkey], + queryFn: async (c) => { + if (!user) return null; + + const signal = AbortSignal.any([c.signal, AbortSignal.timeout(3000)]); + const events = await nostr.query( + [{ kinds: [3], authors: [user.pubkey], limit: 1 }], + { signal } + ); + + return events[0] || null; + }, + enabled: !!user, + }); + + // Extract following list from the contact list event + const followingList = contactListEvent?.tags + .filter(([tag]) => tag === 'p') + .map(([_, pubkey, relay, petname]) => ({ + pubkey, + relay: relay || '', + petname: petname || '', + })) || []; + + const followingCount = followingList.length; + const createdAt = contactListEvent?.created_at; + const createdDate = createdAt ? new Date(createdAt * 1000) : null; + + const handleExport = () => { + if (!contactListEvent) return; + + setIsExporting(true); + + try { + // Prepare export data + const exportData = { + exported_at: new Date().toISOString(), + pubkey: user?.pubkey, + event: { + id: contactListEvent.id, + created_at: contactListEvent.created_at, + kind: contactListEvent.kind, + tags: contactListEvent.tags, + content: contactListEvent.content, + sig: contactListEvent.sig, + }, + following: followingList, + stats: { + total_following: followingCount, + event_created_at: createdDate?.toISOString(), + }, + }; + + // Create blob and download + const blob = new Blob([JSON.stringify(exportData, null, 2)], { + type: 'application/json', + }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `nostr-following-backup-${Date.now()}.json`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + } finally { + setIsExporting(false); + } + }; + + return ( + +
+ +
+
+ +

Export Following List

+
+ +
+ {!user ? ( + + +
+ + + + Please log in to export your following list. + + +
+
+
+ ) : ( +
+ + + Backup Your Following List + + Export your contact list as a JSON file for backup or migration purposes. + + + + {isLoading ? ( +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ ) : !contactListEvent ? ( + + + + No following list found. You may need to follow some users first. + + + ) : ( + <> +
+ + +
+
+ +
+
+

Following

+

{followingCount}

+
+
+
+
+ + + +
+
+ +
+
+

Last Updated

+

+ {createdDate + ? createdDate.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + }) + : 'Unknown'} +

+

+ {createdDate + ? createdDate.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + }) + : ''} +

+
+
+
+
+
+ +
+
+

What's included:

+
    +
  • • Complete list of all {followingCount} accounts you follow
  • +
  • • Public keys (pubkeys) for each account
  • +
  • • Relay hints and petnames (if available)
  • +
  • • Original event data and signature
  • +
  • • Timestamp of when the list was created
  • +
+
+ + + +

+ Your backup file will be saved to your downloads folder +

+
+ + )} +
+
+ + + + About Your Following List + + +

+ Your following list is stored on Nostr as a kind 3 event. This backup includes + the complete event data, allowing you to restore your following list on any + compatible Nostr client. +

+

+ The export includes all public keys (npubs) of accounts you follow, along with + optional relay hints and petnames that help clients find and display these + accounts. +

+
+
+
+ )} +
+
+
+
+ ); +}