diff --git a/src/components/pages/Nip19PreviewRouter.tsx b/src/components/pages/Nip19PreviewRouter.tsx new file mode 100644 index 0000000..d9badc2 --- /dev/null +++ b/src/components/pages/Nip19PreviewRouter.tsx @@ -0,0 +1,34 @@ +import { useParams } from "react-router"; +import PreviewProfilePage from "./PreviewProfilePage"; +import PreviewEventPage from "./PreviewEventPage"; +import PreviewAddressPage from "./PreviewAddressPage"; + +/** + * Nip19PreviewRouter - Routes to the appropriate preview component based on NIP-19 identifier type + * Handles npub, note, nevent, and naddr identifiers + */ +export default function Nip19PreviewRouter() { + const { identifier } = useParams<{ identifier: string }>(); + + if (!identifier) { + return ( +
+

No identifier provided

+
+ ); + } + + // Route based on identifier prefix + if (identifier.startsWith("npub1")) { + return ; + } else if (identifier.startsWith("nevent1")) { + return ; + } else if (identifier.startsWith("note1")) { + return ; + } else if (identifier.startsWith("naddr1")) { + return ; + } + + // Not a recognized NIP-19 identifier + return null; +} diff --git a/src/components/pages/PreviewAddressPage.tsx b/src/components/pages/PreviewAddressPage.tsx index 3eaa108..de2425d 100644 --- a/src/components/pages/PreviewAddressPage.tsx +++ b/src/components/pages/PreviewAddressPage.tsx @@ -7,19 +7,16 @@ import { toast } from "sonner"; /** * PreviewAddressPage - Preview or redirect naddr identifiers - * Route: /naddr1* + * Route: /:identifier (where identifier starts with naddr1) * For spellbooks (kind 30777), redirects to /:actor/:identifier * For all other addressable events, shows detail view */ export default function PreviewAddressPage() { - const params = useParams<{ "*": string }>(); + const { identifier } = useParams<{ identifier: string }>(); const navigate = useNavigate(); - // Get the full naddr from the URL (naddr1 + captured part) - const fullIdentifier = params["*"] ? `naddr1${params["*"]}` : undefined; - // Decode the naddr identifier (synchronous, memoized) - const { decoded, error } = useNip19Decode(fullIdentifier, "naddr"); + const { decoded, error } = useNip19Decode(identifier, "naddr"); // Handle redirect for spellbooks useEffect(() => { diff --git a/src/components/pages/PreviewEventPage.tsx b/src/components/pages/PreviewEventPage.tsx index 7bd3656..5ad35ef 100644 --- a/src/components/pages/PreviewEventPage.tsx +++ b/src/components/pages/PreviewEventPage.tsx @@ -1,5 +1,5 @@ import { useMemo, useEffect } from "react"; -import { useParams, useNavigate, useLocation } from "react-router"; +import { useParams, useNavigate } from "react-router"; import { useNip19Decode } from "@/hooks/useNip19Decode"; import type { EventPointer } from "nostr-tools/nip19"; import { EventDetailViewer } from "../EventDetailViewer"; @@ -7,30 +7,15 @@ import { toast } from "sonner"; /** * PreviewEventPage - Preview a Nostr event from a nevent or note identifier - * Routes: /nevent1*, /note1* + * Route: /:identifier (where identifier starts with nevent1 or note1) * This page shows a single event view without affecting user's workspace layout */ export default function PreviewEventPage() { - const params = useParams<{ "*": string }>(); + const { identifier } = useParams<{ identifier: string }>(); const navigate = useNavigate(); - const location = useLocation(); - - // Determine the prefix based on the current path and reconstruct full identifier - const fullIdentifier = useMemo(() => { - const captured = params["*"]; - if (!captured) return undefined; - - const path = location.pathname; - if (path.startsWith("/nevent1")) { - return `nevent1${captured}`; - } else if (path.startsWith("/note1")) { - return `note1${captured}`; - } - return undefined; - }, [params, location.pathname]); // Decode the event identifier (synchronous, memoized) - const { decoded, error } = useNip19Decode(fullIdentifier); + const { decoded, error } = useNip19Decode(identifier); // Convert decoded entity to EventPointer const pointer: EventPointer | null = useMemo(() => { diff --git a/src/components/pages/PreviewProfilePage.tsx b/src/components/pages/PreviewProfilePage.tsx index 8b2a207..2eab548 100644 --- a/src/components/pages/PreviewProfilePage.tsx +++ b/src/components/pages/PreviewProfilePage.tsx @@ -6,18 +6,15 @@ import { toast } from "sonner"; /** * PreviewProfilePage - Preview a Nostr profile from an npub identifier - * Route: /npub1* + * Route: /:identifier (where identifier starts with npub1) * This page shows a single profile view without affecting user's workspace layout */ export default function PreviewProfilePage() { - const params = useParams<{ "*": string }>(); + const { identifier } = useParams<{ identifier: string }>(); const navigate = useNavigate(); - // Get the full npub from the URL (npub1 + captured part) - const fullIdentifier = params["*"] ? `npub1${params["*"]}` : undefined; - // Decode the npub identifier (synchronous, memoized) - const { decoded, error } = useNip19Decode(fullIdentifier, "npub"); + const { decoded, error } = useNip19Decode(identifier, "npub"); // Show error toast when error occurs useEffect(() => { diff --git a/src/root.tsx b/src/root.tsx index 2833f3a..9aa0cf0 100644 --- a/src/root.tsx +++ b/src/root.tsx @@ -2,9 +2,7 @@ import { createBrowserRouter, RouterProvider } from "react-router"; import { AppShell } from "./components/layouts/AppShell"; import DashboardPage from "./components/pages/DashboardPage"; import SpellbookPage from "./components/pages/SpellbookPage"; -import PreviewProfilePage from "./components/pages/PreviewProfilePage"; -import PreviewEventPage from "./components/pages/PreviewEventPage"; -import PreviewAddressPage from "./components/pages/PreviewAddressPage"; +import Nip19PreviewRouter from "./components/pages/Nip19PreviewRouter"; const router = createBrowserRouter([ { @@ -15,38 +13,6 @@ const router = createBrowserRouter([ ), }, - { - path: "/npub1*", - element: ( - - - - ), - }, - { - path: "/nevent1*", - element: ( - - - - ), - }, - { - path: "/note1*", - element: ( - - - - ), - }, - { - path: "/naddr1*", - element: ( - - - - ), - }, { path: "/preview/:actor/:identifier", element: ( @@ -55,6 +21,32 @@ const router = createBrowserRouter([ ), }, + // NIP-19 identifier preview route - must come before /:actor/:identifier catch-all + { + path: "/:identifier", + element: ( + + + + ), + // Only match single-segment paths that look like NIP-19 identifiers + loader: ({ params }) => { + const id = params.identifier; + if ( + !id || + !( + id.startsWith("npub1") || + id.startsWith("note1") || + id.startsWith("nevent1") || + id.startsWith("naddr1") + ) + ) { + throw new Response("Not Found", { status: 404 }); + } + return null; + }, + }, + // Catch-all for two-segment paths (spellbooks, etc.) { path: "/:actor/:identifier", element: (