SkateSpots/src/AppRouter.tsx

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;