Skip to content

Commit f4dc904

Browse files
fix: referral page wasn't working
🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent 0cc7084 commit f4dc904

File tree

13 files changed

+1262
-47
lines changed

13 files changed

+1262
-47
lines changed

knowledge.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,30 @@ Important constants are centralized in `common/src/constants.ts`:
253253
- `CREDITS_REFERRAL_BONUS`: Credits awarded for successful referral
254254
- Credit limits for different user types
255255

256+
## Referral System
257+
258+
**IMPORTANT**: Referral codes must be applied through the npm-app CLI, not through the web interface.
259+
260+
- Web onboarding flow shows instructions for entering codes in CLI
261+
- Users must type their referral code in the Codebuff terminal after login
262+
- Auto-redemption during web login was removed to prevent abuse
263+
- The `handleReferralCode` function in `npm-app/src/client.ts` handles CLI redemption
264+
- The `redeemReferralCode` function in `web/src/app/api/referrals/helpers.ts` processes the actual credit granting
265+
266+
### OAuth Referral Code Preservation
267+
268+
**Problem**: NextAuth doesn't preserve referral codes through OAuth flow because:
269+
270+
- NextAuth generates its own state parameter for CSRF/PKCE protection
271+
- Custom state parameters are ignored/overwritten
272+
- OAuth callback URLs don't always survive the round trip
273+
274+
**Solution**: Multi-layer approach implemented in SignInButton and ReferralRedirect components:
275+
276+
1. **Primary**: Use absolute callback URLs with referral codes for better NextAuth preservation
277+
2. **Fallback**: Store referral codes in localStorage before OAuth starts
278+
3. **Recovery**: ReferralRedirect component on home page catches missed referrals and redirects to onboard page
279+
256280
## Environment Variables
257281

258282
This project uses [Infisical](https://infisical.com/) for secret management. All secrets are injected at runtime.
@@ -270,6 +294,7 @@ The `.bin/bun` script automatically wraps bun commands with infisical when secre
270294
**Worktree Support**: The wrapper automatically detects and loads `.env.worktree` files when present, allowing worktrees to override Infisical environment variables (like ports) for local development. This enables multiple worktrees to run simultaneously on different ports without conflicts.
271295

272296
The wrapper also loads environment variables in the correct precedence order:
297+
273298
1. Infisical secrets are loaded first (if needed)
274299
2. `.env.worktree` is loaded second to override any conflicting variables
275300
3. This ensures worktree-specific overrides (like custom ports) always take precedence over cached Infisical defaults

web/public/logos/terminal.svg

Lines changed: 10 additions & 0 deletions
Loading

web/src/app/api/auth/[...nextauth]/auth-options.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,26 @@ export const authOptions: NextAuthOptions = {
141141
return session
142142
},
143143
async redirect({ url, baseUrl }) {
144+
console.log('🟡 NextAuth redirect callback:', { url, baseUrl })
145+
144146
const potentialRedirectUrl = new URL(url, baseUrl)
145147
const authCode = potentialRedirectUrl.searchParams.get('auth_code')
148+
let referralCode = potentialRedirectUrl.searchParams.get('referral_code')
149+
150+
console.log('🟡 NextAuth redirect parsed params:', {
151+
authCode: !!authCode,
152+
referralCode,
153+
allParams: Object.fromEntries(
154+
potentialRedirectUrl.searchParams.entries()
155+
),
156+
})
146157

147158
if (authCode) {
148159
const onboardUrl = new URL(`${baseUrl}/onboard`)
149160
potentialRedirectUrl.searchParams.forEach((value, key) => {
150161
onboardUrl.searchParams.set(key, value)
151162
})
163+
console.log('🟡 NextAuth CLI flow redirect to:', onboardUrl.toString())
152164
logger.info(
153165
{ url, authCode, redirectTarget: onboardUrl.toString() },
154166
'Redirecting CLI flow to /onboard'
@@ -157,13 +169,21 @@ export const authOptions: NextAuthOptions = {
157169
}
158170

159171
if (url.startsWith('/') || potentialRedirectUrl.origin === baseUrl) {
172+
console.log(
173+
'🟡 NextAuth web flow redirect to:',
174+
potentialRedirectUrl.toString()
175+
)
160176
logger.info(
161177
{ url, redirectTarget: potentialRedirectUrl.toString() },
162178
'Redirecting web flow to callbackUrl'
163179
)
164180
return potentialRedirectUrl.toString()
165181
}
166182

183+
console.log(
184+
'🟡 NextAuth external/invalid URL, redirect to baseUrl:',
185+
baseUrl
186+
)
167187
logger.info(
168188
{ url, baseUrl, redirectTarget: baseUrl },
169189
'Callback URL is external or invalid, redirecting to baseUrl'

web/src/app/onboard/page.tsx

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { authOptions } from '../api/auth/[...nextauth]/auth-options'
1414
import { redeemReferralCode } from '../api/referrals/helpers'
1515

1616
import CardWithBeams from '@/components/card-with-beams'
17-
import { toast } from '@/components/ui/use-toast'
17+
import { OnboardClientWrapper } from '@/components/onboard/onboard-client-wrapper'
1818

1919
interface PageProps {
2020
searchParams?: {
@@ -29,16 +29,56 @@ const Onboard = async ({ searchParams = {} }: PageProps) => {
2929
const session = await getServerSession(authOptions)
3030
const user = session?.user
3131

32-
// Check if values are present
33-
if (!authCode || !user) {
34-
toast({
35-
title: 'Uh-oh, spaghettio!',
36-
description:
37-
'No valid session or auth code. Please try again and reach out to support@codebuff.com if the problem persists.',
38-
})
32+
console.log('🟢 Onboard Server: Page loaded with params', {
33+
authCode: !!authCode,
34+
referralCode,
35+
hasUser: !!user,
36+
userId: user?.id,
37+
})
38+
39+
// Handle referral-only flow (no CLI auth required)
40+
if (!user) {
41+
console.log('🟢 Onboard Server: No user session, redirecting to app URL')
3942
return redirect(env.NEXT_PUBLIC_CODEBUFF_APP_URL)
4043
}
4144

45+
// Handle all non-CLI flows (web users with or without referral codes)
46+
if (!authCode) {
47+
const hasReferralInUrl = !!referralCode
48+
console.log('🟢 Onboard Server: Non-CLI flow detected', {
49+
hasReferralInUrl,
50+
referralCode,
51+
})
52+
const successCard = CardWithBeams({
53+
title: 'Welcome to Codebuff!',
54+
description: hasReferralInUrl
55+
? "Once you've installed Codebuff, you can close this window."
56+
: 'You can close this window.',
57+
content: (
58+
<div className="flex flex-col space-y-2">
59+
<Image
60+
src="/auth-success.png"
61+
alt="Successful authentication"
62+
width={600}
63+
height={600}
64+
/>
65+
{hasReferralInUrl && (
66+
<p className="text-center text-muted-foreground">
67+
Don't forget to enter your referral code in the CLI to claim your
68+
bonus credits!
69+
</p>
70+
)}
71+
</div>
72+
),
73+
})
74+
75+
return (
76+
<OnboardClientWrapper hasReferralCode={hasReferralInUrl}>
77+
{successCard}
78+
</OnboardClientWrapper>
79+
)
80+
}
81+
4282
const [fingerprintId, expiresAt, receivedfingerprintHash] =
4383
authCode.split('.')
4484

@@ -166,41 +206,25 @@ const Onboard = async ({ searchParams = {} }: PageProps) => {
166206
return !!session.length
167207
})
168208

169-
let redeemReferralMessage = <></>
209+
// No longer auto-redeem referral codes - users must enter them in CLI
210+
let referralMessage = <></>
170211
if (referralCode) {
171-
try {
172-
const redeemReferralResp = await redeemReferralCode(referralCode, user.id)
173-
const respJson = await redeemReferralResp.json()
174-
if (!redeemReferralResp.ok) {
175-
throw new Error(respJson.error)
176-
}
177-
redeemReferralMessage = (
178-
<p>
179-
You just earned an extra {respJson.credits_redeemed} credits from your
180-
referral code!
181-
</p>
182-
)
183-
} catch (e) {
184-
console.error(e)
185-
const error = e as Error
186-
redeemReferralMessage = (
187-
<div className="flex flex-col space-y-2">
188-
<p>Uh-oh, we couldn't apply your referral code. {error.message}</p>
189-
<p>
190-
Please try again and reach out to {env.NEXT_PUBLIC_SUPPORT_EMAIL} if
191-
the problem persists.
192-
</p>
193-
</div>
194-
)
195-
}
212+
referralMessage = (
213+
<p className="text-center text-muted-foreground">
214+
Don't forget to enter your referral code in the CLI to claim your bonus
215+
credits!
216+
</p>
217+
)
196218
}
197219

198-
// Render the result
220+
// Render the result for CLI flow
199221
if (didInsert) {
200-
return CardWithBeams({
222+
const isReferralUser = !!referralCode
223+
const successCard = CardWithBeams({
201224
title: 'Nicely done!',
202-
description:
203-
'Feel free to close this window and head back to your terminal.',
225+
description: isReferralUser
226+
? 'Follow the steps above to install Codebuff, then you can close this window.'
227+
: 'Feel free to close this window and head back to your terminal.',
204228
content: (
205229
<div className="flex flex-col space-y-2">
206230
<Image
@@ -209,10 +233,16 @@ const Onboard = async ({ searchParams = {} }: PageProps) => {
209233
width={600}
210234
height={600}
211235
/>
212-
{redeemReferralMessage}
236+
{referralMessage}
213237
</div>
214238
),
215239
})
240+
241+
return (
242+
<OnboardClientWrapper hasReferralCode={isReferralUser}>
243+
{successCard}
244+
</OnboardClientWrapper>
245+
)
216246
}
217247
return CardWithBeams({
218248
title: 'Uh-oh, spaghettio!',

web/src/app/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { toast } from '@/components/ui/use-toast'
2424
import { useIsMobile } from '@/hooks/use-mobile'
2525
import { storeSearchParams } from '@/lib/trackConversions'
2626
import { cn } from '@/lib/utils'
27+
import { ReferralRedirect } from '@/components/referral-redirect'
2728

2829
function SearchParamsHandler() {
2930
const searchParams = useSearchParams()
@@ -97,6 +98,7 @@ export default function Home() {
9798
<Suspense>
9899
<SearchParamsHandler />
99100
</Suspense>
101+
<ReferralRedirect />
100102

101103
<Section background={SECTION_THEMES.hero.background} hero fullViewport>
102104
<div

0 commit comments

Comments
 (0)