Skip to content

Commit 4bbc17a

Browse files
authored
Improved responsiveness in registry (#169)
* wip * wip * wip * wip * wip * wip
1 parent 95e5ffa commit 4bbc17a

File tree

3 files changed

+152
-102
lines changed

3 files changed

+152
-102
lines changed

apps/registry/app/components/Header.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import React from 'react';
22

33
const Header = ({ left = null, right = null }) => {
44
return (
5-
<nav className="bg-yellow-200 p-2">
6-
<div className="flex flex-row justify-between">
7-
<div>{left}</div>
8-
<div>{right}</div>
5+
<nav className="bg-yellow-200">
6+
<div className="w-full lg:flex lg:justify-between lg:items-center">
7+
{left}
8+
{right}
99
</div>
1010
</nav>
1111
);

apps/registry/app/components/Menu.js

Lines changed: 94 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,125 @@
11
'use client';
22

3-
import React from 'react';
3+
import React, { useState } from 'react';
44
import { usePathname } from 'next/navigation';
55
import { signOut } from 'next-auth/react';
66
import Link from 'next/link';
7+
import { Menu as MenuIcon, X } from 'lucide-react';
78
import Header from './Header';
89

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

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

18+
const toggleMenu = () => {
19+
setIsOpen(!isOpen);
20+
};
21+
22+
const menuItems = [
23+
{ href: '/explore', label: 'Explore' },
24+
{ href: '/jobs', label: 'Jobs' },
25+
{ href: '/job-similarity', label: 'Similarity' },
26+
{
27+
href: 'https://github.com/jsonresume/jsonresume.org',
28+
label: 'Github',
29+
external: true,
30+
},
31+
{ href: 'https://discord.gg/GTZtn8pTXC', label: 'Discord', external: true },
32+
];
33+
34+
const authItems = [
35+
...(username
36+
? [
37+
{ href: `/${username}/dashboard`, label: 'Profile' },
38+
{ href: '/editor', label: 'Editor' },
39+
]
40+
: []),
41+
session
42+
? { onClick: signOut, label: 'Logout' }
43+
: { href: '/', label: 'Sign in' },
44+
];
45+
1646
return (
1747
<div className="bg-accent-200 shadow-md">
1848
<Header
1949
left={
20-
<div className="flex gap-8 items-center h-full p-5">
50+
<div className="flex items-center justify-between w-full lg:w-auto p-4 lg:p-5">
2151
<Link
2252
href="/"
2353
className="text-2xl font-bold text-black hover:text-secondary-900 transition-colors duration-200"
2454
>
2555
JSON Resume Registry
2656
</Link>
27-
<Link
28-
href="/explore"
29-
className={`text-xl font-bold ${
30-
isActive('/explore')
31-
? 'text-secondary-900 underline'
32-
: 'text-black'
33-
} hover:text-secondary-900 transition-colors duration-200`}
34-
>
35-
Explore
36-
</Link>
37-
<Link
38-
href="/jobs"
39-
className={`text-xl font-bold ${
40-
isActive('/jobs') || pathname.startsWith('/jobs/')
41-
? 'text-secondary-900 underline'
42-
: 'text-black'
43-
} hover:text-secondary-900 transition-colors duration-200`}
44-
>
45-
Jobs
46-
</Link>
47-
<Link
48-
href="/job-similarity"
49-
className={`text-xl font-bold ${
50-
isActive('/job-similarity')
51-
? 'text-secondary-900 underline'
52-
: 'text-black'
53-
} hover:text-secondary-900 transition-colors duration-200`}
54-
>
55-
Similarity
56-
</Link>
57-
<a
58-
href="https://github.com/jsonresume/jsonresume.org"
59-
className="text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200"
60-
>
61-
Github
62-
</a>
63-
<a
64-
href="https://discord.gg/GTZtn8pTXC"
65-
className="text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200"
57+
<button
58+
onClick={toggleMenu}
59+
className="lg:hidden p-2 text-black hover:text-secondary-900"
60+
aria-label="Toggle menu"
6661
>
67-
Discord
68-
</a>
62+
{isOpen ? <X size={24} /> : <MenuIcon size={24} />}
63+
</button>
6964
</div>
7065
}
7166
right={
72-
<div className="flex gap-6 items-center h-full p-5">
73-
{username && (
74-
<Link
75-
href={`/${username}/dashboard`}
76-
className={`text-xl font-bold hover:text-secondary-900 transition-colors duration-200 ${
77-
isProfileActive
78-
? 'text-secondary-900 underline'
79-
: 'text-black'
80-
}`}
81-
>
82-
Profile
83-
</Link>
84-
)}
85-
{session && username && (
86-
<Link
87-
href="/editor"
88-
className={`text-xl font-bold hover:text-secondary-900 transition-colors duration-200 ${
89-
isActive('/editor')
90-
? 'text-secondary-900 underline'
91-
: 'text-black'
92-
}`}
93-
>
94-
Editor
95-
</Link>
96-
)}
97-
{session && (
98-
<button
99-
onClick={signOut}
100-
className="text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200"
101-
>
102-
Logout
103-
</button>
104-
)}
105-
{!session && (
106-
<Link
107-
href="/"
108-
className={`text-xl font-bold hover:text-secondary-900 transition-colors duration-200 ${
109-
isActive('/') ? 'text-secondary-900 underline' : 'text-black'
110-
}`}
111-
>
112-
Sign in
113-
</Link>
114-
)}
67+
<div
68+
className={`${
69+
isOpen ? 'block' : 'hidden'
70+
} lg:flex lg:items-center lg:flex-1`}
71+
>
72+
<nav className="flex flex-col lg:flex-row lg:items-center lg:justify-end gap-4 p-4 lg:p-5">
73+
{menuItems.map((item) =>
74+
item.external ? (
75+
<a
76+
key={item.href}
77+
href={item.href}
78+
className="text-lg lg:text-xl font-bold text-black hover:text-secondary-900 transition-colors duration-200 py-2 lg:py-0"
79+
>
80+
{item.label}
81+
</a>
82+
) : (
83+
<Link
84+
key={item.href}
85+
href={item.href}
86+
className={`text-lg lg:text-xl font-bold ${
87+
isActive(item.href) ||
88+
(item.href === '/jobs' && pathname.startsWith('/jobs/'))
89+
? 'text-secondary-900 underline'
90+
: 'text-black'
91+
} hover:text-secondary-900 transition-colors duration-200 py-2 lg:py-0`}
92+
>
93+
{item.label}
94+
</Link>
95+
)
96+
)}
97+
{authItems.map((item) =>
98+
item.onClick ? (
99+
<button
100+
key={item.label}
101+
onClick={item.onClick}
102+
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"
103+
>
104+
{item.label}
105+
</button>
106+
) : (
107+
<Link
108+
key={item.href}
109+
href={item.href}
110+
className={`text-lg lg:text-xl font-bold hover:text-secondary-900 transition-colors duration-200 py-2 lg:py-0 ${
111+
(item.href === `/${username}/dashboard` &&
112+
isProfileActive) ||
113+
isActive(item.href)
114+
? 'text-secondary-900 underline'
115+
: 'text-black'
116+
}`}
117+
>
118+
{item.label}
119+
</Link>
120+
)
121+
)}
122+
</nav>
115123
</div>
116124
}
117125
/>

apps/registry/app/job-similarity/page.js

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ const colors = [
508508
];
509509

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

543543
const Controls = memo(
544544
({ dataSource, setDataSource, algorithm, setAlgorithm }) => (
545-
<div className="prose max-w-3xl mx-auto mb-8">
546-
<div className="flex gap-4 items-center">
545+
<div className="mb-8">
546+
<div className="flex flex-col sm:flex-row gap-4">
547547
<div>
548548
<label className="block text-sm font-medium text-gray-700 mb-1">
549549
Data Source
@@ -588,6 +588,17 @@ const GraphContainer = ({ dataSource, algorithm }) => {
588588
const [loading, setLoading] = useState(true);
589589
const [error, setError] = useState(null);
590590
const [edges, setEdges] = useState([]);
591+
const [isMobile, setIsMobile] = useState(false);
592+
593+
useEffect(() => {
594+
const checkMobile = () => {
595+
setIsMobile(window.innerWidth < 768);
596+
};
597+
598+
checkMobile();
599+
window.addEventListener('resize', checkMobile);
600+
return () => window.removeEventListener('resize', checkMobile);
601+
}, []);
591602

592603
const handleNodeHover = useCallback(
593604
(node) => {
@@ -609,12 +620,19 @@ const GraphContainer = ({ dataSource, algorithm }) => {
609620
const handleNodeClick = useCallback(
610621
(node) => {
611622
if (!node) return;
623+
624+
if (isMobile) {
625+
// On mobile, just show the tooltip
626+
setHoverNode(node);
627+
return;
628+
}
629+
612630
if (node.uuids && node.uuids.length > 0) {
613631
const baseUrl = dataSource === 'jobs' ? '/jobs/' : '/';
614632
window.open(`${baseUrl}${node.uuids[0]}`, '_blank');
615633
}
616634
},
617-
[dataSource]
635+
[dataSource, isMobile]
618636
);
619637

620638
const processData = useCallback(
@@ -791,7 +809,7 @@ const GraphContainer = ({ dataSource, algorithm }) => {
791809
);
792810

793811
return (
794-
<div className="w-full h-full relative">
812+
<div className="w-full h-[600px] relative">
795813
{graphData && (
796814
<ForceGraph2D
797815
graphData={graphData}
@@ -853,7 +871,7 @@ const GraphContainer = ({ dataSource, algorithm }) => {
853871
d3VelocityDecay={0.3}
854872
warmupTicks={100}
855873
width={window.innerWidth}
856-
height={window.innerHeight - 32 * 16}
874+
height={600}
857875
/>
858876
)}
859877
{hoverNode && (
@@ -893,17 +911,41 @@ const GraphContainer = ({ dataSource, algorithm }) => {
893911
<div
894912
key={i}
895913
className="hover:bg-gray-100 p-1 rounded cursor-pointer"
896-
onClick={() => window.open(`/${username}`, '_blank')}
914+
onClick={() => {
915+
const baseUrl = dataSource === 'jobs' ? '/jobs/' : '/';
916+
window.open(`${baseUrl}${hoverNode.uuids[0]}`, '_blank');
917+
}}
897918
>
898919
{username}
899920
</div>
900921
))}
901922
</div>
902923
</div>
903924
)}
904-
<p className="text-sm text-gray-600 mt-2">
905-
Click to view {dataSource === 'jobs' ? 'job' : 'resume'}
906-
</p>
925+
{dataSource === 'jobs' && (
926+
<div className="mt-4">
927+
<a
928+
href={`/jobs/${hoverNode.uuids[0]}`}
929+
target="_blank"
930+
rel="noopener noreferrer"
931+
className="text-blue-600 hover:text-blue-800 underline"
932+
>
933+
View job listing →
934+
</a>
935+
</div>
936+
)}
937+
{dataSource !== 'jobs' && (
938+
<div className="mt-4">
939+
<a
940+
href={`/${hoverNode.uuids[0]}`}
941+
target="_blank"
942+
rel="noopener noreferrer"
943+
className="text-blue-600 hover:text-blue-800 underline"
944+
>
945+
View resume →
946+
</a>
947+
</div>
948+
)}
907949
</div>
908950
)}
909951
</div>
@@ -916,7 +958,7 @@ export default function Page() {
916958

917959
return (
918960
<div className="min-h-screen bg-accent-100">
919-
<div className="prose max-w-3xl mx-auto pt-8">
961+
<div className="prose max-w-3xl mx-auto pt-8 px-4 sm:px-6 lg:px-8">
920962
<Header />
921963
<Controls
922964
dataSource={dataSource}
@@ -925,7 +967,7 @@ export default function Page() {
925967
setAlgorithm={setAlgorithm}
926968
/>
927969
</div>
928-
<div className="w-full h-[calc(100vh-32rem)] bg-white">
970+
<div className="w-full h-[600px] bg-white">
929971
<GraphContainer dataSource={dataSource} algorithm={algorithm} />
930972
</div>
931973
</div>

0 commit comments

Comments
 (0)