diff --git a/app/api/users/wallet/[wallet]/route.ts b/app/api/users/wallet/[publicKey]/route.ts similarity index 100% rename from app/api/users/wallet/[wallet]/route.ts rename to app/api/users/wallet/[publicKey]/route.ts diff --git a/components/explore/Navbar.tsx b/components/explore/Navbar.tsx index d0c8a89c..41ad6c27 100644 --- a/components/explore/Navbar.tsx +++ b/components/explore/Navbar.tsx @@ -7,12 +7,12 @@ import Image from "next/image"; import Link from "next/link"; import { motion, AnimatePresence } from "framer-motion"; import type { SearchResult } from "@/types/explore"; -import { useStellarWallet } from "@/contexts/stellar-wallet-context"; import { useAuth } from "@/components/auth/auth-provider"; import ConnectModal from "../connectWallet"; import ProfileModal from "./ProfileModal"; import Avatar from "@/public/Images/user.png"; import ProfileDropdown from "../ui/profileDropdown"; +import { useWallet } from "stellar-wallet-kit"; interface NavbarProps { onConnectWallet?: () => void; @@ -32,97 +32,50 @@ export default function Navbar({}: NavbarProps) { const searchInputRef = useRef(null); const searchDropdownRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(false); - const { address, isConnected, disconnect } = useStellarWallet(); + const { account, isConnected, disconnect } = useWallet(); const { user, isLoading: authLoading } = useAuth(); const [isSearchDropdownOpen, setIsSearchDropdownOpen] = useState(false); const [profileModalOpen, setProfileModalOpen] = useState(false); const [hasCheckedProfile, setHasCheckedProfile] = useState(false); - const [connectStep, setConnectStep] = useState< - "profile" | "verify" | "success" - >("profile"); + const [connectStep, setConnectStep] = useState<"profile" | "verify" | "success">("profile"); const [isLoading, setIsLoading] = useState(true); const [isProfileDropdownOpen, setIsProfileDropdownOpen] = useState(false); - const getDisplayName = useCallback(() => { - if (user?.username) { - return user.username; - } - - if (typeof window !== "undefined") { - try { - const userData = sessionStorage.getItem("userData"); - if (userData) { - const parsedUser = JSON.parse(userData); - if (parsedUser.username) { - return parsedUser.username; - } - } - } catch (error) { - console.error("Error parsing user data from sessionStorage:", error); - } - } - if (address) { - return `${address.substring(0, 6)}...${address.slice(-4)}`; - } + const publicKey = account?.publicKey; - return "Unknown User"; - }, [user?.username, address]); - - const renderDisplayName = () => { - if (isLoading) { - return ( - <> -
-
- - ); + // safe sessionStorage parse + const getSessionData = useCallback((key: string): T | null => { + if (typeof window === "undefined") return null; + try { + const data = sessionStorage.getItem(key); + return data ? JSON.parse(data) : null; + } catch { + return null; } + }, []); - if (user?.username) { - return user.username; - } + // display name + const getDisplayName = useCallback(() => { + if (user?.username) return user.username; - try { - const storedData = sessionStorage.getItem("userData"); - if (storedData) { - const parsedUser = JSON.parse(storedData); - if (parsedUser.username) { - return parsedUser.username; - } - } - } catch (err) { - console.error("Error reading userData from sessionStorage:", err); - } + const storedUser = getSessionData<{ username?: string }>("userData"); + if (storedUser?.username) return storedUser.username; - if (address) { - return `${address.substring(0, 6)}...${address.slice(-4)}`; - } + if (publicKey) return `${publicKey.slice(0, 6)}...${publicKey.slice(-4)}`; return "Unknown User"; - }; + }, [user?.username, publicKey, getSessionData]); + // avatar logic const getAvatar = useCallback(() => { - if (user?.avatar) { - return user.avatar; - } + if (user?.avatar) return user.avatar; - if (typeof window !== "undefined") { - try { - const userData = sessionStorage.getItem("userData"); - if (userData) { - const parsedUser = JSON.parse(userData); - if (parsedUser.avatar) { - return parsedUser.avatar; - } - } - } catch (error) { - console.error("Error parsing user data from sessionStorage:", error); - } - } + const storedUser = getSessionData<{ avatar?: string }>("userData"); + if (storedUser?.avatar) return storedUser.avatar; return Avatar; - }, [user?.avatar]); + }, [user?.avatar, getSessionData]); const handleCloseProfileModal = () => { setProfileModalOpen(false); @@ -135,154 +88,122 @@ export default function Navbar({}: NavbarProps) { useEffect(() => { setHasCheckedProfile(false); - }, [address]); + }, [publicKey]); + // Close profile dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { + const target = event.target as HTMLElement; if ( - searchInputRef.current && - !searchInputRef.current.contains(event.target as Node) && - searchDropdownRef.current && - !searchDropdownRef.current.contains(event.target as Node) + !target.closest(".profile-dropdown-container") && + !target.closest(".avatar-container") && + !target.closest("#search-input") && + !target.closest("#search-dropdown") ) { + setIsProfileDropdownOpen(false); setIsSearchDropdownOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; + return () => document.removeEventListener("mousedown", handleClickOutside); }, []); + // Autofocus search input useEffect(() => { - if (searchInputRef.current) { - searchInputRef.current.focus(); - } + searchInputRef.current?.focus(); }, []); + // Search results fetch with debounce useEffect(() => { - if (searchQuery.trim() === "") { + if (!searchQuery.trim()) { setSearchResults([]); return; } - const fetchResults = async () => { + const timeout = setTimeout(async () => { try { - const res = await fetch( - `/api/category?title=${encodeURIComponent(searchQuery)}` - ); + const res = await fetch(`/api/category?title=${encodeURIComponent(searchQuery)}`); const data = await res.json(); - - const normalizedResults = (data.categories ?? []).map( - (cat: Category) => ({ - id: cat.id, - title: cat.title, - image: cat.imageurl || "/placeholder.svg", - type: "category", - }) - ); - + const normalizedResults = (data.categories ?? []).map((cat: Category) => ({ + id: cat.id, + title: cat.title, + image: cat.imageurl || "/placeholder.svg", + type: "category", + })); setSearchResults(normalizedResults); - } catch (error) { - console.error("Search fetch error:", error); + } catch { setSearchResults([]); } - }; + }, 300); - const debounce = setTimeout(fetchResults, 300); - return () => clearTimeout(debounce); + return () => clearTimeout(timeout); }, [searchQuery]); const handleConnectWallet = () => { - if (isConnected) { - disconnect(); - } else { - setIsModalOpen(true); - } + if (isConnected) disconnect(); + else setIsModalOpen(true); }; - const toggleProfileDropdown = () => { - setIsProfileDropdownOpen(!isProfileDropdownOpen); - }; + const toggleProfileDropdown = () => setIsProfileDropdownOpen(!isProfileDropdownOpen); + // Profile modal logic useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as HTMLElement; - if ( - !target.closest(".profile-dropdown-container") && - !target.closest(".avatar-container") - ) { - setIsProfileDropdownOpen(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - useEffect(() => { - if (authLoading) { - return; - } - - if (!isConnected || !address) { - setIsLoading(false); - return; - } - - if (hasCheckedProfile) { + if (authLoading) return; + if (!isConnected || !publicKey || hasCheckedProfile) { setIsLoading(false); return; } setHasCheckedProfile(true); - if (!user) { - setProfileModalOpen(true); - } else { + if (!user) setProfileModalOpen(true); + else { sessionStorage.setItem("userData", JSON.stringify(user)); sessionStorage.setItem("username", user.username); } setIsLoading(false); - }, [isConnected, address, authLoading, user, hasCheckedProfile]); + }, [isConnected, publicKey, authLoading, user, hasCheckedProfile]); useEffect(() => { - if (isConnected) { - setIsModalOpen(false); - } + if (isConnected) setIsModalOpen(false); }, [isConnected]); + + + // Function to check for cloudinary URL +const ALLOWED_CLOUDINARY_HOST = "res.cloudinary.com"; // replace with Cloudinary domain + +function isCloudinaryUrl(url: string): boolean { + try { + const parsed = new URL(url); + return parsed.hostname === ALLOWED_CLOUDINARY_HOST; + } catch { + return false; + } +} + + const userAvatar = getAvatar(); const displayName = getDisplayName(); - const truncatedDisplayName = - displayName.length > 12 ? displayName.substring(0, 12) : displayName; + const truncatedDisplayName = displayName.length > 12 ? displayName.slice(0, 12) : displayName; + + return ( <> -
+
- Streamfi Logo - Streamfi Logo + Streamfi Logo + Streamfi Logo
{ - if (searchResults.length > 0) { - setIsSearchDropdownOpen(true); - } - }} - className="w-full bg-input rounded-xl py-2 pl-10 pr-4 text-sm outline-none focus:ring-1 focus:ring-highlight focus:outline-none" - /> - searchResults.length && setIsSearchDropdownOpen(true)} + className="w-full bg-input rounded-xl py-2 pl-10 pr-4 text-sm outline-none focus:ring-1 focus:ring-highlight" /> +
{isSearchDropdownOpen && searchResults.length > 0 && ( (
@@ -330,12 +245,8 @@ export default function Navbar({}: NavbarProps) { />
-
- {result.title} -
-
- {result.type} -
+
{result.title}
+
{result.type}
))} @@ -346,75 +257,37 @@ export default function Navbar({}: NavbarProps) {
- {isConnected && address && ( + {isConnected && publicKey ? ( <> - {!isLoading && ( - - )} - + {!isLoading && }
-
- {isLoading ? ( +
+ {!isLoading ? ( <> -
-
- - ) : ( - <> - - {renderDisplayName()} - - - {typeof userAvatar === "string" && - userAvatar.includes("cloudinary.com") ? ( - Avatar + {displayName} + {typeof userAvatar === "string" && isCloudinaryUrl(userAvatar) ? ( + Avatar ) : ( - Avatar + Avatar )} + ) : ( +
)} -
{isProfileDropdownOpen && (
- { - setTimeout(() => { - toggleProfileDropdown(); - }, 400); - }} - /> + setTimeout(toggleProfileDropdown, 400)} />
)}
- )} - {!isConnected && ( - )} @@ -424,15 +297,9 @@ export default function Navbar({}: NavbarProps) { {isModalOpen && ( -
setIsModalOpen(false)} - /> +
setIsModalOpen(false)} /> - + )} @@ -448,4 +315,4 @@ export default function Navbar({}: NavbarProps) { ); -} +} \ No newline at end of file diff --git a/components/settings/profile/profile-page.tsx b/components/settings/profile/profile-page.tsx index f7a65c70..1f4dc9c7 100644 --- a/components/settings/profile/profile-page.tsx +++ b/components/settings/profile/profile-page.tsx @@ -516,4 +516,4 @@ export default function ProfileSettings() {
); -} +} \ No newline at end of file diff --git a/package.json b/package.json index 12923291..1b6c203f 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "react-icons": "^5.5.0", "sharp": "^0.33.5", "sonner": "^2.0.2", + "stellar-wallet-kit": "^2.0.7", "swiper": "^12.1.2", "swr": "^2.4.0", "tailwind-merge": "^3.0.2", diff --git a/types/images.d.ts b/types/images.d.ts index 75318b8f..a541730c 100644 --- a/types/images.d.ts +++ b/types/images.d.ts @@ -1,34 +1,29 @@ declare module "*.png" { - const value: string; - export default value; + const content: string; + export default content; } declare module "*.jpg" { - const value: string; - export default value; + const content: string; + export default content; } declare module "*.jpeg" { - const value: string; - export default value; -} - -declare module "*.gif" { - const value: string; - export default value; + const content: string; + export default content; } declare module "*.svg" { - const value: string; - export default value; + const content: string; + export default content; } -declare module "*.webp" { - const value: string; - export default value; +declare module "*.gif" { + const content: string; + export default content; } -declare module "*.ico" { - const value: string; - export default value; +declare module "*.webp" { + const content: string; + export default content; }