"use client"; import Link from "next/link"; import { useSession, signOut, signIn } from "next-auth/react"; import { useState, useEffect } from "react"; import { usePathname } from "next/navigation"; import { IoClose, IoMenu } from "react-icons/io5"; import { FiMoon, FiSun, FiLogOut, FiDollarSign, FiPlus, FiUser, FiKey, FiCheck, FiX, FiTrash2 } from "react-icons/fi"; import config from "@/lib/config"; import toast from "react-hot-toast"; function HuggingFaceIcon({ className = "w-3 h-3" }) { return ( ); } const HF_DUPLICATE_URL = "https://huggingface.co/spaces?duplicate=sonic-coders/Hugging-c-ai"; export default function Navbar() { const { data: session, status, update: updateSession } = useSession(); const pathname = usePathname(); const [isOpen, setIsOpen] = useState(false); const [isProfileOpen, setIsProfileOpen] = useState(false); const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false); const [apiKeyInput, setApiKeyInput] = useState(""); const [savingKey, setSavingKey] = useState(false); const appName = config?.appName || "AI SaaS"; const logoLetter = appName.trim().charAt(0).toUpperCase(); const isApiKeyActive = Boolean(session?.user?.customApiKey); useEffect(() => { if (session?.user?.customApiKey) { setApiKeyInput(session.user.customApiKey); } }, [session?.user?.customApiKey]); const appMatch = pathname ? pathname.match(/^\/app\/([^\/]+)/) : null; const currentAppId = appMatch ? appMatch[1] : null; const navLinks = currentAppId ? [ { name: "Workspace", path: `/app/${currentAppId}` }, { name: "Gallery", path: `/app/${currentAppId}/gallery` }, { name: "Pricing", path: `/app/${currentAppId}/pricing` }, ] : [ { name: "Workspace", path: "/" }, { name: "Gallery", path: "/gallery" }, { name: "Pricing", path: "/pricing" }, ]; const handleSaveApiKey = async (e) => { e.preventDefault(); const key = apiKeyInput.trim(); if (!key) { toast.error("Please enter a valid API Key"); return; } setSavingKey(true); try { if (status === "authenticated") { const res = await fetch("/api/user/apikey", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ apiKey: key }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || "Failed to save API key"); await updateSession({ customApiKey: key }); toast.success("Custom API Key updated!"); setIsApiKeyModalOpen(false); window.location.reload(); } else { const res = await signIn("credentials", { apiKey: key, redirect: false, }); if (res?.error) { throw new Error(res.error || "Failed to sign in with API key"); } toast.success("Signed in with API Key!"); setIsApiKeyModalOpen(false); window.location.reload(); } } catch (err) { toast.error(err.message || "Failed to save API Key"); } finally { setSavingKey(false); } }; const handleRemoveApiKey = async () => { setSavingKey(true); try { const res = await fetch("/api/user/apikey", { method: "DELETE" }); const data = await res.json(); if (!res.ok) throw new Error(data.error || "Failed to remove API key"); await updateSession({ customApiKey: null }); setApiKeyInput(""); toast.success("Custom API Key removed"); setIsApiKeyModalOpen(false); window.location.reload(); } catch (err) { toast.error(err.message || "Failed to remove API Key"); } finally { setSavingKey(false); } }; return ( {/* Logo and Brand Title */} {logoLetter} {appName} {/* Desktop Navigation Links */} {navLinks.map((link) => { const isActive = pathname === link.path; return ( {link.name} {isActive && ( )} ); })} {/* Desktop Actions Section */} {/* HF Space Duplicate Button */} Duplicate to HF Space {/* Add/Manage API Key - Directly visible in Navbar */} setIsApiKeyModalOpen(true)} className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-bold border transition-all cursor-pointer ${ isApiKeyActive ? "bg-amber-500/10 border-amber-500/30 text-amber-400 hover:bg-amber-500/20" : "bg-bg-page/50 border-divider text-secondary-text hover:text-white hover:border-primary/40" }`} > {isApiKeyActive ? "Custom API Key" : "Add API Key"} {status === "authenticated" ? ( {/* Credit Balance indicator */} {isApiKeyActive ? "∞ (API Key)" : session.user.credits !== undefined ? session.user.credits : 0} {!isApiKeyActive && ( )} {/* Profile Menu Toggle */} setIsProfileOpen(!isProfileOpen)} onBlur={() => setTimeout(() => setIsProfileOpen(false), 200)} className="h-9 w-9 flex items-center justify-center border-y border-r border-divider rounded-r bg-bg-page/30 hover:bg-bg-page transition-colors cursor-pointer" > {session.user.image ? ( ) : ( )} {/* Profile Dropdown */} {isProfileOpen && ( {session.user.email} setIsApiKeyModalOpen(true)} className="flex w-full items-center gap-2 rounded px-3 py-2 text-left text-xs font-semibold text-primary-text hover:bg-primary/10 transition-colors" > {isApiKeyActive ? "Manage API Key" : "Add API Key"} signOut({ callbackUrl: "/login" })} className="flex w-full items-center gap-2 rounded px-3 py-2 text-left text-xs font-semibold text-red-500 hover:bg-red-500/10 transition-colors" > Sign Out )} ) : ( Sign In )} {/* Mobile Navbar Controls */} {status === "authenticated" && ( {isApiKeyActive ? "∞ Key" : session.user.credits !== undefined ? session.user.credits : 0} )} setIsOpen(!isOpen)} className="hover:bg-bg-card p-2 rounded cursor-pointer transition-colors text-primary-text border border-divider/50" aria-label="Toggle Menu" > {isOpen ? : } {/* Mobile Menu Dropdown */} {isOpen && ( Navigation {navLinks.map((link) => ( setIsOpen(false)} className={`flex items-center py-2.5 rounded text-sm font-semibold transition-all ${ pathname === link.path ? "bg-primary/10 text-primary px-3 border border-primary/20" : "text-primary-text hover:bg-bg-card" }`} > {link.name} ))} { setIsOpen(false); setIsApiKeyModalOpen(true); }} className="flex w-full items-center justify-between rounded border border-amber-500/30 bg-amber-500/10 px-3 py-2.5 text-xs font-bold text-amber-400" > {isApiKeyActive ? "Manage Custom API Key" : "Add API Key"} {/* HF Space Duplicate in Mobile menu */} Duplicate to HF Space {status === "authenticated" ? ( { setIsOpen(false); signOut({ callbackUrl: "/login" }); }} className="flex w-full items-center justify-center gap-2 rounded bg-red-500/10 text-red-500 py-3 text-sm font-bold hover:bg-red-500/20 transition-all border border-red-500/20 mt-2" > Sign Out ) : ( setIsOpen(false)} className="flex w-full items-center justify-center rounded bg-primary text-white py-3 text-sm font-bold hover:bg-primary-hover transition-all shadow-md shadow-primary/20 mt-2" > Sign In )} )} {/* API Key Modal */} {isApiKeyModalOpen && ( Custom API Key Settings setIsApiKeyModalOpen(false)} className="text-secondary-text hover:text-white transition-colors cursor-pointer" > Use your own HF token, OpenAI key, Anthropic key, Groq key, or MuAPI key. We auto-detect the provider from the key prefix. Keys are encrypted at rest. API Key / HF Token setApiKeyInput(e.target.value)} placeholder="hf_... | sk-... | sk-ant... | gsk_... | mu_..." className="w-full bg-bg-page border border-divider rounded-lg px-3.5 py-2.5 text-xs text-white placeholder-secondary-text/50 focus:outline-none focus:border-amber-400 transition-colors" /> {isApiKeyActive && status === "authenticated" && ( Remove Key )} setIsApiKeyModalOpen(false)} className="px-4 py-2 rounded-lg bg-bg-page border border-divider text-xs font-semibold text-secondary-text hover:text-white cursor-pointer" > Cancel {savingKey ? "Processing..." : status === "authenticated" ? "Save Key" : "Sign In with API Key"} )} ); }
Use your own HF token, OpenAI key, Anthropic key, Groq key, or MuAPI key. We auto-detect the provider from the key prefix. Keys are encrypted at rest.