Skip to content

Commit f9823f2

Browse files
authored
Merge pull request #1029 from trycompai/mariano/comp-229-dub-affiliates
[dev] [Marfuen] mariano/comp-229-dub-affiliates
2 parents ec6fbb3 + 8112b5b commit f9823f2

9 files changed

Lines changed: 405 additions & 21 deletions

File tree

apps/app/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@dnd-kit/modifiers": "^9.0.0",
2020
"@dnd-kit/sortable": "^10.0.0",
2121
"@dnd-kit/utilities": "^3.2.2",
22+
"@dub/embed-react": "^0.0.14",
2223
"@hookform/resolvers": "^5.1.1",
2324
"@mendable/firecrawl-js": "^1.24.0",
2425
"@nangohq/frontend": "^0.53.2",
@@ -46,6 +47,7 @@
4647
"axios": "^1.9.0",
4748
"better-auth": "^1.2.8",
4849
"d3": "^7.9.0",
50+
"dub": "^0.63.5",
4951
"framer-motion": "^12.18.1",
5052
"geist": "^1.3.1",
5153
"motion": "^12.9.2",
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use client';
2+
3+
import { DubEmbed } from '@dub/embed-react';
4+
import { useEffect, useState } from 'react';
5+
6+
export const DubReferral = () => {
7+
const [publicToken, setPublicToken] = useState('');
8+
const [error, setError] = useState<string | null>(null);
9+
const [isLoading, setIsLoading] = useState(true);
10+
11+
useEffect(() => {
12+
const fetchPublicToken = async () => {
13+
try {
14+
setIsLoading(true);
15+
setError(null);
16+
17+
const response = await fetch('/api/dub/embed-token');
18+
19+
if (!response.ok) {
20+
throw new Error(`Failed to fetch token: ${response.status} ${response.statusText}`);
21+
}
22+
23+
const data = await response.json();
24+
25+
if (!data.publicToken) {
26+
throw new Error('No public token received from server');
27+
}
28+
29+
setPublicToken(data.publicToken);
30+
} catch (err) {
31+
console.error('Error fetching public token:', err);
32+
setError(err instanceof Error ? err.message : 'An unexpected error occurred');
33+
} finally {
34+
setIsLoading(false);
35+
}
36+
};
37+
38+
fetchPublicToken();
39+
}, []);
40+
41+
if (isLoading) {
42+
return <div>Loading...</div>;
43+
}
44+
45+
if (error) {
46+
return (
47+
<div className="flex min-h-[400px] items-center justify-center">
48+
<div className="w-full max-w-md rounded-md border border-destructive/50 bg-destructive/10 p-6 text-center">
49+
<h3 className="mb-2 text-sm font-medium text-destructive">Oops, something went wrong</h3>
50+
<p className="text-xs text-destructive/80">{error}</p>
51+
<p className="mt-4 text-xs text-muted-foreground">
52+
Please refresh the page and try again. If the problem persists,{' '}
53+
<a
54+
href="https://discord.gg/compai"
55+
target="_blank"
56+
rel="noopener noreferrer"
57+
className="underline underline-offset-2 hover:text-foreground transition-colors"
58+
>
59+
contact us
60+
</a>
61+
.
62+
</p>
63+
</div>
64+
</div>
65+
);
66+
}
67+
68+
if (!publicToken) {
69+
return (
70+
<div className="rounded-md border border-muted bg-muted/50 p-4">
71+
<p className="text-sm text-muted-foreground">
72+
No token available. Please try refreshing the page.
73+
</p>
74+
</div>
75+
);
76+
}
77+
78+
return <DubEmbed data="referrals" token={publicToken} />;
79+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { DubReferral } from './components/DubReferral';
2+
3+
export default function ReferralsPage() {
4+
return <DubReferral />;
5+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { env } from '@/env.mjs';
2+
import { auth } from '@/utils/auth';
3+
import { Dub } from 'dub';
4+
import { NextResponse } from 'next/server';
5+
6+
const dub = new Dub({
7+
token: env.DUB_API_KEY,
8+
});
9+
10+
export async function GET(request: Request) {
11+
const session = await auth.api.getSession({
12+
headers: request.headers,
13+
});
14+
const user = session?.user;
15+
16+
if (!user) {
17+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
18+
}
19+
20+
const programId = env.DUB_PROGRAM_ID;
21+
22+
if (!programId) {
23+
return NextResponse.json({ error: 'Program ID is not set' }, { status: 500 });
24+
}
25+
26+
try {
27+
const { publicToken } = await dub.embedTokens.referrals({
28+
programId, // program ID from your Dub dashboard (in the URL)
29+
tenantId: user.id, // the user's ID within your application
30+
partner: {
31+
name: user.name, // the user's name
32+
email: user.email, // the user's email
33+
image: user.image, // the user's image/avatar
34+
tenantId: user.id, // the user's ID within your application
35+
},
36+
});
37+
38+
return NextResponse.json({ publicToken });
39+
} catch (error) {
40+
console.error('Error fetching embed token:', error);
41+
return NextResponse.json({ error: 'Failed to fetch embed token' }, { status: 500 });
42+
}
43+
}

apps/app/src/components/main-menu.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Blocks,
1010
FlaskConical,
1111
Gauge,
12+
Gift,
1213
ListCheck,
1314
NotebookText,
1415
ShieldEllipsis,
@@ -126,6 +127,14 @@ export function MainMenu({ organizationId, isCollapsed = false, onItemClick }: P
126127
variant: 'secondary',
127128
},
128129
},
130+
{
131+
id: 'referrals',
132+
path: '/:organizationId/referrals',
133+
name: 'Referrals',
134+
disabled: false,
135+
icon: Gift,
136+
protected: false,
137+
},
129138
{
130139
id: 'settings',
131140
path: '/:organizationId/settings',

apps/app/src/env.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export const env = createEnv({
3636
ZAPIER_HUBSPOT_WEBHOOK_URL: z.string().optional(),
3737
FLEET_URL: z.string().optional(),
3838
FLEET_TOKEN: z.string().optional(),
39+
DUB_API_KEY: z.string().optional(),
40+
DUB_PROGRAM_ID: z.string().optional(),
3941
},
4042

4143
client: {
@@ -87,6 +89,8 @@ export const env = createEnv({
8789
process.env.NEXT_PUBLIC_STRIPE_SUBSCRIPTION_MANAGED_MONTHLY_PRICE_ID,
8890
NEXT_PUBLIC_STRIPE_SUBSCRIPTION_MANAGED_YEARLY_PRICE_ID:
8991
process.env.NEXT_PUBLIC_STRIPE_SUBSCRIPTION_MANAGED_YEARLY_PRICE_ID,
92+
DUB_API_KEY: process.env.DUB_API_KEY,
93+
DUB_PROGRAM_ID: process.env.DUB_PROGRAM_ID,
9094
},
9195

9296
skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION,

bun.lock

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

turbo.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
"FLEET_DEVICE_PATH_MAC",
4747
"FLEET_AGENT_BUCKET_NAME",
4848
"FLEET_DEVICE_PATH_WINDOWS",
49-
"LOGO_DEV"
49+
"LOGO_DEV",
50+
"DUB_API_KEY",
51+
"DUB_PROGRAM_ID"
5052
],
5153
"inputs": ["$TURBO_DEFAULT$", ".env"],
5254
"dependsOn": ["^build", "^db:generate", "^auth:build"],

0 commit comments

Comments
 (0)