Skip to content

Commit be13251

Browse files
feat(pricing): inline repo input so visitors convert in place
86 visitors/yr hit /pricing expecting a price. They now get the "Death is free" copy plus an immediate input — no need to bounce back to / and lose context. Submitting parses the input (owner/repo, github URL, or bare username) and routes to /?repo= or /user/[username] so the existing analyzer flow handles it. Tracks pricing_repo_submitted with the kind (repo|user) so we can measure conversion from this page specifically. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 3d834af commit be13251

1 file changed

Lines changed: 110 additions & 9 deletions

File tree

src/app/pricing/PricingContent.tsx

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,48 @@
11
'use client'
22

3-
import Link from 'next/link'
3+
import { FormEvent, useState } from 'react'
4+
import { useRouter } from 'next/navigation'
5+
import { track } from '@vercel/analytics'
46
import SubpageShell from '@/components/SubpageShell'
57

8+
const MONO = `var(--font-courier), system-ui, sans-serif`
9+
const VALID_USERNAME = /^[a-zA-Z0-9_.-]+$/
10+
11+
type ParsedInput =
12+
| { kind: 'repo'; slug: string }
13+
| { kind: 'user'; username: string }
14+
| null
15+
16+
function parsePricingInput(value: string): ParsedInput {
17+
const trimmed = value.trim()
18+
if (!trimmed) return null
19+
20+
const githubRepoUrl = trimmed.match(/(?:https?:\/\/)?(?:www\.)?github\.com\/([^/\s]+)\/([^/\s#?]+)(?:[/?#]|$)/i)
21+
if (githubRepoUrl) {
22+
const owner = githubRepoUrl[1]
23+
const repo = githubRepoUrl[2].replace(/\.git$/i, '')
24+
return { kind: 'repo', slug: `${owner}/${repo}` }
25+
}
26+
27+
const githubUserUrl = trimmed.match(/(?:https?:\/\/)?(?:www\.)?github\.com\/([^/\s?#]+)\/?$/i)
28+
if (githubUserUrl && VALID_USERNAME.test(githubUserUrl[1])) {
29+
return { kind: 'user', username: githubUserUrl[1] }
30+
}
31+
32+
const slugMatch = trimmed.match(/^([^/\s]+)\/([^/\s]+)$/)
33+
if (slugMatch) {
34+
const owner = slugMatch[1]
35+
const repo = slugMatch[2].replace(/\.git$/i, '')
36+
return { kind: 'repo', slug: `${owner}/${repo}` }
37+
}
38+
39+
if (VALID_USERNAME.test(trimmed)) {
40+
return { kind: 'user', username: trimmed }
41+
}
42+
43+
return null
44+
}
45+
646
const SECTIONS = [
747
{
848
id: 'no-tiers',
@@ -33,6 +73,25 @@ const SECTIONS = [
3373
]
3474

3575
export default function PricingContent() {
76+
const router = useRouter()
77+
const [value, setValue] = useState('')
78+
const [invalid, setInvalid] = useState(false)
79+
80+
function handleSubmit(e: FormEvent) {
81+
e.preventDefault()
82+
const parsed = parsePricingInput(value)
83+
if (!parsed) {
84+
setInvalid(true)
85+
return
86+
}
87+
track('pricing_repo_submitted', { kind: parsed.kind })
88+
if (parsed.kind === 'repo') {
89+
router.push(`/?repo=${encodeURIComponent(parsed.slug)}`)
90+
} else {
91+
router.push(`/user/${encodeURIComponent(parsed.username)}`)
92+
}
93+
}
94+
3695
return (
3796
<SubpageShell
3897
title="Death Is Free."
@@ -78,19 +137,61 @@ export default function PricingContent() {
78137
style={{
79138
border: '2px solid var(--c-border)',
80139
background: 'var(--c-panel-2, transparent)',
81-
textAlign: 'center',
82140
padding: '28px 20px',
83141
marginTop: '8px',
84142
}}
85143
>
86-
<p className="record-label" style={{ marginBottom: '14px' }}>Begin the Examination</p>
87-
<Link
88-
href="/"
89-
className="subpage-faq-cta"
90-
style={{ display: 'inline-flex', justifyContent: 'center' }}
144+
<p className="record-label" style={{ marginBottom: '14px', textAlign: 'center' }}>Begin the Examination</p>
145+
<form
146+
onSubmit={handleSubmit}
147+
style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}
91148
>
92-
⚰ Bury a repo →
93-
</Link>
149+
<input
150+
type="text"
151+
value={value}
152+
onChange={e => { setValue(e.target.value); if (invalid) setInvalid(false) }}
153+
placeholder="username or owner/repo"
154+
aria-label="GitHub username or repo"
155+
autoComplete="off"
156+
spellCheck={false}
157+
style={{
158+
fontFamily: MONO,
159+
fontSize: '14px',
160+
padding: '12px 14px',
161+
background: 'var(--c-bg)',
162+
color: 'var(--c-ink)',
163+
border: `2px solid ${invalid ? 'var(--c-red, #8B0000)' : 'var(--c-ink)'}`,
164+
outline: 'none',
165+
width: '100%',
166+
minHeight: '44px',
167+
}}
168+
/>
169+
<button
170+
type="submit"
171+
className="subpage-faq-cta"
172+
style={{
173+
fontFamily: MONO,
174+
fontSize: '13px',
175+
fontWeight: 700,
176+
letterSpacing: '0.06em',
177+
background: 'var(--c-ink)',
178+
color: 'var(--c-bg)',
179+
border: '2px solid var(--c-ink)',
180+
cursor: 'pointer',
181+
minHeight: '44px',
182+
display: 'inline-flex',
183+
alignItems: 'center',
184+
justifyContent: 'center',
185+
}}
186+
>
187+
⚰ Issue death certificate →
188+
</button>
189+
{invalid && (
190+
<p style={{ fontFamily: MONO, fontSize: '12px', color: 'var(--c-red, #8B0000)', margin: 0, textAlign: 'center' }}>
191+
Could not parse. Try a github URL or owner/repo.
192+
</p>
193+
)}
194+
</form>
94195
</div>
95196
</div>
96197
</SubpageShell>

0 commit comments

Comments
 (0)