refactor(layout): hoist Layout into the router so the chrome mounts once

- AppRouter wraps every route (except /amber-callback and the 404) in ONE
  <Route element={<Layout/>}> with an <Outlet/>; the chrome now persists across
  navigation instead of being torn down and rebuilt on every page.
- Layout: children optional (falls back to <Outlet/>); the map is full-bleed via
  a route check (isFullBleed) instead of a per-page noPadding prop.
- All 59 pages + CraftProfileForm/ErrorState/NewsletterIssueView now render
  fragments instead of <Layout> (structure-preserving), imports dropped.

Removes the whole remount class: chrome no longer re-runs its hooks per
navigation, Sasquatch chat state survives navigation, and the header can't
re-open Amber on every page change.
This commit is contained in:
SkateSpots 2026-09-27 13:41:42 +07:00
parent 55b4dd2e86
commit 087f490acc
65 changed files with 263 additions and 308 deletions

View file

@ -150,13 +150,16 @@ session with **no other memory of this project**. So:
MOTIS 2, open CORS) — note origin/destination go to that service, unlike road
modes. **No external map-app handoff anywhere** (product decision) and no
scooter/moped profile.
- **⚠️ `Layout` is NOT a router layout** — ~62 pages wrap themselves in
`<Layout>`, so its chrome (`NotificationBell`, `SasquatchGuide`, nav) **remounts
on every navigation**. Consequence: any *decrypt-on-mount* hook in that chrome
re-opens Amber (NIP-55) on every page change. Encrypted hooks must be gated for
interactive signers: `staleTime/gcTime: Infinity`, `refetchOnMount: false`,
`refetchOnReconnect: false` (see `useSyncedAppData` / `useStash`). Hoisting
`Layout` into the router would fix the whole class — a known follow-up.
- **`Layout` is a router layout** — `AppRouter` wraps every route (except
`/amber-callback` and the `*` 404) in **one** `<Route element={<Layout/>}>`
with an `<Outlet/>`, so the chrome (header, nav, notifications, Sasquatch)
mounts **once** and survives navigation. Pages render **fragments** (`<>…</>`),
never `<Layout>`. The map is full-bleed via a route check (`isFullBleed` in
`Layout`), not a per-page `noPadding` prop. **Don't reintroduce per-page
`<Layout>` wrappers** — remounting the chrome re-runs its hooks (and with Amber
that means signer popups). Encrypted-on-mount hooks are also gated for
interactive signers (`staleTime/gcTime: Infinity`, `refetchOnMount: false`) —
see `useSyncedAppData` / `useStash`.
- **Mints directory**: `/mints` page lists Cashu mints from the community
**MintRadar** project. MintRadar has **no CORS**, so it's fetched same-origin
at `/api/mints/*` — prod proxies via `public/_redirects` (`/api/mints/* →

View file

@ -5,6 +5,7 @@ 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.
@ -93,6 +94,10 @@ export function AppRouter() {
<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 / */}
@ -162,8 +167,6 @@ export function AppRouter() {
<Route path="/license" element={<LicensePage />} />
<Route path="/privacy" element={<PrivacyPage />} />
<Route path="/supporters" element={<SupportersPage />} />
{/* Amber NIP-55 callback — Amber redirects here after signing/login */}
<Route path="/amber-callback" element={<AmberCallbackPage />} />
{/* Marketplace */}
<Route path="/marketplace-listings" element={<MarketplacePage />} />
{/* Newsletter */}
@ -172,6 +175,11 @@ export function AppRouter() {
<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>

View file

@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -295,7 +294,7 @@ export function CraftProfileForm({ config }: { config: CraftFormConfig }) {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -306,7 +305,7 @@ export function CraftProfileForm({ config }: { config: CraftFormConfig }) {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -314,7 +313,7 @@ export function CraftProfileForm({ config }: { config: CraftFormConfig }) {
const entityCoord = `${kind}:${user.pubkey}:${identifier}`;
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
{/* Header */}
<div className="mb-6">
@ -572,6 +571,6 @@ export function CraftProfileForm({ config }: { config: CraftFormConfig }) {
</Button>
</form>
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { AlertTriangle, RefreshCw } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Layout } from '@/components/Layout';
import { cn } from '@/lib/utils';
/**
@ -46,10 +45,10 @@ export function PageError(props: {
onRetry?: () => void;
}) {
return (
<Layout>
<>
<div className="container mx-auto max-w-4xl px-4 py-8">
<ErrorState {...props} />
</div>
</Layout>
</>
);
}

View file

@ -1,5 +1,5 @@
import { useState, useRef, lazy, Suspense } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Link, Outlet, useLocation } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { cn } from '@/lib/utils';
import { isNativePlatform } from '@/lib/platform';
@ -100,9 +100,12 @@ function hasCashuWalletHint(): boolean {
}
interface LayoutProps {
children: React.ReactNode;
/** Page content. Optional: when rendered as a router layout (via <Outlet/>)
* there are no children. */
children?: React.ReactNode;
/** If true, removes the bottom padding that prevents bottom nav overlap.
* Use for full-bleed pages like the map that manage their own height. */
* Use for full-bleed pages like the map that manage their own height.
* The map routes get this automatically (see `isFullBleed`). */
noPadding?: boolean;
}
@ -491,6 +494,9 @@ function MobileNav({
export function Layout({ children, noPadding }: LayoutProps) {
const location = useLocation();
// The map is full-bleed (it manages its own height), so it gets no bottom
// padding even though it shares this one persistent layout with every route.
const isFullBleed = noPadding || location.pathname === '/' || location.pathname === '/map';
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [showStatusDialog, setShowStatusDialog] = useState(false);
const [showCustomizeNav, setShowCustomizeNav] = useState(false);
@ -1008,8 +1014,8 @@ export function Layout({ children, noPadding }: LayoutProps) {
{/* Main Content — extra bottom padding on mobile so the fixed bottom nav never overlaps */}
{/* noPadding: full-bleed pages (map) manage their own height */}
<main id="main" className={noPadding ? undefined : "pb-[calc(5rem+env(safe-area-inset-bottom,0px))] md:pb-6"}>
{children}
<main id="main" className={isFullBleed ? undefined : "pb-[calc(5rem+env(safe-area-inset-bottom,0px))] md:pb-6"}>
{children ?? <Outlet />}
</main>
{/* Footer - Desktop */}

View file

@ -5,7 +5,6 @@ import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { useQuery } from '@tanstack/react-query';
import { ArrowLeft, Radio, Calendar } from 'lucide-react';
import { Layout } from '@/components/Layout';
import { Skeleton } from '@/components/ui/skeleton';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { ZapButton } from '@/components/ZapButton';
@ -126,7 +125,7 @@ export function NewsletterIssueView({ pubkey, identifier }: { pubkey: string; id
if (isLoading) {
return (
<Layout>
<>
<div className="container max-w-2xl px-4 py-8">
<Skeleton className="mb-6 h-6 w-32" />
<Skeleton className="mb-4 aspect-video w-full rounded-2xl" />
@ -137,7 +136,7 @@ export function NewsletterIssueView({ pubkey, identifier }: { pubkey: string; id
<Skeleton className="h-4 w-4/6" />
</div>
</div>
</Layout>
</>
);
}
@ -153,7 +152,7 @@ export function NewsletterIssueView({ pubkey, identifier }: { pubkey: string; id
});
return (
<Layout>
<>
<article className="container max-w-2xl px-4 py-8 pb-24 md:pb-14">
<Link
to="/newsletter"
@ -206,6 +205,6 @@ export function NewsletterIssueView({ pubkey, identifier }: { pubkey: string; id
</div>
</div>
</article>
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { useCurrentUser } from '@/hooks/useCurrentUser';
@ -54,7 +53,7 @@ Version: v0.1.0`;
const bugReportUrl = `/messages/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx?message=${encodeURIComponent(bugReportTemplate)}`;
return (
<Layout>
<>
<div className="container px-4 py-12 max-w-4xl">
{/* Hero */}
<div className="text-center mb-12">
@ -321,6 +320,6 @@ Version: v0.1.0`;
</div>
</div>
</div>
</Layout>
</>
);
}

View file

@ -3,7 +3,6 @@ import { useSeoMeta } from '@unhead/react';
import { useNavigate, Link } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -312,7 +311,7 @@ export default function BecomeBuilderPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -323,14 +322,14 @@ export default function BecomeBuilderPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
const hasNip05 = metadata?.nip05;
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/lab" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -624,6 +623,6 @@ export default function BecomeBuilderPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -3,7 +3,6 @@ import { useSeoMeta } from '@unhead/react';
import { useNavigate, Link } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -250,7 +249,7 @@ export default function BecomeCoachPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -261,14 +260,14 @@ export default function BecomeCoachPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
const hasNip05 = metadata?.nip05;
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/lab" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -535,6 +534,6 @@ export default function BecomeCoachPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -3,7 +3,6 @@ import { useSeoMeta } from '@unhead/react';
import { useNavigate, Link } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -245,7 +244,7 @@ export default function BecomeEditorPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -256,14 +255,14 @@ export default function BecomeEditorPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
const hasNip05 = !!author.data?.metadata?.nip05;
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/lab" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -530,6 +529,6 @@ export default function BecomeEditorPage() {
</div>
</form>
</div>
</Layout>
</>
);
}

View file

@ -3,7 +3,6 @@ import { useSeoMeta } from '@unhead/react';
import { useNavigate, Link } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -274,7 +273,7 @@ export default function BecomeFilmerPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -285,14 +284,14 @@ export default function BecomeFilmerPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
const hasNip05 = metadata?.nip05;
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/lab" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -601,6 +600,6 @@ export default function BecomeFilmerPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -3,7 +3,6 @@ import { useSeoMeta } from '@unhead/react';
import { useNavigate, Link } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -333,7 +332,7 @@ export default function BecomePhotographerPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -344,14 +343,14 @@ export default function BecomePhotographerPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
const hasNip05 = metadata?.nip05;
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/lab" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -650,6 +649,6 @@ export default function BecomePhotographerPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useEffect, useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, Link } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -210,7 +209,7 @@ export default function BecomeSkaterPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-12 max-w-xl">
<Card className="bg-black/80 border-2 border-[#ff0040]/40">
<CardContent className="py-14 text-center">
@ -223,7 +222,7 @@ export default function BecomeSkaterPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -232,7 +231,7 @@ export default function BecomeSkaterPage() {
const levelEntries = Object.entries(SKATER_LEVELS) as [SkaterLevel, { label: string; emoji: string; color: string }][];
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<Link to="/skaters" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
<ArrowLeft className="h-4 w-4 mr-2" />
@ -666,6 +665,6 @@ export default function BecomeSkaterPage() {
</ScrollArea>
</DialogContent>
</Dialog>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { ErrorState } from '@/components/ErrorState';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -190,16 +189,16 @@ export default function BountyLeaderboardPage() {
if (shopsError || spotsError) {
return (
<Layout>
<>
<div className="container mx-auto max-w-3xl px-4 py-8">
<ErrorState onRetry={() => { refetchShops(); refetchSpots(); }} />
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 py-6 sm:py-8 max-w-4xl">
{/* Header */}
<div className="text-center mb-8">
@ -474,6 +473,6 @@ export default function BountyLeaderboardPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useSeoMeta } from '@unhead/react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { PageError } from '@/components/ErrorState';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
@ -55,7 +54,7 @@ import { nip19 } from 'nostr-tools';
function CoachDetailSkeleton() {
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-4xl">
<Skeleton className="h-6 w-32 mb-6" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
@ -78,7 +77,7 @@ function CoachDetailSkeleton() {
</div>
</div>
</div>
</Layout>
</>
);
}
@ -230,7 +229,7 @@ export default function CoachDetailPage() {
const skillsTitle = SKILLS_TITLES[profile.profileType] ?? 'Services & Skills';
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-4xl">
<Link
to="/lab"
@ -772,6 +771,6 @@ export default function CoachDetailPage() {
</div>
</div>
</div>
</Layout>
</>
);
}

View file

@ -3,7 +3,6 @@ import { useParams, Link, useNavigate } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { useNostr } from '@nostrify/react';
import { useQuery } from '@tanstack/react-query';
import { Layout } from '@/components/Layout';
import { PageError } from '@/components/ErrorState';
import { SpotCard, SpotCardSkeleton } from '@/components/SpotCard';
import { SpotMap } from '@/components/SpotMap';
@ -24,7 +23,7 @@ import { useState } from 'react';
function CollectionDetailSkeleton() {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Skeleton className="h-6 w-32 mb-6" />
<div className="space-y-6">
@ -37,7 +36,7 @@ function CollectionDetailSkeleton() {
</div>
</div>
</div>
</Layout>
</>
);
}
@ -206,7 +205,7 @@ export default function CollectionDetailPage() {
}
return (
<Layout>
<>
<div className="container px-4 py-4 sm:py-8">
{/* Back link */}
<Link
@ -376,6 +375,6 @@ export default function CollectionDetailPage() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { useAuthor } from '@/hooks/useAuthor';
@ -194,7 +193,7 @@ export default function CommunityStatsPage() {
const labTotal = useMemo(() => (stats ? labCraftTotal(stats) : 0), [stats]);
return (
<Layout>
<>
<div className="container max-w-6xl px-4 py-10 md:py-14">
{/* ── Header ── */}
@ -512,6 +511,6 @@ export default function CommunityStatsPage() {
</div>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useMemo, useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { PageHeader } from '@/components/PageHeader';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -169,7 +168,7 @@ export default function ContactsPage() {
if (!user) {
return (
<Layout>
<>
<div className="container max-w-2xl px-4 py-12">
<Card className="border-dashed">
<CardContent className="flex flex-col items-center gap-4 py-12 text-center">
@ -185,7 +184,7 @@ export default function ContactsPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -219,7 +218,7 @@ export default function ContactsPage() {
) : undefined;
return (
<Layout>
<>
<div className="container max-w-2xl px-4 py-6">
{/* Header */}
<PageHeader
@ -328,7 +327,7 @@ export default function ContactsPage() {
</TabsContent>
</Tabs>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState, useRef, useEffect } from 'react';
import { useParams, Link, useSearchParams } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { LoginArea } from '@/components/auth/LoginArea';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -243,7 +242,7 @@ export default function ConversationPage() {
// Not logged in
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="border-dashed border-2 border-gray-700">
<CardContent className="py-16 text-center">
@ -255,7 +254,7 @@ export default function ConversationPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -265,7 +264,7 @@ export default function ConversationPage() {
const isMobile = isAndroid || /iPhone|iPad|iPod/i.test(navigator.userAgent);
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="border-dashed border-2 border-orange-500/30">
<CardContent className="py-12 text-center px-4">
@ -287,12 +286,12 @@ export default function ConversationPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 py-2 sm:py-4 max-w-2xl h-[calc(100dvh-56px-80px)] sm:h-[calc(100dvh-64px-60px)] md:h-[calc(100vh-64px-60px)] flex flex-col">
{/* Header */}
<div className="flex items-center gap-2 sm:gap-3 pb-3 sm:pb-4 border-b border-gray-800 flex-shrink-0">
@ -473,6 +472,6 @@ export default function ConversationPage() {
<AmberApprovalHint approvals={4} action="send this message" className="mt-1.5 justify-center" />
</div>
</div>
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { LoginArea } from '@/components/auth/LoginArea';
import { CrewForm } from '@/components/CrewForm';
@ -17,7 +16,7 @@ export default function CreateCrewPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -28,12 +27,12 @@ export default function CreateCrewPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/crews" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -47,6 +46,6 @@ export default function CreateCrewPage() {
</div>
<CrewForm />
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState, useEffect, useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -434,7 +433,7 @@ export default function CreateEventPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -447,14 +446,14 @@ export default function CreateEventPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
const hasNip05 = metadata?.nip05;
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/events" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -1144,6 +1143,6 @@ export default function CreateEventPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState, useEffect, useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, Link, useSearchParams } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { PageHeader } from '@/components/PageHeader';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -336,7 +335,7 @@ export default function CreateListingPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -347,12 +346,12 @@ export default function CreateListingPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link
@ -1032,6 +1031,6 @@ export default function CreateListingPage() {
</Button>
</form>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -245,7 +244,7 @@ export default function CreateShopPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -258,12 +257,12 @@ export default function CreateShopPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/map" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -507,6 +506,6 @@ export default function CreateShopPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -380,7 +379,7 @@ export default function CreateSpotPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -393,12 +392,12 @@ export default function CreateSpotPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to="/map" className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -773,6 +772,6 @@ export default function CreateSpotPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState, useEffect, useRef } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -855,7 +854,7 @@ export default function CreateTourPage({ existing }: { existing?: SkateTour } =
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -868,7 +867,7 @@ export default function CreateTourPage({ existing }: { existing?: SkateTour } =
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -879,7 +878,7 @@ export default function CreateTourPage({ existing }: { existing?: SkateTour } =
// Show loading state while profile is being fetched
if (isLoadingProfile) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -891,23 +890,23 @@ export default function CreateTourPage({ existing }: { existing?: SkateTour } =
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
// Require NIP-05 for tour creation
if (!hasNip05) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Nip05Required role="tour organizers" />
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
{/* Immersive header — layered, category-tinted, skate-punk */}
<div className="mb-8">
@ -2508,6 +2507,6 @@ export default function CreateTourPage({ existing }: { existing?: SkateTour } =
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useParams, Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { PageError } from '@/components/ErrorState';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -135,7 +134,7 @@ export default function CrewDetailPage() {
if (isLoading) {
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-4xl">
<Skeleton className="h-6 w-32 mb-6" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
@ -146,7 +145,7 @@ export default function CrewDetailPage() {
<Skeleton className="h-64 w-full" />
</div>
</div>
</Layout>
</>
);
}
@ -159,7 +158,7 @@ export default function CrewDetailPage() {
const hasMembers = crew.members.length > 0;
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-4xl">
<Link to="/crews" className="inline-flex items-center text-muted-foreground hover:text-foreground transition-colors mb-6">
<ArrowLeft className="h-4 w-4 mr-2" />
@ -567,6 +566,6 @@ export default function CrewDetailPage() {
}}
/>
)}
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState, useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { LazyMount } from '@/components/LazyMount';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -212,7 +211,7 @@ export default function CrewsPage() {
};
return (
<Layout>
<>
<div className="container max-w-3xl px-4 py-6">
{/* Hero header band */}
<div className="relative mb-5 overflow-hidden rounded-3xl border border-teal-500/20 bg-gradient-to-br from-teal-500/[0.12] via-black to-black p-5 sm:p-6">
@ -381,7 +380,7 @@ export default function CrewsPage() {
</div>
)}
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState } from 'react';
import { Link } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { PageHeader } from '@/components/PageHeader';
import { LoginArea } from '@/components/auth/LoginArea';
import { Card, CardContent } from '@/components/ui/card';
@ -229,7 +228,7 @@ export default function DirectMessagesPage() {
// Not logged in
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="border-dashed border-2 border-gray-700">
<CardContent className="py-16 text-center">
@ -244,7 +243,7 @@ export default function DirectMessagesPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -255,7 +254,7 @@ export default function DirectMessagesPage() {
const isMobile = isAndroid || isIOS;
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="border-dashed border-2 border-orange-500/30">
<CardContent className="py-12 sm:py-16 text-center px-4">
@ -297,12 +296,12 @@ export default function DirectMessagesPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 py-4 sm:py-8 max-w-2xl">
{/* Phishing warning — shown once, dismissible */}
<PhishingWarningBanner />
@ -394,6 +393,6 @@ export default function DirectMessagesPage() {
{/* New Message — searchable people picker */}
<NewMessageDialog open={showNewMessage} onOpenChange={setShowNewMessage} />
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useSeoMeta } from '@unhead/react';
import { Link, useParams } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { LoginArea } from '@/components/auth/LoginArea';
@ -22,7 +21,7 @@ export default function EditCrewPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -32,18 +31,18 @@ export default function EditCrewPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
if (isLoading) {
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-2xl">
<Skeleton className="h-6 w-32 mb-6" />
<Skeleton className="h-[600px] w-full" />
</div>
</Layout>
</>
);
}
@ -52,7 +51,7 @@ export default function EditCrewPage() {
// Only the crew's founder can edit it
if (crew.pubkey !== user.pubkey) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto bg-gray-900 border-gray-700">
<CardContent className="py-12 text-center">
@ -62,12 +61,12 @@ export default function EditCrewPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-2xl">
<div className="mb-6">
<Link to={`/crew/${crew.pubkey}/${crew.identifier}`} className="inline-flex items-center text-gray-400 hover:text-white transition-colors mb-4">
@ -80,6 +79,6 @@ export default function EditCrewPage() {
</div>
<CrewForm existing={crew} />
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, useParams } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -46,7 +45,7 @@ function formatTimeForInput(timestamp: number): string {
function EditEventSkeleton() {
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-2xl">
<Skeleton className="h-6 w-32 mb-4" />
<Skeleton className="h-10 w-64 mb-2" />
@ -67,7 +66,7 @@ function EditEventSkeleton() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -377,7 +376,7 @@ export default function EditEventPage() {
// Check if user is logged in
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -390,14 +389,14 @@ export default function EditEventPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
// Check if user is the owner
if (user.pubkey !== event.pubkey) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-destructive">
<CardContent className="py-12 text-center">
@ -412,12 +411,12 @@ export default function EditEventPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-2xl">
<div className="mb-6">
<Link
@ -629,6 +628,6 @@ export default function EditEventPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, useParams } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -31,7 +30,7 @@ import NotFound from './NotFound';
function EditShopSkeleton() {
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-2xl">
<Skeleton className="h-6 w-32 mb-4" />
<Skeleton className="h-10 w-64 mb-2" />
@ -53,7 +52,7 @@ function EditShopSkeleton() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -315,7 +314,7 @@ export default function EditShopPage() {
// Check if user is the owner
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -328,13 +327,13 @@ export default function EditShopPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
if (user.pubkey !== shop.pubkey) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-destructive">
<CardContent className="py-12 text-center">
@ -349,12 +348,12 @@ export default function EditShopPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-2xl">
<div className="mb-6">
<Link
@ -589,6 +588,6 @@ export default function EditShopPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, useParams } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -33,7 +32,7 @@ import NotFound from './NotFound';
function EditSpotSkeleton() {
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-2xl">
<Skeleton className="h-6 w-32 mb-4" />
<Skeleton className="h-10 w-64 mb-2" />
@ -55,7 +54,7 @@ function EditSpotSkeleton() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -308,7 +307,7 @@ export default function EditSpotPage() {
// Check if user is the owner
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -321,13 +320,13 @@ export default function EditSpotPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
if (user.pubkey !== spot.pubkey) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-destructive">
<CardContent className="py-12 text-center">
@ -342,12 +341,12 @@ export default function EditSpotPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-2xl">
<div className="mb-6">
<Link
@ -683,6 +682,6 @@ export default function EditSpotPage() {
</Card>
</form>
</div>
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useParams } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { LoginArea } from '@/components/auth/LoginArea';
@ -28,14 +27,14 @@ export default function EditTourPage() {
if (isLoading) {
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-6 sm:py-8 max-w-3xl space-y-4">
<Skeleton className="h-6 w-32" />
<Skeleton className="h-10 w-64" />
<Skeleton className="h-40 w-full" />
<Skeleton className="h-64 w-full" />
</div>
</Layout>
</>
);
}
@ -45,7 +44,7 @@ export default function EditTourPage() {
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -56,13 +55,13 @@ export default function EditTourPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
if (user.pubkey !== tour.pubkey) {
return (
<Layout>
<>
<div className="container px-4 py-8">
<Card className="max-w-xl mx-auto border-destructive">
<CardContent className="py-12 text-center">
@ -72,7 +71,7 @@ export default function EditTourPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useMemo, useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useParams, Link } from 'react-router-dom';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { PageError } from '@/components/ErrorState';
import { SpotMap } from '@/components/SpotMap';
import { RouteDialog } from '@/components/RouteDialog';
@ -43,7 +42,7 @@ function formatTime(timestamp: number): string {
function EventDetailSkeleton() {
return (
<Layout>
<>
<div className="container px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-6 min-w-0">
@ -64,7 +63,7 @@ function EventDetailSkeleton() {
</div>
</div>
</div>
</Layout>
</>
);
}
@ -266,7 +265,7 @@ export default function EventDetailPage() {
}
return (
<Layout>
<>
<div className="container px-4 py-8">
{/* Back link + owner/admin controls */}
<div className="mb-6 flex items-center justify-between gap-2">
@ -629,6 +628,6 @@ export default function EventDetailPage() {
destination={coordinates}
destinationLabel={event?.title ?? 'Event'}
/>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { LazyMount } from '@/components/LazyMount';
import { EventCard, EventCardSkeleton } from '@/components/EventCard';
import { TourCard, TourCardSkeleton } from '@/components/TourCard';
@ -1057,7 +1056,7 @@ export default function EventsPage() {
const hasTourFilters = !!(tourSearchQuery || tourLocationQuery || tourCategory !== 'all' || tourSkillLevel !== 'all' || tourTrustFilter !== 'all' || tourSortBy !== 'recent');
return (
<Layout>
<>
<div className="container px-4 py-3 md:py-10 max-w-6xl mx-auto">
{/* ── Page header ── */}
@ -1320,6 +1319,6 @@ export default function EventsPage() {
onClearAll={handleClearAll}
activeCount={activeFilterCount}
/>
</Layout>
</>
);
}

View file

@ -6,7 +6,6 @@
import { useEffect } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { HelpCircle, MessageCircle, MapPin, Zap, Shield, Package } from 'lucide-react';
@ -87,7 +86,7 @@ export default function FAQPage() {
};
return (
<Layout>
<>
<div className="container px-3 xs:px-4 py-6 sm:py-10 max-w-3xl mx-auto">
{/* Hero */}
@ -187,6 +186,6 @@ export default function FAQPage() {
The Sasquatch uses live Nostr data from the SkateSpots relay + AI to answer your questions. 🛹
</p>
</div>
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useState, useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { RouteDialog } from '@/components/RouteDialog';
@ -186,7 +185,7 @@ export default function FoodPage() {
} = useFoodSpots(selectedSpot?.geohash, 1);
return (
<Layout>
<>
<div className="container px-4 py-8">
{/* Header */}
<div className="mb-8">
@ -280,6 +279,6 @@ export default function FoodPage() {
</Card>
)}
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState, useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { PageHeader } from '@/components/PageHeader';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -161,7 +160,7 @@ export default function GearSwapPage() {
];
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-7xl">
<PageHeader
title="Gear Swap"
@ -423,6 +422,6 @@ export default function GearSwapPage() {
</div>
)}
</div>
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useState, useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';
import {
@ -47,7 +46,7 @@ export default function GlossaryPage() {
);
return (
<Layout>
<>
<div className="min-h-screen">
{/* Hero */}
<div className="relative overflow-hidden border-b border-red-500/20 bg-gradient-to-b from-red-950/20 to-transparent">
@ -195,6 +194,6 @@ export default function GlossaryPage() {
</div>
</div>
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState, useRef, useEffect, useMemo } from 'react';
import { useParams, Link } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { LoginArea } from '@/components/auth/LoginArea';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -60,17 +59,17 @@ export default function GroupConversationPage() {
if (!participantsParam || otherPubkeys.length === 0) {
return (
<Layout>
<>
<div className="container mx-auto max-w-2xl px-4 py-16 text-center text-gray-400">
This group chat link is invalid.
</div>
</Layout>
</>
);
}
if (!user) {
return (
<Layout>
<>
<div className="container mx-auto max-w-2xl px-4 py-8">
<Card className="border-dashed border-2 border-gray-700">
<CardContent className="py-16 text-center">
@ -82,13 +81,13 @@ export default function GroupConversationPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
if (!checkNip44(user, logins)) {
return (
<Layout>
<>
<div className="container mx-auto max-w-2xl px-4 py-12 text-center">
<Card className="border-2 border-dashed border-orange-500/30">
<CardContent className="py-12">
@ -98,7 +97,7 @@ export default function GroupConversationPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -120,7 +119,7 @@ export default function GroupConversationPage() {
};
return (
<Layout>
<>
<div className="container mx-auto flex h-[calc(100dvh-56px-80px)] max-w-2xl flex-col px-4 py-2 sm:h-[calc(100dvh-64px-60px)] sm:py-4 md:h-[calc(100vh-64px-60px)]">
{/* Header */}
<div className="flex flex-shrink-0 items-center gap-2 border-b border-gray-800 pb-3 sm:gap-3 sm:pb-4">
@ -222,6 +221,6 @@ export default function GroupConversationPage() {
</Button>
</div>
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { SpotCard, SpotCardSkeleton } from '@/components/SpotCard';
import { EventCard, EventCardSkeleton } from '@/components/EventCard';
import { Button } from '@/components/ui/button';
@ -630,7 +629,7 @@ export default function Index() {
const platformSponsors = undefined;
return (
<Layout>
<>
<HeroSection />
<FeaturedSpots />
<TopZappedSection />
@ -645,6 +644,6 @@ export default function Index() {
title="Platform Partners"
/>
<Footer />
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link, useSearchParams } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { LazyMount } from '@/components/LazyMount';
import { Badge } from '@/components/ui/badge';
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
@ -1362,7 +1361,7 @@ export default function LabPage() {
};
return (
<Layout>
<>
<div className="container px-4 py-6 md:py-10 max-w-4xl mx-auto">
{/* ── Page header ── */}
@ -1473,6 +1472,6 @@ export default function LabPage() {
onClearAll={handleClearAll}
activeCount={activeFilterCount}
/>
</Layout>
</>
);
}

View file

@ -1,5 +1,4 @@
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Link } from 'react-router-dom';
import { Scale } from 'lucide-react';
@ -10,7 +9,7 @@ export default function LicensePage() {
});
return (
<Layout>
<>
<div className="container max-w-2xl px-4 py-16 md:py-24">
{/* Big skate emoji hero */}
<div className="text-center mb-16">
@ -87,6 +86,6 @@ export default function LicensePage() {
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -69,20 +68,20 @@ export default function ListingDetailPage() {
if (isLoading) {
return (
<Layout>
<>
<div className="container mx-auto max-w-4xl px-4 py-8 space-y-4">
<Skeleton className="h-6 w-40" />
<Skeleton className="aspect-video w-full rounded-2xl" />
<Skeleton className="h-8 w-2/3" />
<Skeleton className="h-24 w-full" />
</div>
</Layout>
</>
);
}
if (!item) {
return (
<Layout>
<>
<div className="container mx-auto max-w-md px-4 py-16 text-center space-y-4">
<Package className="mx-auto h-10 w-10 text-gray-600" />
<h1 className="text-xl font-black uppercase text-white">Listing not found</h1>
@ -91,7 +90,7 @@ export default function ListingDetailPage() {
<Link to="/marketplace">Back to Gear Swap</Link>
</Button>
</div>
</Layout>
</>
);
}
@ -131,7 +130,7 @@ export default function ListingDetailPage() {
};
return (
<Layout>
<>
<div className="container mx-auto max-w-5xl px-4 py-6">
<button
onClick={() => navigate('/marketplace')}
@ -382,6 +381,6 @@ export default function ListingDetailPage() {
redirectTo="/marketplace"
/>
)}
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState, useMemo, useEffect, lazy, Suspense, useCallback } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { Layout } from '@/components/Layout';
// Lazy-load the map — Leaflet is large (~150KB) and only needed on this page
const SpotMap = lazy(() => import('@/components/SpotMap').then(m => ({ default: m.SpotMap })));
@ -978,7 +977,7 @@ export default function MapPage() {
const totalActiveFilters = activeCategoryCount + (trustFilter !== 'all' ? 1 : 0) + (sortBy !== 'recent' ? 1 : 0);
return (
<Layout noPadding>
<>
{/* Full-height map container: subtract header + bottom nav on mobile, header only on desktop */}
{/* 100dvh handles iOS Safari URL bar shrink correctly */}
<div className="h-[calc(100dvh-var(--header-mobile)-var(--bottom-nav))] md:h-[calc(100vh-4rem-var(--footer-desktop))] flex flex-col">
@ -1915,6 +1914,6 @@ export default function MapPage() {
{selectedSpot && <SpotBottomSheet spot={selectedSpot} onClose={() => setSelectedSpot(null)} />}
</SheetContent>
</Sheet>
</Layout>
</>
);
}

View file

@ -1,5 +1,4 @@
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -12,7 +11,7 @@ export default function MarketplacePage() {
});
return (
<Layout>
<>
<div className="container px-4 py-12 max-w-4xl">
{/* Hero */}
<div className="text-center mb-12">
@ -204,6 +203,6 @@ export default function MarketplacePage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useMemo, useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -184,7 +183,7 @@ export default function MintsPage() {
};
return (
<Layout>
<>
<div className="container mx-auto max-w-3xl px-4 py-8">
<div className="mb-6">
<h1 className="flex items-center gap-3 text-3xl font-black uppercase text-white">
@ -281,6 +280,6 @@ export default function MintsPage() {
</>
)}
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { nip19 } from 'nostr-tools';
import { useParams, Link } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { ImageWithFallback } from '@/components/ImageWithFallback';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -178,7 +177,7 @@ function ProfilePage({ pubkey }: { pubkey: string }) {
if (author.isLoading) {
return (
<Layout>
<>
<div className="container px-4 py-8 max-w-4xl">
<Skeleton className="h-48 w-full rounded-xl mb-4" />
<div className="flex items-end gap-4 -mt-16 px-4 mb-6">
@ -196,12 +195,12 @@ function ProfilePage({ pubkey }: { pubkey: string }) {
</div>
</div>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="container px-4 py-6 max-w-4xl">
{/* Banner */}
<div className="relative h-32 sm:h-48 rounded-xl overflow-hidden bg-gradient-to-br from-[#ff0040]/30 to-[#ccff00]/20 mb-4">
@ -887,7 +886,7 @@ function ProfilePage({ pubkey }: { pubkey: string }) {
npub={npub}
displayName={displayName}
/>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { ImageWithFallback } from '@/components/ImageWithFallback';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -619,7 +618,7 @@ export default function NewsletterPage() {
}, [markNewsletterRead]);
return (
<Layout>
<>
<div className="container max-w-2xl px-4 py-8 md:py-14 pb-24 md:pb-14">
{/* ── Masthead ── */}
@ -689,6 +688,6 @@ export default function NewsletterPage() {
</div>
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
@ -134,7 +133,7 @@ export default function OnboardingPage() {
const bugReportUrl = `/messages/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx`;
return (
<Layout>
<>
<div className="container px-4 py-10 max-w-3xl">
{/* ── Save your login (critical) — pinned ABOVE the hero right after
@ -575,6 +574,6 @@ export default function OnboardingPage() {
/>
</DialogContent>
</Dialog>
</Layout>
</>
);
}

View file

@ -1,5 +1,4 @@
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Link } from 'react-router-dom';
import {
Shield,
@ -68,7 +67,7 @@ export default function PrivacyPage() {
});
return (
<Layout>
<>
<div className="container max-w-2xl px-4 py-10 md:py-16">
{/* Hero */}
<div className="text-center mb-10">
@ -224,6 +223,6 @@ export default function PrivacyPage() {
</div>
</div>
</div>
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useSeoMeta } from '@unhead/react';
import { useMemo } from 'react';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
@ -117,7 +116,7 @@ export default function PyramidPage() {
const userStats = rankings.find(r => r.pubkey === user?.pubkey);
return (
<Layout>
<>
<div className="container px-4 sm:px-6 py-8 sm:py-12">
<div className="text-center mb-12">
<Trophy className="h-16 w-16 mx-auto mb-4 text-orange-500" />
@ -199,6 +198,6 @@ export default function PyramidPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}

View file

@ -19,7 +19,6 @@ import { nip19 } from 'nostr-tools';
// QRCode is imported lazily inside drawCardToCanvas() to avoid canvas API
// access at module load time (crashes the preview environment)
import { Layout } from '@/components/Layout';
import { LoginArea } from '@/components/auth/LoginArea';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -1099,7 +1098,7 @@ export default function RiderCardPage() {
// ── Logged out ────────────────────────────────────────────────────────────
if (!user) {
return (
<Layout>
<>
<div className="min-h-[70vh] flex flex-col items-center justify-center px-4 text-center">
{/* Hero */}
<div className="mb-8 relative">
@ -1118,12 +1117,12 @@ export default function RiderCardPage() {
</p>
<LoginArea className="justify-center" />
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<div className="max-w-2xl mx-auto px-4 pb-16">
{/* ── Page header ── */}
@ -1485,7 +1484,7 @@ export default function RiderCardPage() {
)}
</div>
</Layout>
</>
);
}

View file

@ -1,6 +1,5 @@
import { useSeoMeta } from '@unhead/react';
import { Link, useSearchParams } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { EditProfileForm } from '@/components/EditProfileForm';
import { SkaterDirectoryCard } from '@/components/SkaterDirectoryCard';
import { RelayListManager } from '@/components/RelayListManager';
@ -784,7 +783,7 @@ export default function SettingsPage() {
const handleBack = () => setSection(null);
return (
<Layout>
<>
<div className="container px-4 py-5 max-w-lg mx-auto">
{/* Header — only shown on the main menu */}
{!section && (
@ -819,6 +818,6 @@ export default function SettingsPage() {
{/* Main menu */}
{!section && <SettingsMenu onNavigate={handleNavigate} />}
</div>
</Layout>
</>
);
}

View file

@ -3,7 +3,6 @@ import { useSeoMeta } from '@unhead/react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { PageError } from '@/components/ErrorState';
import { ImageWithFallback } from '@/components/ImageWithFallback';
import { ImageLightbox } from '@/components/ImageLightbox';
@ -59,7 +58,7 @@ import {
function ShopDetailSkeleton() {
return (
<Layout>
<>
<div className="container px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-6 min-w-0">
@ -76,7 +75,7 @@ function ShopDetailSkeleton() {
</div>
</div>
</div>
</Layout>
</>
);
}
@ -207,7 +206,7 @@ export default function ShopDetailPage() {
};
return (
<Layout>
<>
<div className="container px-4 py-8">
{/* Back link */}
<Link
@ -552,6 +551,6 @@ export default function ShopDetailPage() {
onIndexChange={setLightboxIndex}
onClose={() => setLightboxIndex(null)}
/>
</Layout>
</>
);
}

View file

@ -1,5 +1,4 @@
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
@ -235,7 +234,7 @@ export default function SignersPage() {
: ['browser', 'android', 'ios'] as const;
return (
<Layout>
<>
<div className="min-h-screen bg-black">
{/* Hero Section */}
<section className="relative py-12 md:py-16 border-b border-[#ff0040]/30">
@ -360,6 +359,6 @@ export default function SignersPage() {
</section>
</div>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useMemo, useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { LazyMount } from '@/components/LazyMount';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@ -178,7 +177,7 @@ export default function SkatersPage() {
const total = skaters?.length ?? 0;
return (
<Layout>
<>
<div className="min-h-screen">
{/* ── Hero header ── */}
<div className="relative overflow-hidden border-b border-white/10 bg-gradient-to-br from-[#ff0040]/15 via-black to-[#ccff00]/5">
@ -357,6 +356,6 @@ export default function SkatersPage() {
})()}
</div>
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -231,7 +230,7 @@ export default function SpotCollectionsPage() {
};
return (
<Layout>
<>
<div className="container px-4 py-4 sm:py-8">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6 sm:mb-8">
@ -464,6 +463,6 @@ export default function SpotCollectionsPage() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Layout>
</>
);
}

View file

@ -3,7 +3,6 @@ import { useSeoMeta } from '@unhead/react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import ngeohash from 'ngeohash';
import { Layout } from '@/components/Layout';
import { ImageWithFallback } from '@/components/ImageWithFallback';
import { ImageLightbox, type LightboxImage } from '@/components/ImageLightbox';
import { PageError } from '@/components/ErrorState';
@ -87,7 +86,7 @@ import {
function SpotDetailSkeleton() {
return (
<Layout>
<>
<div className="container px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-6 min-w-0">
@ -108,7 +107,7 @@ function SpotDetailSkeleton() {
</div>
</div>
</div>
</Layout>
</>
);
}
@ -239,7 +238,7 @@ export default function SpotDetailPage() {
}
return (
<Layout>
<>
<div className="container px-4 pt-4 pb-8 sm:py-8 overflow-x-hidden">
{/* Back link */}
<Link
@ -1080,6 +1079,6 @@ export default function SpotDetailPage() {
onIndexChange={setLightboxIndex}
onClose={() => setLightboxIndex(null)}
/>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState, useMemo, useRef } from 'react';
import { useSeoMeta } from '@unhead/react';
import { useNavigate, Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
@ -709,7 +708,7 @@ export default function StashPage() {
// ── Not logged in ──
if (!user) {
return (
<Layout>
<>
<div className="container px-4 py-12 max-w-4xl">
<div className="relative overflow-hidden rounded-2xl border-2 border-[#ff6600]/50 bg-black/50 backdrop-blur">
<div className="absolute top-0 right-0 w-64 h-64 bg-[#ff6600] opacity-10 rounded-full blur-[80px]" />
@ -729,14 +728,14 @@ export default function StashPage() {
</div>
</div>
</div>
</Layout>
</>
);
}
// ── No encryption ──
if (!isEncryptionAvailable) {
return (
<Layout>
<>
<div className="container px-4 py-12 max-w-4xl">
<Card className="bg-black/80 border-2 border-yellow-500/50">
<CardContent className="p-8 text-center">
@ -747,12 +746,12 @@ export default function StashPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
return (
<Layout>
<>
<style>{`
@keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
.animate-shimmer { animation: shimmer 2s infinite; }
@ -1159,6 +1158,6 @@ export default function StashPage() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Layout>
</>
);
}

View file

@ -1,5 +1,4 @@
import { useSeoMeta } from '@unhead/react';
import { Layout } from '@/components/Layout';
import { Link } from 'react-router-dom';
import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } from 'lucide-react';
@ -223,7 +222,7 @@ export default function SupportersPage() {
});
return (
<Layout>
<>
<div className="container max-w-4xl px-4 py-12 md:py-16 space-y-16">
{/* ── Hero ── */}
@ -416,6 +415,6 @@ export default function SupportersPage() {
</div>
</div>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useSeoMeta } from '@unhead/react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
@ -80,7 +79,7 @@ export default function TourDetailPage() {
if (isLoading) {
return (
<Layout>
<>
<div className="container px-4 py-6 max-w-3xl">
<Skeleton className="h-6 w-32 mb-6" />
<Card className="bg-black/90 border-cyan-500/30">
@ -98,13 +97,13 @@ export default function TourDetailPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
if (error || !tour) {
return (
<Layout>
<>
<div className="container px-4 py-12">
<Card className="max-w-xl mx-auto border-dashed">
<CardContent className="py-12 text-center">
@ -122,7 +121,7 @@ export default function TourDetailPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}
@ -205,7 +204,7 @@ export default function TourDetailPage() {
: null;
return (
<Layout>
<>
<div className="container px-4 py-6 max-w-3xl">
{/* Back link + owner/admin controls */}
<div className="mb-6 flex items-center justify-between gap-2">
@ -528,6 +527,6 @@ export default function TourDetailPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useState } from 'react';
import { Link } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -144,7 +143,7 @@ export default function TradesPage() {
if (!user) {
return (
<Layout>
<>
<div className="container mx-auto max-w-md px-4 py-16 text-center space-y-4">
<Handshake className="mx-auto h-10 w-10 text-gray-600" />
<h1 className="text-xl font-black uppercase text-white">My Trades</h1>
@ -153,7 +152,7 @@ export default function TradesPage() {
</p>
<LoginArea className="justify-center" />
</div>
</Layout>
</>
);
}
@ -164,7 +163,7 @@ export default function TradesPage() {
(incoming?.length ?? 0) + (pending?.length ?? 0);
return (
<Layout>
<>
<div className="container mx-auto max-w-3xl px-4 py-8 space-y-6">
<div>
<h1 className="flex items-center gap-3 text-3xl font-black uppercase text-white">
@ -367,6 +366,6 @@ export default function TradesPage() {
</div>
</DialogContent>
</Dialog>
</Layout>
</>
);
}

View file

@ -1,7 +1,6 @@
import { useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout';
import { PageHeader } from '@/components/PageHeader';
import { WalletModal } from '@/components/WalletModal';
import { Badge } from '@/components/ui/badge';
@ -242,7 +241,7 @@ export default function WalletPage() {
const walletConnected = !!(webln || hasNWC || hasCashu);
return (
<Layout>
<>
<div className="container px-4 py-5 max-w-lg mx-auto">
<PageHeader title="Wallet" accent="#ff6600" subtitle="Bitcoin Lightning + Cashu" />
@ -279,6 +278,6 @@ export default function WalletPage() {
<NotLoggedIn />
)}
</div>
</Layout>
</>
);
}

View file

@ -2,7 +2,6 @@ import { useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Skeleton } from '@/components/ui/skeleton';
@ -180,7 +179,7 @@ export default function ZapLeaderboardPage() {
);
return (
<Layout>
<>
<div className="container mx-auto max-w-3xl px-4 py-8">
{/* Header */}
<div className="mb-6">
@ -259,6 +258,6 @@ export default function ZapLeaderboardPage() {
</CardContent>
</Card>
</div>
</Layout>
</>
);
}