Spaces:
Build error
Build error
File size: 11,712 Bytes
0702eb8 fdaf912 0702eb8 f730525 caae15f f730525 1d4f56e fb3baa1 b4297ca fdaf912 f730525 0702eb8 fdaf912 0702eb8 caae15f 0702eb8 b4297ca fdaf912 b4297ca d5e0a0f b4297ca caae15f b4297ca 7d9d30d caae15f 7873c88 b4297ca 7873c88 caae15f f730525 caae15f f730525 caae15f 0702eb8 fdaf912 b4297ca 0702eb8 fdaf912 d5e0a0f 0702eb8 caae15f 0702eb8 caae15f 0702eb8 caae15f fb3baa1 caae15f b4297ca 0702eb8 f730525 b4297ca 0702eb8 f730525 b4297ca 0702eb8 f730525 b4297ca caae15f f730525 b4297ca 0702eb8 f730525 fdaf912 0702eb8 caae15f b4297ca caae15f 7d9d30d caae15f f730525 caae15f 0702eb8 caae15f 0702eb8 b4297ca fdaf912 b4297ca fdaf912 b4297ca fdaf912 b4297ca fdaf912 b4297ca fdaf912 b4297ca fdaf912 b4297ca 0702eb8 7d9d30d b4297ca fb3baa1 7d9d30d fdaf912 7d9d30d 0702eb8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
"use client";
import Image from 'next/image';
import { Home, InfoIcon, MessageCircle, Search, FileQuestion, Menu, X, User2, LogOut, LogIn, SlidersHorizontal } from 'lucide-react';
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { useMedia } from 'react-use';
import useSWR from 'swr';
import logo from '@/public/smart-retrieval-logo.webp';
import { HeaderNavLink } from '@/app/components/ui/navlink';
import { MobileMenu } from '@/app/components/ui/mobilemenu';
import { IconSpinner } from '@/app/components/ui/icons';
import { useSession, signOut } from 'next-auth/react';
import { usePathname } from 'next/navigation';
import { Skeleton } from "@nextui-org/react";
const MobileMenuItems = [
{
href: '/',
icon: <Home className="mr-2 h-5 w-5" />,
label: 'Home',
},
{
href: '/about',
icon: <InfoIcon className="mr-2 h-5 w-5" />,
label: 'About',
},
{
href: '/chat',
icon: <MessageCircle className="mr-2 h-5 w-5" />,
label: 'Chat',
},
{
href: '/query',
icon: <FileQuestion className="mr-2 h-5 w-5" />,
label: 'Q&A',
},
{
href: '/search',
icon: <Search className="mr-2 h-5 w-5" />,
label: 'Search',
},
];
const AdminMenuItems = [
{
href: '/admin',
icon: <SlidersHorizontal className="mr-2 h-5 w-5" />,
label: 'Admin',
},
];
export default function Header() {
const isLargeScreen = useMedia('(min-width: 1024px)', false);
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
// Get the current path
const currentPath = usePathname();
// Ensure the currentPath is encoded
const encodedPath = encodeURIComponent(currentPath);
// Add callbackUrl params to the signinPage URL
const signinPage = "/sign-in?callbackUrl=" + encodedPath;
// Check if the user is an admin
const [isAdmin, setIsAdmin] = useState(false);
// Get user session for conditional rendering of user profile and logout buttons and for fetching the API status
const { data: session, status } = useSession()
// console.log('session:', session, 'status:', status);
const supabaseAccessToken = session?.supabaseAccessToken;
// Use SWR for API status fetching
const healthcheck_api = "/api/status";
const { data, error: apiError, isLoading } = useSWR(healthcheck_api, async (url) => {
try {
// Fetch the data
const response = await fetch(url, {
signal: AbortSignal.timeout(5000), // Abort the request if it takes longer than 5 seconds
// Add the access token to the request headers
headers: {
'Authorization': `Bearer ${supabaseAccessToken}`,
}
});
if (!response.ok) {
throw new Error(response.statusText || 'Unknown Error');
}
const data = await response.json();
return data;
} catch (error: any) {
console.error('Error fetching Backend API Status');
throw error;
}
}, {
revalidateOnFocus: true, // Revalidate when the window gains focus
revalidateIfStale: true, // Revalidate if the data is stale
refreshInterval: 60000, // Revalidate every 60 seconds
});
if (apiError) {
if (apiError.name === 'AbortError') {
console.error('[Header] Error fetching Backend API Status: Request timed out');
}
else {
console.error('[Header] Error fetching Backend API Status:', apiError.message);
}
}
const checkAdminRole = async () => {
const response = await fetch('/api/admin/is-admin');
if (!response.ok) {
console.error('Failed to fetch admin data');
return;
}
const data = await response.json();
setIsAdmin(data.isAdmin);
console.log('Admin role fetched successfully! Data:', data);
};
const newMobileMenuItems = isAdmin ? [...MobileMenuItems, ...AdminMenuItems] : MobileMenuItems;
useEffect(() => {
setMounted(true);
checkAdminRole();
}, [session]);
const [isMobileMenuOpen, setMobileMenuOpen] = useState(false);
const toggleMobileMenu = () => {
// Handle the toggle click here
if (isMobileMenuOpen) {
// If the menu is open, close it
setMobileMenuOpen(false);
} else {
// If the menu is closed, open it
setMobileMenuOpen(true);
}
};
if (!mounted) return null;
return (
<div className="z-10 max-w-5xl w-full text-sm">
{/* Navigation Bar */}
<nav className="fixed left-0 top-0 w-full bg-gradient-to-b from-zinc-200 pb-2 pt-2 backdrop-blur-2xl dark:border-neutral-700 dark:bg-zinc-700/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-100 lg:p-4 lg:dark:bg-zinc-800/30 shadow-xl">
<div className="flex items-center flex-wrap lg:flex-nowrap px-4">
{isLargeScreen && (
<div className="flex items-center" style={{ width: '6%', height: 'auto' }}>
<Image
className='rounded-full max-w-full'
src={logo}
alt="Logo"
style={{
width: 'auto',
height: 'auto',
}}
priority
sizes="100vw, 50vw, 33vw"
/>
</div>
)}
<div className="flex items-center pr-2 pl-2 gap-2">
<span className="hidden lg:inline lg:text-lg font-nunito font-bold">Smart Retrieval</span>
<span className="hidden lg:inline lg:text-lg font-nunito">|</span>
</div>
<div className="flex items-center gap-4 lg:hidden">
{/* Toggle button for mobile menu */}
<button
className="flex items-center text-xl transition duration-300 ease-in-out transform hover:scale-125 toggle-button"
title="Toggle mobile menu"
onClick={toggleMobileMenu}
>
{isMobileMenuOpen ? (
<span role="img" aria-label="close icon">
<X />
</span>
) : (
<span role="img" aria-label="menu icon">
<Menu />
</span>)}
</button>
</div>
<div className={`hidden items-center gap-4 lg:flex`}>
<HeaderNavLink href="/" title='Home'>
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
<Home className="mr-1 h-4 w-4" />
Home
</div>
</HeaderNavLink>
<HeaderNavLink href="/about" title='About Us'>
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
<InfoIcon className="mr-1 h-4 w-4" />
About
</div>
</HeaderNavLink>
<HeaderNavLink href="/chat" title='Chat'>
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
<MessageCircle className="mr-1 h-4 w-4" />
Chat
</div>
</HeaderNavLink>
<HeaderNavLink href="/query" title='Q&A'>
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
<FileQuestion className="mr-1 h-4 w-4" />
Q&A
</div>
</HeaderNavLink>
<HeaderNavLink href="/search" title='Search'>
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
<Search className="mr-1 h-4 w-4" />
Search
</div>
</HeaderNavLink>
{isAdmin &&
<HeaderNavLink href="/admin" title='Admin'>
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
<SlidersHorizontal className="mr-1 h-4 w-4" />
Admin
</div>
</HeaderNavLink>
}
</div>
<div className="flex items-center ml-auto">
{/* Status Page Button/Indicator */}
<span className='flex items-center mr-1'>API:</span>
<HeaderNavLink href='/status' title='API Status'>
<div className="flex items-center mr-2 text-xl transition duration-300 ease-in-out transform hover:scale-125">
{isLoading ? (
<IconSpinner className="mr-2 animate-spin" />
) :
apiError ? (
<span role="img" aria-label="red circle">
π΄
</span>
) : (
<span role="img" aria-label="green circle">
π’
</span>
)}
</div>
</HeaderNavLink>
<span className="lg:text-lg font-nunito">|</span>
{/* Toggle button with icon based on the theme */}
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
className="flex items-center ml-2 text-xl transition duration-300 ease-in-out transform hover:scale-125"
title={`Toggle between dark & light mode (Current mode: ${theme})`}>
{theme === 'light' ? (
<span role="img" aria-label="sun emoji">
βοΈ
</span>
) : (
<span role="img" aria-label="moon emoji">
π
</span>
)}
</button>
<span className="lg:text-lg font-nunito ml-6"></span>
{/* Conditionally render the user profile and logout buttons based on the user's authentication status */}
{session ? (
<>
<Skeleton isLoaded={status === 'authenticated'} className="rounded-md p-1">
<div className="flex items-center">
{/* User Profile Button */}
<HeaderNavLink href="/profile" title='Profile'>
<div className="flex items-center mr-6 text-xl transition duration-300 ease-in-out transform hover:scale-125">
<User2 className="mr-1 h-5 w-5" />
</div>
</HeaderNavLink>
{/* Sign Out Button */}
<button title='Sign Out'
onClick={
async () => {
await signOut();
}
}>
<div className="flex items-center text-xl transition duration-300 ease-in-out transform hover:scale-125">
<LogOut className="mr-1 h-5 w-5" />
</div>
</button>
</div>
</Skeleton>
</>
) : (
<Skeleton isLoaded={status !== 'loading'} className="rounded-md p-2">
<div className="flex items-center">
<HeaderNavLink href={signinPage} title='Sign In'>
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-110">
<LogIn className="mr-1 h-5 w-5" />
Sign In
</div>
</HeaderNavLink>
</div>
</Skeleton>
)}
</div>
</div >
{/* Mobile menu component */}
< MobileMenu isOpen={isMobileMenuOpen} onClose={() => setMobileMenuOpen(false)
} logoSrc={logo} items={newMobileMenuItems} />
</nav >
</div >
);
}
|