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:
parent
55b4dd2e86
commit
087f490acc
65 changed files with 263 additions and 308 deletions
17
AGENTS.md
17
AGENTS.md
|
|
@ -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/* →
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue