Skip to content
Merged
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
8 changes: 4 additions & 4 deletions apps/registry/app/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React from 'react';

const Header = ({ left = null, right = null }) => {
return (
<nav className="bg-yellow-200 p-2">
<div className="flex flex-row justify-between">
<div>{left}</div>
<div>{right}</div>
<nav className="bg-yellow-200">
<div className="w-full lg:flex lg:justify-between lg:items-center">
{left}
{right}
</div>
</nav>
);
Expand Down
180 changes: 94 additions & 86 deletions apps/registry/app/components/Menu.js
Original file line number Diff line number Diff line change
@@ -1,117 +1,125 @@
'use client';

import React from 'react';
import React, { useState } from 'react';
import { usePathname } from 'next/navigation';
import { signOut } from 'next-auth/react';
import Link from 'next/link';
import { Menu as MenuIcon, X } from 'lucide-react';
import Header from './Header';

export default function Menu({ session }) {
const pathname = usePathname();
const username = session?.username;
const [isOpen, setIsOpen] = useState(false);

const isActive = (path) => pathname === path;
const isProfileActive = pathname.startsWith(`/${username}`);

const toggleMenu = () => {
setIsOpen(!isOpen);
};

const menuItems = [
{ href: '/explore', label: 'Explore' },
{ href: '/jobs', label: 'Jobs' },
{ href: '/job-similarity', label: 'Similarity' },
{
href: 'https://github.com/jsonresume/jsonresume.org',
label: 'Github',
external: true,
},
{ href: 'https://discord.gg/GTZtn8pTXC', label: 'Discord', external: true },
];

const authItems = [
...(username
? [
{ href: `/${username}/dashboard`, label: 'Profile' },
{ href: '/editor', label: 'Editor' },
]
: []),
session
? { onClick: signOut, label: 'Logout' }
: { href: '/', label: 'Sign in' },
];

return (
<div className="bg-accent-200 shadow-md">
<Header
left={
<div className="flex gap-8 items-center h-full p-5">
<div className="flex items-center justify-between w-full lg:w-auto p-4 lg:p-5">
<Link
href="/"
className="text-2xl font-bold text-black hover:text-secondary-900 transition-colors duration-200"
>
JSON Resume Registry
</Link>
<Link
href="/explore"
className={`text-xl font-bold ${
isActive('/explore')
? 'text-secondary-900 underline'
: 'text-black'
} hover:text-secondary-900 transition-colors duration-200`}
>
Explore
</Link>
<Link
href="/jobs"
className={`text-xl font-bold ${
isActive('/jobs') || pathname.startsWith('/jobs/')
? 'text-secondary-900 underline'
: 'text-black'
} hover:text-secondary-900 transition-colors duration-200`}
>
Jobs
</Link>
<Link
href="/job-similarity"
className={`text-xl font-bold ${
isActive('/job-similarity')
? 'text-secondary-900 underline'
: 'text-black'
} hover:text-secondary-900 transition-colors duration-200`}
>
Similarity
</Link>
<a
href="https://github.com/jsonresume/jsonresume.org"
className="text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200"
>
Github
</a>
<a
href="https://discord.gg/GTZtn8pTXC"
className="text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200"
<button
onClick={toggleMenu}
className="lg:hidden p-2 text-black hover:text-secondary-900"
aria-label="Toggle menu"
>
Discord
</a>
{isOpen ? <X size={24} /> : <MenuIcon size={24} />}
</button>
</div>
}
right={
<div className="flex gap-6 items-center h-full p-5">
{username && (
<Link
href={`/${username}/dashboard`}
className={`text-xl font-bold hover:text-secondary-900 transition-colors duration-200 ${
isProfileActive
? 'text-secondary-900 underline'
: 'text-black'
}`}
>
Profile
</Link>
)}
{session && username && (
<Link
href="/editor"
className={`text-xl font-bold hover:text-secondary-900 transition-colors duration-200 ${
isActive('/editor')
? 'text-secondary-900 underline'
: 'text-black'
}`}
>
Editor
</Link>
)}
{session && (
<button
onClick={signOut}
className="text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200"
>
Logout
</button>
)}
{!session && (
<Link
href="/"
className={`text-xl font-bold hover:text-secondary-900 transition-colors duration-200 ${
isActive('/') ? 'text-secondary-900 underline' : 'text-black'
}`}
>
Sign in
</Link>
)}
<div
className={`${
isOpen ? 'block' : 'hidden'
} lg:flex lg:items-center lg:flex-1`}
>
<nav className="flex flex-col lg:flex-row lg:items-center lg:justify-end gap-4 p-4 lg:p-5">
{menuItems.map((item) =>
item.external ? (
<a
key={item.href}
href={item.href}
className="text-lg lg:text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200 py-2 lg:py-0"
>
{item.label}
</a>
) : (
<Link
key={item.href}
href={item.href}
className={`text-lg lg:text-xl font-bold ${
isActive(item.href) ||
(item.href === '/jobs' && pathname.startsWith('/jobs/'))
? 'text-secondary-900 underline'
: 'text-black'
} hover:text-secondary-900 transition-colors duration-200 py-2 lg:py-0`}
>
{item.label}
</Link>
)
)}
{authItems.map((item) =>
item.onClick ? (
<button
key={item.label}
onClick={item.onClick}
className="text-lg lg:text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200 py-2 lg:py-0 text-left"
>
{item.label}
</button>
) : (
<Link
key={item.href}
href={item.href}
className={`text-lg lg:text-xl font-bold hover:text-secondary-900 transition-colors duration-200 py-2 lg:py-0 ${
(item.href === `/${username}/dashboard` &&
isProfileActive) ||
isActive(item.href)
? 'text-secondary-900 underline'
: 'text-black'
}`}
>
{item.label}
</Link>
)
)}
</nav>
</div>
}
/>
Expand Down
66 changes: 54 additions & 12 deletions apps/registry/app/job-similarity/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ const colors = [
];

const Header = memo(() => (
<div className="prose max-w-3xl mx-auto mb-8">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-4">Job Market Simlarity</h1>
<div className="space-y-4 text-gray-700">
<p>
Expand Down Expand Up @@ -542,8 +542,8 @@ Header.displayName = 'Header';

const Controls = memo(
({ dataSource, setDataSource, algorithm, setAlgorithm }) => (
<div className="prose max-w-3xl mx-auto mb-8">
<div className="flex gap-4 items-center">
<div className="mb-8">
<div className="flex flex-col sm:flex-row gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Data Source
Expand Down Expand Up @@ -588,6 +588,17 @@ const GraphContainer = ({ dataSource, algorithm }) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [edges, setEdges] = useState([]);
const [isMobile, setIsMobile] = useState(false);

useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};

checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);

const handleNodeHover = useCallback(
(node) => {
Expand All @@ -609,12 +620,19 @@ const GraphContainer = ({ dataSource, algorithm }) => {
const handleNodeClick = useCallback(
(node) => {
if (!node) return;

if (isMobile) {
// On mobile, just show the tooltip
setHoverNode(node);
return;
}

if (node.uuids && node.uuids.length > 0) {
const baseUrl = dataSource === 'jobs' ? '/jobs/' : '/';
window.open(`${baseUrl}${node.uuids[0]}`, '_blank');
}
},
[dataSource]
[dataSource, isMobile]
);

const processData = useCallback(
Expand Down Expand Up @@ -791,7 +809,7 @@ const GraphContainer = ({ dataSource, algorithm }) => {
);

return (
<div className="w-full h-full relative">
<div className="w-full h-[600px] relative">
{graphData && (
<ForceGraph2D
graphData={graphData}
Expand Down Expand Up @@ -853,7 +871,7 @@ const GraphContainer = ({ dataSource, algorithm }) => {
d3VelocityDecay={0.3}
warmupTicks={100}
width={window.innerWidth}
height={window.innerHeight - 32 * 16}
height={600}
/>
)}
{hoverNode && (
Expand Down Expand Up @@ -893,17 +911,41 @@ const GraphContainer = ({ dataSource, algorithm }) => {
<div
key={i}
className="hover:bg-gray-100 p-1 rounded cursor-pointer"
onClick={() => window.open(`/${username}`, '_blank')}
onClick={() => {
const baseUrl = dataSource === 'jobs' ? '/jobs/' : '/';
window.open(`${baseUrl}${hoverNode.uuids[0]}`, '_blank');
}}
>
{username}
</div>
))}
</div>
</div>
)}
<p className="text-sm text-gray-600 mt-2">
Click to view {dataSource === 'jobs' ? 'job' : 'resume'}
</p>
{dataSource === 'jobs' && (
<div className="mt-4">
<a
href={`/jobs/${hoverNode.uuids[0]}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
>
View job listing →
</a>
</div>
)}
{dataSource !== 'jobs' && (
<div className="mt-4">
<a
href={`/${hoverNode.uuids[0]}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
>
View resume →
</a>
</div>
)}
</div>
)}
</div>
Expand All @@ -916,7 +958,7 @@ export default function Page() {

return (
<div className="min-h-screen bg-accent-100">
<div className="prose max-w-3xl mx-auto pt-8">
<div className="prose max-w-3xl mx-auto pt-8 px-4 sm:px-6 lg:px-8">
<Header />
<Controls
dataSource={dataSource}
Expand All @@ -925,7 +967,7 @@ export default function Page() {
setAlgorithm={setAlgorithm}
/>
</div>
<div className="w-full h-[calc(100vh-32rem)] bg-white">
<div className="w-full h-[600px] bg-white">
<GraphContainer dataSource={dataSource} algorithm={algorithm} />
</div>
</div>
Expand Down
Loading