mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-21 06:34:30 +01:00
Merge Tag v0.2.18
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Version 0.2.18
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
* nicer loading UI (#147)
|
||||||
|
* header and navigation code refactor
|
||||||
|
|
||||||
## Version 0.2.17
|
## Version 0.2.17
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { JotaiHydrate } from '@/components/jotai-hydrate'
|
import { JotaiHydrate } from '@/components/jotai-hydrate'
|
||||||
import { JotaiProvider } from '@/components/jotai-providers'
|
import { JotaiProvider } from '@/components/jotai-providers'
|
||||||
import Layout from '@/components/Layout'
|
import Layout from '@/components/Layout'
|
||||||
|
import LoadingSpinner from '@/components/LoadingSpinner'
|
||||||
import { ThemeProvider } from "@/components/theme-provider"
|
import { ThemeProvider } from "@/components/theme-provider"
|
||||||
import { Toaster } from '@/components/ui/toaster'
|
import { Toaster } from '@/components/ui/toaster'
|
||||||
import { SessionProvider } from 'next-auth/react'
|
import { SessionProvider } from 'next-auth/react'
|
||||||
|
import { NextIntlClientProvider } from 'next-intl'
|
||||||
|
import { getLocale, getMessages } from 'next-intl/server'
|
||||||
import { DM_Sans } from 'next/font/google'
|
import { DM_Sans } from 'next/font/google'
|
||||||
import { Suspense } from 'react'
|
import { Suspense } from 'react'
|
||||||
import { loadCoinsData, loadHabitsData, loadServerSettings, loadSettings, loadUsersData, loadWishlistData } from './actions/data'
|
import { loadCoinsData, loadHabitsData, loadServerSettings, loadSettings, loadUsersData, loadWishlistData } from './actions/data'
|
||||||
import './globals.css'
|
import './globals.css'
|
||||||
import { NextIntlClientProvider } from 'next-intl';
|
|
||||||
import { getLocale, getMessages } from 'next-intl/server';
|
|
||||||
|
|
||||||
// Inter (clean, modern, excellent readability)
|
// Inter (clean, modern, excellent readability)
|
||||||
// const inter = Inter({
|
// const inter = Inter({
|
||||||
@@ -73,7 +74,7 @@ export default async function RootLayout({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<JotaiProvider>
|
<JotaiProvider>
|
||||||
<Suspense fallback="loading">
|
<Suspense fallback={<LoadingSpinner />}>
|
||||||
<JotaiHydrate
|
<JotaiHydrate
|
||||||
initialValues={{
|
initialValues={{
|
||||||
settings: initialSettings,
|
settings: initialSettings,
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ReactNode, useEffect } from 'react'
|
import { ReactNode, Suspense, useEffect, useState } from 'react'
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom } from 'jotai'
|
||||||
import { aboutOpenAtom, pomodoroAtom, userSelectAtom } from '@/lib/atoms'
|
import { aboutOpenAtom, pomodoroAtom, userSelectAtom } from '@/lib/atoms'
|
||||||
import PomodoroTimer from './PomodoroTimer'
|
import PomodoroTimer from './PomodoroTimer'
|
||||||
import UserSelectModal from './UserSelectModal'
|
import UserSelectModal from './UserSelectModal'
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import AboutModal from './AboutModal'
|
import AboutModal from './AboutModal'
|
||||||
|
import LoadingSpinner from './LoadingSpinner'
|
||||||
|
|
||||||
export default function ClientWrapper({ children }: { children: ReactNode }) {
|
export default function ClientWrapper({ children }: { children: ReactNode }) {
|
||||||
const [pomo] = useAtom(pomodoroAtom)
|
const [pomo] = useAtom(pomodoroAtom)
|
||||||
@@ -14,6 +15,12 @@ export default function ClientWrapper({ children }: { children: ReactNode }) {
|
|||||||
const [aboutOpen, setAboutOpen] = useAtom(aboutOpenAtom)
|
const [aboutOpen, setAboutOpen] = useAtom(aboutOpenAtom)
|
||||||
const { data: session, status } = useSession()
|
const { data: session, status } = useSession()
|
||||||
const currentUserId = session?.user.id
|
const currentUserId = session?.user.id
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
|
|
||||||
|
// block client-side hydration until mounted (this is crucial to wait for all jotai atoms to load), to prevent SSR hydration errors in the children components
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'loading') return
|
if (status === 'loading') return
|
||||||
@@ -22,6 +29,9 @@ export default function ClientWrapper({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
}, [currentUserId, status, userSelect, setUserSelect])
|
}, [currentUserId, status, userSelect, setUserSelect])
|
||||||
|
|
||||||
|
if (!isMounted) {
|
||||||
|
return <LoadingSpinner />
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
33
components/DesktopNavDisplay.tsx
Normal file
33
components/DesktopNavDisplay.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { NavDisplayProps } from './Navigation';
|
||||||
|
|
||||||
|
export default function DesktopNavDisplay({ navItems }: NavDisplayProps) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="hidden lg:flex lg:flex-shrink-0">
|
||||||
|
<div className="flex flex-col w-64">
|
||||||
|
<div className="flex flex-col h-0 flex-1 bg-gray-800">
|
||||||
|
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
|
||||||
|
<nav className="mt-5 flex-1 px-2 space-y-1">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.label}
|
||||||
|
href={item.href}
|
||||||
|
className={"flex items-center px-2 py-2 font-medium rounded-md " +
|
||||||
|
(pathname === (item.href) ?
|
||||||
|
"text-blue-500 hover:text-blue-600 hover:bg-gray-700" :
|
||||||
|
"text-gray-300 hover:text-white hover:bg-gray-700")}
|
||||||
|
>
|
||||||
|
<item.icon className="mr-4 flex-shrink-0 h-6 w-6" aria-hidden="true" />
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,25 +1,13 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { FormattedNumber } from '@/components/FormattedNumber'
|
|
||||||
import { Logo } from '@/components/Logo'
|
import { Logo } from '@/components/Logo'
|
||||||
import { useCoins } from '@/hooks/useCoins'
|
|
||||||
import { settingsAtom } from '@/lib/atoms'
|
|
||||||
import { useAtom } from 'jotai'
|
|
||||||
import { Coins } from 'lucide-react'
|
|
||||||
import dynamic from 'next/dynamic'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import NotificationBell from './NotificationBell'
|
import HeaderActions from './HeaderActions'
|
||||||
import { Profile } from './Profile'
|
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const TodayEarnedCoins = dynamic(() => import('./TodayEarnedCoins'), { ssr: false })
|
|
||||||
|
|
||||||
export default function Header({ className }: HeaderProps) {
|
export default function Header({ className }: HeaderProps) {
|
||||||
const [settings] = useAtom(settingsAtom)
|
|
||||||
const { balance } = useCoins()
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className={`border-b bg-white dark:bg-gray-800 shadow-sm ${className || ''}`}>
|
<header className={`border-b bg-white dark:bg-gray-800 shadow-sm ${className || ''}`}>
|
||||||
@@ -28,23 +16,7 @@ export default function Header({ className }: HeaderProps) {
|
|||||||
<Link href="/" className="mr-3 sm:mr-4">
|
<Link href="/" className="mr-3 sm:mr-4">
|
||||||
<Logo />
|
<Logo />
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-1 sm:gap-2">
|
<HeaderActions />
|
||||||
<Link href="/coins" className="flex items-center gap-1 sm:gap-2 px-3 py-1.5 bg-white hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-full transition-colors border border-gray-200 dark:border-gray-600">
|
|
||||||
<Coins className="h-5 w-5 text-yellow-500 dark:text-yellow-400" />
|
|
||||||
<div className="flex items-baseline gap-1 sm:gap-2">
|
|
||||||
<FormattedNumber
|
|
||||||
amount={balance}
|
|
||||||
settings={settings}
|
|
||||||
className="text-gray-800 dark:text-gray-100 font-medium text-lg"
|
|
||||||
/>
|
|
||||||
<div className="hidden sm:block">
|
|
||||||
<TodayEarnedCoins />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<NotificationBell />
|
|
||||||
<Profile />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
38
components/HeaderActions.tsx
Normal file
38
components/HeaderActions.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import { settingsAtom } from '@/lib/atoms'
|
||||||
|
import { useCoins } from '@/hooks/useCoins'
|
||||||
|
import { FormattedNumber } from '@/components/FormattedNumber'
|
||||||
|
import { Coins } from 'lucide-react'
|
||||||
|
import NotificationBell from './NotificationBell'
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
import { Profile } from './Profile'
|
||||||
|
|
||||||
|
const TodayEarnedCoins = dynamic(() => import('./TodayEarnedCoins'), { ssr: false })
|
||||||
|
|
||||||
|
export default function HeaderActions() {
|
||||||
|
const [settings] = useAtom(settingsAtom)
|
||||||
|
const { balance } = useCoins()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1 sm:gap-2">
|
||||||
|
<Link href="/coins" className="flex items-center gap-1 sm:gap-2 px-3 py-1.5 bg-white hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-full transition-colors border border-gray-200 dark:border-gray-600">
|
||||||
|
<Coins className="h-5 w-5 text-yellow-500 dark:text-yellow-400" />
|
||||||
|
<div className="flex items-baseline gap-1 sm:gap-2">
|
||||||
|
<FormattedNumber
|
||||||
|
amount={balance}
|
||||||
|
settings={settings}
|
||||||
|
className="text-gray-800 dark:text-gray-100 font-medium text-lg"
|
||||||
|
/>
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<TodayEarnedCoins />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<NotificationBell />
|
||||||
|
<Profile />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
<div className="flex flex-col h-screen bg-gray-100 dark:bg-gray-900 overflow-hidden">
|
<div className="flex flex-col h-screen bg-gray-100 dark:bg-gray-900 overflow-hidden">
|
||||||
<Header className="sticky top-0 z-50" />
|
<Header className="sticky top-0 z-50" />
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
<Navigation viewPort='main' />
|
<Navigation position='main' />
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
<main className="flex-1 overflow-x-hidden overflow-y-auto bg-gray-100 dark:bg-gray-900 relative">
|
<main className="flex-1 overflow-x-hidden overflow-y-auto bg-gray-100 dark:bg-gray-900 relative">
|
||||||
{/* responsive container (optimized for mobile) */}
|
{/* responsive container (optimized for mobile) */}
|
||||||
@@ -17,7 +17,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
</ClientWrapper>
|
</ClientWrapper>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<Navigation viewPort='mobile' />
|
<Navigation position='mobile' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
61
components/LoadingSpinner.tsx
Normal file
61
components/LoadingSpinner.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Coins } from 'lucide-react';
|
||||||
|
import { Logo } from '@/components/Logo';
|
||||||
|
|
||||||
|
const subtexts = [
|
||||||
|
"Unearthing your treasures",
|
||||||
|
"Polishing your gems",
|
||||||
|
"Mining for good habits",
|
||||||
|
"Stumbling upon brilliance",
|
||||||
|
"Discovering your potential",
|
||||||
|
"Crafting your success story",
|
||||||
|
"Forging new paths",
|
||||||
|
"Summoning success",
|
||||||
|
"Brewing brilliance",
|
||||||
|
"Charging up your awesome",
|
||||||
|
"Assembling achievements",
|
||||||
|
"Leveling up your day",
|
||||||
|
"Questing for quality",
|
||||||
|
"Unlocking awesomeness",
|
||||||
|
"Plotting your progress",
|
||||||
|
];
|
||||||
|
|
||||||
|
const LoadingSpinner: React.FC = () => {
|
||||||
|
const [currentSubtext, setCurrentSubtext] = useState<string>('Loading your data');
|
||||||
|
const [animatedDots, setAnimatedDots] = useState<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const randomIndex = Math.floor(Math.random() * subtexts.length);
|
||||||
|
setCurrentSubtext(subtexts[randomIndex]);
|
||||||
|
|
||||||
|
const dotAnimationInterval = setInterval(() => {
|
||||||
|
setAnimatedDots(prevDots => {
|
||||||
|
if (prevDots.length >= 3) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return prevDots + '.';
|
||||||
|
});
|
||||||
|
}, 200); // Adjust timing as needed
|
||||||
|
|
||||||
|
return () => clearInterval(dotAnimationInterval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-screen">
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<Coins className="h-12 w-12 animate-bounce text-yellow-500" />
|
||||||
|
<Logo />
|
||||||
|
{currentSubtext && (
|
||||||
|
<p className="text-lg text-gray-600 dark:text-gray-400">
|
||||||
|
{currentSubtext}{animatedDots}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoadingSpinner;
|
||||||
33
components/MobileNavDisplay.tsx
Normal file
33
components/MobileNavDisplay.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
import { NavDisplayProps, NavItemType } from './Navigation';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { useHelpers } from '@/lib/client-helpers';
|
||||||
|
|
||||||
|
export default function MobileNavDisplay({ navItems }: NavDisplayProps) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const { isIOS } = useHelpers()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={isIOS ? "pb-20" : "pb-16"} />
|
||||||
|
<nav className={`lg:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 shadow-lg ${isIOS ? "pb-4" : ""}`}>
|
||||||
|
<div className="grid grid-cols-6 w-full">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.label}
|
||||||
|
href={item.href}
|
||||||
|
className={"flex flex-col items-center py-2 hover:text-blue-600 dark:hover:text-blue-300 " +
|
||||||
|
(pathname === (item.href) ?
|
||||||
|
"text-blue-500 dark:text-blue-500" :
|
||||||
|
"text-gray-300 dark:text-gray-300")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<item.icon className="h-6 w-6" />
|
||||||
|
<span className="text-xs mt-1">{item.label}</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,33 +1,39 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useHelpers } from '@/lib/client-helpers'
|
|
||||||
import { HabitIcon, TaskIcon } from '@/lib/constants'
|
import { HabitIcon, TaskIcon } from '@/lib/constants'
|
||||||
import { Calendar, Coins, Gift, Home } from 'lucide-react'
|
import { Calendar, Coins, Gift, Home } from 'lucide-react'
|
||||||
import { useTranslations } from 'next-intl'
|
import { useTranslations } from 'next-intl'
|
||||||
import Link from 'next/link'
|
import { ElementType, useEffect, useState } from 'react'
|
||||||
import { usePathname } from 'next/navigation'
|
import DesktopNavDisplay from './DesktopNavDisplay'
|
||||||
import { useEffect, useState } from 'react'
|
import MobileNavDisplay from './MobileNavDisplay'
|
||||||
|
|
||||||
type ViewPort = 'main' | 'mobile'
|
type ViewPort = 'main' | 'mobile'
|
||||||
|
|
||||||
interface NavigationProps {
|
export interface NavItemType {
|
||||||
viewPort: ViewPort
|
icon: ElementType;
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Navigation({ viewPort }: NavigationProps) {
|
interface NavigationProps {
|
||||||
const t = useTranslations('Navigation')
|
position: ViewPort
|
||||||
const [showAbout, setShowAbout] = useState(false)
|
}
|
||||||
const [isMobileView, setIsMobileView] = useState(false)
|
|
||||||
const { isIOS } = useHelpers()
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const navItems = () => [
|
export interface NavDisplayProps {
|
||||||
{ icon: Home, label: t('dashboard'), href: '/', position: 'main' },
|
navItems: NavItemType[];
|
||||||
{ icon: HabitIcon, label: t('habits'), href: '/habits', position: 'main' },
|
}
|
||||||
{ icon: TaskIcon, label: t('tasks'), href: '/tasks', position: 'main' },
|
|
||||||
{ icon: Calendar, label: t('calendar'), href: '/calendar', position: 'main' },
|
export default function Navigation({ position: viewPort }: NavigationProps) {
|
||||||
{ icon: Gift, label: t('wishlist'), href: '/wishlist', position: 'main' },
|
const t = useTranslations('Navigation')
|
||||||
{ icon: Coins, label: t('coins'), href: '/coins', position: 'main' },
|
const [isMobileView, setIsMobileView] = useState(false)
|
||||||
|
|
||||||
|
const currentNavItems: NavItemType[] = [
|
||||||
|
{ icon: Home, label: t('dashboard'), href: '/' },
|
||||||
|
{ icon: HabitIcon, label: t('habits'), href: '/habits' },
|
||||||
|
{ icon: TaskIcon, label: t('tasks'), href: '/tasks' },
|
||||||
|
{ icon: Calendar, label: t('calendar'), href: '/calendar' },
|
||||||
|
{ icon: Gift, label: t('wishlist'), href: '/wishlist' },
|
||||||
|
{ icon: Coins, label: t('coins'), href: '/coins' },
|
||||||
]
|
]
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -46,56 +52,12 @@ export default function Navigation({ viewPort }: NavigationProps) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (viewPort === 'mobile' && isMobileView) {
|
if (viewPort === 'mobile' && isMobileView) {
|
||||||
return (
|
return <MobileNavDisplay navItems={currentNavItems} />
|
||||||
<>
|
|
||||||
<div className={isIOS ? "pb-20" : "pb-16"} /> {/* Add padding at the bottom to prevent content from being hidden */}
|
|
||||||
<nav className={`lg:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 shadow-lg ${isIOS ? "pb-4" : ""}`}>
|
|
||||||
<div className="grid grid-cols-6 w-full">
|
|
||||||
{...navItems().map((item) => (
|
|
||||||
<Link
|
|
||||||
key={item.label}
|
|
||||||
href={item.href}
|
|
||||||
className={"flex flex-col items-center py-2 hover:text-blue-600 dark:hover:text-blue-300 " +
|
|
||||||
(pathname === (item.href) ?
|
|
||||||
"text-blue-500 dark:text-blue-500" :
|
|
||||||
"text-gray-300 dark:text-gray-300")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<item.icon className="h-6 w-6" />
|
|
||||||
<span className="text-xs mt-1">{item.label}</span>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewPort === 'main' && !isMobileView) {
|
if (viewPort === 'main' && !isMobileView) {
|
||||||
return (
|
return <DesktopNavDisplay navItems={currentNavItems} />
|
||||||
<div className="hidden lg:flex lg:flex-shrink-0">
|
|
||||||
<div className="flex flex-col w-64">
|
|
||||||
<div className="flex flex-col h-0 flex-1 bg-gray-800">
|
|
||||||
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
|
|
||||||
<nav className="mt-5 flex-1 px-2 space-y-1">
|
|
||||||
{navItems().filter(item => item.position === 'main').map((item) => (
|
|
||||||
<Link
|
|
||||||
key={item.label}
|
|
||||||
href={item.href}
|
|
||||||
className={"flex items-center px-2 py-2 font-medium rounded-md " +
|
|
||||||
(pathname === (item.href) ?
|
|
||||||
"text-blue-500 hover:text-blue-600 hover:bg-gray-700" :
|
|
||||||
"text-gray-300 hover:text-white hover:bg-gray-700")}
|
|
||||||
>
|
|
||||||
<item.icon className="mr-4 flex-shrink-0 h-6 w-6" aria-hidden="true" />
|
|
||||||
{item.label}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null // Explicitly return null if no view matches
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "habittrove",
|
"name": "habittrove",
|
||||||
"version": "0.2.17",
|
"version": "0.2.18",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
|
|||||||
Reference in New Issue
Block a user