feat(routing): serve public landing at / and move authed index to /home
Stripe's compliance crawler fetches the apex URL without executing JS and declined live-mode review when `https://resolutionflow.com/` returned the empty SPA shell that redirected to /landing client-side. Restructure the router so / serves LandingPage directly: - `/` → new `PublicLanding` wrapper (LandingPage for anon; Navigate to /home for authed users so there's no marketing-frame flicker). - Authed tree converted to a path-less layout route with absolute child paths. QuickStartPage moves to `/home`; all other children (`/trees`, `/pilot`, `/admin/*`, `/account/*`, etc.) keep their URLs. - `/landing` kept as a one-release stale-bookmark redirect to /. - `ProtectedRoute` unauth redirect flipped /landing → /; `state.from` preserved for post-login return. Reference updates: - Post-login / post-onboarding destinations → /home: OAuthCallbackPage (incl. `?welcome=teammate` query), WelcomeStep1/2/3 dismiss-rest, AssistantChatPage post-escalate, WelcomeRouter completion/dismiss redirects, VerifyEmailPage's three "Go to dashboard" links. - Authed chrome → /home: TopBar logo, AppLayout mobile nav + drawer logo, CommandPalette Dashboard entry. - Dashboard onboarding → /home: NextStepCard `ran_session.ctaPath`, SetupChecklist `ran_session.path`, SessionHistoryPage empty-state CTA. - Public back-links → /: TermsPage, PrivacyPage, PoliciesPage, ContactPage, PromotionsPage, PublicTemplatesPage (header + footer). SharedSessionPage's `to="/"` left as-is — now correctly lands anon visitors on the public landing. Crawlability: - New `frontend/public/robots.txt` allowlisting public pages and disallowing the authed app. - New `frontend/public/sitemap.xml` for /, /pricing, /contact-sales, /contact, /templates, /terms, /privacy, /policies, /promotions. - `PageMeta` gains an `og:url` (defaults to `window.location.href`) and flips `twitter:card` to `summary_large_image` when an `ogImage` is passed. Tests: - `AppLayout.test.tsx` updated to mount at `/home`. - New `ProtectedRoute.test.tsx` asserts unauthenticated `/home` redirects to `/` (not `/landing`) and preserves origin in `state.from`. If Stripe's crawler still cannot see the site after this (zero-JS crawler), the documented next escalation is server-side prerendering of public routes via `vite-plugin-ssg`. Out of scope here. Plan: docs/plans/2026-05-13-public-landing-routing-refactor.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2416,7 +2416,7 @@ export default function AssistantChatPage() {
|
||||
setShowConclude(false)
|
||||
if (activeSessionStatus === 'escalated') {
|
||||
toast.info('Session escalated. Heading back to your dashboard.')
|
||||
navigate('/')
|
||||
navigate('/home')
|
||||
}
|
||||
}}
|
||||
onConclude={handleConclude}
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function ContactPage() {
|
||||
<PageMeta title="Contact" description="Contact ResolutionFlow customer service, sales, billing, or security." />
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="mx-auto max-w-3xl px-6 py-16">
|
||||
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<Link to="/" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<h1 className="text-3xl font-bold font-heading mb-4">Contact ResolutionFlow</h1>
|
||||
<p className="text-muted-foreground mb-10">
|
||||
We respond to customer inquiries Monday through Friday during U.S. business hours, excluding federal holidays. Email is the fastest path to a response.
|
||||
|
||||
@@ -112,10 +112,10 @@ export function OAuthCallbackPage() {
|
||||
|
||||
// Invitee path lands on the dashboard with the teammate-welcome
|
||||
// marker; new self-serve owners go to the welcome wizard; returning
|
||||
// users to /.
|
||||
let dest = '/'
|
||||
// users to /home.
|
||||
let dest = '/home'
|
||||
if (decoded?.accountInviteCode) {
|
||||
dest = '/?welcome=teammate'
|
||||
dest = '/home?welcome=teammate'
|
||||
} else if (result.is_new_user) {
|
||||
dest = '/welcome'
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function PoliciesPage() {
|
||||
<PageMeta title="Customer Policies" description="ResolutionFlow customer service, billing, refunds, cancellation, legal restrictions, and promotional terms." />
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="mx-auto max-w-3xl px-6 py-16">
|
||||
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<Link to="/" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<h1 className="text-3xl font-bold font-heading mb-4">Customer Policies</h1>
|
||||
<p className="text-muted-foreground mb-2">Last updated: May 7, 2026</p>
|
||||
<p className="text-muted-foreground mb-2"><strong className="text-foreground">Operator:</strong> ResolutionFlow, LLC (the “Company”), operator of ResolutionFlow (“Service”).</p>
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function PrivacyPage() {
|
||||
<PageMeta title="Privacy Policy" description="ResolutionFlow Privacy Policy" />
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="mx-auto max-w-3xl px-6 py-16">
|
||||
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<Link to="/" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<h1 className="text-3xl font-bold font-heading mb-8">Privacy Policy</h1>
|
||||
<p className="text-muted-foreground mb-6">Last updated: March 21, 2026</p>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function PromotionsPage() {
|
||||
<PageMeta title="Promotions" description="Active ResolutionFlow promotional offers and their terms." />
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="mx-auto max-w-3xl px-6 py-16">
|
||||
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<Link to="/" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<h1 className="text-3xl font-bold font-heading mb-4">Promotions</h1>
|
||||
<p className="text-muted-foreground mb-10">Last updated: May 7, 2026</p>
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ export default function PublicTemplatesPage() {
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-40 border-b border-border bg-background/80">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
|
||||
<Link to="/landing" className="flex items-center gap-2.5">
|
||||
<Link to="/" className="flex items-center gap-2.5">
|
||||
<BrandLogo size="sm" />
|
||||
<span className="font-heading text-lg font-semibold">
|
||||
<span className="text-foreground">Resolution</span>
|
||||
@@ -406,7 +406,7 @@ export default function PublicTemplatesPage() {
|
||||
<footer className="border-t border-border py-8 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-7xl mx-auto flex items-center justify-between">
|
||||
<Link
|
||||
to="/landing"
|
||||
to="/"
|
||||
className="text-muted-foreground text-sm hover:text-foreground transition-colors"
|
||||
>
|
||||
Powered by <span className="font-semibold">ResolutionFlow</span>
|
||||
|
||||
@@ -423,7 +423,7 @@ export default function SessionHistoryPage() {
|
||||
description="Start a FlowPilot or chat session to begin. All your sessions will appear here."
|
||||
action={
|
||||
<Link
|
||||
to="/"
|
||||
to="/home"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-primary px-5 py-2.5 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Start a Session
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function TermsPage() {
|
||||
<PageMeta title="Terms of Service" description="ResolutionFlow Terms of Service" />
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="mx-auto max-w-3xl px-6 py-16">
|
||||
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<Link to="/" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<h1 className="text-3xl font-bold font-heading mb-8">Terms of Service</h1>
|
||||
<p className="text-muted-foreground mb-6">Last updated: March 21, 2026</p>
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ export function VerifyEmailPage() {
|
||||
action needed.
|
||||
</p>
|
||||
<Link
|
||||
to="/"
|
||||
to="/home"
|
||||
className={cn(
|
||||
'mt-6 inline-flex items-center rounded-lg bg-primary px-6 py-2 text-sm font-semibold text-primary-foreground',
|
||||
'hover:brightness-110',
|
||||
@@ -181,7 +181,7 @@ export function VerifyEmailPage() {
|
||||
Resend verification email
|
||||
</button>
|
||||
<Link
|
||||
to="/"
|
||||
to="/home"
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center rounded-lg border border-default bg-input px-6 py-2 text-sm font-medium text-foreground',
|
||||
'hover:border-border-hover',
|
||||
@@ -204,7 +204,7 @@ export function VerifyEmailPage() {
|
||||
Try the link in your verification email again.
|
||||
</p>
|
||||
<Link
|
||||
to="/"
|
||||
to="/home"
|
||||
className={cn(
|
||||
'mt-6 inline-flex items-center rounded-lg bg-primary px-6 py-2 text-sm font-semibold text-primary-foreground',
|
||||
'hover:brightness-110',
|
||||
|
||||
@@ -6,8 +6,8 @@ import { PageLoader } from '@/components/common/PageLoader'
|
||||
* `/welcome` index — redirect to the next incomplete step (or `/` if done /
|
||||
* dismissed). Decision table:
|
||||
*
|
||||
* onboarding_dismissed === true → /
|
||||
* onboarding_step_completed >= 3 → /
|
||||
* onboarding_dismissed === true → /home
|
||||
* onboarding_step_completed >= 3 → /home
|
||||
* onboarding_step_completed === null/0 → /welcome/step-1
|
||||
* onboarding_step_completed === 1 → /welcome/step-2
|
||||
* onboarding_step_completed === 2 → /welcome/step-3
|
||||
@@ -19,10 +19,10 @@ export function WelcomeRouter() {
|
||||
// the page loader rather than racing past the redirect.
|
||||
if (!user) return <PageLoader />
|
||||
|
||||
if (user.onboarding_dismissed) return <Navigate to="/" replace />
|
||||
if (user.onboarding_dismissed) return <Navigate to="/home" replace />
|
||||
|
||||
const completed = user.onboarding_step_completed ?? 0
|
||||
if (completed >= 3) return <Navigate to="/" replace />
|
||||
if (completed >= 3) return <Navigate to="/home" replace />
|
||||
if (completed === 2) return <Navigate to="/welcome/step-3" replace />
|
||||
if (completed === 1) return <Navigate to="/welcome/step-2" replace />
|
||||
return <Navigate to="/welcome/step-1" replace />
|
||||
|
||||
@@ -85,7 +85,7 @@ export function WelcomeStep1() {
|
||||
try {
|
||||
await onboardingApi.dismissRest()
|
||||
await fetchUser()
|
||||
navigate('/')
|
||||
navigate('/home')
|
||||
} catch {
|
||||
setError('Could not save. Please try again.')
|
||||
setSubmitting(null)
|
||||
|
||||
@@ -90,7 +90,7 @@ export function WelcomeStep2() {
|
||||
try {
|
||||
await onboardingApi.dismissRest()
|
||||
await fetchUser()
|
||||
navigate('/')
|
||||
navigate('/home')
|
||||
} catch {
|
||||
setError('Could not save. Please try again.')
|
||||
setSubmitting(null)
|
||||
|
||||
@@ -191,7 +191,7 @@ export function WelcomeStep3() {
|
||||
try {
|
||||
await onboardingApi.dismissRest()
|
||||
await fetchUser()
|
||||
navigate('/')
|
||||
navigate('/home')
|
||||
} catch {
|
||||
setError('Could not save. Please try again.')
|
||||
setSubmitting(null)
|
||||
|
||||
Reference in New Issue
Block a user