feat: dub refer embed (#20258)

* feat: dub refer embed

* review fix

* review fix

* @dub/embed-core seems not needed

* @dub/embed-core can stay in web

* move refer page out of main-nav

* update yarn lock

* use loading.tsx

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: hbjORbj <sldisek783@gmail.com>
This commit is contained in:
authored and GitHub committed 2025-03-26 21:06:21 +00:00
1 parent 7aa1d8035c
commit 099c534daa
11 files changed
+634 -112

No files matched your search

@@ -0,0 +1,71 @@
"use client";
import { DubEmbed } from "@dub/embed-react";
import { useTheme } from "next-themes";
import { useState, useEffect } from "react";
import { IS_DUB_REFERRALS_ENABLED } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { showToast } from "@calcom/ui/components/toast";
const fetchReferralsToken = async () => {
try {
const response = await fetch("/api/user/referrals-token");
if (!response.ok) {
const { error } = await response.json();
showToast(error, "error");
return null;
}
const data = await response.json();
return data.publicToken;
} catch (error) {
console.error("Error fetching referrals token:", error);
return null;
}
};
// The enabled referrals page implementation
export const DubReferralsPage = () => {
const [token, setToken] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const { t } = useLocale();
const { resolvedTheme } = useTheme();
useEffect(() => {
const getToken = async () => {
try {
const publicToken = await fetchReferralsToken();
setToken(publicToken);
} catch (err) {
console.error("Error fetching referrals token:", err);
showToast(t("unexpected_error_try_again"), "error");
} finally {
setLoading(false);
}
};
getToken();
}, []);
if (!IS_DUB_REFERRALS_ENABLED || !token) {
return null;
}
const theme = resolvedTheme === "dark" ? "dark" : "light";
return (
<DubEmbed
data="referrals"
token={token}
options={{
theme,
themeOptions: {
backgroundColor: `${theme === "dark" ? "#0F0F0F" : "#FFFFFF"}`,
},
}}
/>
);
};
@@ -0,0 +1,69 @@
"use client";
import {
SkeletonAvatar,
SkeletonButton,
SkeletonContainer,
SkeletonText,
} from "@calcom/ui/components/skeleton";
export default function Loading() {
return (
<SkeletonContainer className="mx-auto max-w-4xl">
<div className="rounded-md p-8">
<div className="mb-2 flex items-center">
<SkeletonText className="h-5 w-32" />
</div>
<div className="mb-6 space-y-2">
<SkeletonText className="h-7 w-3/4" />
<SkeletonText className="h-7 w-1/2" />
</div>
<div className="mb-6">
<SkeletonText className="mb-2 h-5 w-24" />
<div className="flex items-center space-x-2">
<SkeletonText className="h-10 w-full rounded-md" />
<SkeletonButton className="h-10 w-28 rounded-md" />
</div>
</div>
</div>
<div className="mt-4 grid grid-cols-3 gap-4 p-4">
<div className="col-span-1">
<div className="mb-4 grid grid-cols-3 gap-2 p-4">
{[...Array(9)].map((_, i) => (
<div key={i} className="flex justify-center">
<SkeletonAvatar className="h-10 w-10 rounded-md" />
</div>
))}
</div>
<div className="px-4 pb-4">
<SkeletonText className="mb-2 h-6 w-full" />
<SkeletonButton className="h-10 w-full rounded-md" />
</div>
</div>
<div className="col-span-1">
<div className="mb-[60px] flex justify-center p-4 md:mb-10">
<SkeletonAvatar className="mt-10 h-16 w-16 rounded-full md:mt-7 md:h-24 md:w-24" />
</div>
<div className="px-4 pb-4">
<SkeletonText className="mb-2 h-6 w-full" />
<SkeletonButton className="h-10 w-full rounded-md" />
</div>
</div>
<div className="col-span-1">
<div className="mb-[60px] flex justify-center p-4 md:mb-10">
<SkeletonAvatar className="mt-10 h-16 w-16 rounded-md md:mt-7 md:h-24 md:w-24" />
</div>
<div className="px-4 pb-4">
<SkeletonText className="mb-2 h-6 w-full" />
<SkeletonButton className="h-10 w-full rounded-md" />
</div>
</div>
</div>
</SkeletonContainer>
);
}
@@ -0,0 +1,24 @@
import { getTranslate } from "app/_utils";
import Shell from "@calcom/features/shell/Shell";
import { IS_DUB_REFERRALS_ENABLED } from "@calcom/lib/constants";
import { DubReferralsPage } from "./DubReferralsPage";
// Export the appropriate component based on the feature flag
export default async function ReferralsPage() {
const t = await getTranslate();
return (
<Shell withoutMain={true}>
{IS_DUB_REFERRALS_ENABLED ? (
<DubReferralsPage />
) : (
<div className="mx-auto max-w-4xl p-8 text-center">
<h2 className="mb-4 text-xl font-semibold">{t("referral_program")}</h2>
<p>{t("dub_disabled_error_message")}</p>
</div>
)}
</Shell>
);
}
@@ -0,0 +1,38 @@
import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir";
import { headers, cookies } from "next/headers";
import { NextResponse } from "next/server";
import { dub } from "@calcom/feature-auth/lib/dub";
import { getServerSession } from "@calcom/feature-auth/lib/getServerSession";
import { IS_DUB_REFERRALS_ENABLED } from "@calcom/lib/constants";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
export const dynamic = "force-dynamic";
const handler = async () => {
// Return early if the feature is disabled
if (!IS_DUB_REFERRALS_ENABLED) {
return NextResponse.json({ error: "Referrals feature is disabled" }, { status: 404 });
}
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { publicToken } = await dub.embedTokens.referrals({
programId: process.env.NEXT_PUBLIC_DUB_PROGRAM_ID as string,
tenantId: session.user.id.toString(),
partner: {
name: session?.user.name || session?.user.email || "",
email: session?.user.email || session?.user.name || "",
username: session?.user.username || "",
image: session?.user.image || null,
tenantId: session?.user.id.toString() || "",
},
});
return NextResponse.json({ publicToken });
};
export const GET = defaultResponderForAppDir(handler);
+2
View File
@@ -45,6 +45,8 @@
"@daily-co/daily-js": "^0.76.0",
"@daily-co/daily-react": "^0.22.0",
"@dub/analytics": "^0.0.15",
"@dub/embed-core": "^0.0.10",
"@dub/embed-react": "^0.0.10",
"@formkit/auto-animate": "1.0.0-beta.5",
"@glidejs/glide": "^3.5.2",
"@googleapis/admin": "^23.0.0",
@@ -3028,6 +3028,9 @@
"inactive_team_plan": "Inactive team plan",
"inactive_team_plan_description": "Your team plan is inactive. Check your subcription or reach out to customer support",
"limited_access_trial_mode": "Limited access during trial. Feature available after trial ends.",
"earn_20_percent_affiliate": "20% Referral",
"referral_program": "Referral Program",
"dub_disabled_error_message": "You need a Dub API Key and Program Id to use this page.",
"uid": "UID",
"link": "Link",
"rows_per_page": "rows per page",