Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
59c29bf
refactor: simplify Layout component by removing unused theme toggle a…
JOELNATHAN544 Jul 21, 2025
0d5e984
feat: add ProfileDropdown component for user account management with …
JOELNATHAN544 Jul 21, 2025
4285e0a
feat: implement Sidebar component for navigation with user-specific l…
JOELNATHAN544 Jul 21, 2025
e3fbee3
feat: enhance Layout component with search bar and notifications bell…
JOELNATHAN544 Jul 21, 2025
ddb2497
refactor: adjust Layout component structure by removing max-width con…
JOELNATHAN544 Jul 21, 2025
70ca3d6
style: update button styles in Sidebar component for improved accessi…
JOELNATHAN544 Jul 21, 2025
0f6e9da
style: refine button hover styles in Sidebar component for enhanced v…
JOELNATHAN544 Jul 21, 2025
cc85e9c
feat: add Header component with search bar, notifications bell, and p…
JOELNATHAN544 Jul 21, 2025
4293943
refactor: restructure Layout component to include Header and simplify…
JOELNATHAN544 Jul 21, 2025
e5106c7
refactor: update Sidebar component to include collapsible functionali…
JOELNATHAN544 Jul 21, 2025
1939c91
refactor: enhance Sidebar component layout and improve collapse butto…
JOELNATHAN544 Jul 21, 2025
e7ac687
refactor: simplify return statement in Layout component for improved …
JOELNATHAN544 Jul 21, 2025
0f953c0
refactor: update Sidebar component to improve layout and reposition c…
JOELNATHAN544 Jul 21, 2025
68c8bef
feat: implement enhanced search functionality in Header component wit…
JOELNATHAN544 Jul 21, 2025
c9bc41a
refactor: remove search bar from DashboardPage component to streamlin…
JOELNATHAN544 Jul 21, 2025
4ae6abd
refactor: integrate search context into Header component and adjust l…
JOELNATHAN544 Jul 21, 2025
ce8d1a7
refactor: utilize search context in Header component for improved sta…
JOELNATHAN544 Jul 21, 2025
95066aa
refactor: wrap Layout component in SearchProvider to enable search co…
JOELNATHAN544 Jul 21, 2025
6b30069
feat: add SearchContext and SearchProvider for managing search state …
JOELNATHAN544 Jul 21, 2025
91405b1
refactor: remove local search state from DashboardPage and utilize Se…
JOELNATHAN544 Jul 21, 2025
dc89762
feat: add Dashboard button to Header component and enhance search bar…
JOELNATHAN544 Jul 21, 2025
4f64ebb
refactor: adjust indentation in Layout component for improved code re…
JOELNATHAN544 Jul 21, 2025
6063d83
Merge pull request #90 from Vitalisn4/dev
Nkwenti-Severian-Ndongtsop Jul 22, 2025
ffc0fdb
Merge pull request #93 from Vitalisn4/dev
Nkwenti-Severian-Ndongtsop Jul 22, 2025
bc68fed
feat: add settings submenu to ProfileDropdown component for enhanced …
JOELNATHAN544 Jul 24, 2025
9e0d427
feat: add logout button to ProfileDropdown component for improved use…
JOELNATHAN544 Jul 24, 2025
ca6efc4
refactor: remove notification button from Header component to streaml…
JOELNATHAN544 Jul 24, 2025
b87c47c
refactor: update Sidebar component width for improved layout responsi…
JOELNATHAN544 Jul 24, 2025
bf772f6
feat: add notification button to Header component for enhanced user e…
JOELNATHAN544 Jul 24, 2025
c95989b
Merge branch 'Vitalisn4:master' into view-change
JOELNATHAN544 Jul 24, 2025
2f55c5a
refactor: remove unused search functionality from DashboardPage for c…
JOELNATHAN544 Jul 24, 2025
4e3dd5b
refactor: simplify SearchProvider by utilizing external SearchContext…
JOELNATHAN544 Jul 24, 2025
b2cd36e
feat: add SearchContext utility for managing search query state in th…
JOELNATHAN544 Jul 24, 2025
b04934f
fix: update import path for useSearch context to ensure correct funct…
JOELNATHAN544 Jul 24, 2025
d6158f9
refactor: remove unused useSearch hook to streamline search context i…
JOELNATHAN544 Jul 24, 2025
26300db
feat: implement useSearch hook for accessing SearchContext in components
JOELNATHAN544 Jul 24, 2025
a9e219b
fix: update import path for useSearch context to reflect new director…
JOELNATHAN544 Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions my-link-uploader/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useRef, useState } from 'react';
import { Bell, Search, LayoutDashboard } from 'lucide-react';
import ProfileDropdown from './ProfileDropdown';
import { useTheme } from '../hooks/useTheme';
import { useSearch } from '../contexts/useSearch';
import { useNavigate } from 'react-router-dom';

export default function Header() {
const { query, setQuery } = useSearch();
const [isSearchFocused, setIsSearchFocused] = useState(false);
const searchInputRef = useRef<HTMLInputElement>(null);
const { isDark } = useTheme();
const navigate = useNavigate();

return (
<header className="sticky top-0 z-40 backdrop-blur-md border-b bg-white/80 dark:bg-gray-800/80 border-gray-200 dark:border-gray-700">
<div className="w-full px-4 sm:px-6 py-3 flex items-center justify-between">
{/* Centered Wide Search Bar with Dashboard Button */}
<div className="flex-1 flex justify-center">
<div className="flex items-center w-full max-w-3xl">
{/* Dashboard Button */}
<button
className="mr-3 p-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-purple-100 dark:hover:bg-purple-900/40 transition-colors border border-gray-200 dark:border-gray-700"
onClick={() => navigate('/dashboard')}
aria-label="Go to Dashboard"
title="Go to Dashboard"
type="button"
>
<LayoutDashboard size={22} className="text-purple-500" />
</button>
{/* Search Bar */}
<div className="relative flex-1">
<input
ref={searchInputRef}
type="text"
value={query}
onChange={e => setQuery(e.target.value)}
onFocus={() => setIsSearchFocused(true)}
onBlur={() => setIsSearchFocused(false)}
placeholder="Search links by title or description..."
className={`w-full px-6 py-4 rounded-2xl transition-all duration-300
${isDark
? 'bg-gray-800/50 text-white placeholder-gray-500 focus:bg-gray-800'
: 'bg-gray-100/50 text-gray-900 placeholder-gray-400 focus:bg-white'
}
${isSearchFocused
? isDark
? 'shadow-lg shadow-purple-500/10'
: 'shadow-lg shadow-purple-500/5'
: ''
}
`}
/>
<Search
className={`absolute right-6 top-1/2 transform -translate-y-1/2 ${
isDark ? 'text-gray-500' : 'text-gray-400'
}`}
size={20}
/>
</div>
</div>
</div>
{/* Right: Notifications, Profile */}
<div className="flex items-center space-x-4 ml-4">
<button className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
<Bell size={22} className="text-gray-500 dark:text-gray-300" />
</button>
<ProfileDropdown />
</div>
</div>
</header>
);
}
264 changes: 28 additions & 236 deletions my-link-uploader/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,169 +1,27 @@
"use client"

import type { ReactNode } from "react"
import { Link, useLocation } from "react-router-dom"
import { useTheme } from "../hooks/useTheme"
import { motion } from "framer-motion"
import {
Sun,
Moon,
LinkIcon,
Upload,
LayoutDashboard,
LogOut,
LogIn,
UserPlus,
User
} from "lucide-react"
import { useAuth } from "../hooks/useAuth"
import Sidebar from "./Sidebar"
import Header from "./Header" // We will create this new component
import { useLocation } from "react-router-dom"
import { SearchProvider } from "../contexts/search";

interface LayoutProps {
children: ReactNode
}

export default function Layout({ children }: LayoutProps) {
const { isDark, toggleTheme } = useTheme()
const location = useLocation()
const { user, logout } = useAuth()

const isActive = (path: string) => location.pathname === path

const handleLogout = async () => {
try {
await logout();
} catch (error) {
console.error('Logout failed:', error);
}
};
const { user } = useAuth()

if (!user) {
// Render a simple layout for logged-out users
return (
<div className={`min-h-screen flex flex-col ${isDark ? "bg-gray-900 text-white" : "bg-white text-gray-900"}`}>
<header className={`sticky top-0 z-50 backdrop-blur-md border-b ${
isDark
? "bg-gray-800/80 border-gray-700"
: "bg-white/80 border-gray-200"
}`}>
<div className="max-w-7xl mx-auto w-full px-4 sm:px-6 py-3 sm:py-4 flex items-center justify-between">
<Link to={user ? "/dashboard" : "/"} className="flex items-center space-x-2 sm:space-x-3 group">
<div className="w-10 h-10 sm:w-14 sm:h-14 bg-gradient-to-br from-purple-600 to-pink-500 rounded-xl flex items-center justify-center shadow-lg shadow-purple-500/20 group-hover:shadow-purple-500/40 transition-all duration-300 group-hover:scale-105">
<LinkIcon size={20} className="text-white sm:hidden" />
<LinkIcon size={28} className="text-white hidden sm:block" />
</div>
<div>
<h1 className="text-xl sm:text-3xl font-bold tracking-tight bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent">
LinkSphere
</h1>
<p className={`text-sm sm:text-base ${isDark ? "text-gray-400" : "text-gray-600"} hidden sm:block`}>
Link Management System
</p>
</div>
</Link>

<nav className="hidden md:flex items-center space-x-4">
{user ? (
<>
<NavLink to="/dashboard" current={isActive("/dashboard")} isDark={isDark}>
<div className="flex items-center">
<LayoutDashboard size={20} />
<span className="ml-2">Dashboard</span>
</div>
</NavLink>
<NavLink to="/dashboard/upload" current={isActive("/dashboard/upload")} isDark={isDark}>
<div className="flex items-center">
<Upload size={20} />
<span className="ml-2">Upload</span>
</div>
</NavLink>
<NavLink to="/dashboard/my-account" current={isActive("/dashboard/my-account")} isDark={isDark}>
<div className="flex items-center">
<User size={20} />
<span className="ml-2">My Account</span>
</div>
</NavLink>
</>
) : (
<NavLink to="/" current={isActive("/")} isDark={isDark}>
Home
</NavLink>
)}
</nav>

<div className="flex items-center space-x-2 sm:space-x-4">
<button
onClick={toggleTheme}
className={`p-2 sm:p-3 rounded-lg transition-colors ${
isDark
? "hover:bg-gray-700"
: "hover:bg-gray-100"
}`}
aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
>
{isDark ? (
<Sun size={20} className="text-yellow-300 sm:hidden" />
) : (
<Moon size={20} className="text-gray-600 sm:hidden" />
)}
{isDark ? (
<Sun size={24} className="text-yellow-300 hidden sm:block" />
) : (
<Moon size={24} className="text-gray-600 hidden sm:block" />
)}
</button>

{user ? (
<div className="hidden md:flex items-center space-x-4">
<span className={`text-base ${isDark ? "text-gray-300" : "text-gray-700"}`}>
Welcome, {user.username}
</span>
<button
onClick={handleLogout}
className={`flex items-center space-x-2 px-4 py-2 rounded-lg transition-all duration-300 text-base ${
isDark
? "bg-red-500/10 text-red-400 hover:bg-red-500/20"
: "bg-red-100 text-red-600 hover:bg-red-200"
}`}
>
<LogOut size={20} />
<span>Logout</span>
</button>
</div>
) : (
<div className="hidden md:flex items-center space-x-3">
{location.pathname !== "/register" && (
<Link
to="/register"
className={`flex items-center px-4 py-2 rounded-lg transition-all duration-300 text-base ${
isDark
? "bg-purple-500/10 text-purple-400 hover:bg-purple-500/20"
: "bg-purple-100 text-purple-600 hover:bg-purple-200"
}`}
>
<UserPlus size={20} />
<span className="ml-2">Sign Up</span>
</Link>
)}
{location.pathname !== "/login" && (
<Link
to="/login"
className={`flex items-center px-4 py-2 rounded-lg transition-all duration-300 text-base ${
isDark
? "bg-purple-500/10 text-purple-400 hover:bg-purple-500/20"
: "bg-purple-100 text-purple-600 hover:bg-purple-200"
}`}
>
<LogIn size={20} />
<span className="ml-2">Login</span>
</Link>
)}
</div>
)}
</div>
</div>
</header>

<main className="flex-1 w-full max-w-7xl mx-auto px-4 sm:px-6 py-4 sm:py-8 mb-16 md:mb-0">
<main className="flex-1 w-full max-w-7xl mx-auto px-4 sm:px-6 py-8">
<motion.div
key={location.pathname}
key="logged-out"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
Expand All @@ -172,94 +30,28 @@ export default function Layout({ children }: LayoutProps) {
{children}
</motion.div>
</main>
)
}

<footer className={`w-full px-4 py-4 sm:py-6 mt-auto border-t text-center text-xs sm:text-sm ${
isDark
? "border-gray-800 text-gray-400"
: "border-gray-200 text-gray-600"
}`}>
<p>© {new Date().getFullYear()} LinkSphere. All rights reserved.</p>
</footer>

{/* Mobile Navigation */}
<div className={`md:hidden fixed bottom-0 left-0 right-0 backdrop-blur-md border-t z-50 ${
isDark
? "bg-gray-800/90 border-gray-700"
: "bg-white/90 border-gray-200"
}`}>
<div className="flex justify-around items-center py-2 px-4">
{user ? (
<>
<MobileNavLink to="/dashboard" icon={<LayoutDashboard size={20} />} label="Dashboard" isDark={isDark} />
<MobileNavLink to="/dashboard/upload" icon={<Upload size={20} />} label="Upload" isDark={isDark} />
<MobileNavLink to="/dashboard/my-account" icon={<User size={20} />} label="Account" isDark={isDark} />
<button
onClick={handleLogout}
className={`flex flex-col items-center p-2 rounded-lg transition-colors ${
isDark
? "text-red-400 hover:bg-red-500/10"
: "text-red-500 hover:bg-red-50"
}`}
>
<LogOut size={20} />
<span className="text-xs mt-1">Logout</span>
</button>
</>
) : (
<>
<MobileNavLink to="/" icon={<LinkIcon size={20} />} label="Home" isDark={isDark} />
{location.pathname !== "/register" && (
<MobileNavLink to="/register" icon={<UserPlus size={20} />} label="Sign Up" isDark={isDark} />
)}
{location.pathname !== "/login" && (
<MobileNavLink to="/login" icon={<LogIn size={20} />} label="Login" isDark={isDark} />
)}
</>
)}
</div>
</div>
</div>
)
}

function NavLink({ to, current, children, isDark }: { to: string; current: boolean; children: ReactNode; isDark: boolean }) {
return (
<Link
to={to}
className={`px-4 py-2 rounded-lg transition-all duration-300 ${
current
? isDark
? "bg-purple-500/20 text-purple-400"
: "bg-purple-100 text-purple-600"
: isDark
? "text-gray-400 hover:text-gray-200 hover:bg-gray-800"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
<SearchProvider>
<div className="min-h-screen flex bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-white">
<Sidebar />
<div className="flex-1 flex flex-col">
<Header />
<main className="flex-1 w-full max-w-7xl mx-auto px-4 sm:px-6 py-8">
<motion.div
key={location.pathname}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
{children}
</Link>
)
}

function MobileNavLink({ to, icon, label, isDark }: { to: string; icon: ReactNode; label: string; isDark: boolean }) {
const location = useLocation()
const isActive = location.pathname === to

return (
<Link
to={to}
className={`flex flex-col items-center space-y-1 px-4 py-2 rounded-lg transition-all duration-300 ${
isActive
? isDark
? "text-purple-400"
: "text-purple-600"
: isDark
? "text-gray-400 hover:text-gray-200"
: "text-gray-600 hover:text-gray-900"
}`}
>
{icon}
<span className="text-xs">{label}</span>
</Link>
</motion.div>
</main>
</div>
</div>
</SearchProvider>
)
}
Loading