Skip to content

Feat/join start#87

Merged
niklas1simakov merged 7 commits intomainfrom
feat/join-start
Apr 9, 2026
Merged

Feat/join start#87
niklas1simakov merged 7 commits intomainfrom
feat/join-start

Conversation

@ValentinAhrend
Copy link
Copy Markdown
Contributor

@ValentinAhrend ValentinAhrend commented Apr 7, 2026

Join Start Page 2026

Summary by CodeRabbit

  • New Features
    • Added an "Apply Now" CTA to the home hero that becomes active at the launch date.
    • Launched the 2026 application page with a live countdown, responsive video, events/info-session scroller, image/text feature sections, and application links.
  • Chore
    • Updated TypeScript/Next.js reference comment and project type reference.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 7, 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 8, 2026 3:38pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

Added a date-gated "Apply Now" CTA to the homepage; converted /join-start/2026 to a client-rendered component (JoinStartClient) that manages mounted state, a live countdown, event cards, embedded YouTube, and conditional CTAs. Minor TypeScript env comment cleanup.

Changes

Cohort / File(s) Summary
Homepage CTA
app/home/HomeClient.tsx
Reduced hero paragraph bottom margin and added a date-gated primary CTA linking to /join-start/2026 with the same loaded-gated entrance animation classes.
JoinStart (page → client)
app/join-start/2026/page.tsx, app/join-start/2026/JoinStartClient.tsx
Replaced server-rendered Hero with a client component. New JoinStartClient handles mounted state, beta flag, launch-time gating, a per-second countdown (clamped at zero), live vs pre-launch views, embedded responsive YouTube iframe, horizontally scrollable events with conditional CTAs, images, and registration links.
TypeScript env note
next-env.d.ts
Removed triple-slash reference to ./.next/types/routes.d.ts and updated the documentation comment link.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Browser
  participant JoinStart as "JoinStartClient (client)"
  participant Countdown as "Countdown logic"
  participant Events as "Events renderer"
  participant YouTube as "YouTube iframe"
  participant External as "External links/CTAs"

  User->>Browser: navigate to /join-start/2026
  Browser->>JoinStart: load & hydrate (set mounted, check ?beta, compare TARGET_DATE)
  JoinStart->>Countdown: start interval (every 1s)
  Countdown-->>JoinStart: update timeLeft (clamped to 0)
  JoinStart->>Events: render events array (cards with CTAs)
  User->>Events: click event CTA
  Events->>External: open registration / show disabled label
  JoinStart->>YouTube: render responsive iframe
  User->>YouTube: play video
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • SimonBurmer

Poem

🐰 I hopped through code with eager paws,
A button blooms for hopeful cause,
The countdown hums, the video plays,
Events scroll on in cheerful ways,
Come join — I bounce through bytes and laws ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
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.
Title check ❓ Inconclusive The title 'Feat/join start' is vague and lacks specificity about the actual changes. It uses generic naming conventions without conveying meaningful information about what the feature does. Use a more descriptive title such as 'Add Join Start 2026 page with countdown timer and beta preview' or 'Implement Join Start landing page with conditional launch date display'.
✅ 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 feat/join-start

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

🤖 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/join-start/2026/page.tsx`:
- Line 45: The page component currently uses a nested <main> (the element with
className "bg-brand-dark-blue") which duplicates the landmark created by
app/layout.tsx; replace that inner <main> with a <div> or React fragment while
preserving the className and all child content so the styling and structure
remain the same (also apply the same change to the other occurrence noted).
Locate the JSX in page.tsx where the root element is <main
className="bg-brand-dark-blue"> and switch it to a non-landmark container (e.g.,
<div className="bg-brand-dark-blue">) to fix the accessibility issue.
- Line 291: Update the incorrect weekday on the event card where title is
'Student Initiative Showcase' (img '/join-start/initiative-showcase.jpg') and
date currently reads 'Thursday, 24th April 2026' — change the weekday to
'Friday' so the entry becomes 'Friday, 24th April 2026' to match the actual
date.
- Line 94: Remove the inline style style={{"alignItems":"center"}} from the JSX
div and move alignment into Tailwind classes so responsive behavior works;
update the div's className (the element with className "flex flex-col gap-8 px-6
pb-10  md:flex-row md:items-end md:justify-between md:px-16 md:pb-16 md:pt-0
lg:px-24") to include the mobile alignment (items-center) and keep the md
breakpoint override (md:items-end), then delete the style prop entirely.
- Line 6: The constant TARGET_DATE is built from a local-time-only ISO string so
it will vary by visitor timezone; change the initializer for TARGET_DATE to use
an explicit Munich offset (UTC+02:00) or construct the timestamp with UTC values
so the deadline is fixed to April 26, 2026 23:59:59 in Munich (e.g., use
'2026-04-26T23:59:59+02:00' or Date.UTC(...) adjusted accordingly) to ensure the
deadline does not shift across timezones.
🪄 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: 234467a3-d883-418a-8e55-1a33f8aab291

📥 Commits

Reviewing files that changed from the base of the PR and between 32373d6 and 216a85c.

⛔ Files ignored due to path filters (10)
  • package-lock.json is excluded by !**/package-lock.json
  • public/join-start-2026-bg.png is excluded by !**/*.png
  • public/join-start/info-session.jpg is excluded by !**/*.jpg
  • public/join-start/initiative-showcase.jpg is excluded by !**/*.jpg
  • public/join-start/jumpstart.png is excluded by !**/*.png
  • public/join-start/online-info.jpg is excluded by !**/*.jpg
  • public/join-start/running-club.jpg is excluded by !**/*.jpg
  • public/join-start/start-summit.jpeg is excluded by !**/*.jpeg
  • public/join-start/volleyball.png is excluded by !**/*.png
  • public/join-start/yc-event.jpg is excluded by !**/*.jpg
📒 Files selected for processing (4)
  • app/home/HomeClient.tsx
  • app/join-start/2026/page.tsx
  • next-env.d.ts
  • tsconfig.tsbuildinfo

Comment thread app/join-start/2026/page.tsx Outdated
Comment thread app/join-start/2026/page.tsx Outdated
Comment thread app/join-start/2026/page.tsx Outdated
Comment thread app/join-start/2026/page.tsx Outdated
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: 1

🧹 Nitpick comments (2)
app/join-start/2026/page.tsx (2)

131-139: Prefer Tailwind's aspect-video over inline padding hack.

The inline paddingBottom: '56.25%' achieves a 16:9 aspect ratio, but Tailwind's built-in aspect-video class is more maintainable and aligns with the styling guidelines.

♻️ Suggested refactor
-        <div className="relative w-full overflow-hidden rounded-xl" style={{ paddingBottom: '56.25%' }}>
+        <div className="relative aspect-video w-full overflow-hidden rounded-xl">
           <iframe
             src="https://www.youtube.com/embed/T63USk9W_IY"
             title="START Munich Application Video"
             allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
             allowFullScreen
-            className="absolute inset-0 h-full w-full"
+            className="h-full w-full"
           />
         </div>

As per coding guidelines, "Use Tailwind CSS for styling".

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

In `@app/join-start/2026/page.tsx` around lines 131 - 139, Replace the inline
56.25% padding hack with Tailwind's aspect-video utility: remove the style={{
paddingBottom: '56.25%' }} from the wrapper <div> and add the aspect-video class
to its className (the wrapper currently using "relative w-full overflow-hidden
rounded-xl"); keep the inner <iframe> as-is with className="absolute inset-0
h-full w-full" so it fills the Tailwind-managed aspect container.

168-172: Use Tailwind border utilities instead of inline style.

The inline border style can be expressed with Tailwind classes for consistency.

♻️ Suggested refactor
             <div
               key={event.title}
-              className="flex w-72 flex-none flex-col overflow-hidden rounded-lg md:w-80"
-              style={{ border: '1px solid rgba(255,255,255,0.08)' }}
+              className="flex w-72 flex-none flex-col overflow-hidden rounded-lg border border-white/[0.08] md:w-80"
             >

As per coding guidelines, "Use Tailwind CSS for styling".

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

In `@app/join-start/2026/page.tsx` around lines 168 - 172, Replace the inline
style on the event card div with Tailwind classes: remove style={{ border: '1px
solid rgba(255,255,255,0.08)' }} and add the equivalent Tailwind utilities to
the className (e.g., include "border border-[rgba(255,255,255,0.08)]" or another
project-approved Tailwind token) so the div keyed by event.title uses Tailwind
for the border instead of inline CSS.
🤖 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/join-start/2026/page.tsx`:
- Around line 325-333: The padding class "px-12" on the container div does not
affect the nested next/image with fill and object-cover; either remove "px-12"
from that div's className or move the padding to a wrapper element so the
Image's parent keeps full bounds while the wrapper provides horizontal spacing
(locate the div with className containing "relative aspect-[4/1] px-12 w-full
overflow-hidden rounded-lg" and the Image component using fill/object-cover to
apply the chosen fix).

---

Nitpick comments:
In `@app/join-start/2026/page.tsx`:
- Around line 131-139: Replace the inline 56.25% padding hack with Tailwind's
aspect-video utility: remove the style={{ paddingBottom: '56.25%' }} from the
wrapper <div> and add the aspect-video class to its className (the wrapper
currently using "relative w-full overflow-hidden rounded-xl"); keep the inner
<iframe> as-is with className="absolute inset-0 h-full w-full" so it fills the
Tailwind-managed aspect container.
- Around line 168-172: Replace the inline style on the event card div with
Tailwind classes: remove style={{ border: '1px solid rgba(255,255,255,0.08)' }}
and add the equivalent Tailwind utilities to the className (e.g., include
"border border-[rgba(255,255,255,0.08)]" or another project-approved Tailwind
token) so the div keyed by event.title uses Tailwind for the border instead of
inline CSS.
🪄 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: 8f2e9e02-761f-4c45-9870-b49ef303be64

📥 Commits

Reviewing files that changed from the base of the PR and between 216a85c and b67c260.

⛔ Files ignored due to path filters (5)
  • public/join-start/coffee-run-lap.png is excluded by !**/*.png
  • public/join-start/fes.png is excluded by !**/*.png
  • public/join-start/info-session-2026.png is excluded by !**/*.png
  • public/join-start/student-club-fair.jpg is excluded by !**/*.jpg
  • public/join-start/sunset-run.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • app/join-start/2026/page.tsx

Comment thread app/join-start/2026/page.tsx Outdated
@ValentinAhrend
Copy link
Copy Markdown
Contributor Author

FYI this displays currently the old page, but with ?beta=true at the end of the query, you can access the version for april 10th

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: 1

🤖 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/join-start/2026/page.tsx`:
- Around line 34-48: Compute the raw diff first in the useEffect update() (const
raw = TARGET_DATE - Date.now()), derive a boolean isClosed = raw <= 0 and store
it in state (e.g., add setIsClosed/useState for isClosed), then compute the
timeLeft using Math.max(0, raw) and call setTimeLeft as before; update the
UI/CTA logic to read isClosed (not timeLeft === zeros) so the copy and apply
button are disabled/changed when isClosed is true. Ensure the symbols referenced
are TARGET_DATE, update(), setTimeLeft, and add/consume setIsClosed/isClosed in
the component.
🪄 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: 2b59fb3d-0bd8-41d3-9404-255607c7231b

📥 Commits

Reviewing files that changed from the base of the PR and between b67c260 and d97b242.

📒 Files selected for processing (2)
  • app/home/HomeClient.tsx
  • app/join-start/2026/page.tsx

Comment thread app/join-start/2026/page.tsx Outdated
Comment on lines +34 to +48
useEffect(() => {
setMounted(true)
const update = () => {
const diff = Math.max(0, TARGET_DATE - Date.now())
setTimeLeft({
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
minutes: Math.floor((diff / (1000 * 60)) % 60),
seconds: Math.floor((diff / 1000) % 60),
})
}
update()
const interval = setInterval(update, 1000)
return () => clearInterval(interval)
}, [])
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 | 🟠 Major

Track whether applications are already closed.

Math.max(0, TARGET_DATE - Date.now()) removes the signal that the deadline has passed. After April 26, 2026, this will freeze at 00:00:00:00 while the hero still says “Applications close in” and the apply CTA stays active. Derive isClosed from the raw diff first and switch the copy/CTA state off that.

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

In `@app/join-start/2026/page.tsx` around lines 34 - 48, Compute the raw diff
first in the useEffect update() (const raw = TARGET_DATE - Date.now()), derive a
boolean isClosed = raw <= 0 and store it in state (e.g., add
setIsClosed/useState for isClosed), then compute the timeLeft using Math.max(0,
raw) and call setTimeLeft as before; update the UI/CTA logic to read isClosed
(not timeLeft === zeros) so the copy and apply button are disabled/changed when
isClosed is true. Ensure the symbols referenced are TARGET_DATE, update(),
setTimeLeft, and add/consume setIsClosed/isClosed in the component.

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: 2

♻️ Duplicate comments (1)
app/join-start/2026/JoinStartClient.tsx (1)

23-43: ⚠️ Potential issue | 🟠 Major

Recompute launch and closing state on each tick.

setIsLive(...) only runs once, and diff is clamped before you derive any state from it. That means a tab opened before April 10, 2026 never flips from teaser to live, and after April 26, 2026 the countdown just freezes at 00:00:00:00 while the apply CTA still stays enabled. Derive now, rawDiff, isLive, and isClosed from the same update, and rerun when isBeta changes.

🛠️ Suggested direction
   const [mounted, setMounted] = useState(false)
   const [isLive, setIsLive] = useState(false)
+  const [isClosed, setIsClosed] = useState(false)
 
   const searchParams = useSearchParams()
   const isBeta = searchParams.get('beta') === 'true'
 
   useEffect(() => {
     setMounted(true)
-    setIsLive(isBeta || Date.now() >= LAUNCH_DATE)
     const update = () => {
-      const diff = Math.max(0, TARGET_DATE - Date.now())
+      const now = Date.now()
+      const rawDiff = TARGET_DATE - now
+      const diff = Math.max(0, rawDiff)
+      setIsLive(isBeta || now >= LAUNCH_DATE)
+      setIsClosed(rawDiff <= 0)
       setTimeLeft({
         days: Math.floor(diff / (1000 * 60 * 60 * 24)),
         hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
         minutes: Math.floor((diff / (1000 * 60)) % 60),
         seconds: Math.floor((diff / 1000) % 60),
       })
     }
     update()
     const interval = setInterval(update, 1000)
     return () => clearInterval(interval)
-  }, [])
+  }, [isBeta])

The headline/button later in the file should then switch off isClosed instead of inferring closure from zeroed-out units.

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

In `@app/join-start/2026/JoinStartClient.tsx` around lines 23 - 43, The effect
only sets isLive once and clamps diff, causing stale state; change the useEffect
(and its dependency array to include isBeta) so the update() computes a single
now = Date.now(), rawDiff = TARGET_DATE - now, and derives isLive = isBeta ||
now >= LAUNCH_DATE and isClosed = rawDiff <= 0 before calling setIsLive,
setTimeLeft and a new setIsClosed (or existing state variable) from those same
values on every tick; keep the interval/cleanup as-is and ensure the
headline/button logic uses the derived isClosed instead of inferring closure
from zeroed time units.
🤖 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/home/HomeClient.tsx`:
- Around line 158-165: Extract the hardcoded launch timestamp (currently used in
HomeClient.tsx and JoinStartClient.tsx) into a shared constant (e.g., EXPORT
const LAUNCH_DATE = new Date('2026-04-10T00:00:00+02:00')) and import it into
both modules; then change the render condition in HomeClient.tsx to check the
LAUNCH_DATE against Date.now() only after hydration by using the existing
hydration flag (loaded) so the CTA rendering is gated on loaded && Date.now() >=
LAUNCH_DATE.getTime(), preventing prerender/hydration mismatch and centralizing
the date for maintenance.

In `@app/join-start/2026/JoinStartClient.tsx`:
- Around line 52-63: The component currently returns null when mounted is false
which causes a blank page; instead render the pre-launch fallback UI (the same
static Hero used for the !isLive branch) during initial render so the page isn't
blank until hydration. Update the logic in JoinStartClient.tsx around the
mounted and isLive checks (referencing mounted, isLive, and the Hero component)
so that when !mounted you return the static <main> + Hero fallback (or combine
the conditions e.g. if (!mounted || !isLive) show the Hero fallback), leaving
the live countdown UI to render only when mounted && isLive.

---

Duplicate comments:
In `@app/join-start/2026/JoinStartClient.tsx`:
- Around line 23-43: The effect only sets isLive once and clamps diff, causing
stale state; change the useEffect (and its dependency array to include isBeta)
so the update() computes a single now = Date.now(), rawDiff = TARGET_DATE - now,
and derives isLive = isBeta || now >= LAUNCH_DATE and isClosed = rawDiff <= 0
before calling setIsLive, setTimeLeft and a new setIsClosed (or existing state
variable) from those same values on every tick; keep the interval/cleanup as-is
and ensure the headline/button logic uses the derived isClosed instead of
inferring closure from zeroed time units.
🪄 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: e943c222-8541-47f2-a56e-f7d5bd4764b4

📥 Commits

Reviewing files that changed from the base of the PR and between d97b242 and af42e6d.

📒 Files selected for processing (3)
  • app/home/HomeClient.tsx
  • app/join-start/2026/JoinStartClient.tsx
  • app/join-start/2026/page.tsx

Comment thread app/home/HomeClient.tsx
Comment on lines +158 to +165
{Date.now() >= new Date('2026-04-10T00:00:00+02:00').getTime() && (
<Link
href="/join-start/2026"
className={`inline-flex items-center justify-center px-8 py-3 bg-brand-pink text-white font-bold rounded-xl hover:shadow-[0_0_30px_rgba(208,0,111,0.4)] transition-all duration-1000 delay-500 ${loaded ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}
>
Apply Now
</Link>
)}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 8, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files app/home/ app/join-start/ | head -20

Repository: startmunich/website-special-pages

Length of output: 187


🏁 Script executed:

fd -e tsx -e ts app/home HomeClient

Repository: startmunich/website-special-pages

Length of output: 174


🏁 Script executed:

fd -e tsx -e ts app/join-start 2026

Repository: startmunich/website-special-pages

Length of output: 168


🏁 Script executed:

cat -n app/home/HomeClient.tsx | sed -n '150,175p'

Repository: startmunich/website-special-pages

Length of output: 1664


🏁 Script executed:

cat -n app/join-start/2026/JoinStartClient.tsx | sed -n '1,20p'

Repository: startmunich/website-special-pages

Length of output: 743


Stabilize the launch gate across render and hydration cycles.

The Date.now() check on line 158 runs during render, causing the CTA visibility to depend on when the cached homepage HTML was produced. This creates a hydration mismatch on the client around April 10, 2026, where prerendered HTML and the hydrated tree may disagree. The timestamp is also duplicated in app/join-start/2026/JoinStartClient.tsx:8, creating maintenance risk if the launch date changes.

Suggested fix

Extract the timestamp to a shared constant and gate the render on hydration completion:

+const JOIN_START_2026_LAUNCH_AT = new Date('2026-04-10T00:00:00+02:00').getTime()
...
-              {Date.now() >= new Date('2026-04-10T00:00:00+02:00').getTime() && (
+              {loaded && Date.now() >= JOIN_START_2026_LAUNCH_AT && (

This ensures the CTA only appears after the client hydrates, eliminating the mismatch window.

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

In `@app/home/HomeClient.tsx` around lines 158 - 165, Extract the hardcoded launch
timestamp (currently used in HomeClient.tsx and JoinStartClient.tsx) into a
shared constant (e.g., EXPORT const LAUNCH_DATE = new
Date('2026-04-10T00:00:00+02:00')) and import it into both modules; then change
the render condition in HomeClient.tsx to check the LAUNCH_DATE against
Date.now() only after hydration by using the existing hydration flag (loaded) so
the CTA rendering is gated on loaded && Date.now() >= LAUNCH_DATE.getTime(),
preventing prerender/hydration mismatch and centralizing the date for
maintenance.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi, ich approve mal, aber schau dir trotzdem das evtl noch mal an

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the launch date usage in JoinStartClient.tsx
grep -n "2026-04-10\|LAUNCH\|beta\|Date.now\|loaded" app/join-start/2026/JoinStartClient.tsx | head -30

# Also check HomeClient.tsx for current state
grep -n "Date.now\|loaded\|JOIN_START\|LAUNCH" app/home/HomeClient.tsx | head -20

Length of output: 1085


@SimonBurmer Danke! Die beiden Punkte aus meinem ursprünglichen Review sind tatsächlich noch offen:

1. Hydration mismatch (HomeClient.tsx, Line 158)

loaded wird bereits in der className des Buttons verwendet (Line 161), aber nicht in der Render-Bedingung selbst. Das bedeutet der Server rendert den Button basierend auf dem Build-Zeitpunkt, der Client hydratisiert aber zum tatsächlichen Aufruf-Zeitpunkt — das kann kurz um den 10. April zu Inkonsistenzen führen:

- {Date.now() >= new Date('2026-04-10T00:00:00+02:00').getTime() && (
+ {loaded && Date.now() >= LAUNCH_DATE && (

2. Dupliziertes Datum

JoinStartClient.tsx definiert bereits const LAUNCH_DATE = new Date('2026-04-10T00:00:00+02:00').getTime() (Line 8), aber HomeClient.tsx dupliziert denselben Wert inline. Empfehlung: die Konstante in eine geteilte Datei auslagern, z.B.:

// lib/constants.ts
export const LAUNCH_DATE_2026 = new Date('2026-04-10T00:00:00+02:00').getTime()

Und dann in beide Komponenten importieren:

import { LAUNCH_DATE_2026 } from '@/lib/constants'

Da der 10. April bereits vorbei ist und der Button dauerhaft sichtbar ist, ist der Hydration-Bug jetzt weniger kritisch — aber die geteilte Konstante bleibt trotzdem sinnvoll für Wartbarkeit.

Comment on lines +52 to +63
if (!mounted) return null

if (!isLive) {
return (
<main className="min-h-screen bg-brand-dark-blue text-white">
<Hero
backgroundImage="/memberJourney/hero-opt.png"
title={<>JOIN <span className="outline-text">START MUNICH</span></>}
description="Applications for 2026 will open soon. Stay tuned."
/>
</main>
)
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "JoinStartClient.tsx" | head -20

Repository: startmunich/website-special-pages

Length of output: 118


🏁 Script executed:

find . -type f -name "page.tsx" | grep -i "join-start" | head -20

Repository: startmunich/website-special-pages

Length of output: 107


🏁 Script executed:

cat -n ./app/join-start/2026/JoinStartClient.tsx | head -80

Repository: startmunich/website-special-pages

Length of output: 2992


🏁 Script executed:

cat -n ./app/join-start/2026/page.tsx

Repository: startmunich/website-special-pages

Length of output: 933


Render a fallback instead of null to avoid blank page on initial load.

The if (!mounted) return null check (line 52) leaves the route completely blank during server rendering and until the client hydrates, negatively affecting initial page load, SEO, and user experience. Since the pre-launch UI (Hero component, lines 54-63) contains only static content with no client-side dependencies, render it as the default fallback instead of returning null. The live countdown UI can still conditionally render client-side once isLive is determined.

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

In `@app/join-start/2026/JoinStartClient.tsx` around lines 52 - 63, The component
currently returns null when mounted is false which causes a blank page; instead
render the pre-launch fallback UI (the same static Hero used for the !isLive
branch) during initial render so the page isn't blank until hydration. Update
the logic in JoinStartClient.tsx around the mounted and isLive checks
(referencing mounted, isLive, and the Hero component) so that when !mounted you
return the static <main> + Hero fallback (or combine the conditions e.g. if
(!mounted || !isLive) show the Hero fallback), leaving the live countdown UI to
render only when mounted && isLive.

Copy link
Copy Markdown
Collaborator

@SimonBurmer SimonBurmer left a comment

Choose a reason for hiding this comment

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

super nice!

Comment thread app/home/HomeClient.tsx
Comment on lines +158 to +165
{Date.now() >= new Date('2026-04-10T00:00:00+02:00').getTime() && (
<Link
href="/join-start/2026"
className={`inline-flex items-center justify-center px-8 py-3 bg-brand-pink text-white font-bold rounded-xl hover:shadow-[0_0_30px_rgba(208,0,111,0.4)] transition-all duration-1000 delay-500 ${loaded ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}
>
Apply Now
</Link>
)}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi, ich approve mal, aber schau dir trotzdem das evtl noch mal an

@niklas1simakov niklas1simakov merged commit 16522a2 into main Apr 9, 2026
3 checks passed
@niklas1simakov niklas1simakov deleted the feat/join-start branch April 9, 2026 22:45
@coderabbitai coderabbitai Bot mentioned this pull request Apr 9, 2026
ValentinAhrend added a commit that referenced this pull request Apr 9, 2026
- Recompute isLive/isClosed on each tick instead of once
- Show fallback Hero UI instead of blank page before hydration
- Replace nested <main> with <div> to avoid duplicate landmark
- Use Tailwind aspect-video instead of inline padding hack
- Replace inline border style with Tailwind classes
- Remove ineffective px-12 on fill-image container

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ValentinAhrend added a commit that referenced this pull request Apr 9, 2026
- Recompute isLive/isClosed on each tick instead of once
- Show fallback Hero UI instead of blank page before hydration
- Replace nested <main> with <div> to avoid duplicate landmark
- Use Tailwind aspect-video instead of inline padding hack
- Replace inline border style with Tailwind classes
- Remove ineffective px-12 on fill-image container

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

3 participants