From 63a154f2afca3298dbdab8937c35e74f15f96f6d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 16:42:49 +0000 Subject: [PATCH 1/6] Add exhibition rewrites and mobile navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Domenik Töfflinger --- .../components/layout/Header.test.tsx | 15 ++++-- src/components/layout/Header.tsx | 48 ++++++++++++++++++- vercel.json | 10 ++++ 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 vercel.json diff --git a/src/__tests__/components/layout/Header.test.tsx b/src/__tests__/components/layout/Header.test.tsx index c860c746..a1ce1e81 100644 --- a/src/__tests__/components/layout/Header.test.tsx +++ b/src/__tests__/components/layout/Header.test.tsx @@ -59,11 +59,18 @@ describe('Header Component', () => { expect(articlesLink).toHaveAttribute('href', '/articles'); }); + it('should render Exhibition link', () => { + render(
); + const exhibitionLink = screen.getByRole('link', { name: /exhibition/i }); + expect(exhibitionLink).toBeInTheDocument(); + expect(exhibitionLink).toHaveAttribute('href', '/exhibition/'); + }); + it('should have all navigation links', () => { render(
); const navLinks = screen.getAllByRole('link'); - // Should have at least 4 links (logo + 3 nav items) - expect(navLinks.length).toBeGreaterThanOrEqual(4); + // Should have at least 5 links (logo + 4 nav items) + expect(navLinks.length).toBeGreaterThanOrEqual(5); }); }); @@ -111,9 +118,11 @@ describe('Header Component', () => { const aboutLink = screen.getByRole('link', { name: /about/i }); const teamLink = screen.getByRole('link', { name: /team/i }); const articlesLink = screen.getByRole('link', { name: /articles/i }); + const exhibitionLink = screen.getByRole('link', { name: /exhibition/i }); expect(aboutLink).toBeInTheDocument(); expect(teamLink).toBeInTheDocument(); expect(articlesLink).toBeInTheDocument(); + expect(exhibitionLink).toBeInTheDocument(); }); }); @@ -127,7 +136,7 @@ describe('Header Component', () => { it('should render list items for each nav link', () => { render(
); const listItems = screen.getAllByRole('listitem'); - expect(listItems.length).toBe(3); // About, Team, Articles + expect(listItems.length).toBe(4); // About, Team, Articles, Exhibition }); it('should use NavLink components', () => { diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index e2c639bd..a3d312c1 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,4 +1,8 @@ + 'use client'; + import Link from 'next/link'; +import { Menu, X } from 'lucide-react'; +import * as React from 'react'; import { Logo } from '@/components/ui/icons/Logo'; import NavLink from '@/components/ui/NavLink'; @@ -8,6 +12,7 @@ const navLinks = [ { href: '/about', text: 'About' }, { href: '/team', text: 'Team' }, { href: '/articles', text: 'Articles' }, + { href: '/exhibition/', text: 'Exhibition' }, ]; function Navigation() { @@ -25,6 +30,9 @@ function Navigation() { } export default function Header() { + const [mobileNavOpen, setMobileNavOpen] = React.useState(false); + const mobileNavId = React.useId(); + return (
@@ -35,10 +43,48 @@ export default function Header() { > +
- +
+ +
+ + +
+ +
); diff --git a/vercel.json b/vercel.json new file mode 100644 index 00000000..4175585b --- /dev/null +++ b/vercel.json @@ -0,0 +1,10 @@ +{ + "rewrites": [ + { "source": "/exhibition", "destination": "https://sentiment-exhibition.vercel.app/" }, + { "source": "/exhibition/", "destination": "https://sentiment-exhibition.vercel.app/" }, + { + "source": "/exhibition/:path*", + "destination": "https://sentiment-exhibition.vercel.app/:path*" + } + ] +} From e6751d10529956894a7773348fcfcadc6b99a3e7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 16:45:11 +0000 Subject: [PATCH 2/6] chore: fix lint script and header tests --- package.json | 2 +- src/__tests__/components/layout/Header.test.tsx | 2 +- src/components/layout/Header.tsx | 4 ++-- vercel.json | 10 ++++++++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 22fc8c22..555178ed 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "next lint", + "lint": "eslint src", "lint:fix": "eslint src --fix && pnpm format", "lint:strict": "eslint --max-warnings=0 src", "typecheck": "tsc --noEmit", diff --git a/src/__tests__/components/layout/Header.test.tsx b/src/__tests__/components/layout/Header.test.tsx index a1ce1e81..1261a20a 100644 --- a/src/__tests__/components/layout/Header.test.tsx +++ b/src/__tests__/components/layout/Header.test.tsx @@ -63,7 +63,7 @@ describe('Header Component', () => { render(
); const exhibitionLink = screen.getByRole('link', { name: /exhibition/i }); expect(exhibitionLink).toBeInTheDocument(); - expect(exhibitionLink).toHaveAttribute('href', '/exhibition/'); + expect(exhibitionLink).toHaveAttribute('href', '/exhibition'); }); it('should have all navigation links', () => { diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index a3d312c1..1ce3fc0d 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,7 +1,7 @@ - 'use client'; +'use client'; -import Link from 'next/link'; import { Menu, X } from 'lucide-react'; +import Link from 'next/link'; import * as React from 'react'; import { Logo } from '@/components/ui/icons/Logo'; diff --git a/vercel.json b/vercel.json index 4175585b..b6ad0f59 100644 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,13 @@ { "rewrites": [ - { "source": "/exhibition", "destination": "https://sentiment-exhibition.vercel.app/" }, - { "source": "/exhibition/", "destination": "https://sentiment-exhibition.vercel.app/" }, + { + "source": "/exhibition", + "destination": "https://sentiment-exhibition.vercel.app/" + }, + { + "source": "/exhibition/", + "destination": "https://sentiment-exhibition.vercel.app/" + }, { "source": "/exhibition/:path*", "destination": "https://sentiment-exhibition.vercel.app/:path*" From 076196d13a6db87b7adc8ac63324ee909bd1d6bf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 16:48:27 +0000 Subject: [PATCH 3/6] fix: proxy /public assets to exhibition app --- vercel.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vercel.json b/vercel.json index b6ad0f59..8154e3d3 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,9 @@ { "rewrites": [ + { + "source": "/public/:path*", + "destination": "https://sentiment-exhibition.vercel.app/public/:path*" + }, { "source": "/exhibition", "destination": "https://sentiment-exhibition.vercel.app/" From d258f98984ba684279db5dd2e4e8581b9def5acf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 16:51:52 +0000 Subject: [PATCH 4/6] fix: proxy exhibition /api/events --- vercel.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vercel.json b/vercel.json index 8154e3d3..7c6b7ba9 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,17 @@ { "rewrites": [ + { + "source": "/api/events", + "destination": "https://sentiment-exhibition.vercel.app/api/events/" + }, + { + "source": "/api/events/", + "destination": "https://sentiment-exhibition.vercel.app/api/events/" + }, + { + "source": "/api/events/:path*", + "destination": "https://sentiment-exhibition.vercel.app/api/events/:path*" + }, { "source": "/public/:path*", "destination": "https://sentiment-exhibition.vercel.app/public/:path*" From d677d9200d01f52119b8217bdfd999848bf47717 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 16:58:34 +0000 Subject: [PATCH 5/6] fix: show mobile nav dropdown --- src/components/layout/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 1ce3fc0d..169f6f3a 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -35,7 +35,7 @@ export default function Header() { return (
-
+
Date: Thu, 23 Apr 2026 17:56:47 +0000 Subject: [PATCH 6/6] fix: close mobile nav on link, escape, outside click --- src/components/layout/Header.tsx | 36 ++++++++++++++++++++++++++++++-- src/components/ui/NavLink.tsx | 3 +++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 169f6f3a..d2cbc0a9 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -12,7 +12,7 @@ const navLinks = [ { href: '/about', text: 'About' }, { href: '/team', text: 'Team' }, { href: '/articles', text: 'Articles' }, - { href: '/exhibition/', text: 'Exhibition' }, + { href: '/exhibition', text: 'Exhibition' }, ]; function Navigation() { @@ -32,6 +32,32 @@ function Navigation() { export default function Header() { const [mobileNavOpen, setMobileNavOpen] = React.useState(false); const mobileNavId = React.useId(); + const mobileNavRef = React.useRef(null); + + React.useEffect(() => { + if (!mobileNavOpen) return; + + function onKeyDown(e: KeyboardEvent) { + if (e.key === 'Escape') setMobileNavOpen(false); + } + + function onPointerDown(e: MouseEvent | TouchEvent) { + const target = e.target; + if (!(target instanceof Node)) return; + if (mobileNavRef.current?.contains(target)) return; + setMobileNavOpen(false); + } + + document.addEventListener('keydown', onKeyDown); + document.addEventListener('mousedown', onPointerDown); + document.addEventListener('touchstart', onPointerDown, { passive: true }); + + return () => { + document.removeEventListener('keydown', onKeyDown); + document.removeEventListener('mousedown', onPointerDown); + document.removeEventListener('touchstart', onPointerDown); + }; + }, [mobileNavOpen]); return (
@@ -69,6 +95,7 @@ export default function Header() {