Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions p2p-safe-swap/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="relative flex min-h-full flex-1 flex-col pb-20">
{children}
<BottomNav />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -61,6 +62,7 @@ const MOCK_ORDERS: P2POrder[] = [
const BEST_PRICE = 0.9201;

export default function OrdersPage() {
const router = useRouter();
const [mode, setMode] = useState<OrderMode>("buy");

return (
Expand All @@ -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}`);
}}
/>
</main>
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -89,13 +91,13 @@ export default function TransactionsPage() {
return (
<div className="mx-auto flex min-h-full w-full max-w-md flex-col bg-background px-4 py-6">
<header className="mb-6 flex items-center justify-between">
<button
type="button"
<Link
href="/p2p/orders"
aria-label="Volver"
className="flex size-10 items-center justify-center rounded-full text-foreground transition-colors hover:bg-muted"
className="flex size-10 items-center justify-center rounded-full text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<ArrowLeft className="size-5" />
</button>
</Link>

<h1 className="text-lg font-semibold text-foreground">Transacciones</h1>

Expand All @@ -108,13 +110,15 @@ export default function TransactionsPage() {
</button>
</header>

<TransactionList
transactions={mockTransactions}
searchQuery={searchQuery}
activeTab={activeTab}
onSearch={setSearchQuery}
onTabChange={(tab) => setActiveTab(tab as TransactionTab)}
/>
<Reveal>
<TransactionList
transactions={mockTransactions}
searchQuery={searchQuery}
activeTab={activeTab}
onSearch={setSearchQuery}
onTabChange={(tab) => setActiveTab(tab as TransactionTab)}
/>
</Reveal>
</div>
);
}
3 changes: 2 additions & 1 deletion p2p-safe-swap/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export default function RootLayout({
disableTransitionOnChange
>
{children}
<ThemeToggle className="fixed bottom-4 right-4 z-50" />
{/* Sit above the fixed BottomNav (h-16 + safe area) on app routes */}
<ThemeToggle className="fixed bottom-20 right-4 z-50" />
</ThemeProvider>
</body>
</html>
Expand Down
84 changes: 84 additions & 0 deletions p2p-safe-swap/frontend/components/ui/bottom-nav.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<nav
data-slot="bottom-nav"
aria-label="Navegación principal"
className={cn(
"fixed inset-x-0 bottom-0 z-40 border-t border-border bg-card",
"pb-[env(safe-area-inset-bottom)]",
className
)}
>
<ul className="mx-auto flex h-16 max-w-md items-stretch justify-around px-1">
{NAV_ITEMS.map((item) => {
const active = isActive(pathname, item);
const Icon = item.icon;

return (
<li key={item.href} className="flex flex-1">
<Link
href={item.href}
aria-current={active ? "page" : undefined}
className={cn(
"flex flex-1 flex-col items-center justify-center gap-0.5 rounded-xl px-1 text-[10px] transition-colors sm:text-xs",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
active
? "bg-primary/15 font-medium text-primary"
: "font-normal text-muted-foreground hover:text-foreground"
)}
>
<Icon className="size-5 shrink-0" aria-hidden="true" />
<span className="truncate">{item.label}</span>
</Link>
</li>
);
})}
</ul>
</nav>
);
}