feat: Implement Dub.co for conversion tracking (#16165)
* FEAT: Implement Dub.co for conversion tracking * add link creation, @vercel/functions * use refer.cal.com/:username instead of go.cal.com/r/:username * sale conversion tracking * add copy referral link * limit to IS_CALCOM only * Update yarn.lock * fix DubAnalytics * use workaround for isNewUser * pass req to getOptions * Update next-auth-options.ts * fix ts errors * only show DubAnalytics outside EU * add Dub Analytics to /signup * use WEBSITE_URL instead * on-demand generate links * add migration * add check for existing link + change fetch method to poast * remove refer.cal.com from PoweredByCal * limit DubAnalytics to /signup only * simplify generate-referral-link * restore yarn.lock * add yarn with dub sdk in * add yarn with dub sdk in * yarn * Update yarn.lock --------- Co-authored-by: sean <sean@brydon.io> Co-authored-by: Peer Richelsen <peer@cal.com>
This commit is contained in:
co-authored by
sean
Peer Richelsen
parent
f4bc7d2ea4
commit
cf16cd3ed2
@@ -215,6 +215,7 @@ const generateTeamCheckoutSession = async ({
|
||||
metadata: {
|
||||
pendingPaymentTeamId,
|
||||
ownerId,
|
||||
dubCustomerId: ownerId, // pass the userId during checkout creation for sales conversion tracking: https://d.to/conversions/stripe
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import { ScheduleRepository } from "@calcom/lib/server/repository/schedule";
|
||||
import { TravelScheduleRepository } from "@calcom/lib/server/repository/travelSchedule";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
@@ -34,7 +33,7 @@ const Page = async ({ params }: PageProps) => {
|
||||
}
|
||||
const scheduleId = Number(params.schedule);
|
||||
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const userId = session?.user?.id;
|
||||
if (!userId) {
|
||||
notFound();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
|
||||
|
||||
import AvailabilityPage from "~/availability/availability-view";
|
||||
@@ -16,7 +15,7 @@ export const generateMetadata = async () => {
|
||||
};
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const userId = session?.user?.id;
|
||||
const orgId = session?.user?.org?.id;
|
||||
if (!userId || !orgId) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import AdminAppsList from "@calcom/features/apps/AdminAppsList";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
@@ -13,7 +12,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import ImpersonationView from "~/settings/admin/impersonation-view";
|
||||
@@ -14,7 +13,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import AdminOrgTable from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgPage";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
@@ -14,7 +13,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import BillingView from "~/settings/billing/billing-view";
|
||||
@@ -14,7 +13,8 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
// FIXME: Refactor me once next-auth endpoint is migrated to App Router
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getFixedT, _generateMetadata } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
@@ -15,7 +14,8 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
// FIXME: Refactor me once next-auth endpoint is migrated to App Router
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { PageProps } from "app/_types";
|
||||
import { getFixedT, _generateMetadata } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { EditWebhookView } from "@calcom/features/webhooks/pages/webhook-edit-view";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
@@ -15,7 +14,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async ({ params }: PageProps) => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
const id = typeof params?.id === "string" ? params.id : undefined;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getFixedT, _generateMetadata } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { NewWebhookView } from "@calcom/features/webhooks/pages/webhook-new-view";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
@@ -13,7 +12,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import AppearancePage from "~/settings/my-account/appearance-view";
|
||||
@@ -14,7 +13,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
@@ -15,7 +14,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
const AddCalendarButton = () => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
@@ -15,7 +14,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
const AddConferencingButton = () => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import GeneralQueryView from "~/settings/my-account/general-view";
|
||||
@@ -14,7 +13,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import CreateNewOutOfOfficeEntryButton from "@calcom/features/settings/outOfOffice/CreateNewOutOfOfficeEntryButton";
|
||||
import { OutOfOfficeEntriesList } from "@calcom/features/settings/outOfOffice/OutOfOfficeEntriesList";
|
||||
@@ -14,7 +13,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
@@ -15,7 +14,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getFixedT, _generateMetadata } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import { AdminAPIView } from "@calcom/features/ee/organizations/pages/settings/admin-api";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
@@ -12,7 +11,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { _generateMetadata, getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import DirectorySyncTeamView from "@calcom/features/ee/dsync/page/team-dsync-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
@@ -12,7 +11,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { _generateMetadata, getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import PrivacyView from "@calcom/features/ee/organizations/pages/settings/privacy";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
@@ -12,7 +11,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import ProfileImpersonationViewWrapper from "~/settings/security/impersonation-view";
|
||||
@@ -14,7 +13,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import PasswordViewWrapper from "~/settings/security/password-view";
|
||||
@@ -14,7 +13,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SAMLSSO from "@calcom/features/ee/sso/page/user-sso-view";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
@@ -13,7 +12,7 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { getFixedT } from "app/_utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
|
||||
import TwoFactorAuthView from "~/settings/security/two-factor-auth-view";
|
||||
@@ -13,7 +12,7 @@ export const generateMetadata = async () =>
|
||||
(t) => t("add_an_extra_layer_of_security")
|
||||
);
|
||||
const Page = async () => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
|
||||
const t = await getFixedT(session?.user.locale || "en");
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { notFound } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import LegacyPage from "@calcom/features/ee/workflows/pages/workflow";
|
||||
import { WorkflowRepository } from "@calcom/lib/server/repository/workflow";
|
||||
|
||||
@@ -30,7 +29,8 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
export const generateStaticParams = () => [];
|
||||
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
// FIXME: Refactor me once next-auth endpoint is migrated to App Router
|
||||
const session = await getServerSessionForAppDir();
|
||||
const user = session?.user;
|
||||
const parsed = querySchema.safeParse({ ...params, ...searchParams });
|
||||
if (!parsed.success) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import LegacyPage from "@calcom/features/ee/workflows/pages/index";
|
||||
import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
|
||||
import { WorkflowRepository } from "@calcom/lib/server/repository/workflow";
|
||||
@@ -15,7 +14,8 @@ export const generateMetadata = async () =>
|
||||
);
|
||||
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
// FIXME: Refactor me once next-auth endpoint is migrated to App Router
|
||||
const session = await getServerSessionForAppDir();
|
||||
const user = session?.user;
|
||||
|
||||
const filters = getTeamsFiltersFromQuery({ ...searchParams, ...params });
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import dynamic from "next/dynamic";
|
||||
import React from "react";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import SettingsLayoutAppDir from "@calcom/features/settings/appDir/SettingsLayoutAppDir";
|
||||
|
||||
import type { AdminLayoutProps } from "./AdminLayoutAppDirClient";
|
||||
@@ -14,7 +13,8 @@ const AdminLayoutAppDirClient = dynamic(() => import("./AdminLayoutAppDirClient"
|
||||
type AdminLayoutAppDirProps = Omit<AdminLayoutProps, "userRole">;
|
||||
|
||||
export default async function AdminLayoutAppDir(props: AdminLayoutAppDirProps) {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
// FIXME: Refactor me once next-auth endpoint is migrated to App Router
|
||||
const session = await getServerSessionForAppDir();
|
||||
const userRole = session?.user?.role;
|
||||
|
||||
return await SettingsLayoutAppDir({ children: <AdminLayoutAppDirClient {...props} userRole={userRole} /> });
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getServerSession } from "@calcom/feature-auth/lib/getServerSession";
|
||||
import { AUTH_OPTIONS } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
import { getOptions } from "@calcom/feature-auth/lib/next-auth-options";
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const session = await getServerSession({
|
||||
req: context.req,
|
||||
authOptions: AUTH_OPTIONS,
|
||||
res: context.res,
|
||||
authOptions: getOptions({ getDclid: () => context.req.cookies.dclid }),
|
||||
});
|
||||
// Disable this check if we ever make this self serve.
|
||||
if (session?.user.role !== "ADMIN") {
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"@calcom/ui": "*",
|
||||
"@daily-co/daily-js": "^0.59.0",
|
||||
"@daily-co/daily-react": "^0.17.2",
|
||||
"@dub/analytics": "^0.0.15",
|
||||
"@formkit/auto-animate": "1.0.0-beta.5",
|
||||
"@glidejs/glide": "^3.5.2",
|
||||
"@hookform/error-message": "^2.0.0",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import NextAuth from "next-auth";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getOptions } from "@calcom/features/auth/lib/next-auth-options";
|
||||
|
||||
export default NextAuth(AUTH_OPTIONS);
|
||||
// pass req to NextAuth: https://github.com/nextauthjs/next-auth/discussions/469
|
||||
const handler = (req: NextApiRequest, res: NextApiResponse) =>
|
||||
NextAuth(req, res, getOptions({ getDclid: () => req.cookies.dclid }));
|
||||
|
||||
export default handler;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { dub } from "@calcom/features/auth/lib/dub";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
|
||||
const session = await getServerSession({ req, res });
|
||||
|
||||
if (!session?.user?.username) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
try {
|
||||
return await dub.links.get({
|
||||
externalId: `ext_${session.user.id.toString()}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Referral link not found, creating...");
|
||||
}
|
||||
|
||||
const { id: referralLinkId, shortLink } = await dub.links.create({
|
||||
domain: "refer.cal.com",
|
||||
key: session.user.username,
|
||||
url: "https://cal.com",
|
||||
externalId: session.user.id.toString(), // @see https://d.to/externalId
|
||||
trackConversion: true, // enable conversion tracking @see https://d.to/conversions
|
||||
});
|
||||
|
||||
/*
|
||||
Even though we are relying in externalId for retrieving the referral link,
|
||||
we still save the referralLinkId in the database for future reference.
|
||||
E.g. we can use this to tell how many users created a referral link.
|
||||
*/
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
data: {
|
||||
referralLinkId,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({ shortLink });
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Analytics as DubAnalytics } from "@dub/analytics/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { Trans } from "next-i18next";
|
||||
@@ -26,7 +27,9 @@ import {
|
||||
CLOUDFLARE_SITE_ID,
|
||||
WEBSITE_PRIVACY_POLICY_URL,
|
||||
WEBSITE_TERMS_URL,
|
||||
WEBSITE_URL,
|
||||
} from "@calcom/lib/constants";
|
||||
import { isENVDev } from "@calcom/lib/env";
|
||||
import { fetchUsername } from "@calcom/lib/fetchUsername";
|
||||
import { pushGTMEvent } from "@calcom/lib/gtm";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
@@ -309,6 +312,11 @@ export default function Signup({
|
||||
__html: `<iframe src="https://www.googletagmanager.com/ns.html?id=${process.env.NEXT_PUBLIC_GTM_ID}" height="0" width="0" style="display:none;visibility:hidden"></iframe>`,
|
||||
}}
|
||||
/>
|
||||
<DubAnalytics
|
||||
cookieOptions={{
|
||||
domain: isENVDev ? undefined : `.${new URL(WEBSITE_URL).hostname}`,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
<div
|
||||
|
||||
@@ -34,7 +34,10 @@ export function createWorkflowPageFixture(page: Page) {
|
||||
};
|
||||
|
||||
const saveWorkflow = async () => {
|
||||
const submitPromise = page.waitForResponse("/api/trpc/workflows/update?batch=1");
|
||||
await page.getByTestId("save-workflow").click();
|
||||
const response = await submitPromise;
|
||||
expect(response.status()).toBe(200);
|
||||
};
|
||||
|
||||
const assertListCount = async (count: number) => {
|
||||
|
||||
@@ -26,7 +26,7 @@ test.describe("Workflow Tab - Event Type", () => {
|
||||
await fillNameInput("Edited Workflow");
|
||||
await saveWorkflow();
|
||||
await page.getByTestId("go-back-button").click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
page.getByTestId("workflow-list").waitFor();
|
||||
await hasWorkflowInList("Edited Workflow");
|
||||
});
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@
|
||||
"view_notifications": "View notifications",
|
||||
"view_public_page": "View public page",
|
||||
"copy_public_page_link": "Copy public page link",
|
||||
"copy_referral_link": "Copy referral link",
|
||||
"sign_out": "Sign out",
|
||||
"add_another": "Add another",
|
||||
"install_another": "Install another",
|
||||
|
||||
@@ -37,6 +37,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
const availability = await checkUsername(usernameParam, currentOrgDomain);
|
||||
if (availability.available && availability.premium && IS_PREMIUM_USERNAME_ENABLED) {
|
||||
const stripePremiumUrl = await getStripePremiumUsernameUrl({
|
||||
userId: session.user.id.toString(),
|
||||
userEmail: session.user.email,
|
||||
username: usernameParam,
|
||||
successDestination,
|
||||
@@ -104,12 +105,14 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
};
|
||||
|
||||
type GetStripePremiumUsernameUrl = {
|
||||
userId: string;
|
||||
userEmail: string;
|
||||
username: string;
|
||||
successDestination: string;
|
||||
};
|
||||
|
||||
const getStripePremiumUsernameUrl = async ({
|
||||
userId,
|
||||
userEmail,
|
||||
username,
|
||||
successDestination,
|
||||
@@ -136,6 +139,9 @@ const getStripePremiumUsernameUrl = async ({
|
||||
success_url: `${process.env.NEXT_PUBLIC_WEBAPP_URL}${successDestination}&session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: process.env.NEXT_PUBLIC_WEBAPP_URL || "https://app.cal.com",
|
||||
allow_promotion_codes: true,
|
||||
metadata: {
|
||||
dubCustomerId: userId, // pass the userId during checkout creation for sales conversion tracking: https://d.to/conversions/stripe
|
||||
},
|
||||
});
|
||||
|
||||
return checkoutSession.url;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Dub } from "dub";
|
||||
|
||||
export const dub = new Dub({
|
||||
token: process.env.DUB_API_KEY,
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
import { getOptions } from "./next-auth-options";
|
||||
|
||||
export async function getServerSessionForAppDir() {
|
||||
return await getServerSession(getOptions({ getDclid: () => cookies().get("dclid")?.value }));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Membership, Team, UserPermissionRole } from "@prisma/client";
|
||||
import type { AuthOptions, Session } from "next-auth";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import type { AuthOptions, Session, User } from "next-auth";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
import { encode } from "next-auth/jwt";
|
||||
import type { Provider } from "next-auth/providers";
|
||||
@@ -13,7 +14,7 @@ import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/Imperso
|
||||
import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { HOSTED_CAL_FEATURES, IS_CALCOM } from "@calcom/lib/constants";
|
||||
import { ENABLE_PROFILE_SWITCHER, IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "@calcom/lib/crypto";
|
||||
import { defaultCookies } from "@calcom/lib/default-cookies";
|
||||
@@ -29,6 +30,7 @@ import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums";
|
||||
import { teamMetadataSchema, userMetadata } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { ErrorCode } from "./ErrorCode";
|
||||
import { dub } from "./dub";
|
||||
import { isPasswordValid } from "./isPasswordValid";
|
||||
import CalComAdapter from "./next-auth-custom-adapter";
|
||||
import { verifyPassword } from "./verifyPassword";
|
||||
@@ -235,6 +237,7 @@ const providers: Provider[] = [
|
||||
belongsToActiveTeam: hasActiveTeams,
|
||||
locale: user.locale,
|
||||
profile: user.allProfiles[0],
|
||||
createdAt: user.createdDate,
|
||||
};
|
||||
},
|
||||
}),
|
||||
@@ -404,7 +407,12 @@ const mapIdentityProvider = (providerName: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const AUTH_OPTIONS: AuthOptions = {
|
||||
export const getOptions = ({
|
||||
getDclid,
|
||||
}: {
|
||||
/** so we can extract the Dub cookie in both pages and app routers */
|
||||
getDclid: () => string | undefined;
|
||||
}): AuthOptions => ({
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
adapter: calcomAdapter,
|
||||
@@ -924,7 +932,43 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||
return baseUrl;
|
||||
},
|
||||
},
|
||||
};
|
||||
events: {
|
||||
async signIn(message) {
|
||||
/* only run this code if:
|
||||
- it's a hosted cal account
|
||||
- DUB_API_KEY is configured
|
||||
- it's a new user
|
||||
*/
|
||||
const user = message.user as User & {
|
||||
username: string;
|
||||
createdAt: string;
|
||||
};
|
||||
// check if the user was created in the last 10 minutes
|
||||
// this is a workaround – in the future once we move to use the Account model in the DB
|
||||
// we should use NextAuth's isNewUser flag instead: https://next-auth.js.org/configuration/events#signin
|
||||
const isNewUser = new Date(user.createdAt) > new Date(Date.now() - 10 * 60 * 1000);
|
||||
if ((isENVDev || IS_CALCOM) && process.env.DUB_API_KEY && isNewUser) {
|
||||
const dclid = getDclid();
|
||||
// check if there's a dclid cookie set by @dub/analytics
|
||||
if (dclid) {
|
||||
// here we use waitUntil – meaning this code will run async to not block the main thread
|
||||
waitUntil(
|
||||
// if so, send a lead event to Dub
|
||||
// @see https://d.to/conversions/next-auth
|
||||
dub.track.lead({
|
||||
clickId: dclid,
|
||||
eventName: "Sign Up",
|
||||
customerId: user.id.toString(),
|
||||
customerName: user.name,
|
||||
customerEmail: user.email,
|
||||
customerAvatar: user.image,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Identifies the profile the user should be logged into.
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@calcom/trpc": "*",
|
||||
"@calcom/ui": "*",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dub": "^0.35.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"jose": "^4.13.1",
|
||||
"lru-cache": "^9.0.3",
|
||||
|
||||
@@ -79,6 +79,7 @@ export const generateTeamCheckoutSession = async ({
|
||||
teamName,
|
||||
teamSlug,
|
||||
userId,
|
||||
dubCustomerId: userId, // pass the userId during checkout creation for sales conversion tracking: https://d.to/conversions/stripe
|
||||
},
|
||||
});
|
||||
return session;
|
||||
@@ -173,6 +174,7 @@ export const purchaseTeamOrOrgSubscription = async (input: {
|
||||
subscription_data: {
|
||||
metadata: {
|
||||
teamId,
|
||||
dubCustomerId: userId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@calcom/ui": "*",
|
||||
"@lexical/react": "^0.9.0",
|
||||
"@tanstack/react-table": "^8.9.3",
|
||||
"@vercel/functions": "^1.4.0",
|
||||
"framer-motion": "^10.12.8",
|
||||
"lexical": "^0.9.0",
|
||||
"react-select": "^5.7.0",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import dynamic from "next/dynamic";
|
||||
import React from "react";
|
||||
|
||||
import { AUTH_OPTIONS } from "@calcom/features/auth/lib/next-auth-options";
|
||||
import { getServerSessionForAppDir } from "@calcom/feature-auth/lib/get-server-session-for-app-dir";
|
||||
import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
|
||||
|
||||
import type { SettingsLayoutProps } from "./SettingsLayoutAppDirClient";
|
||||
@@ -14,7 +13,7 @@ const SettingsLayoutAppDirClient = dynamic(() => import("./SettingsLayoutAppDirC
|
||||
type SettingsLayoutAppDir = Omit<SettingsLayoutProps, "currentOrg" | "otherTeams">;
|
||||
|
||||
export default async function SettingsLayoutAppDir(props: SettingsLayoutAppDir) {
|
||||
const session = await getServerSession(AUTH_OPTIONS);
|
||||
const session = await getServerSessionForAppDir();
|
||||
const userId = session?.user?.id ?? -1;
|
||||
const orgId = session?.user?.org?.id ?? -1;
|
||||
let currentOrg = null;
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
APP_NAME,
|
||||
DESKTOP_APP_LINK,
|
||||
ENABLE_PROFILE_SWITCHER,
|
||||
IS_CALCOM,
|
||||
IS_VISUAL_REGRESSION_TESTING,
|
||||
JOIN_COMMUNITY,
|
||||
ROADMAP,
|
||||
@@ -52,6 +53,7 @@ import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useFormbricks } from "@calcom/lib/formbricks-client";
|
||||
import getBrandColours from "@calcom/lib/getBrandColours";
|
||||
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
|
||||
import { useCopy } from "@calcom/lib/hooks/useCopy";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useNotifications, ButtonState } from "@calcom/lib/hooks/useNotifications";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
@@ -927,6 +929,7 @@ function SideBarContainer({ bannersHeight, isPlatformUser = false }: SideBarCont
|
||||
}
|
||||
|
||||
function SideBar({ bannersHeight, user }: SideBarProps) {
|
||||
const { isCopied, copyToClipboard, resetCopyStatus } = useCopy();
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
const orgBranding = useOrgBranding();
|
||||
const pathname = usePathname();
|
||||
@@ -960,12 +963,32 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
|
||||
},
|
||||
icon: "copy",
|
||||
},
|
||||
IS_CALCOM
|
||||
? {
|
||||
name: "copy_referral_link",
|
||||
href: "",
|
||||
onClick: async (e: { preventDefault: () => void }) => {
|
||||
e.preventDefault();
|
||||
const res = await fetch("/api/generate-referral-link", {
|
||||
method: "POST",
|
||||
});
|
||||
const { shortLink } = await res.json();
|
||||
copyToClipboard(shortLink);
|
||||
|
||||
if (isCopied) {
|
||||
showToast(t("link_copied"), "success");
|
||||
}
|
||||
},
|
||||
icon: "copy",
|
||||
}
|
||||
: null,
|
||||
{
|
||||
name: "settings",
|
||||
href: user?.org ? `/settings/organizations/profile` : "/settings/my-account/profile",
|
||||
icon: "settings",
|
||||
},
|
||||
];
|
||||
].filter(Boolean) as NavigationItemType[];
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<aside
|
||||
|
||||
@@ -9,6 +9,11 @@ export function useCopy() {
|
||||
.writeText(text)
|
||||
.then(() => setIsCopied(true))
|
||||
.catch((error) => console.error("Copy to clipboard failed:", error));
|
||||
} else {
|
||||
console.warn(
|
||||
"You need to use a secure context to use clipboard \n Please use the following link: ",
|
||||
text
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -225,6 +225,7 @@ export class UserRepository {
|
||||
},
|
||||
},
|
||||
},
|
||||
createdDate: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "referralLinkId" TEXT;
|
||||
@@ -335,6 +335,7 @@ model User {
|
||||
smsLockState SMSLockState @default(UNLOCKED)
|
||||
smsLockReviewedByAdmin Boolean @default(false)
|
||||
NotificationsSubscriptions NotificationsSubscriptions[]
|
||||
referralLinkId String?
|
||||
|
||||
@@unique([email])
|
||||
@@unique([email, username])
|
||||
|
||||
@@ -289,6 +289,7 @@
|
||||
"DATABASE_DIRECT_URL",
|
||||
"DATABASE_URL",
|
||||
"DEBUG",
|
||||
"DUB_API_KEY",
|
||||
"E2E_TEST_APPLE_CALENDAR_EMAIL",
|
||||
"E2E_TEST_APPLE_CALENDAR_PASSWORD",
|
||||
"E2E_TEST_CALCOM_QA_EMAIL",
|
||||
|
||||
Reference in New Issue
Block a user