Skip to content

Commit 2b880b2

Browse files
committed
Freebuff getting started page
1 parent 804958d commit 2b880b2

File tree

6 files changed

+390
-0
lines changed

6 files changed

+390
-0
lines changed
62.7 KB
Loading
393 Bytes
Loading
Lines changed: 10 additions & 0 deletions
Loading
1.29 KB
Loading
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
'use client'
2+
3+
import { AnimatePresence, motion } from 'framer-motion'
4+
import {
5+
ChevronDown,
6+
ChevronUp,
7+
ExternalLink,
8+
Rocket,
9+
} from 'lucide-react'
10+
import Image from 'next/image'
11+
import Link from 'next/link'
12+
import { useEffect, useState } from 'react'
13+
14+
import { BackgroundBeams } from '@/components/background-beams'
15+
import { CopyButton } from '@/components/copy-button'
16+
import { HeroGrid } from '@/components/hero-grid'
17+
import { Icons } from '@/components/icons'
18+
import { cn } from '@/lib/utils'
19+
20+
const INSTALL_COMMAND = 'npm install -g freebuff'
21+
22+
const editors = [
23+
{ name: 'VS Code', icon: '/logos/visual-studio.png' },
24+
{ name: 'Cursor', icon: '/logos/cursor.png' },
25+
{
26+
name: 'IntelliJ',
27+
icon: '/logos/intellij.png',
28+
needsWhiteBg: true,
29+
},
30+
{
31+
name: "Good ol' Terminal",
32+
icon: '/logos/terminal.svg',
33+
},
34+
]
35+
36+
type OS = 'windows' | 'macos' | 'linux'
37+
38+
const detectOS = (): OS => {
39+
if (typeof window !== 'undefined') {
40+
const userAgent = window.navigator.userAgent.toLowerCase()
41+
if (userAgent.includes('mac')) return 'macos'
42+
if (userAgent.includes('win')) return 'windows'
43+
}
44+
return 'linux'
45+
}
46+
47+
function StepBadge({ number }: { number: number }) {
48+
return (
49+
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-acid-matrix flex items-center justify-center text-black font-bold text-sm">
50+
{number}
51+
</div>
52+
)
53+
}
54+
55+
function StepContainer({
56+
children,
57+
isLast = false,
58+
}: {
59+
children: React.ReactNode
60+
isLast?: boolean
61+
}) {
62+
return (
63+
<motion.div
64+
initial={{ opacity: 0, y: 20 }}
65+
whileInView={{ opacity: 1, y: 0 }}
66+
viewport={{ once: true, margin: '-50px' }}
67+
transition={{ duration: 0.4, ease: 'easeOut' }}
68+
className="relative"
69+
>
70+
{!isLast && (
71+
<div className="absolute left-[15px] top-12 bottom-0 w-[2px] bg-gradient-to-b from-acid-matrix/50 to-acid-matrix/10" />
72+
)}
73+
{children}
74+
</motion.div>
75+
)
76+
}
77+
78+
function CommandBlock({ command }: { command: string }) {
79+
return (
80+
<div className="bg-zinc-800/60 border border-zinc-700/40 rounded-md px-3 py-2.5 flex items-center justify-between hover:border-acid-matrix/30 transition-colors duration-200">
81+
<code className="font-mono text-white/90 select-all text-sm">
82+
{command}
83+
</code>
84+
<CopyButton value={command} />
85+
</div>
86+
)
87+
}
88+
89+
interface GetStartedClientProps {
90+
referrerName: string | null
91+
}
92+
93+
export default function GetStartedClient({
94+
referrerName,
95+
}: GetStartedClientProps) {
96+
const [os, setOs] = useState<OS>('linux')
97+
const [helpExpanded, setHelpExpanded] = useState(false)
98+
99+
useEffect(() => {
100+
setOs(detectOS())
101+
}, [])
102+
103+
return (
104+
<div className="relative min-h-screen">
105+
{/* Background layers */}
106+
<div className="absolute inset-0 bg-gradient-to-b from-dark-forest-green via-black/95 to-black" />
107+
<div className="absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_-10%,rgba(124,255,63,0.12),transparent_50%)]" />
108+
<HeroGrid />
109+
<BackgroundBeams />
110+
111+
{/* Nav */}
112+
<motion.div
113+
initial={{ opacity: 0, y: -10 }}
114+
animate={{ opacity: 1, y: 0 }}
115+
transition={{ duration: 0.5, delay: 0.1 }}
116+
className="absolute top-0 left-0 right-0 z-20 container mx-auto px-4 py-4 flex justify-between items-center"
117+
>
118+
<Link
119+
href="/"
120+
className="flex items-center space-x-2 group transition-all duration-300 hover:translate-x-0.5"
121+
>
122+
<Image
123+
src="/logo-icon.png"
124+
alt="Freebuff"
125+
width={28}
126+
height={28}
127+
className="rounded-sm opacity-60 group-hover:opacity-100 transition-all duration-300 group-hover:brightness-110"
128+
/>
129+
<span className="text-xl tracking-widest font-serif text-zinc-400 group-hover:text-white transition-colors duration-200">
130+
freebuff
131+
</span>
132+
</Link>
133+
134+
<nav className="flex items-center space-x-1">
135+
<Link
136+
href="https://github.com/CodebuffAI/codebuff"
137+
target="_blank"
138+
rel="noopener noreferrer"
139+
className="relative font-medium px-3 py-2 rounded-md transition-all duration-200 text-zinc-400 hover:text-white flex items-center gap-2 text-sm"
140+
>
141+
<Icons.github className="h-4 w-4" />
142+
<span className="hidden sm:inline">GitHub</span>
143+
</Link>
144+
</nav>
145+
</motion.div>
146+
147+
{/* Main content */}
148+
<div className="relative z-10 container mx-auto px-4 pt-28 pb-16 md:pt-36 md:pb-24 flex flex-col items-center">
149+
<div className="w-full max-w-2xl">
150+
<div className="bg-background/80 backdrop-blur-sm border border-zinc-800 rounded-xl overflow-hidden">
151+
{/* Header */}
152+
<motion.div
153+
initial={{ opacity: 0, y: -10 }}
154+
animate={{ opacity: 1, y: 0 }}
155+
transition={{ duration: 0.4 }}
156+
className="p-8 pb-6 border-b border-zinc-800"
157+
>
158+
<h1 className="text-2xl md:text-3xl font-bold mb-2 font-serif">
159+
{referrerName
160+
? `${referrerName} invited you to try Freebuff!`
161+
: 'Welcome to Freebuff! 🎉'}
162+
</h1>
163+
<p className="text-muted-foreground">
164+
{referrerName
165+
? 'Get set up in under a minute — it\'s completely free.'
166+
: 'The free coding agent. Get set up in under a minute.'}
167+
</p>
168+
</motion.div>
169+
170+
{/* Steps */}
171+
<div className="p-8 space-y-6">
172+
{/* Step 1: Install */}
173+
<StepContainer>
174+
<div className="flex items-start gap-4">
175+
<StepBadge number={1} />
176+
<div className="flex-1 space-y-4">
177+
<h3 className="text-lg font-semibold">Install Freebuff</h3>
178+
<CommandBlock command={INSTALL_COMMAND} />
179+
180+
{/* Collapsible help */}
181+
<div className="rounded-lg overflow-hidden">
182+
<button
183+
onClick={() => setHelpExpanded(!helpExpanded)}
184+
className="w-full flex items-center justify-between px-4 py-3 text-sm text-muted-foreground hover:text-foreground hover:bg-zinc-800/50 transition-colors cursor-pointer"
185+
>
186+
<span>Need help setting up?</span>
187+
{helpExpanded ? (
188+
<ChevronUp className="w-4 h-4" />
189+
) : (
190+
<ChevronDown className="w-4 h-4" />
191+
)}
192+
</button>
193+
<AnimatePresence>
194+
{helpExpanded && (
195+
<motion.div
196+
initial={{ opacity: 0 }}
197+
animate={{ opacity: 1 }}
198+
exit={{ opacity: 0 }}
199+
transition={{ duration: 0.2 }}
200+
className="px-4 pb-4 border-t border-zinc-700"
201+
>
202+
<div className="space-y-4 mt-4">
203+
<div>
204+
<p className="text-sm font-medium mb-2">
205+
Open your IDE or Terminal
206+
</p>
207+
<p className="text-sm text-muted-foreground mb-3">
208+
Choose your preferred development
209+
environment:
210+
</p>
211+
<div className="grid grid-cols-2 gap-2">
212+
{editors.map((editor) => (
213+
<div
214+
key={editor.name}
215+
className="flex items-center gap-2 px-3 py-2 bg-zinc-800/60 border border-zinc-700/40 rounded-lg hover:border-zinc-600 transition-colors duration-200 cursor-default"
216+
>
217+
<div
218+
className={cn(
219+
'w-5 h-5 relative flex-shrink-0',
220+
editor.needsWhiteBg &&
221+
'bg-white rounded-sm p-[1px]',
222+
)}
223+
>
224+
<Image
225+
src={editor.icon}
226+
alt={editor.name}
227+
fill
228+
className="object-contain"
229+
/>
230+
</div>
231+
<span className="text-sm font-medium text-zinc-200">
232+
{editor.name}
233+
</span>
234+
</div>
235+
))}
236+
</div>
237+
</div>
238+
239+
<div className="border-t border-zinc-700 pt-4">
240+
<div className="bg-zinc-800/50 border border-zinc-700 rounded-lg p-4">
241+
<p className="text-zinc-300 text-sm">
242+
<strong>
243+
Check your Node.js installation:
244+
</strong>{' '}
245+
Open your terminal and run:
246+
</p>
247+
<div className="mt-2 text-xs font-mono">
248+
<code className="bg-zinc-700 px-2 py-1 rounded">
249+
node --version
250+
</code>
251+
</div>
252+
</div>
253+
</div>
254+
255+
{os === 'windows' && (
256+
<div className="bg-yellow-950/50 border border-yellow-800 rounded-lg p-4">
257+
<p className="text-yellow-200 text-sm">
258+
<strong>Windows users:</strong> You may need
259+
to run your terminal as Administrator for
260+
global npm installs.
261+
</p>
262+
</div>
263+
)}
264+
265+
<div className="space-y-2">
266+
<p className="text-sm font-medium">
267+
Need Node.js?
268+
</p>
269+
<a
270+
href="https://nodejs.org/en/download"
271+
target="_blank"
272+
rel="noopener noreferrer"
273+
className="inline-flex items-center gap-1 text-sm text-acid-matrix hover:underline"
274+
>
275+
Download Node.js{' '}
276+
<ExternalLink className="w-3 h-3" />
277+
</a>
278+
</div>
279+
</div>
280+
</motion.div>
281+
)}
282+
</AnimatePresence>
283+
</div>
284+
</div>
285+
</div>
286+
</StepContainer>
287+
288+
{/* Step 2: Navigate to project */}
289+
<StepContainer>
290+
<div className="flex items-start gap-4">
291+
<StepBadge number={2} />
292+
<div className="flex-1 space-y-4">
293+
<h3 className="text-lg font-semibold">
294+
Navigate to your project
295+
</h3>
296+
<p className="text-muted-foreground text-sm">
297+
Open any terminal and <code className="font-mono">cd</code>{' '}
298+
into the project you want to work on.
299+
</p>
300+
<CommandBlock
301+
command={
302+
os === 'windows'
303+
? 'cd C:\\Users\\YourName\\my-project'
304+
: 'cd ~/my-project'
305+
}
306+
/>
307+
</div>
308+
</div>
309+
</StepContainer>
310+
311+
{/* Step 3: Run Freebuff */}
312+
<StepContainer isLast>
313+
<div className="flex items-start gap-4">
314+
<StepBadge number={3} />
315+
<div className="flex-1 space-y-4">
316+
<h3 className="text-lg font-semibold">Run Freebuff</h3>
317+
<p className="text-muted-foreground text-sm">
318+
That&apos;s it — start chatting with the AI to build
319+
faster.
320+
</p>
321+
<CommandBlock command="freebuff" />
322+
</div>
323+
</div>
324+
</StepContainer>
325+
</div>
326+
327+
{/* Footer */}
328+
<motion.div
329+
initial={{ opacity: 0 }}
330+
whileInView={{ opacity: 1 }}
331+
viewport={{ once: true }}
332+
transition={{ duration: 0.4, delay: 0.2 }}
333+
className="p-8 pt-4 border-t border-zinc-800 bg-gradient-to-b from-transparent to-acid-matrix/5"
334+
>
335+
<div className="flex items-center justify-center gap-3 text-center">
336+
<Rocket className="w-5 h-5 text-acid-matrix" />
337+
<p className="text-muted-foreground">
338+
No subscription needed. No configuration. Just works.
339+
</p>
340+
</div>
341+
</motion.div>
342+
</div>
343+
</div>
344+
</div>
345+
</div>
346+
)
347+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import GetStartedClient from './get-started-client'
2+
3+
import type { Metadata } from 'next'
4+
5+
import { siteConfig } from '@/lib/constant'
6+
7+
export async function generateMetadata({
8+
searchParams,
9+
}: {
10+
searchParams: Promise<{ ref?: string }>
11+
}): Promise<Metadata> {
12+
const resolvedSearchParams = await searchParams
13+
const referrerName = resolvedSearchParams.ref
14+
const title = referrerName
15+
? `${referrerName} invited you to try Freebuff!`
16+
: 'Get Started with Freebuff'
17+
18+
return {
19+
title,
20+
description: siteConfig.description,
21+
}
22+
}
23+
24+
export default async function GetStartedPage({
25+
searchParams,
26+
}: {
27+
searchParams: Promise<{ ref?: string }>
28+
}) {
29+
const resolvedSearchParams = await searchParams
30+
const referrerName = resolvedSearchParams.ref?.slice(0, 50) ?? null
31+
32+
return <GetStartedClient referrerName={referrerName} />
33+
}

0 commit comments

Comments
 (0)