feat(header): reflect signed-in state in the marketing header - #52
Conversation
The marketing header always showed Sign In + Sign Up, even for a signed-in user. Read Privy auth state (`usePrivy`) and, once resolved, show a single "Open app" CTA instead. Gated on `ready` so the server and first client render both show the signed-out CTAs (no hydration mismatch), then swap in place. Moves `<Header />` inside `<PrivyAuthProvider>` in the root layout so the header can consume Privy context; the provider is SSR-safe (plain context on the server) so pages stay statically prerenderable. Refs chat#1885. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe root layout now provides Privy authentication context to shared layout components. The header uses Privy state to conditionally render desktop and mobile sign-in, sign-up, open-app, and logout actions. ChangesPrivy authentication layout
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Preview verification — 2026-07-27Preview: https://marketing-4bbca0fux-recoup.vercel.app — built from Results
Signed out Signed in Row 1 is the one worth calling out: the How the signed-in state was reached (and the caveat that comes with it)The marketing header has no login control of its own — it only links out to chat. The single I first tried the zero-side-effect route: reusing a browser context already authenticated against a chat preview. It stayed signed out. That means this preview cannot reproduce the production condition this PR actually targets: a session established on To keep the forced valuation cheap I picked the smallest artist I could find — Sound of Fractures StemsDAO, 5 followers. The run returned $389 (range $267–$547) across 6 measured tracks, and it also confirms the flow still works end-to-end under the reparented provider. Note on the
|
The signed-in header shipped an entry point with no exit: marketing has no logout anywhere (grep for logout/signout across the repo returned zero), so a visitor who signs in through the valuation flow — the only login trigger on the site — was stuck showing 'Open app' with no way to sign out or switch accounts without going to chat. Desktop gets an icon-only button matching the theme toggle, since the header chrome is achromatic and text-free and 'Open app' should stay the only CTA with weight. It carries aria-label and title because a bare glyph has no accessible name. The mobile menu gets a labelled row instead — an icon-only entry reads as decoration in a text list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| Sign Up | ||
| </Link> | ||
| </> | ||
| )} |
There was a problem hiding this comment.
OCP
- actual: signed in button set defined in Header component inline
- required: new component for logged in header button set
There was a problem hiding this comment.
Done in eeade36. The desktop button set now lives in components/layout/HeaderAuthActions.tsx — it owns its own auth state rather than taking a signedIn prop, so Header carries no auth logic at all: no usePrivy import, no signedIn, no branching. Adding another auth-dependent action now touches only that file, not the header shell.
The ready && authenticated guard moved to a shared useSignedIn() hook so it isn't duplicated across the desktop and mobile components.
Header went 134 → 98 lines.
| {signedIn && ( | ||
| <button | ||
| onClick={() => { | ||
| setMobileOpen(false); | ||
| void logout(); | ||
| }} | ||
| className="flex w-full items-center gap-2 px-3 py-2.5 text-sm font-ui font-medium text-(--foreground)/70 sm:hidden" | ||
| > | ||
| <LogOut size={16} /> | ||
| Log out | ||
| </button> | ||
| )} |
There was a problem hiding this comment.
OCP - new inline components / logic should be abstracted to a new component file
There was a problem hiding this comment.
Done in eeade36. The mobile menu's auth rows are now components/layout/HeaderMobileAuthLinks.tsx, taking a single onNavigate callback to close the menu. Same pattern as the desktop counterpart — it resolves its own auth state, so the inline {signedIn && ...} logic is gone from Header.
Addresses review on #52. OCP/SRP (sweetmantech): the signed-in and signed-out button sets were branching inline inside Header, so every new auth-dependent action meant editing the header shell. They now live in HeaderAuthActions (desktop) and HeaderMobileAuthLinks (menu), each owning its own auth state via a shared useSignedIn hook. Header carries no auth logic at all — it went from 134 to 98 lines and no longer imports usePrivy. P2 (cubic): the desktop log-out icon had no breakpoint class, so on phone widths it rendered alongside the labelled menu row — two log-out controls at once. It is now 'hidden sm:inline-flex', mirroring how 'Sign In' has always been hidden below sm in favour of its menu row. useSignedIn also de-duplicates the 'ready && authenticated' guard, which would otherwise be repeated in both components. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>


What
The marketing site header always rendered Sign In + Sign Up, even for a user who is already signed in. This makes the header reflect the signed-in state: once Privy resolves and the user is authenticated, the header shows a single "Open app" CTA (→
siteConfig.appUrl) instead of the sign-in/sign-up pair. The mobile menu's auth link swaps to "Open app" too.Changes
components/layout/Header.tsxusePrivy()(ready,authenticated).signedIn = ready && authenticated— gated onreadyso the server render and first client render both show the signed-out CTAs, avoiding a hydration mismatch; the header then swaps in place once Privy resolves.app/layout.tsx<Header />inside<PrivyAuthProvider>so the header can consume Privy context (it previously sat outside the provider). The provider is documented SSR-safe — it renders a plain context on the server — so pages stay statically prerenderable.Achromatic chrome preserved (no new color); reuses the existing pill styles per DESIGN.md.
Verification
pnpm exec tsc --noEmit— clean for both changed files.pnpm build— compilation + TypeScript pass (✓ Compiled successfully,Running TypeScript ...).Cannot initialize the Privy provider with an invalid Privy app IDbecause there is no realNEXT_PUBLIC_PRIVY_APP_IDsecret in this environment (Privy rejects placeholder IDs at init). This is pre-existing and env-driven —PrivyAuthProvideralready wraps every page onmain, so any static page throws the identical error without the production secret; it is not introduced by this change. Vercel builds/previews with the real app id prerender normally.Scope
Implements only the "marketing header signed-in state" slice of the P3 long tail in chat#1885. Explicitly out of scope (remaining P3 follow-ups):
bash/skilltool components, and "Unknown artist" rows (needssong_artistsingestion).Targets
main. Part of chat#1885.🤖 Generated with Claude Code
Summary by cubic
Update the marketing header to reflect the signed-in state and add logout. After
@privy-io/react-authresolves, show a single “Open app” CTA; provide logout on desktop and mobile.New Features
useSignedIn(built onusePrivyready/authenticated) to gate initial render, then swap to “Open app” →siteConfig.appUrl.hidden sm:inline-flex); mobile menu adds a “Log out” row that closes the menu and callslogout().Refactors
HeaderAuthActions(desktop) andHeaderMobileAuthLinks(menu) with shareduseSignedIn;Headerno longer importsusePrivyor owns auth logic.<Header />,<Footer />, andViewModeBarinsidePrivyAuthProviderinapp/layout.tsxso the header can consume Privy context (SSR-safe).Written for commit eeade36. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes