diff --git a/src/app/api/referral-source/route.ts b/src/app/api/referral-source/route.ts
new file mode 100644
index 000000000..168585d2d
--- /dev/null
+++ b/src/app/api/referral-source/route.ts
@@ -0,0 +1,38 @@
+import { type NextRequest, NextResponse } from 'next/server'
+
+export async function POST(req: NextRequest) {
+ const webhookUrl = process.env.SLACK_USER_SIGNUP_WEBHOOK_URL
+ if (!webhookUrl) {
+ return NextResponse.json(
+ { error: 'Slack webhook not configured' },
+ { status: 500 }
+ )
+ }
+
+ const { email, source } = await req.json()
+
+ if (!email) {
+ return NextResponse.json({ error: 'Missing email' }, { status: 400 })
+ }
+
+ const message = `:mag: *Referral Source*\n*User:* ${email}\n*Source:* ${source || 'Skipped'}`
+
+ try {
+ await fetch(webhookUrl, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ channel: 'user-signups',
+ text: message,
+ }),
+ })
+
+ return NextResponse.json({ ok: true })
+ } catch (error) {
+ console.error('Failed to send Slack message:', error)
+ return NextResponse.json(
+ { error: 'Failed to send Slack message' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/src/features/dashboard/context.tsx b/src/features/dashboard/context.tsx
index ec8ec4505..b44da17c1 100644
--- a/src/features/dashboard/context.tsx
+++ b/src/features/dashboard/context.tsx
@@ -1,8 +1,15 @@
'use client'
import type { User } from '@supabase/supabase-js'
-import { createContext, type ReactNode, useContext, useState } from 'react'
+import {
+ createContext,
+ type ReactNode,
+ useContext,
+ useEffect,
+ useState,
+} from 'react'
import type { ClientTeam } from '@/types/dashboard.types'
+import { ReferralSourceDialog } from './referral-source-dialog'
interface DashboardContextValue {
team: ClientTeam
@@ -29,6 +36,14 @@ export function DashboardContextProvider({
}: DashboardContextProviderProps) {
const [team, setTeam] = useState(initialTeam)
const [user, setUser] = useState(initialUser)
+ const [showReferralDialog, setShowReferralDialog] = useState(false)
+
+ useEffect(() => {
+ const referralAsked = user?.user_metadata?.referral_asked
+ if (user && !referralAsked) {
+ setShowReferralDialog(true)
+ }
+ }, [user])
const value = {
team,
@@ -41,6 +56,11 @@ export function DashboardContextProvider({
return (
{children}
+
)
}
diff --git a/src/features/dashboard/referral-source-dialog.tsx b/src/features/dashboard/referral-source-dialog.tsx
new file mode 100644
index 000000000..ff8195cf6
--- /dev/null
+++ b/src/features/dashboard/referral-source-dialog.tsx
@@ -0,0 +1,108 @@
+'use client'
+
+import { useState } from 'react'
+import { supabase } from '@/lib/clients/supabase/client'
+import { Button } from '@/ui/primitives/button'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/ui/primitives/dialog'
+import { Input } from '@/ui/primitives/input'
+
+interface ReferralSourceDialogProps {
+ userEmail: string
+ open: boolean
+ onOpenChange: (open: boolean) => void
+}
+
+export function ReferralSourceDialog({
+ userEmail,
+ open,
+ onOpenChange,
+}: ReferralSourceDialogProps) {
+ const [source, setSource] = useState('')
+ const [isSubmitting, setIsSubmitting] = useState(false)
+
+ async function updateUserMetadata(referralSource: string | null) {
+ await supabase.auth.updateUser({
+ data: {
+ referral_source: referralSource,
+ referral_asked: true,
+ },
+ })
+ }
+
+ async function notifySlack(referralSource: string | null) {
+ try {
+ await fetch('/api/referral-source', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ email: userEmail,
+ source: referralSource,
+ }),
+ })
+ } catch {
+ // Non-critical, don't block the user
+ }
+ }
+
+ async function handleSubmit() {
+ if (!source.trim()) return
+ setIsSubmitting(true)
+
+ await Promise.all([
+ updateUserMetadata(source.trim()),
+ notifySlack(source.trim()),
+ ])
+
+ onOpenChange(false)
+ }
+
+ async function handleSkip() {
+ await updateUserMetadata(null)
+ onOpenChange(false)
+ }
+
+ return (
+
+ )
+}