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: (