From 4c0b18e5e4240fd914354093a90d9772da87de4d Mon Sep 17 00:00:00 2001 From: yusufipk Date: Tue, 18 Aug 2026 09:23:21 +0300 Subject: [PATCH] feat(marketing): say no card where the trial is actually claimed Three places still carried the old promise. The register form, which is where the CTA lands and where the trial is granted, said nothing about it at all, so the landing page's claim had to be taken on faith across a page load. The comparison profile summary still described the trial without mentioning the card. And the CtaLink comment quoted a button label that no longer exists. The hero and closing CTAs now read 'Open dashboard' for a signed-in visitor. That button already pointed at /dashboard for them, and offering a free trial to someone who is mid-subscription reads as a bug. The pricing card keeps the short label because it sits directly under 'No credit card' and the card narrows to about 230px at the md breakpoint, where the long label would wrap out of a fixed-height button. --- app/(auth)/register/page.tsx | 3 ++- app/(auth)/register/register-page-client.tsx | 15 +++++++++++++++ components/LandingPage.tsx | 10 +++++++--- components/marketing/comparison-page.tsx | 5 +++-- components/marketing/cta-link.tsx | 7 +++---- lib/marketing/comparison-sources.ts | 2 +- 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/(auth)/register/page.tsx b/app/(auth)/register/page.tsx index 84930c1..6a296ab 100644 --- a/app/(auth)/register/page.tsx +++ b/app/(auth)/register/page.tsx @@ -1,6 +1,6 @@ import { after } from 'next/server'; import { readPageVisitor, recordVisitorEvent } from '@/lib/analytics/visitor'; -import { isInviteCodeRequired } from '@/lib/feature-flags'; +import { isInviteCodeRequired, isStripeFeatureEnabled } from '@/lib/feature-flags'; import { getInvitationPreviewByToken } from '@/lib/invitations'; import { isInvitationPreviewAllowed } from '@/lib/invitation-preview-limit'; import RegisterPageClient from './register-page-client'; @@ -40,6 +40,7 @@ export default async function RegisterPage({ searchParams }: RegisterPageProps) return ( Join OpenFrame to collaborate on video projects + {/* The landing page CTA promises a trial and no card. Say it again here, + where the promise is actually kept, rather than making people take the + previous page's word for it. Invitees are joining someone else's + workspace, so the trial is not what brought them. */} + {trialOnSignup && !isInvitationFlow && ( + + Your 7-day free trial starts as soon as you create the account. No credit card. + + )} {/* OAuth Buttons */} diff --git a/components/LandingPage.tsx b/components/LandingPage.tsx index a99c3ef..63c7146 100644 --- a/components/LandingPage.tsx +++ b/components/LandingPage.tsx @@ -90,6 +90,10 @@ export function LandingPage({ isLoggedIn }: LandingPageProps) { const rootRef = useRef(null); const navbarRef = useRef(null); const hostedCtaHref = isLoggedIn ? '/dashboard' : '/register'; + // Same button, two audiences. Once you are signed in it goes to the dashboard, + // and offering a trial to someone who is already using the product reads as a + // mistake rather than as an offer. + const hostedCtaLabel = isLoggedIn ? 'Open dashboard' : 'Start 7-day free trial, no card required'; useEffect(() => { const cleanupHandlers: Array<() => void> = []; @@ -263,7 +267,7 @@ export function LandingPage({ isLoggedIn }: LandingPageProps) { href={hostedCtaHref} className="group relative isolate inline-flex h-12 min-w-max items-center justify-center overflow-hidden border border-primary bg-primary px-10 text-sm font-medium whitespace-nowrap text-primary-foreground transition-transform duration-300 hover:scale-[1.02]" > - Start 7-day free trial, no card required + {hostedCtaLabel} @@ -722,7 +726,7 @@ export function LandingPage({ isLoggedIn }: LandingPageProps) { href={hostedCtaHref} className="mt-auto group relative isolate inline-flex h-12 w-full items-center justify-center overflow-hidden bg-[#06b6d4] font-medium text-black transition-colors hover:bg-[#06b6d4]/90 text-sm" > - Start free trial + {isLoggedIn ? 'Open dashboard' : 'Start free trial'} @@ -906,7 +910,7 @@ export function LandingPage({ isLoggedIn }: LandingPageProps) { href={hostedCtaHref} className="group relative isolate inline-flex h-12 min-w-max items-center justify-center overflow-hidden border border-primary bg-primary px-10 text-sm font-medium whitespace-nowrap text-primary-foreground transition-transform duration-300 hover:scale-[1.02] md:min-w-[240px]" > - Start 7-day free trial, no card required + {hostedCtaLabel} diff --git a/components/marketing/comparison-page.tsx b/components/marketing/comparison-page.tsx index c97ab47..d6e2705 100644 --- a/components/marketing/comparison-page.tsx +++ b/components/marketing/comparison-page.tsx @@ -17,6 +17,7 @@ interface ComparisonPageProps { export function ComparisonPage({ page, isLoggedIn }: ComparisonPageProps) { const hostedCtaHref = isLoggedIn ? '/dashboard' : '/register'; + const hostedCtaLabel = isLoggedIn ? 'Open dashboard' : 'Start 7-day free trial, no card required'; const competitorName = getCompetitorName(page) ?? 'Alternatives'; const relatedSlugs = page.relatedSlugs.filter((slug) => slug in comparisonPageMap); @@ -46,7 +47,7 @@ export function ComparisonPage({ page, isLoggedIn }: ComparisonPageProps) { href={hostedCtaHref} className="group relative isolate inline-flex h-12 items-center justify-center overflow-hidden border border-primary bg-primary px-8 text-sm font-medium text-primary-foreground transition-transform duration-300 hover:scale-[1.02]" > - Start 7-day free trial, no card required + {hostedCtaLabel} - Start 7-day free trial, no card required + {hostedCtaLabel} = { export const openFrameProfile = { name: 'OpenFrame', tagline: 'Fair-source video review and approval with hosted and self-hosted options.', - pricingSummary: 'Self-hosted free · Hosted $10/mo after 7-day trial', + pricingSummary: 'Self-hosted free · Hosted $10/mo after a 7-day no-card trial', trial: '7-day free trial on hosted plan', strengths: [ 'Timestamped text, voice, image, and drawn annotations',