Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
81 changes: 53 additions & 28 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,64 @@ import LandingPage from './pages/LandingPage'
import ProfilePage from './pages/ProfilePage'
import { AppLayout } from './components/AppLayout'
import JobsPage from './pages/JobPage'
import SavedPage from './pages/SavedPage'
import { ToastProvider, BookmarksProvider, ErrorBoundary } from './components/ui'

function App() {
function AppRoutes() {
return (
<Router>
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/profile" element={
<ProtectedRoute>
<AppLayout>
<ProfilePage />
</AppLayout>
</ProtectedRoute>
<Routes>
<Route path="/" element={
<AppLayout>
<LandingPage />
</AppLayout>
} />
<Route path="/jobs" element={
<Route path="/profile" element={
<ProtectedRoute>
<AppLayout>
<ProfilePage />
</AppLayout>
</ProtectedRoute>
} />
<Route path="/jobs" element={
<ProtectedRoute>
<AppLayout>
<JobsPage onNavigateToJob={(jobId) => console.log("Navigate to job:", jobId)} />
</AppLayout>
</ProtectedRoute>
} />
<Route path="/saved" element={
<ProtectedRoute>
<AppLayout>
<SavedPage onNavigateToJob={(jobId) => console.log("Navigate to job:", jobId)} />
</AppLayout>
</ProtectedRoute>
} />
<Route path="/sign-in" element={<SignInPage />} />
<Route path="/sign-up" element={<SignUpPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<AppLayout>
<JobsPage onNavigateToJob={(jobId) => console.log("Navigate to job:", jobId)} />
</AppLayout>
<DashboardPage />
</ProtectedRoute>
} />
<Route path="/sign-in" element={<SignInPage />} />
<Route path="/sign-up" element={<SignUpPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Router>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
)
}

function App() {
return (
<ErrorBoundary>
<BookmarksProvider>
<ToastProvider>
<Router>
<AppRoutes />
</Router>
</ToastProvider>
</BookmarksProvider>
</ErrorBoundary>
)
}

Expand Down
35 changes: 35 additions & 0 deletions frontend/src/assets/logo1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
172 changes: 120 additions & 52 deletions frontend/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { useUser, useClerk } from "@clerk/react";
import { useState, useRef, useEffect } from "react";
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import { Button } from "./ui/Button";
import LogoSvg from "../assets/logo1.svg";

export function Header() {
const { user } = useUser();
const { user, isSignedIn } = useUser();
const { signOut } = useClerk();
const navigate = useNavigate();

const [open, setOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

const handleLogout = async () => {
await signOut();
navigate("/");
};

// Close dropdown on outside click
Expand All @@ -25,64 +29,128 @@ export function Header() {
}, []);

return (
<header className="w-full border-b border-gray-800 bg-[#0b0f19]/80 backdrop-blur-md">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<header className="sticky top-0 z-50 w-full border-b border-border-normal bg-bg-secondary/95 backdrop-blur-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-3 sm:py-4 flex items-center justify-between">
{/* LEFT: Logo + Branding */}
<Link
to="/"
className="flex items-center gap-2 transition-opacity hover:opacity-80 active:scale-95 flex-shrink-0"
>
<img
src={LogoSvg}
alt="CVPilot Logo"
className="h-8 sm:h-10 w-auto"
/>
<span className="hidden sm:block text-base sm:text-lg font-bold text-text-primary font-display">CVpilot</span>
</Link>

{/* LEFT: Branding */}
{/* CENTER: Navigation (only for logged-in) */}
{isSignedIn && (
<nav className="hidden md:flex items-center gap-8 flex-1 justify-center">
<Link
to="/jobs"
className="text-sm font-medium text-text-secondary hover:text-text-primary transition-colors"
>
Find Jobs
</Link>
<Link
to="/profile"
className="text-sm font-medium text-text-secondary hover:text-text-primary transition-colors"
>
Profile
</Link>
</nav>
)}

<span className="text-sm tracking-[0.35em] text-gray-400 font-medium">
CVPILOT — AI JOB INTELLIGENCE
</span>


{/* RIGHT: Avatar + Dropdown */}
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setOpen(!open)}
className="flex items-center gap-2 focus:outline-none"
>
<img
src={user?.imageUrl}
alt="avatar"
className="w-9 h-9 rounded-full border border-gray-700"
/>
</button>

{/* Dropdown */}
{open && (
<div className="absolute right-0 mt-3 w-44 bg-[#0d111c] border border-gray-800 rounded-xl shadow-xl overflow-hidden">

{/* User Info */}
<div className="px-4 py-3 border-b border-gray-800">
<p className="text-sm text-white font-medium">
{/* RIGHT: Auth Actions or User Menu */}
<div className="flex items-center gap-3 sm:gap-4">
{!isSignedIn ? (
// Anonymous users: Show login/signup buttons
<div className="flex items-center gap-2">
<Button
onClick={() => navigate("/sign-in")}
variant="ghost"
size="md"
>
Sign In
</Button>
<Button
onClick={() => navigate("/sign-up")}
variant="primary"
size="md"
>
Sign Up
</Button>
</div>
) : (
// Logged-in users: Show user menu
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setOpen(!open)}
className="flex items-center gap-2 focus:outline-none transition-opacity hover:opacity-80 p-1.5"
title="User menu"
>
<img
src={user?.imageUrl}
alt="avatar"
className="w-8 h-8 rounded-full border border-border-normal"
/>
<span className="text-sm font-medium text-text-secondary hidden sm:inline truncate max-w-[120px]">
{user?.firstName}
</p>
<p className="text-xs text-gray-500 truncate">
{user?.primaryEmailAddress?.emailAddress}
</p>
</div>
</span>
</button>

{/* Actions */}
<div className="flex flex-col">
<Link
to="/dashboard"
className="px-4 py-2 text-sm text-gray-300 hover:bg-gray-800 transition"
onClick={() => setOpen(false)}
>
Dashboard
</Link>
{/* User Dropdown Menu */}
{open && (
<div className="absolute right-0 mt-2 w-48 bg-bg-surface border border-border-normal rounded-lg shadow-lg overflow-hidden animate-fade-in">
{/* User Info */}
<div className="px-4 py-3 border-b border-border-light">
<p className="text-sm font-semibold text-text-primary">
{user?.firstName} {user?.lastName}
</p>
<p className="text-xs text-text-tertiary truncate mt-1">
{user?.primaryEmailAddress?.emailAddress}
</p>
</div>

<button
onClick={handleLogout}
className="text-left px-4 py-2 text-sm text-red-400 hover:bg-gray-800 transition"
>
Logout
</button>
</div>
{/* Menu Items */}
<nav className="flex flex-col">
<Link
to="/jobs"
className="px-4 py-2 text-sm text-text-secondary hover:text-text-primary hover:bg-bg-hover transition-colors md:hidden"
onClick={() => setOpen(false)}
>
Find Jobs
</Link>
<Link
to="/profile"
className="px-4 py-2 text-sm text-text-secondary hover:text-text-primary hover:bg-bg-hover transition-colors md:hidden"
onClick={() => setOpen(false)}
>
Profile
</Link>
<Link
to="/saved"
className="px-4 py-2 text-sm text-text-secondary hover:text-text-primary hover:bg-bg-hover transition-colors flex items-center justify-between"
onClick={() => setOpen(false)}
>
<span>Saved Jobs</span>
</Link>
<button
onClick={() => {
handleLogout();
setOpen(false);
}}
className="text-left px-4 py-2 text-sm text-status-danger hover:bg-status-danger/10 transition-colors"
>
Sign Out
</button>
</nav>
</div>
)}
</div>
)}
</div>

</div>
</header>
);
Expand Down
Loading
Loading