From d43f334a2e51f300e4dc96ffb74438f4706f8c5e Mon Sep 17 00:00:00 2001 From: PARTH J ROHIT Date: Tue, 1 Sep 2026 20:02:49 +0100 Subject: [PATCH] feat(frontend): landing page CTA goes straight to login/register (#386) PARTHA stays self-hosted only for the foreseeable future, with no hosted service planned -- there's no reason for a fresh guest on a self-hosted instance to be funneled through a waitlist in the UI before they can even try registering. The landing page's unauthenticated "Analyze a Repository" hotspots (nav, hero, and footer positions -- all three share the same analysisCta handler) now link directly to /register, the same way the "Log In" nav hotspot already links directly to /login. Removes the WaitlistModal import and waitlistOpen state from LandingPage.tsx entirely. Registration itself is unchanged: it still enforces the admin-managed email allowlist (#374/#375) or the local development bypass (#384) exactly as before. This only changes what the landing page's CTA points at, not what completing registration requires. Deliberately not touched, per the issue's own scope: the backend /waitlist route, WaitlistEntry model, and the frontend's features/waitlist module (WaitlistModal, useWaitlistForm) -- now unreferenced by the landing page but still present and still covered by their own tests. Whether that infrastructure should be removed entirely is a separate, larger, cross-cutting question (flagged in PR #383, not decided yet). Updated router.test.tsx's assertion to match the new link-based CTA instead of the old waitlist-button one. Verified: tsc/eslint clean, full vitest suite green (439/439, including LandingPage.test.tsx, router.test.tsx, RegisterPage.test.tsx, and WaitlistModal.test.tsx, which still passes since the component itself is untouched), production build clean. Real in-browser click-through against actual dev servers (backend + frontend) in both light and dark mode: "Log In" opens the sign-in page, "Analyze a Repository" opens account creation, no waitlist prompt anywhere in either path, and no "waitlist" text anywhere on the rendered landing page. --- apps/frontend/src/app/pages/LandingPage.tsx | 18 ++++++++---------- apps/frontend/src/app/routes/router.test.tsx | 9 +++++---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/apps/frontend/src/app/pages/LandingPage.tsx b/apps/frontend/src/app/pages/LandingPage.tsx index abc789e..c01e9aa 100644 --- a/apps/frontend/src/app/pages/LandingPage.tsx +++ b/apps/frontend/src/app/pages/LandingPage.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { useAuthStore } from '@/app/store/useAuthStore'; -import { WaitlistModal } from '@/features/waitlist/components/WaitlistModal'; import { ThemeSwitcher } from '@/features/landing/components/ThemeSwitcher'; import { useLandingTheme } from '@/features/landing/hooks/useLandingTheme'; import landingReference from '@/assets/landing/landing-reference.svg'; @@ -25,7 +24,6 @@ export function LandingPage() { const authenticated = useAuthStore((state) => state.status === 'authenticated'); const [faqIndex, setFaqIndex] = useState(null); const [footerNotice, setFooterNotice] = useState(null); - const [waitlistOpen, setWaitlistOpen] = useState(false); const workspaceHref = authenticated ? '/dashboard' : '/login'; const theme = useLandingTheme(); const dark = theme.resolved === 'dark'; @@ -36,16 +34,18 @@ export function LandingPage() { document.documentElement.removeAttribute('data-landing-theme-boot'); }, []); - // Registration is invite-only (#341): an unauthenticated visitor's "analyze - // a repository" intent opens the waitlist instead of navigating to - // /register, which they cannot usefully complete without an invite yet. + // Self-hosted is the only deployment model for the foreseeable future + // (#382): an unauthenticated visitor's "analyze a repository" intent goes + // straight to account creation on this instance, same as it would for + // anyone standing up their own copy of PARTHA. Registration itself still + // enforces the admin-managed email allowlist (#374/#375) or the + // development bypass (#384) exactly as before -- this only changes what + // the landing page's CTA points at, not what registering requires. const analysisCta = (className: string) => authenticated ? ( Analyze a repository ) : ( - + Create an account ); return ( @@ -124,8 +124,6 @@ export function LandingPage() { )} - {waitlistOpen && setWaitlistOpen(false)} />} - {footerNotice && (
{footerNotice} diff --git a/apps/frontend/src/app/routes/router.test.tsx b/apps/frontend/src/app/routes/router.test.tsx index ed550c4..bece15b 100644 --- a/apps/frontend/src/app/routes/router.test.tsx +++ b/apps/frontend/src/app/routes/router.test.tsx @@ -47,11 +47,12 @@ describe('deep-linked route reachability (#179)', () => { expect( await screen.findByRole('heading', { name: 'Reveal the system behind the code.', level: 1 }, { timeout: ROUTE_RENDER_TIMEOUT_MS }), ).toBeInTheDocument(); - // Registration is invite-only (#341): an unauthenticated visitor's - // "analyze a repository" intent opens the waitlist rather than linking - // to /register, which they cannot usefully complete without an invite. + // Self-hosted is the only deployment model (#382): an unauthenticated + // visitor's "analyze a repository" intent links straight to account + // creation on this instance, not a waitlist -- registration itself + // still enforces the allowlist (#374/#375) or dev bypass (#384). expect(screen.queryAllByRole('link', { name: /analyze a repository/i })).toHaveLength(0); - expect(screen.getAllByRole('button', { name: /join the waitlist/i }).length).toBeGreaterThan(0); + expect(screen.getAllByRole('link', { name: /create an account/i }).length).toBeGreaterThan(0); }); it('renders Dashboard directly at /dashboard instead of 404ing', async () => {