Skip to content

Commit 6f75461

Browse files
committed
Remove /strong page. Merge it into /pricing for simplicity
1 parent e67902b commit 6f75461

File tree

6 files changed

+240
-394
lines changed

6 files changed

+240
-394
lines changed

cli/src/commands/command-registry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ export const COMMAND_REGISTRY: CommandDefinition[] = [
384384
name: 'subscribe',
385385
aliases: ['strong'],
386386
handler: (params) => {
387-
open(WEBSITE_URL + '/strong')
387+
open(WEBSITE_URL + '/pricing')
388388
clearInput(params)
389389
},
390390
}),

web/src/app/api/stripe/create-subscription/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export async function POST(req: NextRequest) {
7575
line_items: [{ price: priceId, quantity: 1 }],
7676
allow_promotion_codes: true,
7777
success_url: `${env.NEXT_PUBLIC_CODEBUFF_APP_URL}/profile?tab=usage&subscription_success=true`,
78-
cancel_url: `${env.NEXT_PUBLIC_CODEBUFF_APP_URL}/strong?canceled=true`,
78+
cancel_url: `${env.NEXT_PUBLIC_CODEBUFF_APP_URL}/pricing?canceled=true`,
7979
metadata: {
8080
userId,
8181
type: 'strong_subscription',

web/src/app/pricing/pricing-client.tsx

Lines changed: 237 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,247 @@ import {
66
SUBSCRIPTION_DISPLAY_NAME,
77
type SubscriptionTierPrice,
88
} from '@codebuff/common/constants/subscription-plans'
9-
import { Gift, Shield, Sparkles, Zap, Brain } from 'lucide-react'
10-
import Link from 'next/link'
9+
import { env } from '@codebuff/common/env'
10+
import { loadStripe } from '@stripe/stripe-js'
11+
import { motion } from 'framer-motion'
12+
import { Gift, Shield, Loader2 } from 'lucide-react'
13+
import { useRouter } from 'next/navigation'
1114
import { useSession } from 'next-auth/react'
15+
import { useState } from 'react'
1216

1317
import { BlockColor } from '@/components/ui/decorative-blocks'
1418
import { SECTION_THEMES } from '@/components/ui/landing/constants'
1519
import { FeatureSection } from '@/components/ui/landing/feature'
20+
import { toast } from '@/components/ui/use-toast'
21+
import { cn } from '@/lib/utils'
22+
23+
const USAGE_MULTIPLIER: Record<number, string> = {
24+
100: '1×',
25+
200: '3×',
26+
500: '8×',
27+
}
28+
29+
function SubscribeButton({
30+
className,
31+
tier,
32+
}: {
33+
className?: string
34+
tier?: number
35+
}) {
36+
const { status } = useSession()
37+
const router = useRouter()
38+
const [isLoading, setIsLoading] = useState(false)
39+
40+
const handleSubscribe = async () => {
41+
if (status !== 'authenticated') {
42+
router.push('/login?callbackUrl=/pricing')
43+
return
44+
}
45+
46+
setIsLoading(true)
47+
try {
48+
const res = await fetch('/api/stripe/create-subscription', {
49+
method: 'POST',
50+
headers: { 'Content-Type': 'application/json' },
51+
body: JSON.stringify({ tier }),
52+
})
53+
if (!res.ok) {
54+
const err = await res.json().catch(() => ({}))
55+
throw new Error(err.error || 'Failed to start checkout')
56+
}
57+
const { sessionId } = await res.json()
58+
const stripe = await loadStripe(env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY)
59+
if (!stripe) throw new Error('Stripe failed to load')
60+
const { error } = await stripe.redirectToCheckout({ sessionId })
61+
if (error) throw new Error(error.message)
62+
} catch (err) {
63+
toast({
64+
title: 'Error',
65+
description:
66+
err instanceof Error ? err.message : 'Something went wrong',
67+
variant: 'destructive',
68+
})
69+
} finally {
70+
setIsLoading(false)
71+
}
72+
}
73+
74+
return (
75+
<button
76+
onClick={handleSubscribe}
77+
disabled={isLoading}
78+
className={cn(
79+
'inline-flex items-center justify-center gap-2 rounded-lg px-3 py-2 sm:px-10 sm:py-3.5 text-xs sm:text-base font-semibold transition-all duration-200',
80+
'bg-acid-green text-black hover:bg-acid-green/90 shadow-[0_0_30px_rgba(0,255,149,0.2)] hover:shadow-[0_0_50px_rgba(0,255,149,0.3)]',
81+
'disabled:opacity-60 disabled:cursor-not-allowed',
82+
className,
83+
)}
84+
>
85+
{isLoading ? (
86+
<Loader2 className="h-5 w-5 animate-spin" />
87+
) : (
88+
<>Subscribe</>
89+
)}
90+
</button>
91+
)
92+
}
93+
94+
function StrongHeroSection() {
95+
return (
96+
<div className="min-h-[calc(100vh-64px)] bg-black flex flex-col items-center justify-center relative overflow-hidden px-4 py-12">
97+
{/* Subtle radial glow behind content */}
98+
<div
99+
className="absolute inset-0 pointer-events-none"
100+
style={{
101+
background:
102+
'radial-gradient(ellipse 60% 50% at 50% 40%, rgba(0,255,149,0.06) 0%, transparent 70%)',
103+
}}
104+
/>
105+
106+
{/* Animated gradient blobs */}
107+
<div className="absolute inset-0 pointer-events-none" aria-hidden="true">
108+
<motion.div
109+
className="absolute -inset-[200px] opacity-70"
110+
style={{
111+
background:
112+
'radial-gradient(circle at 30% 40%, rgba(0,255,149,0.1) 0%, transparent 50%)',
113+
filter: 'blur(40px)',
114+
}}
115+
animate={{
116+
x: [0, 100, -50, 0],
117+
y: [0, -80, 60, 0],
118+
scale: [1, 1.1, 0.95, 1],
119+
}}
120+
transition={{ duration: 18, repeat: Infinity, ease: 'easeInOut' }}
121+
/>
122+
<motion.div
123+
className="absolute -inset-[200px] opacity-70"
124+
style={{
125+
background:
126+
'radial-gradient(circle at 70% 60%, rgba(0,255,149,0.07) 0%, transparent 50%)',
127+
filter: 'blur(40px)',
128+
}}
129+
animate={{
130+
x: [0, -80, 60, 0],
131+
y: [0, 50, -70, 0],
132+
scale: [1, 0.95, 1.1, 1],
133+
}}
134+
transition={{ duration: 22, repeat: Infinity, ease: 'easeInOut' }}
135+
/>
136+
</div>
137+
138+
{/* Giant background text */}
139+
<motion.div
140+
className="absolute inset-0 flex items-center justify-center select-none pointer-events-none"
141+
aria-hidden="true"
142+
style={{
143+
fontSize: 'clamp(6rem, 22vw, 20rem)',
144+
fontWeight: 900,
145+
letterSpacing: '-0.02em',
146+
lineHeight: 1,
147+
color: 'transparent',
148+
WebkitTextStroke: '1.5px rgba(0,255,149,0.11)',
149+
background:
150+
'linear-gradient(180deg, rgba(0,255,149,0.14) 0%, rgba(0,255,149,0.02) 100%)',
151+
WebkitBackgroundClip: 'text',
152+
backgroundClip: 'text',
153+
}}
154+
initial={{ opacity: 0, scale: 0.95 }}
155+
animate={{ opacity: 1, scale: 1 }}
156+
transition={{ duration: 2, ease: [0.16, 1, 0.3, 1] }}
157+
>
158+
{SUBSCRIPTION_DISPLAY_NAME.toUpperCase()}
159+
</motion.div>
160+
161+
{/* Foreground content */}
162+
<div className="relative z-10 flex flex-col items-center text-center max-w-4xl">
163+
<div className="max-w-2xl">
164+
<motion.p
165+
className="font-mono text-xs sm:text-sm tracking-[0.3em] text-acid-green/50 uppercase mb-8"
166+
initial={{ opacity: 0 }}
167+
animate={{ opacity: 1 }}
168+
transition={{ duration: 0.8, delay: 0.5 }}
169+
>
170+
codebuff
171+
</motion.p>
172+
173+
<motion.h1
174+
className="text-4xl sm:text-5xl md:text-5xl font-bold text-white mb-3 tracking-tight"
175+
initial={{ opacity: 0, y: 20 }}
176+
animate={{ opacity: 1, y: 0 }}
177+
transition={{ duration: 0.7, delay: 0.7 }}
178+
>
179+
The strongest coding agent
180+
</motion.h1>
181+
182+
<motion.p
183+
className="text-base sm:text-lg text-white/50 mb-12 font-light"
184+
initial={{ opacity: 0, y: 20 }}
185+
animate={{ opacity: 1, y: 0 }}
186+
transition={{ duration: 0.7, delay: 0.9 }}
187+
>
188+
Deep thinking. Multi-agent orchestration. Ship faster.
189+
</motion.p>
190+
</div>
191+
192+
{/* Pricing cards grid */}
193+
<motion.div
194+
className="grid grid-cols-3 gap-2 sm:gap-5 mb-10 w-full"
195+
initial={{ opacity: 0, y: 20 }}
196+
animate={{ opacity: 1, y: 0 }}
197+
transition={{ duration: 0.7, delay: 1.1 }}
198+
>
199+
{Object.entries(SUBSCRIPTION_TIERS).map(([key, tier]) => {
200+
const price = Number(key) as SubscriptionTierPrice
201+
const isHighlighted = price === 200
202+
203+
return (
204+
<div
205+
key={price}
206+
className={cn(
207+
'rounded-xl p-3 sm:p-8 backdrop-blur-sm border flex flex-col items-center transition-all duration-300',
208+
'hover:scale-[1.02]',
209+
isHighlighted
210+
? 'border-acid-green/30 bg-acid-green/[0.04] shadow-[0_0_40px_rgba(0,255,149,0.08)] hover:shadow-[0_0_60px_rgba(0,255,149,0.15)]'
211+
: 'border-white/10 bg-white/[0.02] hover:border-white/20 hover:bg-white/[0.04]',
212+
)}
213+
>
214+
<div className="flex items-baseline justify-center gap-1 mb-1">
215+
<span className="text-xl sm:text-5xl font-bold text-white tracking-tight">
216+
${tier.monthlyPrice}
217+
</span>
218+
<span className="text-xs sm:text-sm text-white/30">/mo</span>
219+
</div>
220+
221+
<p className="text-xs sm:text-sm text-white/40 mb-3 sm:mb-6">
222+
{USAGE_MULTIPLIER[price]} usage
223+
</p>
224+
225+
<SubscribeButton
226+
tier={price}
227+
className={cn(
228+
'w-full',
229+
!isHighlighted &&
230+
'bg-white/10 text-white hover:bg-white/20 shadow-none hover:shadow-none',
231+
)}
232+
/>
233+
</div>
234+
)
235+
})}
236+
</motion.div>
237+
238+
<motion.p
239+
className="text-xs text-white/30 tracking-wide"
240+
initial={{ opacity: 0 }}
241+
animate={{ opacity: 1 }}
242+
transition={{ duration: 0.8, delay: 1.6 }}
243+
>
244+
Cancel anytime · Tax not included · Usage amounts subject to change
245+
</motion.p>
246+
</div>
247+
</div>
248+
)
249+
}
16250

17251
function CreditVisual() {
18252
return (
@@ -68,97 +302,6 @@ function PricingCard() {
68302
)
69303
}
70304

71-
const USAGE_MULTIPLIER: Record<number, string> = {
72-
100: '1×',
73-
200: '3×',
74-
500: '8×',
75-
}
76-
77-
function StrongSubscriptionIllustration() {
78-
return (
79-
<div className="flex flex-col items-center text-center pt-6">
80-
<div className="bg-black rounded-2xl border border-white/10 shadow-[0_0_60px_rgba(0,255,149,0.08)] p-6 w-full max-w-md">
81-
<div className="flex flex-col items-center space-y-5">
82-
{/* Header with Strong branding */}
83-
<div className="flex flex-col items-center gap-1">
84-
<div className="flex items-center gap-3">
85-
<div className="p-2 rounded-full bg-acid-green/10 border border-acid-green/20">
86-
<Sparkles className="h-5 w-5 text-acid-green" />
87-
</div>
88-
<div className="text-xl font-bold text-white">
89-
{SUBSCRIPTION_DISPLAY_NAME} Subscription
90-
</div>
91-
</div>
92-
<div className="text-xs text-white/40">Monthly plans</div>
93-
</div>
94-
95-
{/* Benefits */}
96-
<div className="grid grid-cols-1 gap-2 w-full">
97-
<div className="flex items-center gap-3 bg-white/5 rounded-lg p-3 border border-white/5">
98-
<div className="p-1.5 rounded-full bg-acid-green/10">
99-
<Brain className="h-4 w-4 text-acid-green" />
100-
</div>
101-
<div className="text-left">
102-
<div className="text-sm font-medium text-white">Deep thinking</div>
103-
<div className="text-xs text-white/40">
104-
Multi-agent orchestration for complex tasks
105-
</div>
106-
</div>
107-
</div>
108-
109-
<div className="flex items-center gap-3 bg-white/5 rounded-lg p-3 border border-white/5">
110-
<div className="p-1.5 rounded-full bg-acid-green/10">
111-
<Zap className="h-4 w-4 text-acid-green" />
112-
</div>
113-
<div className="text-left">
114-
<div className="text-sm font-medium text-white">Save on credits</div>
115-
<div className="text-xs text-white/40">
116-
Get more usage compared to pay-as-you-go
117-
</div>
118-
</div>
119-
</div>
120-
</div>
121-
122-
{/* Pricing tiers */}
123-
<div className="grid grid-cols-3 gap-2 w-full">
124-
{Object.entries(SUBSCRIPTION_TIERS).map(([key, tier]) => {
125-
const price = Number(key) as SubscriptionTierPrice
126-
const isHighlighted = price === 200
127-
128-
return (
129-
<div
130-
key={price}
131-
className={`rounded-lg p-3 border flex flex-col items-center ${
132-
isHighlighted
133-
? 'border-acid-green/30 bg-acid-green/[0.08]'
134-
: 'border-white/10 bg-white/5'
135-
}`}
136-
>
137-
<div className="text-lg font-bold text-white">
138-
${tier.monthlyPrice}
139-
</div>
140-
<div className="text-xs text-white/40">/mo</div>
141-
<div className="text-xs text-white/50 mt-1">
142-
{USAGE_MULTIPLIER[price]} usage
143-
</div>
144-
</div>
145-
)
146-
})}
147-
</div>
148-
149-
{/* CTA */}
150-
<Link
151-
href="/strong"
152-
className="w-full bg-white hover:bg-white/90 text-black font-semibold py-3 px-6 rounded-lg transition-colors text-center"
153-
>
154-
Learn More
155-
</Link>
156-
</div>
157-
</div>
158-
</div>
159-
)
160-
}
161-
162305
function TeamPlanIllustration() {
163306
return (
164307
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6 w-full max-w-screen-lg mx-auto">
@@ -247,20 +390,7 @@ export default function PricingClient() {
247390

248391
return (
249392
<>
250-
<FeatureSection
251-
title={<span>Codebuff {SUBSCRIPTION_DISPLAY_NAME}</span>}
252-
description="Deep thinking, multi-agent orchestration, and unlimited potential. Subscribe to save credits with plans starting at $100/mo."
253-
backdropColor={BlockColor.DarkForestGreen}
254-
decorativeColors={[BlockColor.DarkForestGreen, BlockColor.AcidMatrix]}
255-
textColor="text-white"
256-
tagline="POWER SUBSCRIPTION"
257-
highlightText="The strongest coding agent subscription"
258-
highlightIcon="💪"
259-
illustration={<StrongSubscriptionIllustration />}
260-
learnMoreText="Learn More"
261-
learnMoreLink="/strong"
262-
imagePosition="left"
263-
/>
393+
<StrongHeroSection />
264394

265395
<FeatureSection
266396
title={<span>Simple, Usage-Based Pricing</span>}

web/src/app/profile/components/subscription-section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ function SubscriptionCta() {
160160
</p>
161161
</div>
162162
</div>
163-
<Link href="/strong">
163+
<Link href="/pricing">
164164
<Button className="bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-500 dark:hover:bg-indigo-600">
165165
Learn More
166166
</Button>

0 commit comments

Comments
 (0)