Skip to content

Niklas 1#64

Merged
SimonBurmer merged 1 commit intomainfrom
Niklas
Apr 5, 2026
Merged

Niklas 1#64
SimonBurmer merged 1 commit intomainfrom
Niklas

Conversation

@SimonBurmer
Copy link
Copy Markdown
Collaborator

@SimonBurmer SimonBurmer commented Apr 5, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added interactive photo gallery with pagination controls on the Partners page.
    • Timeline slider now supports mouse-drag scrolling for improved navigation.
    • Scroll indicator is now interactive and draggable.
  • Bug Fixes

    • Fixed image asset path on Member Journey page.
  • Improvements

    • Optimized navigation with internal routing for buttons.
    • Enhanced FAQ accordion with improved user interaction.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
start-munich Ready Ready Preview, Comment Apr 5, 2026 9:53am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the partners page from a client component to a server component, extracts photo gallery logic into a dedicated PhotoGallery component, adds interactive drag-based scrolling to timeline controls, migrates CTA buttons from anchor tags to Next.js Link components, and converts internal navigation from external URLs to relative routes.

Changes

Cohort / File(s) Summary
Photo Gallery Extraction
app/for-partners/PhotoGallery.tsx, app/for-partners/page.tsx
New PhotoGallery component with pagination state management extracted from page.tsx; page refactored to import and render PhotoGallery instead of inline gallery logic.
Server-Side Rendering Migration
app/for-partners/page.tsx
Converted from client to async server component; replaced useState/useEffect with server-side fetchPartners() call; updated caching strategy from force-dynamic to revalidate: 3600; replaced controlled FAQ accordion with static <details> element.
Navigation Link Updates
components/CTA.tsx, app/member-journey/page.tsx
Migrated CTA buttons from raw <a> tags to Next.js <Link> components; removed external?: boolean property and related target="_blank" behavior; updated member-journey CTA destinations to internal routes (/apply, /about).
Interactive Scrolling Features
components/EventComponents.tsx, app/member-journey/page.tsx
Enhanced ScrollIndicator to support mouse-drag interactions with track reference and drag state tracking; added drag handlers to member-journey timeline slider for pointer-based scrolling; fixed image asset path capitalization (2.jpg2.JPG).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • bug fixes #46: Modifies the same event photo gallery rendering logic in the for-partners page, switching visibility patterns for photo items during pagination.
  • Marius: Partnership Page Adjustments #12: Updates partner page photo handling by introducing optional href properties on photo cards and conditional anchor-wrapping behavior.
  • Feat: Update Image on Member page #16: Alters member-journey timeline rendering and event card data structure, overlapping with asset path and event display changes.

Poem

🐰 A gallery hops into a brand new file,
Server-side scrolls make the timeline smile,
Links dance from anchors to Next.js's way,
Dragging through photos—hooray, hooray!
Refactored with flair in every component today!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Niklas 1' does not clearly summarize the changeset; it appears to be a branch name rather than a descriptive pull request title. Use a descriptive title that summarizes the main changes, such as 'Migrate for-partners page to server component and extract PhotoGallery component' or 'Refactor partner page with PhotoGallery component and drag-enabled scrolling'.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch Niklas

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
app/for-partners/PhotoGallery.tsx (2)

109-141: Consider using CSS-based pagination instead of hiding items with hidden class.

All 10 photos are rendered and hidden conditionally via the hidden class. For larger galleries, this could affect performance. Consider slicing the array to only render visible items:

♻️ Proposed refactor
       <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
-        {eventPhotos.map((photo, index) => {
-          const isVisible = index >= photoIndex && index < photoIndex + 6
+        {eventPhotos.slice(photoIndex, photoIndex + 6).map((photo) => {
-          ...
-          className={`...${!isVisible ? ' hidden' : ''}`}
+          ...
+          className={`...`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/for-partners/PhotoGallery.tsx` around lines 109 - 141, The gallery
currently maps over the entire eventPhotos array and toggles visibility with the
hidden class (using photoIndex), which renders all items and harms performance;
change the rendering to only map the visible subset by computing a slice like
eventPhotos.slice(photoIndex, photoIndex + 6) (or equivalent) and iterate over
that slice in place of eventPhotos so you only render visible photos; keep the
existing Wrapper selection (Wrapper = photo.href ? 'a' : 'div'), wrapperProps,
Image props, key={photo.id}, and all classNames but remove the !isVisible hidden
conditional since items outside the slice won't be rendered.

112-118: Use next/link for internal navigation instead of <a> tags.

Using plain <a> tags for internal routes (e.g., /events/rtsh) bypasses Next.js client-side navigation, causing full page reloads. Use next/link's Link component for better UX and performance.

♻️ Proposed refactor
 import { useState } from 'react'
 import Image from 'next/image'
+import Link from 'next/link'
 
 ...
 
-          const Wrapper = photo.href ? 'a' : 'div'
-          const wrapperProps = photo.href ? { href: photo.href } : {}
+          const wrapperProps = {
+            className: `group relative overflow-hidden rounded-3xl aspect-video border border-white/10 hover:border-brand-pink/40 transition-all duration-300 block shadow-lg hover:shadow-brand-pink/10${!isVisible ? ' hidden' : ''}`
+          }
+          
+          if (photo.href) {
+            return (
+              <Link key={photo.id} href={photo.href} {...wrapperProps}>
+                {/* Image and overlay content */}
+              </Link>
+            )
+          }
           return (
-            <Wrapper
-              key={photo.id}
-              {...wrapperProps}
-              className={`group relative overflow-hidden rounded-3xl aspect-video border border-white/10 hover:border-brand-pink/40 transition-all duration-300 block shadow-lg hover:shadow-brand-pink/10${!isVisible ? ' hidden' : ''}`}
-            >
+            <div key={photo.id} {...wrapperProps}>
+              {/* Image and overlay content */}
+            </div>
           )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/for-partners/PhotoGallery.tsx` around lines 112 - 118, The code uses a
plain 'a' by setting Wrapper = photo.href ? 'a' : 'div', which loses Next.js
client navigation for internal routes; change the logic to use next/link's Link
for internal hrefs: detect isInternal = Boolean(photo.href &&
photo.href.startsWith('/')), set Wrapper = isInternal ? Link : (photo.href ? 'a'
: 'div'), adjust wrapperProps to { href: photo.href } for Link/'a' and add
target/rel for external links as needed, ensure you import Link from 'next/link'
and keep passing className, key, and other props unchanged (update places
referencing Wrapper and wrapperProps in the PhotoGallery component).
app/for-partners/page.tsx (2)

226-230: Consider using next/image instead of native <img> for partner logos.

The partner logo marquee uses native <img> tags, missing Next.js image optimization benefits (lazy loading, format optimization, etc.). Since these are external URLs from NocoDB, you'll need to configure remotePatterns in next.config.js.

♻️ Proposed approach

If you add the NocoDB domain to next.config.js:

images: {
  remotePatterns: [
    { protocol: 'https', hostname: 'ndb.startmunich.de' },
    { protocol: 'https', hostname: 'ui-avatars.com' },
  ],
},

Then replace:

-                    <img
+                    <Image
                       src={partner.logoUrl}
                       alt={partner.name}
-                      className="max-h-10 max-w-[100px] object-contain"
+                      width={100}
+                      height={40}
+                      className="max-h-10 max-w-[100px] object-contain"
                     />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/for-partners/page.tsx` around lines 226 - 230, Replace the native <img>
used to render partner logos with Next.js Image and configure remotePatterns for
the external hosts; in next.config.js add the NocoDB and ui-avatars domains
(e.g., ndb.startmunich.de and ui-avatars.com) under images.remotePatterns, then
in app/for-partners/page.tsx import Image from 'next/image' and replace the <img
src={partner.logoUrl} alt={partner.name} ... /> with <Image
src={partner.logoUrl} alt={partner.name} className="..." /> using a supported
sizing approach (provide width/height props or use fill with a positioned
container) so partner.logoUrl and partner.name continue to be used and Next.js
image optimizations apply.

210-215: Use next/link for internal navigation.

The /partners links use plain <a> tags, causing full page reloads. Use Next.js Link component for client-side navigation.

♻️ Proposed fix

Add import at the top:

import Link from 'next/link'

Then update the links:

-            <a
+            <Link
               href="/partners"
               className="hidden md:inline-flex px-6 py-2.5 border-2 border-brand-pink text-brand-pink hover:bg-brand-pink hover:text-white font-bold rounded-full transition-all duration-300 whitespace-nowrap text-center"
             >
               View All Partners →
-            </a>
+            </Link>
-          <a href="/partners" className="group inline-flex items-center gap-2 text-white/70 hover:text-brand-pink font-medium transition-colors">
+          <Link href="/partners" className="group inline-flex items-center gap-2 text-white/70 hover:text-brand-pink font-medium transition-colors">
             View all our partners <span className="group-hover:translate-x-1 transition-transform">→</span>
-          </a>
+          </Link>

Also applies to: 238-241

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/for-partners/page.tsx` around lines 210 - 215, Import Link from
'next/link' and replace the plain anchor elements that navigate to "/partners"
(the elements rendering the "View All Partners →" link at the shown locations)
with Next.js Link components so navigation is client-side; keep the same href,
className and inner text, and update both occurrences (the one around lines
210–215 and the one around 238–241) so they use <Link href="/partners"> (or Link
wrapping an <a> if your Next version requires it) instead of a raw <a>.
components/EventComponents.tsx (2)

245-246: Thumb calculations may produce incorrect values on initial render or window resize.

thumbWidth and thumbLeft are computed during render by accessing sliderRef.current directly. On initial render, the ref may be null (producing 30/0 fallbacks). Also, these won't update on window resize since there's no re-render trigger.

Consider using state initialized via useEffect or accept that the scroll event will re-render and correct the values.

♻️ Optional: Add resize handling for more robust behavior
+import React, { useRef, useState, useEffect } from 'react'

 export const ScrollIndicator = ({ sliderRef, scrollProgress }: ScrollIndicatorProps) => {
   const trackRef = useRef<HTMLDivElement>(null)
   const isDragging = useRef(false)
+  const [dimensions, setDimensions] = useState({ clientWidth: 0, scrollWidth: 1 })
+
+  useEffect(() => {
+    const updateDimensions = () => {
+      if (sliderRef.current) {
+        setDimensions({
+          clientWidth: sliderRef.current.clientWidth,
+          scrollWidth: sliderRef.current.scrollWidth
+        })
+      }
+    }
+    updateDimensions()
+    window.addEventListener('resize', updateDimensions)
+    return () => window.removeEventListener('resize', updateDimensions)
+  }, [sliderRef])

-  const thumbWidth = sliderRef.current ? (sliderRef.current.clientWidth / sliderRef.current.scrollWidth) * 100 : 30
-  const thumbLeft = sliderRef.current ? scrollProgress * (1 - sliderRef.current.clientWidth / sliderRef.current.scrollWidth) : 0
+  const thumbWidth = dimensions.scrollWidth > 0 ? (dimensions.clientWidth / dimensions.scrollWidth) * 100 : 30
+  const thumbLeft = dimensions.scrollWidth > 0 ? scrollProgress * (1 - dimensions.clientWidth / dimensions.scrollWidth) : 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/EventComponents.tsx` around lines 245 - 246, thumbWidth and
thumbLeft are being computed directly during render from sliderRef.current which
can be null on first render and won't update on window resize; move these
computations into a useEffect that runs when sliderRef.current, scrollProgress,
or window size changes and store results in state (e.g., thumbWidthState and
thumbLeftState) so the component re-renders with correct values; inside the
effect guard sliderRef.current, compute width/left using
sliderRef.current.clientWidth and scrollWidth, set fallback defaults only once
if null, and optionally add a window "resize" listener to trigger the same
effect cleanup to keep values correct on resize.

236-243: Potential memory leak if component unmounts during drag.

If the user starts dragging and the component unmounts before mouseup, the window event listeners won't be removed.

♻️ Add cleanup on unmount
+import React, { useRef, useEffect } from 'react'

 export const ScrollIndicator = ({ sliderRef, scrollProgress }: ScrollIndicatorProps) => {
   const trackRef = useRef<HTMLDivElement>(null)
   const isDragging = useRef(false)
+  const cleanupRef = useRef<(() => void) | null>(null)
+
+  useEffect(() => {
+    return () => {
+      if (cleanupRef.current) cleanupRef.current()
+    }
+  }, [])

   const handleMouseDown = (e: React.MouseEvent) => {
     isDragging.current = true
     scrollToRatio(getScrollRatio(e.clientX))
     const onMove = (ev: MouseEvent) => { if (isDragging.current) scrollToRatio(getScrollRatio(ev.clientX)) }
-    const onUp = () => { isDragging.current = false; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp) }
+    const onUp = () => { 
+      isDragging.current = false
+      window.removeEventListener('mousemove', onMove)
+      window.removeEventListener('mouseup', onUp)
+      cleanupRef.current = null
+    }
+    cleanupRef.current = onUp
     window.addEventListener('mousemove', onMove)
     window.addEventListener('mouseup', onUp)
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/EventComponents.tsx` around lines 236 - 243, handleMouseDown can
register window listeners that won't be removed if the component unmounts during
a drag; fix by storing the listeners in a ref and adding a useEffect cleanup
that removes them on unmount. Concretely, create a ref like dragHandlersRef,
assign dragHandlersRef.current = { onMove, onUp } inside handleMouseDown (use
the existing onMove/onUp functions that call scrollToRatio/getScrollRatio and
toggle isDragging), and add a useEffect which on cleanup checks
dragHandlersRef.current and calls window.removeEventListener('mousemove',
onMove) and window.removeEventListener('mouseup', onUp) and sets
isDragging.current = false; this ensures the window listeners registered by
handleMouseDown are always removed when the component unmounts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/for-partners/page.tsx`:
- Around line 445-458: The FAQ accordion icon isn't rotating because the span
with className "faq-icon" has transition-transform but lacks a group-open
Tailwind modifier; update the span (the element with className containing
"faq-icon") to include a group-open rotation class such as
"group-open:rotate-45" (or "group-open:rotate-180" if you prefer a full spin) so
it rotates when the parent <details> (which already has the "group" class) is
opened; alternatively, implement icon swapping by conditionally rendering "+" vs
"−" inside the same span if you prefer switching characters instead of rotating.

In `@app/for-partners/PhotoGallery.tsx`:
- Around line 40-41: Typo in PhotoGallery.tsx: update the href value(s) from
"/events/leagel-hack" to the correct "/events/legal-hack" wherever they appear
(e.g., the href property in the gallery item objects around the occurrences
shown); edit the href strings in the relevant JSX/objects so both instances (the
one at the first object and the second object) use "/events/legal-hack".
- Around line 85-86: The page counter calculation using Math.floor(photoIndex /
6) + 1 is out of sync with the navigation cap (photoIndex is clamped to
Math.max(0, eventPhotos.length - 6)), so compute the displayed page by checking
for the capped "last start" and otherwise using the normal division: let
totalPages = Math.ceil(eventPhotos.length / 6); let lastStart = Math.max(0,
eventPhotos.length - 6); let currentPage = photoIndex === lastStart ? totalPages
: Math.floor(photoIndex / 6) + 1; then use currentPage / totalPages in the span
(or alternatively maintain a dedicated pageIndex state updated in the next/prev
handlers). Reference symbols: photoIndex, eventPhotos.length, totalPages,
lastStart.

In `@components/CTA.tsx`:
- Around line 34-40: The CTA component currently always renders buttons with
Next.js Link, causing external URLs passed via button.href to behave like
internal routes; update the render logic in the CTA component to detect
absolute/external URLs (e.g., test button.href for scheme "http://" or
"https://" or new URL parsing) and for externals render a plain anchor element
with className constructed from baseClasses + (isPrimary ? primaryClasses :
secondaryClasses), plus target="_blank" and rel="noopener noreferrer"; continue
to render internal routes with the existing Link component to preserve client
routing. Ensure this logic references the existing symbols: CTA component,
button.href, baseClasses, primaryClasses, secondaryClasses, and Link.

---

Nitpick comments:
In `@app/for-partners/page.tsx`:
- Around line 226-230: Replace the native <img> used to render partner logos
with Next.js Image and configure remotePatterns for the external hosts; in
next.config.js add the NocoDB and ui-avatars domains (e.g., ndb.startmunich.de
and ui-avatars.com) under images.remotePatterns, then in
app/for-partners/page.tsx import Image from 'next/image' and replace the <img
src={partner.logoUrl} alt={partner.name} ... /> with <Image
src={partner.logoUrl} alt={partner.name} className="..." /> using a supported
sizing approach (provide width/height props or use fill with a positioned
container) so partner.logoUrl and partner.name continue to be used and Next.js
image optimizations apply.
- Around line 210-215: Import Link from 'next/link' and replace the plain anchor
elements that navigate to "/partners" (the elements rendering the "View All
Partners →" link at the shown locations) with Next.js Link components so
navigation is client-side; keep the same href, className and inner text, and
update both occurrences (the one around lines 210–215 and the one around
238–241) so they use <Link href="/partners"> (or Link wrapping an <a> if your
Next version requires it) instead of a raw <a>.

In `@app/for-partners/PhotoGallery.tsx`:
- Around line 109-141: The gallery currently maps over the entire eventPhotos
array and toggles visibility with the hidden class (using photoIndex), which
renders all items and harms performance; change the rendering to only map the
visible subset by computing a slice like eventPhotos.slice(photoIndex,
photoIndex + 6) (or equivalent) and iterate over that slice in place of
eventPhotos so you only render visible photos; keep the existing Wrapper
selection (Wrapper = photo.href ? 'a' : 'div'), wrapperProps, Image props,
key={photo.id}, and all classNames but remove the !isVisible hidden conditional
since items outside the slice won't be rendered.
- Around line 112-118: The code uses a plain 'a' by setting Wrapper = photo.href
? 'a' : 'div', which loses Next.js client navigation for internal routes; change
the logic to use next/link's Link for internal hrefs: detect isInternal =
Boolean(photo.href && photo.href.startsWith('/')), set Wrapper = isInternal ?
Link : (photo.href ? 'a' : 'div'), adjust wrapperProps to { href: photo.href }
for Link/'a' and add target/rel for external links as needed, ensure you import
Link from 'next/link' and keep passing className, key, and other props unchanged
(update places referencing Wrapper and wrapperProps in the PhotoGallery
component).

In `@components/EventComponents.tsx`:
- Around line 245-246: thumbWidth and thumbLeft are being computed directly
during render from sliderRef.current which can be null on first render and won't
update on window resize; move these computations into a useEffect that runs when
sliderRef.current, scrollProgress, or window size changes and store results in
state (e.g., thumbWidthState and thumbLeftState) so the component re-renders
with correct values; inside the effect guard sliderRef.current, compute
width/left using sliderRef.current.clientWidth and scrollWidth, set fallback
defaults only once if null, and optionally add a window "resize" listener to
trigger the same effect cleanup to keep values correct on resize.
- Around line 236-243: handleMouseDown can register window listeners that won't
be removed if the component unmounts during a drag; fix by storing the listeners
in a ref and adding a useEffect cleanup that removes them on unmount.
Concretely, create a ref like dragHandlersRef, assign dragHandlersRef.current =
{ onMove, onUp } inside handleMouseDown (use the existing onMove/onUp functions
that call scrollToRatio/getScrollRatio and toggle isDragging), and add a
useEffect which on cleanup checks dragHandlersRef.current and calls
window.removeEventListener('mousemove', onMove) and
window.removeEventListener('mouseup', onUp) and sets isDragging.current = false;
this ensures the window listeners registered by handleMouseDown are always
removed when the component unmounts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea71da75-5233-493e-b107-7566f6c99040

📥 Commits

Reviewing files that changed from the base of the PR and between 77c6e94 and 7b773cf.

📒 Files selected for processing (5)
  • app/for-partners/PhotoGallery.tsx
  • app/for-partners/page.tsx
  • app/member-journey/page.tsx
  • components/CTA.tsx
  • components/EventComponents.tsx

Comment thread app/for-partners/page.tsx
Comment on lines +445 to +458
<details
key={faq.id}
className="bg-white/[0.06] rounded-2xl border border-white/10 overflow-hidden hover:border-brand-pink/30 transition-all duration-300"
className="group bg-white/[0.06] rounded-2xl border border-white/10 overflow-hidden hover:border-brand-pink/30 transition-all duration-300"
>
<button
onClick={() => setOpenFaq(openFaq === faq.id ? null : faq.id)}
className="w-full px-7 py-5 text-left flex items-center justify-between hover:bg-white/[0.04] transition-colors duration-200 rounded-2xl"
>
<summary className="w-full px-7 py-5 flex items-center justify-between cursor-pointer hover:bg-white/[0.04] transition-colors duration-200 rounded-2xl list-none">
<h3 className="text-lg font-bold text-white pr-8">{faq.question}</h3>
<span className={`text-brand-pink text-2xl flex-shrink-0 transition-transform duration-300 ${openFaq === faq.id ? 'rotate-45' : ''}`}>
<span className="faq-icon text-brand-pink text-2xl flex-shrink-0 transition-transform duration-300">
+
</span>
</button>
{openFaq === faq.id && (
<div className="px-7 pb-5" style={{ animation: 'slideDown 0.3s ease-out' }}>
<p className="text-gray-400 leading-relaxed">{faq.answer}</p>
</div>
)}
</div>
</summary>
<div className="px-7 pb-5">
<p className="text-gray-400 leading-relaxed">{faq.answer}</p>
</div>
</details>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

FAQ accordion icon doesn't rotate when open.

The .faq-icon span has transition-transform but no Tailwind class to rotate when the <details> is open. Use group-open: modifier.

🐛 Proposed fix
-                    <span className="faq-icon text-brand-pink text-2xl flex-shrink-0 transition-transform duration-300">
-                      +
+                    <span className="faq-icon text-brand-pink text-2xl flex-shrink-0 transition-transform duration-300 group-open:rotate-45">
+                      +
                     </span>

Alternatively, switch between + and icons:

-                    <span className="faq-icon text-brand-pink text-2xl flex-shrink-0 transition-transform duration-300">
-                      +
-                    </span>
+                    <span className="faq-icon text-brand-pink text-2xl flex-shrink-0">
+                      <span className="group-open:hidden">+</span>
+                      <span className="hidden group-open:inline">−</span>
+                    </span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<details
key={faq.id}
className="bg-white/[0.06] rounded-2xl border border-white/10 overflow-hidden hover:border-brand-pink/30 transition-all duration-300"
className="group bg-white/[0.06] rounded-2xl border border-white/10 overflow-hidden hover:border-brand-pink/30 transition-all duration-300"
>
<button
onClick={() => setOpenFaq(openFaq === faq.id ? null : faq.id)}
className="w-full px-7 py-5 text-left flex items-center justify-between hover:bg-white/[0.04] transition-colors duration-200 rounded-2xl"
>
<summary className="w-full px-7 py-5 flex items-center justify-between cursor-pointer hover:bg-white/[0.04] transition-colors duration-200 rounded-2xl list-none">
<h3 className="text-lg font-bold text-white pr-8">{faq.question}</h3>
<span className={`text-brand-pink text-2xl flex-shrink-0 transition-transform duration-300 ${openFaq === faq.id ? 'rotate-45' : ''}`}>
<span className="faq-icon text-brand-pink text-2xl flex-shrink-0 transition-transform duration-300">
+
</span>
</button>
{openFaq === faq.id && (
<div className="px-7 pb-5" style={{ animation: 'slideDown 0.3s ease-out' }}>
<p className="text-gray-400 leading-relaxed">{faq.answer}</p>
</div>
)}
</div>
</summary>
<div className="px-7 pb-5">
<p className="text-gray-400 leading-relaxed">{faq.answer}</p>
</div>
</details>
<details
key={faq.id}
className="group bg-white/[0.06] rounded-2xl border border-white/10 overflow-hidden hover:border-brand-pink/30 transition-all duration-300"
>
<summary className="w-full px-7 py-5 flex items-center justify-between cursor-pointer hover:bg-white/[0.04] transition-colors duration-200 rounded-2xl list-none">
<h3 className="text-lg font-bold text-white pr-8">{faq.question}</h3>
<span className="faq-icon text-brand-pink text-2xl flex-shrink-0 transition-transform duration-300 group-open:rotate-45">
</span>
</summary>
<div className="px-7 pb-5">
<p className="text-gray-400 leading-relaxed">{faq.answer}</p>
</div>
</details>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/for-partners/page.tsx` around lines 445 - 458, The FAQ accordion icon
isn't rotating because the span with className "faq-icon" has
transition-transform but lacks a group-open Tailwind modifier; update the span
(the element with className containing "faq-icon") to include a group-open
rotation class such as "group-open:rotate-45" (or "group-open:rotate-180" if you
prefer a full spin) so it rotates when the parent <details> (which already has
the "group" class) is opened; alternatively, implement icon swapping by
conditionally rendering "+" vs "−" inside the same span if you prefer switching
characters instead of rotating.

Comment on lines +40 to +41
href: "/events/leagel-hack"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: "leagel-hack" should likely be "legal-hack".

The href /events/leagel-hack appears to have a typo — "leagel" instead of "legal".

🐛 Proposed fix
   {
     id: "ep6",
     url: "/partners/partnerEvents/MHL1.jpg",
     caption: "Munich Hacking Legal 25",
-    href: "/events/leagel-hack"
+    href: "/events/legal-hack"
   },
   {
     id: "ep7",
     url: "/partners/partnerEvents/MHL2.jpg",
     caption: "Munich Hacking Legal 25",
-    href: "/events/leagel-hack"
+    href: "/events/legal-hack"
   },

Also applies to: 45-46

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/for-partners/PhotoGallery.tsx` around lines 40 - 41, Typo in
PhotoGallery.tsx: update the href value(s) from "/events/leagel-hack" to the
correct "/events/legal-hack" wherever they appear (e.g., the href property in
the gallery item objects around the occurrences shown); edit the href strings in
the relevant JSX/objects so both instances (the one at the first object and the
second object) use "/events/legal-hack".

Comment on lines +85 to +86
<span className="text-sm text-gray-500 tabular-nums">
{String(Math.floor(photoIndex / 6) + 1).padStart(2, '0')} / {String(Math.ceil(eventPhotos.length / 6)).padStart(2, '0')}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Page counter calculation is misaligned with navigation logic.

The page counter uses Math.floor(photoIndex / 6) + 1, but the "next" button caps photoIndex at eventPhotos.length - 6 (which is 4 for 10 photos). When photoIndex becomes 4, Math.floor(4/6) + 1 = 1, so the counter still shows "01" instead of "02".

Consider tracking page number directly or adjusting the counter calculation:

🐛 Proposed fix: Track page index directly
 export default function PhotoGallery() {
-  const [photoIndex, setPhotoIndex] = useState(0)
+  const [pageIndex, setPageIndex] = useState(0)
+  const pageSize = 6
+  const totalPages = Math.ceil(eventPhotos.length / pageSize)
+  const photoIndex = pageIndex * pageSize

   return (
     <section>
       <div className="mb-12 flex flex-col md:flex-row md:items-end md:justify-between gap-6">
         ...
         <div className="flex items-center gap-3 flex-shrink-0">
           <span className="text-sm text-gray-500 tabular-nums">
-            {String(Math.floor(photoIndex / 6) + 1).padStart(2, '0')} / {String(Math.ceil(eventPhotos.length / 6)).padStart(2, '0')}
+            {String(pageIndex + 1).padStart(2, '0')} / {String(totalPages).padStart(2, '0')}
           </span>
           <button
-            onClick={() => setPhotoIndex(Math.max(0, photoIndex - 6))}
-            disabled={photoIndex === 0}
+            onClick={() => setPageIndex(Math.max(0, pageIndex - 1))}
+            disabled={pageIndex === 0}
             ...
           >
           <button
-            onClick={() => setPhotoIndex(Math.min(eventPhotos.length - 6, photoIndex + 6))}
-            disabled={photoIndex + 6 >= eventPhotos.length}
+            onClick={() => setPageIndex(Math.min(totalPages - 1, pageIndex + 1))}
+            disabled={pageIndex >= totalPages - 1}
             ...
           >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/for-partners/PhotoGallery.tsx` around lines 85 - 86, The page counter
calculation using Math.floor(photoIndex / 6) + 1 is out of sync with the
navigation cap (photoIndex is clamped to Math.max(0, eventPhotos.length - 6)),
so compute the displayed page by checking for the capped "last start" and
otherwise using the normal division: let totalPages =
Math.ceil(eventPhotos.length / 6); let lastStart = Math.max(0,
eventPhotos.length - 6); let currentPage = photoIndex === lastStart ? totalPages
: Math.floor(photoIndex / 6) + 1; then use currentPage / totalPages in the span
(or alternatively maintain a dedicated pageIndex state updated in the next/prev
handlers). Reference symbols: photoIndex, eventPhotos.length, totalPages,
lastStart.

Comment thread components/CTA.tsx
Comment on lines +34 to +40
<Link
key={index}
href={button.href}
target={button.external ? "_blank" : undefined}
rel={button.external ? "noopener noreferrer" : undefined}
className={`${baseClasses} ${isPrimary ? primaryClasses : secondaryClasses}`}
>
{button.label}
</a>
</Link>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all CTA component usages and check for external URLs
rg -n "href:\s*[\"']https?://" --type=ts --type=tsx -C3
rg -n "<CTA" --type=ts --type=tsx -A20 | rg -A5 "href.*http"

Repository: startmunich/website-special-pages

Length of output: 132


🏁 Script executed:

#!/bin/bash
# First, find the CTA.tsx file and examine its content
find . -type f -name "CTA.tsx" | head -5

Repository: startmunich/website-special-pages

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Find all TypeScript/TSX files to understand the structure
git ls-files | grep -E '\.(tsx?|jsx?)$' | head -20

Repository: startmunich/website-special-pages

Length of output: 611


🏁 Script executed:

#!/bin/bash
# Look for files containing "CTA" in their name or content
fd -e ts -e tsx | grep -i cta

Repository: startmunich/website-special-pages

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Read the CTA.tsx file to understand current implementation
cat -n components/CTA.tsx

Repository: startmunich/website-special-pages

Length of output: 3504


🏁 Script executed:

#!/bin/bash
# Find app/partners/page.tsx (the original review mentioned app/partners/page.tsx but I see app/for-partners/page.tsx)
find . -type f -name "*partners*page.tsx"

Repository: startmunich/website-special-pages

Length of output: 59


🏁 Script executed:

#!/bin/bash
# Check for both potential partner pages
cat -n app/for-partners/page.tsx | head -100

Repository: startmunich/website-special-pages

Length of output: 4375


🏁 Script executed:

#!/bin/bash
# Search for CTA component usages throughout the codebase
rg "CTA\s*\{" --no-heading -A 10 | head -100

Repository: startmunich/website-special-pages

Length of output: 59


🏁 Script executed:

#!/bin/bash
# Search specifically for any href with http or https in CTA contexts
rg -A 20 "buttons\s*=" app/for-partners/page.tsx | head -80

Repository: startmunich/website-special-pages

Length of output: 59


🏁 Script executed:

#!/bin/bash
# Search for CTA component import and usage
rg "import.*CTA" --type ts --type tsx

Repository: startmunich/website-special-pages

Length of output: 104


🏁 Script executed:

#!/bin/bash
# Look for CTA usage more broadly
rg "<CTA" -B 2 -A 15 2>/dev/null | head -150

Repository: startmunich/website-special-pages

Length of output: 3106


🏁 Script executed:

#!/bin/bash
# Check the entire for-partners page to find CTA usage
cat app/for-partners/page.tsx | tail -200

Repository: startmunich/website-special-pages

Length of output: 10528


🏁 Script executed:

#!/bin/bash
# Verify if there are any other external URL usages with CTA
rg "href.*https?" app/startups/page.tsx app/member-journey/page.tsx

Repository: startmunich/website-special-pages

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Check the exact implementation in app/partners/page.tsx for the CTA usage
grep -n -A 10 "Contact Us" app/partners/page.tsx

Repository: startmunich/website-special-pages

Length of output: 279


External URLs fail silently with <Link> component — must handle absolute URLs separately.

The CTA component unconditionally renders all buttons using Next.js <Link>, but app/partners/page.tsx (line 361) passes an external URL (https://tally.so/r/3xpGQG) to it. This causes:

  1. Navigation within the same tab instead of opening a new window
  2. Loss of rel="noopener noreferrer" security attributes

Detect external URLs and use <a> tags for them instead.

Proposed fix
const renderButton = (button: CTAButton, index: number) => {
   const isPrimary = button.variant === 'primary' || (index === 0 && !button.variant)
   const baseClasses = "px-8 py-3 font-bold rounded-lg transition-all duration-300"
   const primaryClasses = "bg-[`#d0006f`] hover:bg-[`#d0006f`]/90 text-white hover:shadow-lg hover:shadow-[`#d0006f`]/50"
   const secondaryClasses = "border border-[`#d0006f`] text-[`#d0006f`] hover:bg-[`#d0006f`]/10"
+  const isExternal = button.href.startsWith('http://') || button.href.startsWith('https://')

+  if (isExternal) {
+    return (
+      <a
+        key={index}
+        href={button.href}
+        target="_blank"
+        rel="noopener noreferrer"
+        className={`${baseClasses} ${isPrimary ? primaryClasses : secondaryClasses}`}
+      >
+        {button.label}
+      </a>
+    )
+  }

   return (
     <Link
       key={index}
       href={button.href}
       className={`${baseClasses} ${isPrimary ? primaryClasses : secondaryClasses}`}
     >
       {button.label}
     </Link>
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/CTA.tsx` around lines 34 - 40, The CTA component currently always
renders buttons with Next.js Link, causing external URLs passed via button.href
to behave like internal routes; update the render logic in the CTA component to
detect absolute/external URLs (e.g., test button.href for scheme "http://" or
"https://" or new URL parsing) and for externals render a plain anchor element
with className constructed from baseClasses + (isPrimary ? primaryClasses :
secondaryClasses), plus target="_blank" and rel="noopener noreferrer"; continue
to render internal routes with the existing Link component to preserve client
routing. Ensure this logic references the existing symbols: CTA component,
button.href, baseClasses, primaryClasses, secondaryClasses, and Link.

@SimonBurmer SimonBurmer merged commit dd10174 into main Apr 5, 2026
3 checks passed
@SimonBurmer SimonBurmer deleted the Niklas branch April 5, 2026 11:38
@coderabbitai coderabbitai Bot mentioned this pull request Apr 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant