Skip to content
Merged
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
12 changes: 9 additions & 3 deletions components/icons/StellarIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React from 'react';

type Props = {
className?: string
className?: string;
style?: React.CSSProperties;
}

const StellarIcon = (props: Props) => {
return (
<div className={`h-[20px] flex items-center justify-center bg-black rounded-full p-1 w-[20px] ${props.className}`}>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="#ffffff" viewBox="0 0 24 24">
<div
className={`h-[20px] flex items-center justify-center rounded-full p-1 w-[20px] ${props.className}`}
style={props.style}
>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24">
<path d="M12.283 1.851A10.154 10.154 0 0 0 1.876 12.775 1.85 1.85 0 0 1 .872 14.56L0 15.005v2.074l2.568-1.309.832-.424.82-.417 14.71-7.496 1.653-.842L24 4.85V2.776l-3.387 1.728-2.89 1.473-13.955 7.108a8.313 8.313 0 0 1 12.296-8.333l1.654-.843.247-.126a10.15 10.15 0 0 0-5.682-1.932M24 6.925 5.055 16.571l-1.653.844L0 19.15v2.072L3.378 19.5l2.89-1.473 13.97-7.117q.07.544.07 1.092A8.312 8.312 0 0 1 7.93 19.248l-.101.054-1.793.914a10.155 10.155 0 0 0 16.089-8.994 1.85 1.85 0 0 1 1.003-1.785L24 8.992z"></path>
</svg>
</div>
Expand Down
29 changes: 25 additions & 4 deletions components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import Image from "next/image";
import { mockUser as user, mockNavbarState as navbarState } from "./navbar.mock";
import { WhatsNewDrawer } from "@/components/changelog/WhatsNewDrawer";
import { getNetworkTint } from "@/lib/network-tint";
import { useEffect } from "react";

const NAV_ITEMS = [
{ name: "Markets", href: "/markets", icon: "trending_up" },
Expand All @@ -24,9 +26,20 @@ const NAV_ITEMS = [

export function Navbar() {
const pathname = usePathname();
const [network, setNetwork] = useState(navbarState.networkName);
const [network, setNetwork] = useState(() => {
if (typeof window !== "undefined") {
return localStorage.getItem("predictify_network") || navbarState.networkName;
}
return navbarState.networkName;
});
const { theme, setTheme } = useTheme();
const [isWalletModalOpen, setIsWalletModalOpen] = useState(false);

useEffect(() => {
localStorage.setItem("predictify_network", network);
}, [network]);

const activeTint = getNetworkTint(network);
const { connected, isLoading } = useWalletContext();

const toggleTheme = () => {
Expand All @@ -51,9 +64,13 @@ export function Navbar() {
href={item.href}
className={`text-sm transition-all duration-300 font-medium ${
isActive
? "text-cyan-400 border-b-2 border-cyan-400 pb-1"
? "pb-1"
: "text-slate-400 hover:text-slate-200"
}`}
style={isActive ? {
color: activeTint.tint,
borderBottom: `2px solid ${activeTint.tint}`
} : {}}
>
{item.name}
</Link>
Expand Down Expand Up @@ -82,7 +99,7 @@ export function Navbar() {
Loading...
</button>
) : connected ? (
<WalletMenu />
<WalletMenu network={network} />
) : (
<button
onClick={() => setIsWalletModalOpen(true)}
Expand Down Expand Up @@ -142,9 +159,13 @@ export function Navbar() {
aria-current={isActive ? "page" : undefined}
className={
isActive
? "flex flex-col items-center justify-center bg-cyan-500/20 text-cyan-300 rounded-xl px-3 min-h-[44px] min-w-[44px] py-1 transition-all"
? "flex flex-col items-center justify-center rounded-xl px-3 min-h-[44px] min-w-[44px] py-1 transition-all"
: "flex flex-col items-center justify-center text-slate-500 hover:text-cyan-200 min-h-[44px] min-w-[44px] py-1 transition-all"
}
style={isActive ? {
backgroundColor: activeTint.bg,
color: activeTint.tint
} : {}}
>
<span
aria-hidden="true"
Expand Down
40 changes: 30 additions & 10 deletions components/navbar/NetworkSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,58 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { StellarIcon, ArrowDown } from "../icons";
import { getNetworkTint } from "@/lib/network-tint";

interface NetworkSwitcherProps {
network: string;
onChange?: (next: string) => void;
className?: string;
}

const NETWORKS = ["Stellar"];
const NETWORKS = ["Mainnet", "Testnet", "Futurenet"];

export function NetworkSwitcher({ network, onChange, className }: NetworkSwitcherProps) {
const activeTint = getNetworkTint(network);

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="secondary"
className="h-8 px-2 rounded-full flex gap-1.5 items-center border border-[#540D8D] dark:border-[#FFFFFF] bg-[#540D8D1F] dark:text-white dark:bg-[#FFFFFF1C]"
className="h-8 px-2 rounded-full flex gap-1.5 items-center border bg-opacity-10 dark:bg-opacity-10 transition-colors"
style={{
borderColor: activeTint.border,
backgroundColor: activeTint.bg,
color: activeTint.text
}}
aria-label="Select network"
>
<StellarIcon className="h-[20px] w-[20px]" />
<span className="text-[#540D8D] lg:text-sm text-xs dark:text-white mr-1">{network}</span>
<StellarIcon className="h-[20px] w-[20px]" style={{ color: activeTint.tint }} />
<span className="lg:text-sm text-xs mr-1">{network}</span>
<ArrowDown className="h-[12px] w-[12px]" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel>Network</DropdownMenuLabel>
<DropdownMenuSeparator />
{NETWORKS.map((n) => (
<DropdownMenuItem key={n} onClick={() => onChange?.(n)} className="cursor-pointer" role="menuitemradio" aria-checked={n === network}>
<StellarIcon className="h-[20px] w-[20px]" />
{n}
</DropdownMenuItem>
))}
{NETWORKS.map((n) => {
const t = getNetworkTint(n);
return (
<DropdownMenuItem
key={n}
onClick={() => onChange?.(n)}
className="cursor-pointer flex items-center gap-2"
role="menuitemradio"
aria-checked={n === network}
>
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: t.tint }}
/>
{n}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
229 changes: 122 additions & 107 deletions components/navbar/WalletMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,108 +1,123 @@
"use client";


import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useWalletContext } from "@/context/WalletContext";
import { useWallet } from "@/hooks/useWallet.hook";
import { ConnectWalletModal } from "@/components/connect-wallet-modal";
import { Copy as CopyIcon, RefreshCcw, LogOut as LogOutIcon } from "lucide-react";
import ArrowDownIcon from "../icons/ArrowDown";
import { Switch } from "@/components/ui/switch";
import { usePrivacy } from "@/context/PrivacyContext";
import { maskAmount } from "@/utils/maskAmount";

function truncateMiddle(address: string, visible = 4) {
if (address.length <= visible * 2) return address;
return `${address.slice(0, visible)}…${address.slice(-visible)}`;
}

export function WalletMenu() {
const { address, connected, isLoading } = useWalletContext();
const { disconnectWallet } = useWallet();
const [isOpen, setIsOpen] = React.useState(false);
const { hideBalances, setHideBalances } = usePrivacy();

const display = connected && address ? (hideBalances ? maskAmount(address) : truncateMiddle(address)) : "Connect wallet";

if (isLoading) {
return (
<Button
variant="secondary"
className="h-8 rounded-full flex items-center border border-[#540D8D] dark:border-white dark:border-[0.24px] bg-[#540D8D1F] dark:bg-[#FFFFFF1C] dark:text-white opacity-50"
disabled
aria-label="Loading wallet state"
>
<span className="text-[#540D8D] text-sm dark:text-white">Loading...</span>
</Button>
);
}

function handleCopy() {
if (address) navigator.clipboard.writeText(address);
}

if (!connected) {
return (
<>
<Button
variant="secondary"
className="h-8 rounded-full flex items-center border border-[#540D8D] dark:border-white dark:border-[0.24px] bg-[#540D8D1F] dark:bg-[#FFFFFF1C] dark:text-white"
onClick={() => setIsOpen(true)}
aria-label="Connect wallet"
>
<span className="text-[#540D8D] text-sm dark:text-white">{display}</span>
</Button>
<ConnectWalletModal isOpen={isOpen} onOpenChange={setIsOpen} />
</>
);
}

return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="secondary"
className="h-8 rounded-full flex items-center border border-[#540D8D] dark:border-white dark:border-[0.24px] bg-[#540D8D1F] dark:bg-[#FFFFFF1C] dark:text-white"
aria-haspopup="menu"
aria-label="Wallet menu"
>
<span className="text-[#540D8D] text-sm dark:text-white">{display}</span>
<ArrowDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56" role="menu">
<DropdownMenuLabel>Wallet</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem role="menuitem" onClick={handleCopy} className="cursor-pointer" aria-label="Copy address">
<CopyIcon className="mr-2 h-4 w-4" />
Copy
</DropdownMenuItem>
<DropdownMenuItem role="menuitem" onClick={() => setIsOpen(true)} className="cursor-pointer" aria-label="Switch wallet">
<RefreshCcw className="mr-2 h-4 w-4" />
Switch
</DropdownMenuItem>
<DropdownMenuItem role="menuitem" onClick={() => { void disconnectWallet(); }} className="cursor-pointer" aria-label="Disconnect wallet">
<LogOutIcon className="mr-2 h-4 w-4" />
Disconnect
</DropdownMenuItem>
<DropdownMenuItem asChild>
<label className="flex items-center justify-between w-full px-2 py-1">
<span className="text-sm">Hide balances</span>
<Switch checked={hideBalances} onCheckedChange={setHideBalances} />
</label>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<ConnectWalletModal isOpen={isOpen} onOpenChange={setIsOpen} />
</>
);
"use client";

import React from 'react';
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useWalletContext } from "@/context/WalletContext";
import { useWallet } from "@/hooks/useWallet.hook";
import { ConnectWalletModal } from "@/components/connect-wallet-modal";
import { Copy as CopyIcon, RefreshCcw, LogOut as LogOutIcon } from "lucide-react";
import ArrowDownIcon from "../icons/ArrowDown";
import { Switch } from "@/components/ui/switch";
import { usePrivacy } from "@/context/PrivacyContext";
import { maskAmount } from "@/utils/maskAmount";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { getNetworkTint } from "@/lib/network-tint";
import { mockUser as user } from "./navbar.mock";

function truncateMiddle(address: string, visible = 4) {
if (address.length <= visible * 2) return address;
return `${address.slice(0, visible)}…${address.slice(-visible)}`;
}

export function WalletMenu({ network }: { network: string }) {
const { address, connected, isLoading } = useWalletContext();
const { disconnectWallet } = useWallet();
const [isOpen, setIsOpen] = React.useState(false);
const { hideBalances, setHideBalances } = usePrivacy();

const activeTint = getNetworkTint(network);
const display = connected && address ? (hideBalances ? maskAmount(address) : truncateMiddle(address)) : "Connect wallet";

if (isLoading) {
return (
<Button
variant="secondary"
className="h-8 rounded-full flex items-center border bg-opacity-10 opacity-50 transition-colors"
style={{ borderColor: activeTint.border, backgroundColor: activeTint.bg, color: activeTint.text }}
disabled
aria-label="Loading wallet state"
>
<span className="text-sm">Loading...</span>
</Button>
);
}

function handleCopy() {
if (address) navigator.clipboard.writeText(address);
}

if (!connected) {
return (
<>
<Button
variant="secondary"
className="h-8 rounded-full flex items-center border bg-opacity-10 transition-colors"
style={{ borderColor: activeTint.border, backgroundColor: activeTint.bg, color: activeTint.text }}
onClick={() => setIsOpen(true)}
aria-label="Connect wallet"
>
<span className="text-sm">{display}</span>
</Button>
<ConnectWalletModal isOpen={isOpen} onOpenChange={setIsOpen} />
</>
);
}

return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="secondary"
className="h-9 px-1.5 rounded-full flex items-center gap-2 border transition-all"
style={{
borderColor: activeTint.border,
backgroundColor: activeTint.bg,
color: activeTint.text
}}
aria-haspopup="menu"
aria-label="Wallet menu"
>
<Avatar className="h-6 w-6 border-2" style={{ borderColor: activeTint.tint }}>
<AvatarImage src={user?.avatarUrl} alt={user?.name} />
<AvatarFallback>{user?.name?.[0] || "U"}</AvatarFallback>
</Avatar>
<span className="text-sm font-medium">{display}</span>
<ArrowDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56" role="menu">
<DropdownMenuLabel>Wallet</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem role="menuitem" onClick={handleCopy} className="cursor-pointer" aria-label="Copy address">
<CopyIcon className="mr-2 h-4 w-4" />
Copy
</DropdownMenuItem>
<DropdownMenuItem role="menuitem" onClick={() => setIsOpen(true)} className="cursor-pointer" aria-label="Switch wallet">
<RefreshCcw className="mr-2 h-4 w-4" />
Switch
</DropdownMenuItem>
<DropdownMenuItem role="menuitem" onClick={() => { void disconnectWallet(); }} className="cursor-pointer" aria-label="Disconnect wallet">
<LogOutIcon className="mr-2 h-4 w-4" />
Disconnect
</DropdownMenuItem>
<DropdownMenuItem asChild>
<label className="flex items-center justify-between w-full px-2 py-1">
<span className="text-sm">Hide balances</span>
<Switch checked={hideBalances} onCheckedChange={setHideBalances} />
</label>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<ConnectWalletModal isOpen={isOpen} onOpenChange={setIsOpen} />
</>
);
}
Loading