From 3cd65193eac4a2e5d9049828b73f98b7ec337c05 Mon Sep 17 00:00:00 2001 From: mitchellecm7 Date: Thu, 23 Apr 2026 11:21:03 -0700 Subject: [PATCH 1/4] feat: add wallet connect/disconnect component with Freighter integration --- components/navbar.tsx | 192 ++++++++++++++++++++++++---------- components/wallet-connect.tsx | 126 ++++++++++++++++++++++ 2 files changed, 263 insertions(+), 55 deletions(-) create mode 100644 components/wallet-connect.tsx diff --git a/components/navbar.tsx b/components/navbar.tsx index e9ba224..6ca2600 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -1,28 +1,143 @@ +// 'use client' + +// import Link from 'next/link' +// import { Button } from '@/components/ui/button' +// import { Menu, X, Wallet } from 'lucide-react' +// import { useState, useEffect } from 'react' +// import Image from 'next/image' +// import { ThemeToggle } from './ui/ThemeToggle' + +// export function Navbar() { +// const [mobileMenuOpen, setMobileMenuOpen] = useState(false) +// const [address, setAddress] = useState(null) + +// useEffect(() => { +// const savedAddress = localStorage.getItem('stellar_wallet_address') +// if (savedAddress && savedAddress !== address) { +// // eslint-disable-next-line react-hooks/set-state-in-effect +// setAddress(savedAddress) +// } +// }, [address]) + +// const formatAddress = (addr: string) => { +// if (!addr || addr.length <= 10) return addr; +// return `${addr.substring(0, 5)}...${addr.substring(addr.length - 4)}` +// } + +// return ( +// +// ) +// } 'use client' import Link from 'next/link' import { Button } from '@/components/ui/button' -import { Menu, X, Wallet } from 'lucide-react' -import { useState, useEffect } from 'react' +import { Menu, X } from 'lucide-react' +import { useState } from 'react' import Image from 'next/image' import { ThemeToggle } from './ui/ThemeToggle' +import { WalletConnect } from '@/components/wallet-connect' export function Navbar() { const [mobileMenuOpen, setMobileMenuOpen] = useState(false) - const [address, setAddress] = useState(null) - - useEffect(() => { - const savedAddress = localStorage.getItem('stellar_wallet_address') - if (savedAddress && savedAddress !== address) { - // eslint-disable-next-line react-hooks/set-state-in-effect - setAddress(savedAddress) - } - }, [address]) - - const formatAddress = (addr: string) => { - if (!addr || addr.length <= 10) return addr; - return `${addr.substring(0, 5)}...${addr.substring(addr.length - 4)}` - } return ( ) -} +} \ No newline at end of file diff --git a/components/wallet-connect.tsx b/components/wallet-connect.tsx new file mode 100644 index 0000000..a5f4712 --- /dev/null +++ b/components/wallet-connect.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { Button } from "@/components/ui/button"; +import { Wallet, LogOut } from "lucide-react"; + +type WalletState = { + connected: boolean; + publicKey: string | null; + error: string | null; +}; + +export function WalletConnect() { + const [wallet, setWallet] = useState({ + connected: false, + publicKey: null, + error: null, + }); + const [loading, setLoading] = useState(false); + + // Check if Freighter is available and already connected + const checkConnection = useCallback(async () => { + try { + // Freighter API v6: window.freighterApi is the global object + if (typeof window === "undefined" || !(window as any).freighterApi) { + return; + } + + const freighter = (window as any).freighterApi; + const publicKey = await freighter.getPublicKey(); + + if (publicKey) { + setWallet({ connected: true, publicKey, error: null }); + localStorage.setItem("stellar_wallet_address", publicKey); + } + } catch { + // User hasn't connected yet — that's fine + } + }, []); + + useEffect(() => { + checkConnection(); + }, [checkConnection]); + + const connect = async () => { + setLoading(true); + setWallet((prev) => ({ ...prev, error: null })); + + try { + const freighter = (window as any).freighterApi; + + if (!freighter) { + throw new Error( + "Freighter wallet is not installed. Please install the Freighter browser extension." + ); + } + + const publicKey: string = await freighter.getPublicKey(); + + if (!publicKey) { + throw new Error("Failed to retrieve wallet address. Please try again."); + } + + localStorage.setItem("stellar_wallet_address", publicKey); + setWallet({ connected: true, publicKey, error: null }); + } catch (err) { + const message = + err instanceof Error ? err.message : "Failed to connect wallet"; + setWallet((prev) => ({ + ...prev, + error: message, + })); + } finally { + setLoading(false); + } + }; + + const disconnect = () => { + localStorage.removeItem("stellar_wallet_address"); + setWallet({ connected: false, publicKey: null, error: null }); + }; + + const truncateKey = (key: string): string => { + if (key.length <= 10) return key; + return `${key.slice(0, 5)}...${key.slice(-4)}`; + }; + + // Error state + if (wallet.error) { + return ( +
+

{wallet.error}

+
+ + +
+
+ ); + } + + // Connected state + if (wallet.connected && wallet.publicKey) { + return ( +
+ + {truncateKey(wallet.publicKey)} + + +
+ ); + } + + // Disconnected state + return ( + + ); +} \ No newline at end of file From 2de75120ad42ff44d2bc2a398ae26aecde596a44 Mon Sep 17 00:00:00 2001 From: mitchellecm7 Date: Thu, 23 Apr 2026 11:26:45 -0700 Subject: [PATCH 2/4] feat: add wallet connect/disconnect component From 6f571c27e352c1680cf7d3f7dde23be5f74b680c Mon Sep 17 00:00:00 2001 From: mitchellecm7 Date: Thu, 23 Apr 2026 21:32:01 -0700 Subject: [PATCH 3/4] chore: resolve merge conflicts --- app/page.tsx | 1 + components/navbar.tsx | 246 +++++++++++++++++++++++----------- components/wallet-connect.tsx | 208 ++++++++++++++++++++++------ 3 files changed, 342 insertions(+), 113 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index f4d19f6..2be56ee 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -12,6 +12,7 @@ export default function Home() {
+ diff --git a/components/navbar.tsx b/components/navbar.tsx index 6ca2600..6d85c74 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -1,28 +1,144 @@ +// // 'use client' + +// // import Link from 'next/link' +// // import { Button } from '@/components/ui/button' +// // import { Menu, X, Wallet } from 'lucide-react' +// // import { useState, useEffect } from 'react' +// // import Image from 'next/image' +// // import { ThemeToggle } from './ui/ThemeToggle' + +// // export function Navbar() { +// // const [mobileMenuOpen, setMobileMenuOpen] = useState(false) +// // const [address, setAddress] = useState(null) + +// // useEffect(() => { +// // const savedAddress = localStorage.getItem('stellar_wallet_address') +// // if (savedAddress && savedAddress !== address) { +// // // eslint-disable-next-line react-hooks/set-state-in-effect +// // setAddress(savedAddress) +// // } +// // }, [address]) + +// // const formatAddress = (addr: string) => { +// // if (!addr || addr.length <= 10) return addr; +// // return `${addr.substring(0, 5)}...${addr.substring(addr.length - 4)}` +// // } + +// // return ( +// // +// // ) +// // } + // 'use client' // import Link from 'next/link' // import { Button } from '@/components/ui/button' -// import { Menu, X, Wallet } from 'lucide-react' -// import { useState, useEffect } from 'react' +// import { Menu, X } from 'lucide-react' +// import { useState } from 'react' // import Image from 'next/image' // import { ThemeToggle } from './ui/ThemeToggle' +// import { WalletConnect } from '@/components/wallet-connect' // export function Navbar() { // const [mobileMenuOpen, setMobileMenuOpen] = useState(false) -// const [address, setAddress] = useState(null) - -// useEffect(() => { -// const savedAddress = localStorage.getItem('stellar_wallet_address') -// if (savedAddress && savedAddress !== address) { -// // eslint-disable-next-line react-hooks/set-state-in-effect -// setAddress(savedAddress) -// } -// }, [address]) - -// const formatAddress = (addr: string) => { -// if (!addr || addr.length <= 10) return addr; -// return `${addr.substring(0, 5)}...${addr.substring(addr.length - 4)}` -// } // return ( // // ) // } -'use client' -import Link from 'next/link' -import { Button } from '@/components/ui/button' -import { Menu, X } from 'lucide-react' -import { useState } from 'react' -import Image from 'next/image' -import { ThemeToggle } from './ui/ThemeToggle' -import { WalletConnect } from '@/components/wallet-connect' +"use client"; + +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Menu, X } from "lucide-react"; +import { useState } from "react"; +import Image from "next/image"; +import { ThemeToggle } from "./ui/ThemeToggle"; +import { WalletConnect } from "@/components/wallet-connect"; export function Navbar() { - const [mobileMenuOpen, setMobileMenuOpen] = useState(false) + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); return ( - ) + ); } \ No newline at end of file diff --git a/components/wallet-connect.tsx b/components/wallet-connect.tsx index a5f4712..24d1734 100644 --- a/components/wallet-connect.tsx +++ b/components/wallet-connect.tsx @@ -1,8 +1,129 @@ +// "use client"; + +// import { useState, useEffect, useCallback } from "react"; +// import { Button } from "@/components/ui/button"; +// import { Wallet, LogOut } from "lucide-react"; +// import { +// isConnected, +// requestAccess, +// getUserInfo, +// } from "@stellar/freighter-api"; +// type WalletState = { +// connected: boolean; +// publicKey: string | null; +// error: string | null; +// }; + +// export function WalletConnect() { +// const [wallet, setWallet] = useState({ +// connected: false, +// publicKey: null, +// error: null, +// }); +// const [loading, setLoading] = useState(false); + +// // Check if Freighter is available and already connected +// const checkConnection = useCallback(async () => { +// try { +// const connected = await isConnected(); +// if (connected) { +// const user = await getUserInfo(); +// setWallet({ +// connected: true, +// publicKey: user.publicKey, +// error: null, +// }); +// } +// } catch {} +// }, []); + +// useEffect(() => { +// checkConnection(); +// }, [checkConnection]); + +// const connect = async () => { +// setLoading(true); +// setWallet((prev) => ({ ...prev, error: null })); + +// try { +// await requestAccess(); +// const user = await getUserInfo(); + +// setWallet({ +// connected: true, +// publicKey: user.publicKey, +// error: null, +// }); +// } catch (err) { +// const message = +// err instanceof Error ? err.message : "Failed to connect wallet"; +// setWallet((prev) => ({ ...prev, error: message })); +// } finally { +// setLoading(false); +// } +// }; + + +// const disconnect = () => { +// localStorage.removeItem("stellar_wallet_address"); +// setWallet({ connected: false, publicKey: null, error: null }); +// }; + +// const truncateKey = (key: string): string => { +// if (key.length <= 10) return key; +// return `${key.slice(0, 5)}...${key.slice(-4)}`; +// }; + +// // Error state +// if (wallet.error) { +// return ( +//
+//

{wallet.error}

+//
+// +// +//
+//
+// ); +// } + +// // Connected state +// if (wallet.connected && wallet.publicKey) { +// return ( +//
+// +// {truncateKey(wallet.publicKey)} +// +// +//
+// ); +// } + +// // Disconnected state +// return ( +// +// ); +// } + "use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Wallet, LogOut } from "lucide-react"; +import { + isConnected, + requestAccess, + getUserInfo, +} from "@stellar/freighter-api"; type WalletState = { connected: boolean; @@ -18,23 +139,21 @@ export function WalletConnect() { }); const [loading, setLoading] = useState(false); - // Check if Freighter is available and already connected + // Check connection on load const checkConnection = useCallback(async () => { try { - // Freighter API v6: window.freighterApi is the global object - if (typeof window === "undefined" || !(window as any).freighterApi) { - return; - } - - const freighter = (window as any).freighterApi; - const publicKey = await freighter.getPublicKey(); + const connected = await isConnected(); - if (publicKey) { - setWallet({ connected: true, publicKey, error: null }); - localStorage.setItem("stellar_wallet_address", publicKey); + if (connected) { + const user = await getUserInfo(); + setWallet({ + connected: true, + publicKey: user.publicKey, + error: null, + }); } } catch { - // User hasn't connected yet — that's fine + // silent fail (user not connected yet) } }, []); @@ -47,25 +166,18 @@ export function WalletConnect() { setWallet((prev) => ({ ...prev, error: null })); try { - const freighter = (window as any).freighterApi; + await requestAccess(); + const user = await getUserInfo(); - if (!freighter) { - throw new Error( - "Freighter wallet is not installed. Please install the Freighter browser extension." - ); - } - - const publicKey: string = await freighter.getPublicKey(); - - if (!publicKey) { - throw new Error("Failed to retrieve wallet address. Please try again."); - } - - localStorage.setItem("stellar_wallet_address", publicKey); - setWallet({ connected: true, publicKey, error: null }); + setWallet({ + connected: true, + publicKey: user.publicKey, + error: null, + }); } catch (err) { const message = err instanceof Error ? err.message : "Failed to connect wallet"; + setWallet((prev) => ({ ...prev, error: message, @@ -76,25 +188,40 @@ export function WalletConnect() { }; const disconnect = () => { - localStorage.removeItem("stellar_wallet_address"); - setWallet({ connected: false, publicKey: null, error: null }); + setWallet({ + connected: false, + publicKey: null, + error: null, + }); }; - const truncateKey = (key: string): string => { - if (key.length <= 10) return key; + const truncateKey = (key: string) => { return `${key.slice(0, 5)}...${key.slice(-4)}`; }; - // Error state + // 🔴 Error State if (wallet.error) { return (
-

{wallet.error}

+

+ {wallet.error} +

- -
@@ -102,21 +229,26 @@ export function WalletConnect() { ); } - // Connected state + // 🟢 Connected State if (wallet.connected && wallet.publicKey) { return (
{truncateKey(wallet.publicKey)} -
); } - // Disconnected state + // ⚪ Default (Disconnected) return ( +// +//
+// +// ); +// } + +// // 🟢 Connected State +// if (wallet.connected && wallet.publicKey) { +// return ( +//
+// +// {truncateKey(wallet.publicKey)} +// +// +//
+// ); +// } + +// // ⚪ Default (Disconnected) +// return ( +// +// ); +// }"use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; @@ -122,7 +263,7 @@ import { Wallet, LogOut } from "lucide-react"; import { isConnected, requestAccess, - getUserInfo, + getAddress, } from "@stellar/freighter-api"; type WalletState = { @@ -139,21 +280,23 @@ export function WalletConnect() { }); const [loading, setLoading] = useState(false); - // Check connection on load const checkConnection = useCallback(async () => { try { const connected = await isConnected(); if (connected) { - const user = await getUserInfo(); + const { address, error } = await getAddress(); + + if (error || !address) return; + setWallet({ connected: true, - publicKey: user.publicKey, + publicKey: address, error: null, }); } } catch { - // silent fail (user not connected yet) + // ignore } }, []); @@ -167,11 +310,16 @@ export function WalletConnect() { try { await requestAccess(); - const user = await getUserInfo(); + + const { address, error } = await getAddress(); + + if (error || !address) { + throw new Error(error || "Failed to retrieve wallet address"); + } setWallet({ connected: true, - publicKey: user.publicKey, + publicKey: address, error: null, }); } catch (err) { @@ -195,11 +343,9 @@ export function WalletConnect() { }); }; - const truncateKey = (key: string) => { - return `${key.slice(0, 5)}...${key.slice(-4)}`; - }; + const truncateKey = (key: string) => + `${key.slice(0, 5)}...${key.slice(-4)}`; - // 🔴 Error State if (wallet.error) { return (
@@ -229,7 +375,6 @@ export function WalletConnect() { ); } - // 🟢 Connected State if (wallet.connected && wallet.publicKey) { return (
@@ -248,7 +393,6 @@ export function WalletConnect() { ); } - // ⚪ Default (Disconnected) return (