diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index a83aa996..2541692b 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -1,32 +1,28 @@ -import { lazy, Suspense, useState } from "react"; -import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; -import { AudioNavigationGuard } from "@/components/AudioNavigationGuard"; -import { DeepLinkHandler } from "@/components/DeepLinkHandler"; -import { MinimizedAudioBar } from "@/components/MinimizedAudioBar"; -import { AudioPlayerProvider } from "@/contexts/AudioPlayerContext"; -import { sidebarItemIcon } from "@/lib/sidebarItems"; +import { lazy, Suspense, useMemo, useRef, useState } from "react"; +import { BrowserRouter, Navigate, Outlet, Route, Routes } from "react-router-dom"; import { Toaster } from "./components/ui/toaster"; -import { FundraiserLayout } from "./components/FundraiserLayout"; +import { TopNav } from "./components/TopNav"; import { ScrollToTop } from "./components/ScrollToTop"; import { VersionCheck } from "./components/VersionCheck"; import { useCurrentUser } from "./hooks/useCurrentUser"; import { useProfileUrl } from "./hooks/useProfileUrl"; -import { getExtraKindDef } from "./lib/extraKinds"; +import { + CenterColumnContext, + DrawerContext, + LayoutStore, + LayoutStoreContext, + NavHiddenContext, +} from "@/contexts/LayoutContext"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Link } from "react-router-dom"; +import { cn } from "@/lib/utils"; // Critical-path pages: eagerly loaded (landing + fallback) import Index from "./pages/Index"; import NotFound from "./pages/NotFound"; -import MessagesPage from "./pages/Messages"; - -// Lazy-loaded compose modal (pulls in emoji-mart ~620K) -const ReplyComposeModal = lazy(() => import("@/components/ReplyComposeModal").then(m => ({ default: m.ReplyComposeModal }))); - -// Lazy-loaded emoji pack dialog -const EmojiPackDialog = lazy(() => import("@/components/EmojiPackDialog").then(m => ({ default: m.EmojiPackDialog }))); // Campaigns: home + create. (Campaign detail is dispatched from NIP19Page -// when an naddr resolves to kind 33863.) The campaigns list IS the homepage; -// the configurable HomePage delegation from the Twitter-era app is gone. +// when an naddr resolves to kind 33863.) const CampaignsPage = lazy(() => import("./pages/CampaignsPage").then(m => ({ default: m.CampaignsPage }))); const CreateCampaignPage = lazy(() => import("./pages/CreateCampaignPage").then(m => ({ default: m.CreateCampaignPage }))); const AllCampaignsPage = lazy(() => import("./pages/AllCampaignsPage").then(m => ({ default: m.AllCampaignsPage }))); @@ -35,111 +31,32 @@ const AllCampaignsPage = lazy(() => import("./pages/AllCampaignsPage").then(m => const ActionsPage = lazy(() => import("./pages/ActionsPage")); const CreateActionPage = lazy(() => import("./pages/CreateActionPage").then(m => ({ default: m.CreateActionPage }))); const AdvancedSettingsPage = lazy(() => import("./pages/AdvancedSettingsPage").then(m => ({ default: m.AdvancedSettingsPage }))); -const AIChatPage = lazy(() => import("./pages/AIChatPage").then(m => ({ default: m.AIChatPage }))); const AppearanceSettingsPage = lazy(() => import("./pages/AppearanceSettingsPage").then(m => ({ default: m.AppearanceSettingsPage }))); -const ArchivePage = lazy(() => import("./pages/ArchivePage").then(m => ({ default: m.ArchivePage }))); -const ArticleEditorPage = lazy(() => import("./pages/ArticleEditorPage").then(m => ({ default: m.ArticleEditorPage }))); -const BadgesPage = lazy(() => import("./pages/BadgesPage").then(m => ({ default: m.BadgesPage }))); -const BlueskyPage = lazy(() => import("./pages/BlueskyPage").then(m => ({ default: m.BlueskyPage }))); -const BookmarksPage = lazy(() => import("./pages/BookmarksPage").then(m => ({ default: m.BookmarksPage }))); -const BooksPage = lazy(() => import("./pages/BooksPage").then(m => ({ default: m.BooksPage }))); const ChangelogPage = lazy(() => import("./pages/ChangelogPage").then(m => ({ default: m.ChangelogPage }))); const CommunitiesPage = lazy(() => import("./pages/CommunitiesPage").then(m => ({ default: m.CommunitiesPage }))); const CreateCommunityPage = lazy(() => import("./pages/CreateCommunityPage").then(m => ({ default: m.CreateCommunityPage }))); -const CreateEventPage = lazy(() => import("./pages/CreateEventPage").then(m => ({ default: m.CreateEventPage }))); -const ContentSettingsPage = lazy(() => import("./pages/ContentSettingsPage").then(m => ({ default: m.ContentSettingsPage }))); const CSAEPolicyPage = lazy(() => import("./pages/CSAEPolicyPage").then(m => ({ default: m.CSAEPolicyPage }))); -const DomainFeedPage = lazy(() => import("./pages/DomainFeedPage").then(m => ({ default: m.DomainFeedPage }))); -const EventsFeedPage = lazy(() => import("./pages/EventsFeedPage").then(m => ({ default: m.EventsFeedPage }))); const ExternalContentPage = lazy(() => import("./pages/ExternalContentPage").then(m => ({ default: m.ExternalContentPage }))); const GeotagPage = lazy(() => import("./pages/GeotagPage").then(m => ({ default: m.GeotagPage }))); const HashtagPage = lazy(() => import("./pages/HashtagPage").then(m => ({ default: m.HashtagPage }))); const HelpPage = lazy(() => import("./pages/HelpPage").then(m => ({ default: m.HelpPage }))); const DonorGuidePage = lazy(() => import("./pages/DonorGuidePage").then(m => ({ default: m.DonorGuidePage }))); const ActivistGuidePage = lazy(() => import("./pages/ActivistGuidePage").then(m => ({ default: m.ActivistGuidePage }))); -const KindFeedPage = lazy(() => import("./pages/KindFeedPage").then(m => ({ default: m.KindFeedPage }))); -const LetterComposePage = lazy(() => import("./pages/LetterComposePage").then(m => ({ default: m.LetterComposePage }))); -const LetterPreferencesPage = lazy(() => import("./pages/LetterPreferencesPage").then(m => ({ default: m.LetterPreferencesPage }))); -const LettersPage = lazy(() => import("./pages/LettersPage").then(m => ({ default: m.LettersPage }))); -const MusicPage = lazy(() => import("./pages/MusicPage").then(m => ({ default: m.MusicPage }))); const NetworkSettingsPage = lazy(() => import("./pages/NetworkSettingsPage").then(m => ({ default: m.NetworkSettingsPage }))); const NIP19Page = lazy(() => import("./pages/NIP19Page").then(m => ({ default: m.NIP19Page }))); const NotificationSettings = lazy(() => import("./pages/NotificationSettings").then(m => ({ default: m.NotificationSettings }))); const NotificationsPage = lazy(() => import("./pages/NotificationsPage").then(m => ({ default: m.NotificationsPage }))); const OrganizersPage = lazy(() => import("./pages/OrganizersPage").then(m => ({ default: m.OrganizersPage }))); const EventDashboardPage = lazy(() => import("./pages/EventDashboardPage").then(m => ({ default: m.EventDashboardPage }))); -const PhotosFeedPage = lazy(() => import("./pages/PhotosFeedPage").then(m => ({ default: m.PhotosFeedPage }))); -const PodcastsFeedPage = lazy(() => import("./pages/PodcastsFeedPage").then(m => ({ default: m.PodcastsFeedPage }))); const PrivacyPolicyPage = lazy(() => import("./pages/PrivacyPolicyPage").then(m => ({ default: m.PrivacyPolicyPage }))); const ProfileSettings = lazy(() => import("./pages/ProfileSettings").then(m => ({ default: m.ProfileSettings }))); -const RelayPage = lazy(() => import("./pages/RelayPage").then(m => ({ default: m.RelayPage }))); const SearchPage = lazy(() => import("./pages/SearchPage").then(m => ({ default: m.SearchPage }))); const SettingsPage = lazy(() => import("./pages/SettingsPage").then(m => ({ default: m.SettingsPage }))); -const TreasuresPage = lazy(() => import("./pages/TreasuresPage").then(m => ({ default: m.TreasuresPage }))); -const TrendsPage = lazy(() => import("./pages/TrendsPage").then(m => ({ default: m.TrendsPage }))); -const UserListsPage = lazy(() => import("./pages/UserListsPage").then(m => ({ default: m.UserListsPage }))); -const VerifiedPage = lazy(() => import("./pages/VerifiedPage").then(m => ({ default: m.VerifiedPage }))); -const VideosFeedPage = lazy(() => import("./pages/VideosFeedPage").then(m => ({ default: m.VideosFeedPage }))); -const VinesFeedPage = lazy(() => import("./pages/VinesFeedPage").then(m => ({ default: m.VinesFeedPage }))); const WalletPage = lazy(() => import("./pages/WalletPage").then(m => ({ default: m.WalletPage }))); const WalletRecoveryPage = lazy(() => import("./pages/WalletRecoveryPage").then(m => ({ default: m.WalletRecoveryPage }))); const WalletSettingsPage = lazy(() => import("./pages/WalletSettingsPage").then(m => ({ default: m.WalletSettingsPage }))); -const WebxdcFeedPage = lazy(() => import("./pages/WebxdcFeedPage").then(m => ({ default: m.WebxdcFeedPage }))); -const WikipediaPage = lazy(() => import("./pages/WikipediaPage").then(m => ({ default: m.WikipediaPage }))); -const WorldPage = lazy(() => import("./pages/WorldPage").then(m => ({ default: m.WorldPage }))); -const FollowPage = lazy(() => import("./pages/FollowPage").then(m => ({ default: m.FollowPage }))); -const ReceivePage = lazy(() => import("./pages/ReceivePage").then(m => ({ default: m.ReceivePage }))); const RemoteLoginSuccessPage = lazy(() => import("./pages/RemoteLoginSuccessPage").then(m => ({ default: m.RemoteLoginSuccessPage }))); -const pollsDef = getExtraKindDef("polls")!; -const colorsDef = getExtraKindDef("colors")!; -const packsDef = getExtraKindDef("packs")!; -const articlesDef = getExtraKindDef("articles")!; -const decksDef = getExtraKindDef("decks")!; -const emojisDef = getExtraKindDef("emojis")!; -const developmentDef = getExtraKindDef("development")!; -const highlightsDef = getExtraKindDef("highlights")!; - -/** Polls feed page with a FAB that opens the compose modal (poll mode via + menu). */ -function PollsFeedPage() { - const [composeOpen, setComposeOpen] = useState(false); - return ( - <> - setComposeOpen(true)} - /> - {composeOpen && ( - - - - )} - - ); -} - -/** Emoji feed page with a FAB that opens the emoji pack creation dialog. */ -function EmojiFeedPage() { - const [composeOpen, setComposeOpen] = useState(false); - return ( - <> - setComposeOpen(true)} - /> - {composeOpen && ( - - - - )} - - ); -} - /** Redirects /profile to the user's canonical profile URL (nip05 or npub). */ function ProfileRedirect() { const { user, metadata } = useCurrentUser(); @@ -148,185 +65,121 @@ function ProfileRedirect() { return ; } +function PageSkeleton() { + return ( +
+ + + + +
+ ); +} + +function SiteFooter() { + return ( +
+
+ © {new Date().getFullYear()} Agora. Fundraisers on Nostr. + +
+
+ ); +} + +/** + * Persistent app shell. GoFundMe-style top-nav-only chrome wrapping the + * full-width route outlet. + */ +function FundraiserLayout() { + const store = useMemo(() => new LayoutStore(), []); + const centerColumnRef = useRef(null); + const [centerColumnEl, setCenterColumnEl] = useState(null); + + return ( + + + {}}> + +
+ + }> +
{ + centerColumnRef.current = el; + setCenterColumnEl(el); + }} + className={cn("flex-1 min-w-0 w-full mx-auto max-w-3xl")} + > + +
+
+ +
+
+
+
+
+ ); +} + export function AppRouter() { return ( - - - - - - - - - - {/* Auto-follow deep link: fullscreen immersive (no sidebars/nav) */} - } /> - } /> + + + + + + {/* All routes share the persistent FundraiserLayout (top nav + footer) */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> - {/* All routes share the persistent FundraiserLayout (top nav + footer) */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - {/* /streams redirects to /videos for backward compatibility */} - } - /> - } /> - } /> - } /> - } /> - } /> - - } - /> - - } - /> - } /> - } /> - } /> - - } - /> - - } - /> - - } - /> - } /> - - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - {/* Callback target for remote signers (e.g. Amber, Primal) after NIP-46 approval */} - } /> - {/* NIP-19 route for npub1, note1, naddr1, nevent1, nprofile1 */} - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - + {/* Callback target for remote signers (e.g. Amber, Primal) after NIP-46 approval */} + } /> + {/* NIP-19 route for npub1, note1, naddr1, nevent1, nprofile1 */} + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> + + + ); } export default AppRouter; diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 34cfaff9..cd730e0f 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -34,12 +34,6 @@ const settingsSections: SettingsSection[] = [ description: 'System, light, or dark mode.', path: '/settings/appearance', }, - { - id: 'feed', - label: 'Home Feed', - description: 'Choose which post types appear in your home feed, and manage muted users, hashtags, and sensitive content.', - path: '/settings/feed', - }, { id: 'network', label: 'Network',