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:
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);
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user