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>
38 lines
1.8 KiB
TypeScript
38 lines
1.8 KiB
TypeScript
import { Link } from 'react-router-dom'
|
|
import { PageMeta } from '@/components/common/PageMeta'
|
|
|
|
export default function PromotionsPage() {
|
|
return (
|
|
<>
|
|
<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="/" 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>
|
|
|
|
<div className="space-y-6 text-muted-foreground leading-relaxed">
|
|
<section>
|
|
<h2 className="text-xl font-semibold text-foreground mb-3">Current promotions</h2>
|
|
<p>No promotions are currently active.</p>
|
|
<p className="mt-3">
|
|
Promotional offers, when running, will be listed on this page with their specific terms (eligible plans, duration, redemption rules, expiration). The general terms that apply to all promotions are described in{' '}
|
|
<Link to="/policies" className="text-primary hover:underline">Section 6 of our Customer Policies</Link>.
|
|
</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h2 className="text-xl font-semibold text-foreground mb-3">Questions</h2>
|
|
<p>
|
|
Email{' '}
|
|
<a href="mailto:billing@resolutionflow.com" className="text-primary hover:underline">billing@resolutionflow.com</a>{' '}
|
|
for questions about a promotion you received by email, or to ask about upcoming offers.
|
|
</p>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|