217 lines
13 KiB
TypeScript
217 lines
13 KiB
TypeScript
import { Suspense } from "react";
|
|
import { lazyWithRetry } from "./lib/lazyWithRetry";
|
|
import { HashRouter, Navigate, Route, Routes } from "react-router-dom";
|
|
import { ScrollToTop } from "./components/ScrollToTop";
|
|
import { BECOME_ROLES } from "./lib/becomeRoles";
|
|
import { DeepLinkHandler } from "./components/DeepLinkHandler";
|
|
import { RouteErrorBoundary } from "./components/RouteErrorBoundary";
|
|
import { Layout } from "./components/Layout";
|
|
|
|
// The map is the homepage, but lazy-loading it keeps its chunk (plus the
|
|
// IndexedDB offline-cache layer) out of the initial entry bundle.
|
|
const MapPage = lazyWithRetry(() => import("./pages/MapPage"));
|
|
|
|
// Lazy load everything else — each page becomes its own JS chunk
|
|
const Index = lazyWithRetry(() => import("./pages/Index"));
|
|
const EventsPage = lazyWithRetry(() => import("./pages/EventsPage"));
|
|
const LabPage = lazyWithRetry(() => import("./pages/LabPage"));
|
|
const WalletPage = lazyWithRetry(() => import("./pages/WalletPage"));
|
|
const MintsPage = lazyWithRetry(() => import("./pages/MintsPage"));
|
|
const AppShopPage = lazyWithRetry(() => import("./pages/AppShopPage"));
|
|
const DiyPage = lazyWithRetry(() => import("./pages/DiyPage"));
|
|
const DeckCostPage = lazyWithRetry(() => import("./pages/DeckCostPage"));
|
|
const DeckDesignerPage = lazyWithRetry(() => import("./pages/DeckDesignerPage"));
|
|
const AdminReviewPage = lazyWithRetry(() => import("./pages/AdminReviewPage"));
|
|
const SettingsPage = lazyWithRetry(() => import("./pages/SettingsPage"));
|
|
const CreateSpotPage = lazyWithRetry(() => import("./pages/CreateSpotPage"));
|
|
const CreateShopPage = lazyWithRetry(() => import("./pages/CreateShopPage"));
|
|
const CreateEventPage = lazyWithRetry(() => import("./pages/CreateEventPage"));
|
|
const CreateListingPage = lazyWithRetry(() => import("./pages/CreateListingPage"));
|
|
const CreateTourPage = lazyWithRetry(() => import("./pages/CreateTourPage"));
|
|
const EditSpotPage = lazyWithRetry(() => import("./pages/EditSpotPage"));
|
|
const EditShopPage = lazyWithRetry(() => import("./pages/EditShopPage"));
|
|
const EditEventPage = lazyWithRetry(() => import("./pages/EditEventPage"));
|
|
const EditTourPage = lazyWithRetry(() => import("./pages/EditTourPage"));
|
|
const MarketplaceHubPage = lazyWithRetry(() => import("./pages/MarketplaceHubPage"));
|
|
const ListingDetailPage = lazyWithRetry(() => import("./pages/ListingDetailPage"));
|
|
const TradesPage = lazyWithRetry(() => import("./pages/TradesPage"));
|
|
const SpotDetailPage = lazyWithRetry(() => import("./pages/SpotDetailPage"));
|
|
const ShopDetailPage = lazyWithRetry(() => import("./pages/ShopDetailPage"));
|
|
const EventDetailPage = lazyWithRetry(() => import("./pages/EventDetailPage"));
|
|
const OnboardingPage = lazyWithRetry(() => import("./pages/OnboardingPage"));
|
|
const PyramidPage = lazyWithRetry(() => import("./pages/PyramidPage"));
|
|
// All "become a <role>" flows now share a single param route backed by a role
|
|
// registry (see src/lib/becomeRoles.tsx) instead of 12 separate route lines.
|
|
const BecomeRolePage = lazyWithRetry(() => import("./pages/BecomeRolePage"));
|
|
const SkatersPage = lazyWithRetry(() => import("./pages/SkatersPage"));
|
|
const CrewsPage = lazyWithRetry(() => import("./pages/CrewsPage"));
|
|
const CreateCrewPage = lazyWithRetry(() => import("./pages/CreateCrewPage"));
|
|
const EditCrewPage = lazyWithRetry(() => import("./pages/EditCrewPage"));
|
|
const CrewDetailPage = lazyWithRetry(() => import("./pages/CrewDetailPage"));
|
|
const CoachDetailPage = lazyWithRetry(() => import("./pages/CoachDetailPage"));
|
|
const TourDetailPage = lazyWithRetry(() => import("./pages/TourDetailPage"));
|
|
const AboutPage = lazyWithRetry(() => import("./pages/AboutPage"));
|
|
const FAQPage = lazyWithRetry(() => import("./pages/FAQPage"));
|
|
const FoodPage = lazyWithRetry(() => import("./pages/FoodPage"));
|
|
const CommunityStatsPage = lazyWithRetry(() => import("./pages/CommunityStatsPage"));
|
|
const CommunityPage = lazyWithRetry(() => import("./pages/CommunityPage"));
|
|
const SpotCollectionsPage = lazyWithRetry(() => import("./pages/SpotCollectionsPage"));
|
|
const CollectionDetailPage = lazyWithRetry(() => import("./pages/CollectionDetailPage"));
|
|
const DirectMessagesPage = lazyWithRetry(() => import("./pages/DirectMessagesPage"));
|
|
const ConversationPage = lazyWithRetry(() => import("./pages/ConversationPage"));
|
|
const GroupConversationPage = lazyWithRetry(() => import("./pages/GroupConversationPage"));
|
|
const ContactsPage = lazyWithRetry(() => import("./pages/ContactsPage"));
|
|
const BountyLeaderboardPage = lazyWithRetry(() => import("./pages/BountyLeaderboardPage"));
|
|
const ZapLeaderboardPage = lazyWithRetry(() => import("./pages/ZapLeaderboardPage"));
|
|
const SignersPage = lazyWithRetry(() => import("./pages/SignersPage"));
|
|
const StashPage = lazyWithRetry(() => import("./pages/StashPage"));
|
|
const NIP19Page = lazyWithRetry(() => import("./pages/NIP19Page").then(m => ({ default: m.NIP19Page })));
|
|
const LicensePage = lazyWithRetry(() => import("./pages/LicensePage"));
|
|
const PrivacyPage = lazyWithRetry(() => import("./pages/PrivacyPage"));
|
|
const TrustPage = lazyWithRetry(() => import("./pages/TrustPage"));
|
|
const NetworkAlertsPage = lazyWithRetry(() => import("./pages/NetworkAlertsPage"));
|
|
const MarketplaceGuidelinesPage = lazyWithRetry(() => import("./pages/MarketplaceGuidelinesPage"));
|
|
const SupportersPage = lazyWithRetry(() => import("./pages/SupportersPage"));
|
|
const DonatePage = lazyWithRetry(() => import("./pages/DonatePage"));
|
|
const RiderCardPage = lazyWithRetry(() => import("./pages/RiderCardPage"));
|
|
const PublicCardPage = lazyWithRetry(() => import("./pages/PublicCardPage"));
|
|
const VerifyCardPage = lazyWithRetry(() => import("./pages/VerifyCardPage"));
|
|
const AmberCallbackPage = lazyWithRetry(() => import("./pages/AmberCallbackPage"));
|
|
const MarketplacePage = lazyWithRetry(() => import("./pages/MarketplacePage"));
|
|
const NewsletterPage = lazyWithRetry(() => import("./pages/NewsletterPage"));
|
|
const GlossaryPage = lazyWithRetry(() => import("./pages/GlossaryPage"));
|
|
const NotFound = lazyWithRetry(() => import("./pages/NotFound"));
|
|
|
|
// Minimal inline fallback — no extra component needed
|
|
function PageLoader() {
|
|
return (
|
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
|
<div className="w-8 h-8 border-2 border-[#ff0040] border-t-transparent rounded-full animate-spin" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Legacy `/become-<slug>` URLs are linked from many pages and may be
|
|
// bookmarked/shared, so each redirects to the unified `/become/<slug>` route.
|
|
const BECOME_SLUGS = Object.keys(BECOME_ROLES);
|
|
|
|
export function AppRouter() {
|
|
return (
|
|
<HashRouter>
|
|
<DeepLinkHandler />
|
|
<ScrollToTop />
|
|
{/* Per-route boundary: a crash in one page shows a recoverable message
|
|
instead of blanking the whole app. Resets automatically on navigate
|
|
(keyed by pathname inside RouteErrorBoundary). */}
|
|
<RouteErrorBoundary>
|
|
<Suspense fallback={<PageLoader />}>
|
|
<Routes>
|
|
{/* One persistent layout for the whole app: the chrome (header, nav,
|
|
notifications, Sasquatch, scroll position) mounts ONCE and survives
|
|
navigation. Pages no longer wrap themselves in <Layout>. */}
|
|
<Route element={<Layout />}>
|
|
{/* Map is the homepage */}
|
|
<Route path="/" element={<MapPage />} />
|
|
{/* /map redirects to / */}
|
|
<Route path="/map" element={<MapPage />} />
|
|
{/* Legacy home page still accessible */}
|
|
<Route path="/home" element={<Index />} />
|
|
<Route path="/events" element={<EventsPage />} />
|
|
<Route path="/lab" element={<LabPage />} />
|
|
{/* Legacy route redirect */}
|
|
<Route path="/coaches" element={<LabPage />} />
|
|
{/* Unified role route + legacy /become-<slug> redirects */}
|
|
<Route path="/become/:role" element={<BecomeRolePage />} />
|
|
{BECOME_SLUGS.map((slug) => (
|
|
<Route
|
|
key={slug}
|
|
path={`/become-${slug}`}
|
|
element={<Navigate to={`/become/${slug}`} replace />}
|
|
/>
|
|
))}
|
|
<Route path="/skaters" element={<SkatersPage />} />
|
|
<Route path="/crews" element={<CrewsPage />} />
|
|
<Route path="/create-crew" element={<CreateCrewPage />} />
|
|
<Route path="/edit-crew/:pubkey/:identifier" element={<EditCrewPage />} />
|
|
<Route path="/crew/:pubkey/:identifier" element={<CrewDetailPage />} />
|
|
<Route path="/coach/:pubkey/:identifier" element={<CoachDetailPage />} />
|
|
{/* Canonical per-craft provider URL (the craft segment is cosmetic). */}
|
|
<Route path="/lab/:craft/:pubkey/:identifier" element={<CoachDetailPage />} />
|
|
<Route path="/onboarding" element={<OnboardingPage />} />
|
|
<Route path="/pyramid" element={<PyramidPage />} />
|
|
<Route path="/marketplace" element={<MarketplaceHubPage />} />
|
|
<Route path="/marketplace-guidelines" element={<MarketplaceGuidelinesPage />} />
|
|
<Route path="/listing/:pubkey/:identifier" element={<ListingDetailPage />} />
|
|
<Route path="/trades" element={<TradesPage />} />
|
|
<Route path="/stats" element={<CommunityStatsPage />} />
|
|
<Route path="/community" element={<CommunityPage />} />
|
|
<Route path="/about" element={<AboutPage />} />
|
|
<Route path="/trust" element={<TrustPage />} />
|
|
<Route path="/alerts" element={<NetworkAlertsPage />} />
|
|
<Route path="/faq" element={<FAQPage />} />
|
|
<Route path="/food" element={<FoodPage />} />
|
|
<Route path="/wallet" element={<WalletPage />} />
|
|
<Route path="/mints" element={<MintsPage />} />
|
|
<Route path="/app-shop" element={<AppShopPage />} />
|
|
<Route path="/diy" element={<DiyPage />} />
|
|
<Route path="/diy/deck-cost" element={<DeckCostPage />} />
|
|
<Route path="/diy/deck-designer" element={<DeckDesignerPage />} />
|
|
<Route path="/review" element={<AdminReviewPage />} />
|
|
<Route path="/settings" element={<SettingsPage />} />
|
|
<Route path="/create-spot" element={<CreateSpotPage />} />
|
|
<Route path="/create-shop" element={<CreateShopPage />} />
|
|
<Route path="/create-event" element={<CreateEventPage />} />
|
|
<Route path="/create-listing" element={<CreateListingPage />} />
|
|
<Route path="/create-tour" element={<CreateTourPage />} />
|
|
<Route path="/edit-spot/:pubkey/:identifier" element={<EditSpotPage />} />
|
|
<Route path="/edit-shop/:pubkey/:identifier" element={<EditShopPage />} />
|
|
<Route path="/edit-event/:pubkey/:identifier" element={<EditEventPage />} />
|
|
<Route path="/edit-tour/:pubkey/:identifier" element={<EditTourPage />} />
|
|
<Route path="/spot/:pubkey/:identifier" element={<SpotDetailPage />} />
|
|
<Route path="/shop/:pubkey/:identifier" element={<ShopDetailPage />} />
|
|
<Route path="/event/:pubkey/:identifier" element={<EventDetailPage />} />
|
|
<Route path="/tour/:pubkey/:identifier" element={<TourDetailPage />} />
|
|
<Route path="/collections" element={<SpotCollectionsPage />} />
|
|
<Route path="/collections/:pubkey/:identifier" element={<CollectionDetailPage />} />
|
|
<Route path="/messages" element={<DirectMessagesPage />} />
|
|
<Route path="/messages/group/:participants" element={<GroupConversationPage />} />
|
|
<Route path="/messages/:npub" element={<ConversationPage />} />
|
|
<Route path="/contacts" element={<ContactsPage />} />
|
|
{/* Bounty System */}
|
|
<Route path="/bounties" element={<BountyLeaderboardPage />} />
|
|
{/* Zap Leaderboards */}
|
|
<Route path="/leaderboards" element={<ZapLeaderboardPage />} />
|
|
{/* Security & Signers */}
|
|
<Route path="/signers" element={<SignersPage />} />
|
|
{/* Stash - Private Inventory */}
|
|
<Route path="/stash" element={<StashPage />} />
|
|
{/* Info pages */}
|
|
<Route path="/card" element={<RiderCardPage />} />
|
|
<Route path="/card/:npub" element={<PublicCardPage />} />
|
|
<Route path="/verify/:naddr" element={<VerifyCardPage />} />
|
|
<Route path="/license" element={<LicensePage />} />
|
|
<Route path="/privacy" element={<PrivacyPage />} />
|
|
<Route path="/supporters" element={<SupportersPage />} />
|
|
<Route path="/donate" element={<DonatePage />} />
|
|
{/* Marketplace */}
|
|
<Route path="/marketplace-listings" element={<MarketplacePage />} />
|
|
{/* Newsletter */}
|
|
<Route path="/newsletter" element={<NewsletterPage />} />
|
|
{/* Trick Glossary */}
|
|
<Route path="/glossary" element={<GlossaryPage />} />
|
|
{/* NIP-19 route for npub1, note1, naddr1, nevent1, nprofile1 */}
|
|
<Route path="/:nip19" element={<NIP19Page />} />
|
|
</Route>
|
|
|
|
{/* Outside the layout: Amber's callback + 404 (render no chrome). */}
|
|
{/* Amber NIP-55 callback — Amber redirects here after signing/login */}
|
|
<Route path="/amber-callback" element={<AmberCallbackPage />} />
|
|
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
|
<Route path="*" element={<NotFound />} />
|
|
</Routes>
|
|
</Suspense>
|
|
</RouteErrorBoundary>
|
|
</HashRouter>
|
|
);
|
|
}
|
|
export default AppRouter;
|