Skip to content
47 changes: 45 additions & 2 deletions apps/web/app/api/partner-profile/programs/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,51 @@ export const GET = withPartnerProfile(async ({ partner, searchParams }) => {
program: searchParams.includeRewardsDiscounts
? {
include: {
rewards: true,
discounts: true,
rewards: {
where: {
OR: [
// program-wide rewards
{
partners: {
none: {},
},
},

// partner-specific rewards
{
partners: {
some: {
programEnrollment: {
partnerId: partner.id,
},
},
},
},
],
},
},

discounts: {
where: {
OR: [
// program-wide discounts
{
programEnrollments: {
none: {},
},
},

// partner-specific discounts
{
programEnrollments: {
some: {
partnerId: partner.id,
},
},
},
],
},
},
},
}
: true,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/api/track/click/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const POST = withAxiom(async (req: AxiomRequest) => {
workspaceId: cachedLink.projectId,
skipRatelimit: true,
...(referrer && { referrer }),
trackConversion: cachedLink.trackConversion,
shouldPassClickId: true,
});
}
})(),
Expand Down
1 change: 0 additions & 1 deletion apps/web/app/api/track/visit/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ export const POST = withAxiom(async (req: AxiomRequest) => {
workspaceId: cachedLink.projectId,
skipRatelimit: true,
...(referrer && { referrer }),
trackConversion: cachedLink.trackConversion,
});
}
})(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export function ProgramApplicationForm({
<label>
<span className="text-sm font-medium text-neutral-800">
Website / Social media channel
<span className="font-normal text-neutral-500"> (optional)</span>
</span>
<input
type="text"
Expand All @@ -144,7 +143,9 @@ export function ProgramApplicationForm({
: "border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-[var(--brand)] focus:ring-[var(--brand)]",
)}
placeholder="https://example.com"
{...register("website")}
{...register("website", {
required: true,
})}
/>
</label>

Expand Down
31 changes: 15 additions & 16 deletions apps/web/lib/middleware/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ export default async function LinkMiddleware(

const url = testUrl || cachedLink.url;

// we only pass the clickId if:
// - trackConversion is enabled
// - it's a partner link
const shouldPassClickId = trackConversion || isPartnerLink;

// by default, we only index default dub domain links (e.g. dub.sh)
// everything else is not indexed by default, unless the user has explicitly set it to be indexed
const shouldIndex = isDubDomain(domain) || doIndex === true;
Expand Down Expand Up @@ -244,8 +249,8 @@ export default async function LinkMiddleware(
const cookieStore = cookies();
let clickId = cookieStore.get(dubIdCookieName)?.value;
if (!clickId) {
// if trackConversion is enabled, check if clickId is cached in Redis
if (trackConversion) {
// if we need to pass the clickId, check if clickId is cached in Redis
if (shouldPassClickId) {
const ip = process.env.VERCEL === "1" ? ipAddress(req) : LOCALHOST_IP;

clickId = (await clickCache.get({ domain, key, ip })) || undefined;
Expand Down Expand Up @@ -276,7 +281,7 @@ export default async function LinkMiddleware(
url,
webhookIds,
workspaceId,
trackConversion,
shouldPassClickId,
}),
);

Expand All @@ -297,12 +302,6 @@ export default async function LinkMiddleware(
const { country } =
process.env.VERCEL === "1" && req.geo ? req.geo : LOCALHOST_GEO_DATA;

// we only pass the clickId if:
// - trackConversion is enabled
// - not a partner link (TODO: add this later) !isPartnerLink
// - there is a clickId
const shouldPassClickId = trackConversion && clickId;

// rewrite to proxy page (/proxy/[domain]/[key]) if it's a bot and proxy is enabled
if (isBot && proxy) {
return createResponseWithCookies(
Expand Down Expand Up @@ -330,7 +329,7 @@ export default async function LinkMiddleware(
url,
webhookIds,
workspaceId,
trackConversion,
shouldPassClickId,
}),
);

Expand Down Expand Up @@ -368,7 +367,7 @@ export default async function LinkMiddleware(
url,
webhookIds,
workspaceId,
trackConversion,
shouldPassClickId,
}),
);

Expand Down Expand Up @@ -408,7 +407,7 @@ export default async function LinkMiddleware(
url: ios,
webhookIds,
workspaceId,
trackConversion,
shouldPassClickId,
}),
);

Expand Down Expand Up @@ -442,7 +441,7 @@ export default async function LinkMiddleware(
url: android,
webhookIds,
workspaceId,
trackConversion,
shouldPassClickId,
}),
);

Expand Down Expand Up @@ -476,7 +475,7 @@ export default async function LinkMiddleware(
url: geo[country],
webhookIds,
workspaceId,
trackConversion,
shouldPassClickId,
}),
);

Expand Down Expand Up @@ -510,7 +509,7 @@ export default async function LinkMiddleware(
url,
webhookIds,
workspaceId,
trackConversion,
shouldPassClickId,
}),
);

Expand All @@ -521,7 +520,7 @@ export default async function LinkMiddleware(
headers: new Headers({
destination: getFinalUrl(url, {
req,
clickId: trackConversion ? clickId : undefined,
clickId: shouldPassClickId ? clickId : undefined,
}),
}),
},
Expand Down
6 changes: 3 additions & 3 deletions apps/web/lib/tinybird/record-click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function recordClick({
skipRatelimit,
timestamp,
referrer,
trackConversion,
shouldPassClickId,
}: {
req: Request;
clickId?: string;
Expand All @@ -50,7 +50,7 @@ export async function recordClick({
skipRatelimit?: boolean;
timestamp?: string;
referrer?: string;
trackConversion?: boolean;
shouldPassClickId?: boolean;
}) {
if (!clickId) {
return null;
Expand Down Expand Up @@ -165,7 +165,7 @@ export async function recordClick({

// cache the click data for 5 mins
// we're doing this because ingested click events are not available immediately in Tinybird
trackConversion &&
shouldPassClickId &&
redis.set(`clickCache:${clickId}`, clickData, {
ex: 60 * 5,
}),
Expand Down
17 changes: 14 additions & 3 deletions apps/web/tests/redirects/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ describe.runIf(env.CI)("Link Redirects", async () => {
expect(response.status).toBe(302);
});

test("with dub_id and via", async () => {
const response = await fetch(`${h.baseUrl}/track-test`, {
...fetchOptions,
headers: {},
});

// the location should contain `?dub_id=` query param
expect(response.headers.get("location")).toMatch(/dub_id=[a-zA-Z0-9]+/);
// the location should contain `?via=track-test` query param
expect(response.headers.get("location")).toMatch(/via=track-test/);
expect(response.headers.get("x-powered-by")).toBe(poweredBy);
expect(response.status).toBe(302);
});

test("with dub_client_reference_id", async () => {
const response = await fetch(`${h.baseUrl}/client_reference_id`, {
...fetchOptions,
Expand Down Expand Up @@ -167,7 +181,4 @@ describe.runIf(env.CI)("Link Redirects", async () => {
expect(response.headers.get("x-powered-by")).toBe(poweredBy);
expect(response.status).toBe(302);
});

// DUMMY test to record a hit on track-test
await fetch(`${h.baseUrl}/track-test`);
});
2 changes: 1 addition & 1 deletion apps/web/ui/partners/program-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function ProgramCard({
const card = (
<div
className={cn(
"block flex h-full flex-col justify-between rounded-xl border border-neutral-200 bg-white p-5",
"flex h-full flex-col justify-between rounded-xl border border-neutral-200 bg-white p-5",
clickable && "hover:drop-shadow-card-hover transition-[filter]",
)}
>
Expand Down
16 changes: 14 additions & 2 deletions apps/web/ui/partners/program-invite-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ export function ProgramInviteCard({
},
});

const reward =
program.rewards && program.rewards.length > 0
? program.rewards.find((r) => r.id !== program.defaultRewardId) ||
program.rewards[0]
: null;

const discount =
program.discounts && program.discounts.length > 0
? program.discounts.find((d) => d.id !== program.defaultDiscountId) ||
program.discounts[0]
: null;

return (
<div className="hover:drop-shadow-card-hover relative flex flex-col rounded-xl border border-neutral-200 bg-neutral-50 p-5 transition-[filter]">
<div className="flex justify-between gap-2">
Expand All @@ -57,8 +69,8 @@ export function ProgramInviteCard({
)}
<p className="mt-2 text-balance text-xs text-neutral-600">
<ProgramRewardDescription
reward={program.rewards?.[0]}
discount={program.discounts?.[0]}
reward={reward}
discount={discount}
amountClassName="font-light"
periodClassName="font-light"
/>
Expand Down