Skip to content

Commit aaa4c1c

Browse files
committed
cleanup referral attributes
1 parent 31113e5 commit aaa4c1c

File tree

4 files changed

+25
-52
lines changed

4 files changed

+25
-52
lines changed

apps/sim/app/api/attribution/route.ts

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
/**
22
* POST /api/attribution
33
*
4-
* Automatic UTM-based referral attribution for new signups.
4+
* Automatic UTM-based referral attribution.
55
*
6-
* Reads the `sim_utm` cookie (set by proxy on auth pages), verifies the user
7-
* account was created after the cookie was set, matches a campaign by UTM
8-
* specificity, and atomically inserts an attribution record + applies bonus credits.
6+
* Reads the `sim_utm` cookie (set by proxy on auth pages), matches a campaign
7+
* by UTM specificity, and atomically inserts an attribution record + applies
8+
* bonus credits.
99
*
1010
* Idempotent — the unique constraint on `userId` prevents double-attribution.
1111
*/
1212

1313
import { db } from '@sim/db'
14-
import { referralAttribution, referralCampaigns, user, userStats } from '@sim/db/schema'
14+
import { referralAttribution, referralCampaigns, userStats } from '@sim/db/schema'
1515
import { createLogger } from '@sim/logger'
1616
import { eq } from 'drizzle-orm'
1717
import { nanoid } from 'nanoid'
@@ -24,7 +24,6 @@ import { applyBonusCredits } from '@/lib/billing/credits/bonus'
2424
const logger = createLogger('AttributionAPI')
2525

2626
const COOKIE_NAME = 'sim_utm'
27-
const CLOCK_DRIFT_TOLERANCE_MS = 60 * 1000
2827

2928
const UtmCookieSchema = z.object({
3029
utm_source: z.string().optional(),
@@ -33,7 +32,7 @@ const UtmCookieSchema = z.object({
3332
utm_content: z.string().optional(),
3433
referrer_url: z.string().optional(),
3534
landing_page: z.string().optional(),
36-
created_at: z.string().min(1),
35+
created_at: z.string().optional(),
3736
})
3837

3938
/**
@@ -114,34 +113,6 @@ export async function POST() {
114113
return NextResponse.json({ attributed: false, reason: 'invalid_cookie' })
115114
}
116115

117-
const cookieCreatedAt = Number(utmData.created_at)
118-
if (!Number.isFinite(cookieCreatedAt)) {
119-
logger.warn('UTM cookie has invalid created_at timestamp', { userId: session.user.id })
120-
cookieStore.delete(COOKIE_NAME)
121-
return NextResponse.json({ attributed: false, reason: 'invalid_cookie' })
122-
}
123-
124-
const userRows = await db
125-
.select({ createdAt: user.createdAt })
126-
.from(user)
127-
.where(eq(user.id, session.user.id))
128-
.limit(1)
129-
130-
if (userRows.length === 0) {
131-
return NextResponse.json({ error: 'User not found' }, { status: 404 })
132-
}
133-
134-
const userCreatedAt = userRows[0].createdAt.getTime()
135-
if (userCreatedAt < cookieCreatedAt - CLOCK_DRIFT_TOLERANCE_MS) {
136-
logger.info('User account predates UTM cookie, skipping attribution', {
137-
userId: session.user.id,
138-
userCreatedAt: new Date(userCreatedAt).toISOString(),
139-
cookieCreatedAt: new Date(cookieCreatedAt).toISOString(),
140-
})
141-
cookieStore.delete(COOKIE_NAME)
142-
return NextResponse.json({ attributed: false, reason: 'account_predates_cookie' })
143-
}
144-
145116
const matchedCampaign = await findMatchingCampaign(utmData)
146117
if (!matchedCampaign) {
147118
cookieStore.delete(COOKIE_NAME)

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/referral-code/referral-code.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ export function ReferralCode({ onRedeemComplete }: ReferralCodeProps) {
6767
}
6868

6969
return (
70-
<div className='flex items-center justify-between gap-[12px]'>
71-
<Label className='shrink-0'>Referral Code</Label>
72-
<div className='flex items-center gap-[8px]'>
73-
<div className='flex flex-col'>
70+
<div className='flex flex-col'>
71+
<div className='flex items-center justify-between gap-[12px]'>
72+
<Label className='shrink-0'>Referral Code</Label>
73+
<div className='flex items-center gap-[8px]'>
7474
<Input
7575
type='text'
7676
value={code}
@@ -85,16 +85,18 @@ export function ReferralCode({ onRedeemComplete }: ReferralCodeProps) {
8585
className='h-[32px] w-[140px] text-[12px]'
8686
disabled={isRedeeming}
8787
/>
88-
{error && <span className='mt-[4px] text-[11px] text-[var(--text-error)]'>{error}</span>}
88+
<Button
89+
variant='active'
90+
className='h-[32px] shrink-0 rounded-[6px] text-[12px]'
91+
onClick={handleRedeem}
92+
disabled={isRedeeming || !code.trim()}
93+
>
94+
{isRedeeming ? 'Redeeming...' : 'Redeem'}
95+
</Button>
8996
</div>
90-
<Button
91-
variant='active'
92-
className='h-[32px] shrink-0 rounded-[6px] text-[12px]'
93-
onClick={handleRedeem}
94-
disabled={isRedeeming || !code.trim()}
95-
>
96-
{isRedeeming ? 'Redeeming...' : 'Redeem'}
97-
</Button>
97+
</div>
98+
<div className='mt-[4px] min-h-[18px] text-right'>
99+
{error && <span className='text-[11px] text-[var(--text-error)]'>{error}</span>}
98100
</div>
99101
</div>
100102
)

apps/sim/hooks/use-referral-attribution.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const logger = createLogger('ReferralAttribution')
88
const COOKIE_NAME = 'sim_utm'
99

1010
const TERMINAL_REASONS = new Set([
11-
'account_predates_cookie',
1211
'invalid_cookie',
1312
'no_utm_cookie',
1413
'no_matching_campaign',

apps/sim/proxy.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,7 @@ const UTM_COOKIE_MAX_AGE = 3600
143143

144144
/**
145145
* Sets a `sim_utm` cookie when UTM params are present on auth pages.
146-
* Captures UTM values, the HTTP Referer, landing page, and a timestamp
147-
* used by the attribution API to verify the user signed up after visiting the link.
146+
* Captures UTM values, the HTTP Referer, landing page, and a timestamp.
148147
*/
149148
function setUtmCookie(request: NextRequest, response: NextResponse): void {
150149
const { searchParams, pathname } = request.nextUrl
@@ -179,7 +178,9 @@ export async function proxy(request: NextRequest) {
179178

180179
if (url.pathname === '/login' || url.pathname === '/signup') {
181180
if (hasActiveSession) {
182-
return NextResponse.redirect(new URL('/workspace', request.url))
181+
const redirect = NextResponse.redirect(new URL('/workspace', request.url))
182+
setUtmCookie(request, redirect)
183+
return redirect
183184
}
184185
const response = NextResponse.next()
185186
response.headers.set('Content-Security-Policy', generateRuntimeCSP())

0 commit comments

Comments
 (0)