From 79867948e3b8b4d6ba03dc97fa654ef8ac7e8457 Mon Sep 17 00:00:00 2001 From: Sainath More Date: Mon, 20 Jul 2026 03:38:31 +0000 Subject: [PATCH] feat: add shared bottom navigation shell Mount BottomNav via app/(app) route group so marketing / stays nav-free, wire orders list to detail routes, and raise ThemeToggle above the bar. --- p2p-safe-swap/app/(app)/layout.tsx | 19 +++++ .../app/{ => (app)}/p2p/orders/page.tsx | 4 +- .../app/{ => (app)}/transactions/page.tsx | 26 +++--- p2p-safe-swap/app/layout.tsx | 3 +- .../frontend/components/ui/bottom-nav.tsx | 84 +++++++++++++++++++ 5 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 p2p-safe-swap/app/(app)/layout.tsx rename p2p-safe-swap/app/{ => (app)}/p2p/orders/page.tsx (93%) rename p2p-safe-swap/app/{ => (app)}/transactions/page.tsx (82%) create mode 100644 p2p-safe-swap/frontend/components/ui/bottom-nav.tsx diff --git a/p2p-safe-swap/app/(app)/layout.tsx b/p2p-safe-swap/app/(app)/layout.tsx new file mode 100644 index 0000000..68a29df --- /dev/null +++ b/p2p-safe-swap/app/(app)/layout.tsx @@ -0,0 +1,19 @@ +import { BottomNav } from "@/frontend/components/ui/bottom-nav"; + +/** + * App chrome for authenticated/product screens. + * Marketing home (`app/page.tsx` at `/`) stays outside this group so it + * never mounts the bottom navigation bar. + */ +export default function AppLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+ {children} + +
+ ); +} diff --git a/p2p-safe-swap/app/p2p/orders/page.tsx b/p2p-safe-swap/app/(app)/p2p/orders/page.tsx similarity index 93% rename from p2p-safe-swap/app/p2p/orders/page.tsx rename to p2p-safe-swap/app/(app)/p2p/orders/page.tsx index 27f68df..1e03e9f 100644 --- a/p2p-safe-swap/app/p2p/orders/page.tsx +++ b/p2p-safe-swap/app/(app)/p2p/orders/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { useRouter } from "next/navigation"; import { P2POrderList } from "@/frontend/components/p2p"; import type { OrderMode, P2POrder } from "@/frontend/components/p2p"; @@ -61,6 +62,7 @@ const MOCK_ORDERS: P2POrder[] = [ const BEST_PRICE = 0.9201; export default function OrdersPage() { + const router = useRouter(); const [mode, setMode] = useState("buy"); return ( @@ -71,7 +73,7 @@ export default function OrdersPage() { mode={mode} onModeChange={setMode} onBuy={(orderId) => { - console.log("Order action:", { mode, orderId }); + router.push(`/p2p/orders/${orderId}`); }} /> diff --git a/p2p-safe-swap/app/transactions/page.tsx b/p2p-safe-swap/app/(app)/transactions/page.tsx similarity index 82% rename from p2p-safe-swap/app/transactions/page.tsx rename to p2p-safe-swap/app/(app)/transactions/page.tsx index 622070f..08e5b12 100644 --- a/p2p-safe-swap/app/transactions/page.tsx +++ b/p2p-safe-swap/app/(app)/transactions/page.tsx @@ -1,7 +1,9 @@ "use client"; import * as React from "react"; +import Link from "next/link"; import { ArrowLeft, SlidersHorizontal } from "lucide-react"; +import { Reveal } from "@/frontend/components/motion/reveal"; import { TransactionList, type Transaction, @@ -89,13 +91,13 @@ export default function TransactionsPage() { return (
- +

Transacciones

@@ -108,13 +110,15 @@ export default function TransactionsPage() {
- setActiveTab(tab as TransactionTab)} - /> + + setActiveTab(tab as TransactionTab)} + /> +
); } diff --git a/p2p-safe-swap/app/layout.tsx b/p2p-safe-swap/app/layout.tsx index baf9922..a14fb03 100644 --- a/p2p-safe-swap/app/layout.tsx +++ b/p2p-safe-swap/app/layout.tsx @@ -40,7 +40,8 @@ export default function RootLayout({ disableTransitionOnChange > {children} - + {/* Sit above the fixed BottomNav (h-16 + safe area) on app routes */} + diff --git a/p2p-safe-swap/frontend/components/ui/bottom-nav.tsx b/p2p-safe-swap/frontend/components/ui/bottom-nav.tsx new file mode 100644 index 0000000..54776c2 --- /dev/null +++ b/p2p-safe-swap/frontend/components/ui/bottom-nav.tsx @@ -0,0 +1,84 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + Home, + ListOrdered, + MessageCircle, + Receipt, + Wallet, + type LucideIcon, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface NavItem { + href: string; + label: string; + icon: LucideIcon; + /** Exact match only (marketing/home). Nested routes use prefix match. */ + exact?: boolean; + /** Optional path prefix for active state when href is a specific entry URL. */ + matchPrefix?: string; +} + +const NAV_ITEMS: NavItem[] = [ + { href: "/", label: "Inicio", icon: Home, exact: true }, + { href: "/p2p/orders", label: "Órdenes", icon: ListOrdered }, + { href: "/wallet", label: "Billetera", icon: Wallet }, + { href: "/transactions", label: "Transacciones", icon: Receipt }, + { + href: "/chat/demo", + label: "Chat", + icon: MessageCircle, + matchPrefix: "/chat", + }, +]; + +function isActive(pathname: string, item: NavItem): boolean { + if (item.exact) return pathname === item.href; + const base = item.matchPrefix ?? item.href; + return pathname === base || pathname.startsWith(`${base}/`); +} + +export function BottomNav({ className }: { className?: string }) { + const pathname = usePathname(); + + return ( + + ); +}