diff --git a/.env.example b/.env.example
index 72a2e0f769..a2d142392f 100644
--- a/.env.example
+++ b/.env.example
@@ -15,6 +15,8 @@
# - You can not repackage or sell the codebase
# - Acquire a commercial license to remove these terms by visiting: cal.com/sales
NEXT_PUBLIC_LICENSE_CONSENT=''
+# To enable enterprise-only features, fill your license key in here
+CALCOM_LICENSE_KEY=
# ***********************************************************************************************************
# - DATABASE ************************************************************************************************
@@ -25,6 +27,7 @@ NEXT_PUBLIC_LICENSE_CONSENT=''
NEXT_PUBLIC_WEBAPP_URL='http://localhost:3000'
# Change to 'http://localhost:3001' if running the website simultaneously
NEXT_PUBLIC_WEBSITE_URL='http://localhost:3000'
+NEXT_PUBLIC_CONSOLE_URL='http://localhost:3004'
NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js'
# To enable SAML login, set both these variables
diff --git a/apps/api b/apps/api
index 14aca7ef2b..76b3f1ba3a 160000
--- a/apps/api
+++ b/apps/api
@@ -1 +1 @@
-Subproject commit 14aca7ef2b81421bdf4c95020bf54255abe34dcb
+Subproject commit 76b3f1ba3a6a7aad37b32ce4afee3c26420b18f4
diff --git a/apps/console b/apps/console
index dfa050650c..3c6d61a703 160000
--- a/apps/console
+++ b/apps/console
@@ -1 +1 @@
-Subproject commit dfa050650c1507f24ec81d972c0c697ec07934d9
+Subproject commit 3c6d61a703f1b1ce8739f0fe86fcd78b67085a22
diff --git a/apps/web/components/AppsShell.tsx b/apps/web/components/AppsShell.tsx
index 51f55c7448..23755ab80d 100644
--- a/apps/web/components/AppsShell.tsx
+++ b/apps/web/components/AppsShell.tsx
@@ -1,23 +1,21 @@
import { useSession } from "next-auth/react";
import React from "react";
-import { useLocale } from "@calcom/lib/hooks/useLocale";
-
import NavTabs from "./NavTabs";
+const tabs = [
+ {
+ name: "app_store",
+ href: "/apps",
+ },
+ {
+ name: "installed_apps",
+ href: "/apps/installed",
+ },
+];
+
export default function AppsShell({ children }: { children: React.ReactNode }) {
- const { t } = useLocale();
const { status } = useSession();
- const tabs = [
- {
- name: t("app_store"),
- href: "/apps",
- },
- {
- name: t("installed_apps"),
- href: "/apps/installed",
- },
- ];
return (
<>
diff --git a/apps/web/components/BookingsShell.tsx b/apps/web/components/BookingsShell.tsx
index 014e379eca..bbf5894dbb 100644
--- a/apps/web/components/BookingsShell.tsx
+++ b/apps/web/components/BookingsShell.tsx
@@ -1,30 +1,27 @@
import React from "react";
-import { useLocale } from "@lib/hooks/useLocale";
-
import NavTabs from "./NavTabs";
-export default function BookingsShell({ children }: { children: React.ReactNode }) {
- const { t } = useLocale();
- const tabs = [
- {
- name: t("upcoming"),
- href: "/bookings/upcoming",
- },
- {
- name: t("recurring"),
- href: "/bookings/recurring",
- },
- {
- name: t("past"),
- href: "/bookings/past",
- },
- {
- name: t("cancelled"),
- href: "/bookings/cancelled",
- },
- ];
+const tabs = [
+ {
+ name: "upcoming",
+ href: "/bookings/upcoming",
+ },
+ {
+ name: "recurring",
+ href: "/bookings/recurring",
+ },
+ {
+ name: "past",
+ href: "/bookings/past",
+ },
+ {
+ name: "cancelled",
+ href: "/bookings/cancelled",
+ },
+];
+export default function BookingsShell({ children }: { children: React.ReactNode }) {
return (
<>
diff --git a/apps/web/components/EmptyScreen.tsx b/apps/web/components/EmptyScreen.tsx
index 4118800cbe..d8ba88fea6 100644
--- a/apps/web/components/EmptyScreen.tsx
+++ b/apps/web/components/EmptyScreen.tsx
@@ -9,17 +9,17 @@ export default function EmptyScreen({
}: {
Icon: SVGComponent;
headline: string;
- description: string;
+ description: string | React.ReactElement;
}) {
return (
<>
-
-
+
+
-
{headline}
-
{description}
+
{headline}
+
{description}
>
diff --git a/apps/web/components/NavTabs.tsx b/apps/web/components/NavTabs.tsx
index bc3de08adb..fa42293f46 100644
--- a/apps/web/components/NavTabs.tsx
+++ b/apps/web/components/NavTabs.tsx
@@ -4,6 +4,8 @@ import Link, { LinkProps } from "next/link";
import { useRouter } from "next/router";
import { FC, Fragment, MouseEventHandler } from "react";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+
import classNames from "@lib/classNames";
import { SVGComponent } from "@lib/types/SVGComponent";
@@ -22,6 +24,7 @@ export interface NavTabProps {
const NavTabs: FC
= ({ tabs, linkProps, ...props }) => {
const router = useRouter();
+ const { t } = useLocale();
return (
<>
{/* show bottom navigation for md and smaller (tablet and phones) */}
{status === "authenticated" && (
diff --git a/apps/web/components/ui/ModalContainer.tsx b/apps/web/components/ui/ModalContainer.tsx
index 6396420757..5fa6353350 100644
--- a/apps/web/components/ui/ModalContainer.tsx
+++ b/apps/web/components/ui/ModalContainer.tsx
@@ -18,7 +18,7 @@ export default function ModalContainer(props: Props) {
= ({ children, as = "", ...rest }) => {
+ const session = useSession();
+ const Component = as || Fragment;
+ return (
+
+ {session.data?.hasValidLicense ? (
+ children
+ ) : (
+
+ To enable this feature, get a deployment key at{" "}
+
+ Cal.com console
+
+ .
+ >
+ }
+ />
+ )}
+
+ );
+};
+
+export function withLicenseRequired
(Component: ComponentType) {
+ // eslint-disable-next-line react/display-name
+ return (hocProps: T) => {
+ return (
+
+ ;
+
+ );
+ };
+}
+
+export default LicenseRequired;
diff --git a/apps/web/ee/components/apiKeys/ApiKeyDialogForm.tsx b/apps/web/ee/components/apiKeys/ApiKeyDialogForm.tsx
index 99730950cc..783036f464 100644
--- a/apps/web/ee/components/apiKeys/ApiKeyDialogForm.tsx
+++ b/apps/web/ee/components/apiKeys/ApiKeyDialogForm.tsx
@@ -15,11 +15,12 @@ import { trpc } from "@lib/trpc";
import { DatePicker } from "@components/ui/form/DatePicker";
-import { TApiKeys } from "./ApiKeyListItem";
+import LicenseRequired from "../LicenseRequired";
+import type { TApiKeys } from "./ApiKeyListItem";
export default function ApiKeyDialogForm(props: {
title: string;
- defaultValues?: Omit & { neverExpires: boolean };
+ defaultValues?: Omit & { neverExpires?: boolean };
handleClose: () => void;
}) {
const { t } = useLocale();
@@ -49,7 +50,7 @@ export default function ApiKeyDialogForm(props: {
const watchNeverExpires = form.watch("neverExpires");
return (
- <>
+
{successfulNewApiKeyModal ? (
<>
@@ -92,12 +93,12 @@ export default function ApiKeyDialogForm(props: {
>
) : (
-
)}
- >
+
);
}
diff --git a/apps/web/ee/components/apiKeys/ApiKeyListContainer.tsx b/apps/web/ee/components/apiKeys/ApiKeyListContainer.tsx
index 5e71b13c84..0bc28d54d1 100644
--- a/apps/web/ee/components/apiKeys/ApiKeyListContainer.tsx
+++ b/apps/web/ee/components/apiKeys/ApiKeyListContainer.tsx
@@ -12,66 +12,76 @@ import { trpc } from "@lib/trpc";
import { List } from "@components/List";
-export default function ApiKeyListContainer() {
+import LicenseRequired from "../LicenseRequired";
+
+function ApiKeyListContainer() {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.apiKeys.list"]);
const [newApiKeyModal, setNewApiKeyModal] = useState(false);
const [editModalOpen, setEditModalOpen] = useState(false);
- const [apiKeyToEdit, setApiKeyToEdit] = useState<(TApiKeys & { neverExpires: boolean }) | null>(null);
+ const [apiKeyToEdit, setApiKeyToEdit] = useState<(TApiKeys & { neverExpires?: boolean }) | null>(null);
return (
-
(
- <>
-
-
-
{t("api_keys")}
-
{t("api_keys_subtitle")}
-
-
-
-
-
-
- {data.length > 0 && (
-
- {data.map((item: any) => (
- {
- setApiKeyToEdit(item);
- setEditModalOpen(true);
- }}
- />
- ))}
-
- )}
-
- {/* New api key dialog */}
-
- {/* Edit api key dialog */}
-
- >
- )}
- />
+
+ {/* New api key dialog */}
+
+ {/* Edit api key dialog */}
+
+ >
+ )}
+ />
+
+ >
);
}
+
+export default ApiKeyListContainer;
diff --git a/apps/web/ee/components/saml/Configuration.tsx b/apps/web/ee/components/saml/Configuration.tsx
index 4b32f7ab51..fa4a632d7e 100644
--- a/apps/web/ee/components/saml/Configuration.tsx
+++ b/apps/web/ee/components/saml/Configuration.tsx
@@ -1,18 +1,20 @@
-import React, { useEffect, useState, useRef } from "react";
+import React, { useEffect, useRef, useState } from "react";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import { Dialog, DialogTrigger } from "@calcom/ui/Dialog";
import { TextArea } from "@calcom/ui/form/fields";
-import { useLocale } from "@lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { trpc } from "@lib/trpc";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
import Badge from "@components/ui/Badge";
+import LicenseRequired from "../LicenseRequired";
+
export default function SAMLConfiguration({
teamsView,
teamId,
@@ -92,7 +94,7 @@ export default function SAMLConfiguration({
return (
<>
{isSAMLLoginEnabled ? (
- <>
+
@@ -157,7 +159,7 @@ export default function SAMLConfiguration({
- >
+
) : null}
>
);
diff --git a/apps/web/ee/components/stripe/PaymentPage.tsx b/apps/web/ee/components/stripe/PaymentPage.tsx
index b68fdbbc50..76ca06ee1e 100644
--- a/apps/web/ee/components/stripe/PaymentPage.tsx
+++ b/apps/web/ee/components/stripe/PaymentPage.tsx
@@ -11,6 +11,7 @@ import { FormattedNumber, IntlProvider } from "react-intl";
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core";
import getStripe from "@calcom/stripe/client";
+import LicenseRequired from "@ee/components/LicenseRequired";
import PaymentComponent from "@ee/components/stripe/Payment";
import { PaymentPageProps } from "@ee/pages/payment/[uid]";
@@ -70,7 +71,8 @@ const PaymentPage: FC = (props) => {
-
)}
-
+
diff --git a/apps/web/ee/components/team/availability/TeamAvailabilityModal.tsx b/apps/web/ee/components/team/availability/TeamAvailabilityModal.tsx
index 43e5640d8b..583f196e78 100644
--- a/apps/web/ee/components/team/availability/TeamAvailabilityModal.tsx
+++ b/apps/web/ee/components/team/availability/TeamAvailabilityModal.tsx
@@ -3,8 +3,9 @@ import utc from "dayjs/plugin/utc";
import React, { useState, useEffect } from "react";
import { WEBSITE_URL } from "@calcom/lib/constants";
+import LicenseRequired from "@ee/components/LicenseRequired";
-import { trpc, inferQueryOutput } from "@lib/trpc";
+import { inferQueryOutput, trpc } from "@lib/trpc";
import Avatar from "@components/ui/Avatar";
import { DatePicker } from "@components/ui/form/DatePicker";
@@ -33,62 +34,66 @@ export default function TeamAvailabilityModal(props: Props) {
}, [utils, selectedTimeZone, selectedDate]);
return (
-
-
-
-
-
-
{props.member?.name}
-
{props.member?.email}
+
+
+
+
+
+
+ {props.member?.name}
+ {props.member?.email}
+
+
+
+ Date
+ {
+ setSelectedDate(dayjs(newDate));
+ }}
+ />
+
+
+ Timezone
+ setSelectedTimeZone(timezone.value)}
+ classNamePrefix="react-select"
+ className="react-select-container mt-1 block w-full rounded-sm border border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
+ />
+
+
+ Slot Length
+
-
- Date
- {
- setSelectedDate(dayjs(newDate));
- }}
+ {props.team && props.member && (
+
-
-
- Timezone
- setSelectedTimeZone(timezone.value)}
- className="mt-1 block w-full rounded-sm border border-gray-300 shadow-sm sm:text-sm"
- />
-
-
- Slot Length
-
+ )}
- {props.team && props.member && (
-
- )}
-
+
);
}
diff --git a/apps/web/ee/components/web3/CryptoSection.tsx b/apps/web/ee/components/web3/CryptoSection.tsx
index 4d570429ee..0056c65f85 100644
--- a/apps/web/ee/components/web3/CryptoSection.tsx
+++ b/apps/web/ee/components/web3/CryptoSection.tsx
@@ -11,6 +11,7 @@ import { Button } from "@calcom/ui/Button";
import { useContracts } from "../../../contexts/contractsContext";
import genericAbi from "../../../web3/abis/abiWithGetBalance.json";
import verifyAccount, { AUTH_MESSAGE } from "../../../web3/utils/verifyAccount";
+import { withLicenseRequired } from "../LicenseRequired";
interface Window {
ethereum: AbstractProvider & { selectedAddress: string };
@@ -150,4 +151,4 @@ const CryptoSection = (props: CryptoSectionProps) => {
);
};
-export default CryptoSection;
+export default withLicenseRequired(CryptoSection);
diff --git a/apps/web/ee/pages/settings/teams/[id]/availability.tsx b/apps/web/ee/pages/settings/teams/[id]/availability.tsx
index 49cba9598c..d920cfb6aa 100644
--- a/apps/web/ee/pages/settings/teams/[id]/availability.tsx
+++ b/apps/web/ee/pages/settings/teams/[id]/availability.tsx
@@ -2,6 +2,7 @@ import { useRouter } from "next/router";
import { useMemo, useState } from "react";
import { Alert } from "@calcom/ui/Alert";
+import LicenseRequired from "@ee/components/LicenseRequired";
import TeamAvailabilityScreen from "@ee/components/team/availability/TeamAvailabilityScreen";
import { getPlaceholderAvatar } from "@lib/getPlaceholderAvatar";
@@ -49,17 +50,19 @@ export function TeamAvailabilityPage() {
/>
)
}>
- {!!errorMessage &&
}
- {isLoading &&
}
- {isFreeUser ? (
-
- ) : (
- TeamAvailability
- )}
+
+ {!!errorMessage && }
+ {isLoading && }
+ {isFreeUser ? (
+
+ ) : (
+ TeamAvailability
+ )}
+
);
}
diff --git a/apps/web/lib/ErrorBoundary.tsx b/apps/web/lib/ErrorBoundary.tsx
new file mode 100644
index 0000000000..9fee8fa915
--- /dev/null
+++ b/apps/web/lib/ErrorBoundary.tsx
@@ -0,0 +1,35 @@
+import React, { ErrorInfo } from "react";
+
+class ErrorBoundary extends React.Component<
+ { children: React.ReactNode },
+ { error: Error | null; errorInfo: ErrorInfo | null }
+> {
+ constructor(props: { children: React.ReactNode } | Readonly<{ children: React.ReactNode }>) {
+ super(props);
+ this.state = { error: null, errorInfo: null };
+ }
+
+ componentDidCatch?(error: Error, errorInfo: ErrorInfo) {
+ // Catch errors in any components below and re-render with error message
+ this.setState({ error, errorInfo });
+ // You can also log error messages to an error reporting service here
+ }
+
+ render() {
+ if (this.state.errorInfo) {
+ // Error path
+ return (
+
+
Something went wrong.
+
+ {this.state.error && this.state.error.toString()}
+
+
+ );
+ }
+ // Normally, just render children
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/apps/web/lib/app-providers.tsx b/apps/web/lib/app-providers.tsx
index 6fb3f97717..d6f6bfa333 100644
--- a/apps/web/lib/app-providers.tsx
+++ b/apps/web/lib/app-providers.tsx
@@ -16,7 +16,8 @@ const I18nextAdapter = appWithTranslation
& {
+ Component: NextAppProps["Component"] & { requiresLicense?: boolean };
/** Will be defined only is there was an error */
err?: Error;
};
diff --git a/apps/web/package.json b/apps/web/package.json
index 811ef7849d..1a28b1ed3a 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -76,6 +76,7 @@
"jimp": "^0.16.1",
"libphonenumber-js": "^1.9.53",
"lodash": "^4.17.21",
+ "memory-cache": "^0.2.0",
"micro": "^9.3.4",
"mime-types": "^2.1.35",
"next": "^12.1.6",
@@ -125,6 +126,7 @@
"@types/glidejs__glide": "^3.4.2",
"@types/jest": "^27.5.1",
"@types/lodash": "^4.14.182",
+ "@types/memory-cache": "^0.2.2",
"@types/micro": "7.3.6",
"@types/mime-types": "^2.1.1",
"@types/module-alias": "^2.0.1",
diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx
index 794102f2b9..3f1f1eae4e 100644
--- a/apps/web/pages/_app.tsx
+++ b/apps/web/pages/_app.tsx
@@ -3,6 +3,7 @@ import Head from "next/head";
import superjson from "superjson";
import "@calcom/embed-core/src/embed-iframe";
+import LicenseRequired from "@ee/components/LicenseRequired";
import AppProviders, { AppProps } from "@lib/app-providers";
import { seoConfig } from "@lib/config/next-seo.config";
@@ -37,7 +38,13 @@ function MyApp(props: AppProps) {
-
+ {Component.requiresLicense ? (
+
+
+
+ ) : (
+
+ )}
);
diff --git a/apps/web/pages/api/auth/[...nextauth].tsx b/apps/web/pages/api/auth/[...nextauth].tsx
index ccd947bc00..f63ec6d407 100644
--- a/apps/web/pages/api/auth/[...nextauth].tsx
+++ b/apps/web/pages/api/auth/[...nextauth].tsx
@@ -10,6 +10,7 @@ import nodemailer, { TransportOptions } from "nodemailer";
import { authenticator } from "otplib";
import path from "path";
+import checkLicense from "@calcom/ee/server/checkLicense";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { defaultCookies } from "@calcom/lib/default-cookies";
@@ -276,8 +277,10 @@ export default NextAuth({
return token;
},
async session({ session, token }) {
+ const hasValidLicense = await checkLicense(process.env.CALCOM_LICENSE_KEY || "");
const calendsoSession: Session = {
...session,
+ hasValidLicense,
user: {
...session.user,
id: token.id as number,
diff --git a/apps/web/pages/settings/billing.tsx b/apps/web/pages/settings/billing.tsx
index 7a22a388e6..96acb03822 100644
--- a/apps/web/pages/settings/billing.tsx
+++ b/apps/web/pages/settings/billing.tsx
@@ -1,14 +1,13 @@
import { ExternalLinkIcon } from "@heroicons/react/solid";
import { ReactNode } from "react";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import { useIntercom } from "@ee/lib/intercom/useIntercom";
-import { useLocale } from "@lib/hooks/useLocale";
import useMeQuery from "@lib/hooks/useMeQuery";
import SettingsShell from "@components/SettingsShell";
-import Shell from "@components/Shell";
type CardProps = { title: string; description: string; className?: string; children: ReactNode };
const Card = ({ title, description, className = "", children }: CardProps): JSX.Element => (
@@ -30,8 +29,8 @@ export default function Billing() {
const { boot, show } = useIntercom();
return (
-
-
+
+ <>
{data?.plan && ["FREE", "TRIAL"].includes(data.plan) && (
-
-
+ >
+
);
}
diff --git a/apps/web/pages/settings/profile.tsx b/apps/web/pages/settings/profile.tsx
index a798945022..229421e441 100644
--- a/apps/web/pages/settings/profile.tsx
+++ b/apps/web/pages/settings/profile.tsx
@@ -5,6 +5,7 @@ import { signOut } from "next-auth/react";
import { useRouter } from "next/router";
import { ComponentProps, FormEvent, RefObject, useEffect, useMemo, useRef, useState } from "react";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
@@ -15,7 +16,6 @@ import { withQuery } from "@lib/QueryCell";
import { asStringOrNull, asStringOrUndefined } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";
import { nameOfDay } from "@lib/core/i18n/weekday";
-import { useLocale } from "@lib/hooks/useLocale";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import prisma from "@lib/prisma";
import { trpc } from "@lib/trpc";
@@ -23,7 +23,6 @@ import { inferSSRProps } from "@lib/types/inferSSRProps";
import ImageUploader from "@components/ImageUploader";
import SettingsShell from "@components/SettingsShell";
-import Shell from "@components/Shell";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
import Avatar from "@components/ui/Avatar";
import Badge from "@components/ui/Badge";
@@ -488,11 +487,9 @@ export default function Settings(props: Props) {
const { t } = useLocale();
return (
-
-
- } />
-
-
+
+ } />
+
);
}
diff --git a/apps/web/pages/settings/security.tsx b/apps/web/pages/settings/security.tsx
index 1fda179798..a88784319a 100644
--- a/apps/web/pages/settings/security.tsx
+++ b/apps/web/pages/settings/security.tsx
@@ -9,7 +9,6 @@ import { identityProviderNameMap } from "@lib/auth";
import { trpc } from "@lib/trpc";
import SettingsShell from "@components/SettingsShell";
-import Shell from "@components/Shell";
import ChangePasswordSection from "@components/security/ChangePasswordSection";
import DisableUserImpersonation from "@components/security/DisableUserImpersonation";
import TwoFactorAuthSection from "@components/security/TwoFactorAuthSection";
@@ -18,8 +17,8 @@ export default function Security() {
const user = trpc.useQuery(["viewer.me"]).data;
const { t } = useLocale();
return (
-
-
+
+ <>
{user && user.identityProvider !== IdentityProvider.CAL ? (
<>
@@ -45,7 +44,7 @@ export default function Security() {
)}
-
-
+ >
+
);
}
diff --git a/apps/web/pages/settings/teams/index.tsx b/apps/web/pages/settings/teams/index.tsx
index be391687cf..1ad9065c7b 100644
--- a/apps/web/pages/settings/teams/index.tsx
+++ b/apps/web/pages/settings/teams/index.tsx
@@ -4,21 +4,20 @@ import { useSession } from "next-auth/react";
import { Trans } from "next-i18next";
import { useState } from "react";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
-import { useLocale } from "@lib/hooks/useLocale";
import useMeQuery from "@lib/hooks/useMeQuery";
import { trpc } from "@lib/trpc";
import EmptyScreen from "@components/EmptyScreen";
import Loader from "@components/Loader";
import SettingsShell from "@components/SettingsShell";
-import Shell from "@components/Shell";
import TeamCreateModal from "@components/team/TeamCreateModal";
import TeamList from "@components/team/TeamList";
-export default function Teams() {
+function Teams() {
const { t } = useLocale();
const { status } = useSession();
const loading = status === "loading";
@@ -40,8 +39,8 @@ export default function Teams() {
const isFreePlan = me.data?.plan === "FREE";
return (
-
-
+
+ <>
{!!errorMessage && }
{isFreePlan && (
)}
{teams.length > 0 && }
-
-
+ >
+
);
}
+
+Teams.requiresLicense = false;
+
+export default Teams;
diff --git a/package.json b/package.json
index bb0d14e792..056db78c68 100644
--- a/package.json
+++ b/package.json
@@ -16,10 +16,11 @@
"db-studio": "yarn workspace @calcom/prisma db-studio",
"deploy": "turbo run deploy",
"dev": "turbo run dev --scope=\"@calcom/web\"",
- "dev:website": "yarn predev && turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/website\"",
- "dev:api": "yarn predev && turbo run dev --scope=\"@calcom/api\"",
- "dev:swagger": "yarn predev && turbo run dev --scope=\"@calcom/api\" --scope=\"@calcom/swagger\"",
+ "dev:all": "yarn predev && turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/website\" --scope=\"@calcom/console\"",
+ "dev:api": "yarn predev && turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/api\"",
"dev:console": "yarn predev && turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/console\"",
+ "dev:swagger": "yarn predev && turbo run dev --scope=\"@calcom/api\" --scope=\"@calcom/swagger\"",
+ "dev:website": "yarn predev && turbo run dev --scope=\"@calcom/web\" --scope=\"@calcom/website\"",
"docs-dev": "yarn predev && turbo run dev --scope=\"@calcom/docs\"",
"docs-build": "turbo run build --scope=\"@calcom/docs\" --include-dependencies",
"docs-start": "turbo run start --scope=\"@calcom/docs\"",
diff --git a/packages/ee/server/checkLicense.ts b/packages/ee/server/checkLicense.ts
new file mode 100644
index 0000000000..cf359912e4
--- /dev/null
+++ b/packages/ee/server/checkLicense.ts
@@ -0,0 +1,25 @@
+import cache from "memory-cache";
+
+import { CONSOLE_URL } from "@calcom/lib/constants";
+
+const CACHING_TIME = 86400000; // 24 hours in milliseconds
+
+async function checkLicense(license: string): Promise
{
+ if (!!process.env.NEXT_PUBLIC_IS_E2E) return true;
+ const url = `${CONSOLE_URL}/api/license?key=${license}`;
+ const cachedResponse = cache.get(url);
+ if (cachedResponse) {
+ return cachedResponse;
+ } else {
+ try {
+ const response = await fetch(url);
+ const data = await response.json();
+ cache.put(url, data.valid, CACHING_TIME);
+ return data.valid;
+ } catch (error) {
+ return false;
+ }
+ }
+}
+
+export default checkLicense;
diff --git a/packages/lib/availability.ts b/packages/lib/availability.ts
index 4bb39609a9..a27932e573 100644
--- a/packages/lib/availability.ts
+++ b/packages/lib/availability.ts
@@ -4,9 +4,10 @@ import customParseFormat from "dayjs/plugin/customParseFormat";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
-import { nameOfDay } from "@calcom/lib/weekday";
import type { Schedule, TimeRange, WorkingHours } from "@calcom/types/schedule";
+import { nameOfDay } from "./weekday";
+
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(customParseFormat);
diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts
index 18bb1da7d2..b7e3050ad3 100644
--- a/packages/lib/defaultEvents.ts
+++ b/packages/lib/defaultEvents.ts
@@ -1,4 +1,5 @@
-import { PeriodType, SchedulingType, UserPlan, EventTypeCustomInput } from "@prisma/client";
+import type { EventTypeCustomInput } from "@prisma/client";
+import { PeriodType, SchedulingType, UserPlan } from "@prisma/client";
const availability = [
{
diff --git a/packages/lib/isPrismaObj.ts b/packages/lib/isPrismaObj.ts
index 797f4be7b4..d405ab32b3 100644
--- a/packages/lib/isPrismaObj.ts
+++ b/packages/lib/isPrismaObj.ts
@@ -1,4 +1,4 @@
-import { Prisma } from "@prisma/client";
+import type { Prisma } from "@prisma/client";
function isPrismaObj(obj: unknown): obj is Prisma.JsonObject {
return typeof obj === "object" && !Array.isArray(obj);
diff --git a/packages/lib/package.json b/packages/lib/package.json
index cd3e443f45..017004bd7e 100644
--- a/packages/lib/package.json
+++ b/packages/lib/package.json
@@ -5,6 +5,7 @@
"types": "./index.ts",
"license": "MIT",
"dependencies": {
+ "@prisma/client": "^3.14.0",
"bcryptjs": "^2.4.3",
"dayjs": "^1.11.2",
"ical.js": "^1.4.0",
@@ -17,6 +18,7 @@
},
"devDependencies": {
"@calcom/tsconfig": "*",
+ "@calcom/types": "*",
"typescript": "^4.6.4"
}
}
diff --git a/packages/types/environment.d.ts b/packages/types/environment.d.ts
index 1156cec473..29592266df 100644
--- a/packages/types/environment.d.ts
+++ b/packages/types/environment.d.ts
@@ -10,6 +10,8 @@ declare namespace NodeJS {
* - Acquire a commercial license to remove these terms by visiting: cal.com/sales
**/
readonly NEXT_PUBLIC_LICENSE_CONSENT: "agree" | undefined;
+ /** Needed to enable enterprise-only features */
+ readonly CALCOM_LICENSE_KEY: string | undefined;
readonly CALENDSO_ENCRYPTION_KEY: string | undefined;
readonly DATABASE_URL: string | undefined;
readonly GOOGLE_API_CREDENTIALS: string | undefined;
diff --git a/packages/types/next-auth.d.ts b/packages/types/next-auth.d.ts
index bf0227f304..b447c8fb78 100644
--- a/packages/types/next-auth.d.ts
+++ b/packages/types/next-auth.d.ts
@@ -13,6 +13,7 @@ declare module "next-auth" {
* Returned by `useSession`, `getSession` and received as a prop on the `Provider` React Context
*/
interface Session {
+ hasValidLicense: boolean;
user: CalendsoSessionUser;
}
}
diff --git a/turbo.json b/turbo.json
index 4d88845e79..c9dc905c12 100644
--- a/turbo.json
+++ b/turbo.json
@@ -3,9 +3,6 @@
"baseBranch": "origin/main",
"globalDependencies": [".env", "packages/prisma/.env"],
"pipeline": {
- "@calcom/admin#post-install": {
- "outputs": ["./node_modules/@prisma/admin-client/**", "./apps/console/prisma/schema.prisma"]
- },
"@calcom/prisma#post-install": {
"outputs": ["./node_modules/@prisma/client/**", "./packages/prisma/schema.prisma"]
},
diff --git a/yarn.lock b/yarn.lock
index 7278ee7b64..7301a7575b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -102,20 +102,20 @@
semver "^6.3.0"
"@babel/core@^7.11.6":
- version "7.17.12"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.12.tgz#b4eb2d7ebc3449b062381644c93050db545b70ee"
- integrity sha512-44ODe6O1IVz9s2oJE3rZ4trNNKTX9O7KpQpfAP4t8QII/zwrVRHL7i2pxhqtcY7tqMLrrKfMlBKnm1QlrRFs5w==
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.0.tgz#c58d04d7c6fbfb58ea7681e2b9145cfb62726756"
+ integrity sha512-Xyw74OlJwDijToNi0+6BBI5mLLR5+5R3bcSH80LXzjzEGEUlvNzujEE71BaD/ApEZHAvFI/Mlmp4M5lIkdeeWw==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.16.7"
- "@babel/generator" "^7.17.12"
+ "@babel/generator" "^7.18.0"
"@babel/helper-compilation-targets" "^7.17.10"
- "@babel/helper-module-transforms" "^7.17.12"
- "@babel/helpers" "^7.17.9"
- "@babel/parser" "^7.17.12"
+ "@babel/helper-module-transforms" "^7.18.0"
+ "@babel/helpers" "^7.18.0"
+ "@babel/parser" "^7.18.0"
"@babel/template" "^7.16.7"
- "@babel/traverse" "^7.17.12"
- "@babel/types" "^7.17.12"
+ "@babel/traverse" "^7.18.0"
+ "@babel/types" "^7.18.0"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
@@ -188,6 +188,15 @@
jsesc "^2.5.1"
source-map "^0.5.0"
+"@babel/generator@^7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.0.tgz#46d28e8a18fc737b028efb25ab105d74473af43f"
+ integrity sha512-81YO9gGx6voPXlvYdZBliFXAZU8vZ9AZ6z+CjlmcnaeOcYSFbMTpdeDUO9xD9dh/68Vq03I8ZspfUTPfitcDHg==
+ dependencies:
+ "@babel/types" "^7.18.0"
+ "@jridgewell/gen-mapping" "^0.3.0"
+ jsesc "^2.5.1"
+
"@babel/helper-annotate-as-pure@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862"
@@ -274,10 +283,10 @@
"@babel/traverse" "^7.17.3"
"@babel/types" "^7.17.0"
-"@babel/helper-module-transforms@^7.17.12":
- version "7.17.12"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.12.tgz#bec00139520cb3feb078ef7a4578562480efb77e"
- integrity sha512-t5s2BeSWIghhFRPh9XMn6EIGmvn8Lmw5RVASJzkIx1mSemubQQBNIZiQD7WzaFmaHIrjAec4x8z9Yx8SjJ1/LA==
+"@babel/helper-module-transforms@^7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd"
+ integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==
dependencies:
"@babel/helper-environment-visitor" "^7.16.7"
"@babel/helper-module-imports" "^7.16.7"
@@ -285,8 +294,8 @@
"@babel/helper-split-export-declaration" "^7.16.7"
"@babel/helper-validator-identifier" "^7.16.7"
"@babel/template" "^7.16.7"
- "@babel/traverse" "^7.17.12"
- "@babel/types" "^7.17.12"
+ "@babel/traverse" "^7.18.0"
+ "@babel/types" "^7.18.0"
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0":
version "7.16.7"
@@ -340,6 +349,15 @@
"@babel/traverse" "^7.17.9"
"@babel/types" "^7.17.0"
+"@babel/helpers@^7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.0.tgz#aff37c3590de42102b54842446146d0205946370"
+ integrity sha512-AE+HMYhmlMIbho9nbvicHyxFwhrO+xhKB6AhRxzl8w46Yj0VXTZjEsAoBVC7rB2I0jzX+yWyVybnO08qkfx6kg==
+ dependencies:
+ "@babel/template" "^7.16.7"
+ "@babel/traverse" "^7.18.0"
+ "@babel/types" "^7.18.0"
+
"@babel/highlight@^7.16.7":
version "7.16.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88"
@@ -374,6 +392,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef"
integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==
+"@babel/parser@^7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.0.tgz#10a8d4e656bc01128d299a787aa006ce1a91e112"
+ integrity sha512-AqDccGC+m5O/iUStSJy3DGRIUFu7WbY/CppZYwrEUB4N0tZlnI8CSTsgL7v5fHVFmUbRv2sd+yy27o8Ydt4MGg==
+
"@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
@@ -590,22 +613,6 @@
debug "^4.1.0"
globals "^11.1.0"
-"@babel/traverse@^7.17.12", "@babel/traverse@^7.7.2":
- version "7.17.12"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.12.tgz#011874d2abbca0ccf1adbe38f6f7a4ff1747599c"
- integrity sha512-zULPs+TbCvOkIFd4FrG53xrpxvCBwLIgo6tO0tJorY7YV2IWFxUfS/lXDJbGgfyYt9ery/Gxj2niwttNnB0gIw==
- dependencies:
- "@babel/code-frame" "^7.16.7"
- "@babel/generator" "^7.17.12"
- "@babel/helper-environment-visitor" "^7.16.7"
- "@babel/helper-function-name" "^7.17.9"
- "@babel/helper-hoist-variables" "^7.16.7"
- "@babel/helper-split-export-declaration" "^7.16.7"
- "@babel/parser" "^7.17.12"
- "@babel/types" "^7.17.12"
- debug "^4.1.0"
- globals "^11.1.0"
-
"@babel/traverse@^7.17.9":
version "7.17.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d"
@@ -622,6 +629,38 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.0.tgz#0e5ec6db098660b2372dd63d096bf484e32d27ba"
+ integrity sha512-oNOO4vaoIQoGjDQ84LgtF/IAlxlyqL4TUuoQ7xLkQETFaHkY1F7yazhB4Kt3VcZGL0ZF/jhrEpnXqUb0M7V3sw==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/generator" "^7.18.0"
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-function-name" "^7.17.9"
+ "@babel/helper-hoist-variables" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/parser" "^7.18.0"
+ "@babel/types" "^7.18.0"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/traverse@^7.7.2":
+ version "7.17.12"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.12.tgz#011874d2abbca0ccf1adbe38f6f7a4ff1747599c"
+ integrity sha512-zULPs+TbCvOkIFd4FrG53xrpxvCBwLIgo6tO0tJorY7YV2IWFxUfS/lXDJbGgfyYt9ery/Gxj2niwttNnB0gIw==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/generator" "^7.17.12"
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-function-name" "^7.17.9"
+ "@babel/helper-hoist-variables" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/parser" "^7.17.12"
+ "@babel/types" "^7.17.12"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/types@7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80"
@@ -655,6 +694,14 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.0.tgz#ef523ea349722849cb4bf806e9342ede4d071553"
+ integrity sha512-vhAmLPAiC8j9K2GnsnLPCIH5wCrPpYIVBCWRBFDCB7Y/BXLqi/O+1RSTTM2bsmg6U/551+FCf9PNPxjABmxHTw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -3454,6 +3501,11 @@
resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.1.tgz#e4c05d355d092d7b58db1abfe460e53f41102ac8"
integrity sha512-JPEv4iAl0I+o7g8yVWDwk30es8mfVrjkvh5UeVR2sYPpZCK44vrAPsbJpIS+rJAUxLgaSAMKTEH5Vn5qd9XsrQ==
+"@types/memory-cache@^0.2.2":
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/@types/memory-cache/-/memory-cache-0.2.2.tgz#f8fb6d8aa0eb006ed44fc659bf8bfdc1a5cc57fa"
+ integrity sha512-xNnm6EkmYYhTnLiOHC2bdKgcYY5qjjrq5vl9KXD2nh0em0koZoFS500EL4Q4V/eW+A3P7NC7P7GIYzNOSQp7jQ==
+
"@types/micro@7.3.6":
version "7.3.6"
resolved "https://registry.yarnpkg.com/@types/micro/-/micro-7.3.6.tgz#7d68eb5a780ac4761e3b80687b4ee7328ebc3f2e"
@@ -10366,11 +10418,6 @@ libphonenumber-js@^1.9.52, libphonenumber-js@^1.9.53:
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.53.tgz#f4f3321f8fb0ee62952c2a8df4711236d2626088"
integrity sha512-3cuMrA2CY3TbKVC0wKye5dXYgxmVVi4g13gzotprQSguFHMqf0pIrMM2Z6ZtMsSWqvtIqi5TuQhGjMhxz0O9Mw==
-libphonenumber-js@^1.9.53:
- version "1.9.53"
- resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.53.tgz#f4f3321f8fb0ee62952c2a8df4711236d2626088"
- integrity sha512-3cuMrA2CY3TbKVC0wKye5dXYgxmVVi4g13gzotprQSguFHMqf0pIrMM2Z6ZtMsSWqvtIqi5TuQhGjMhxz0O9Mw==
-
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
@@ -10993,6 +11040,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+memory-cache@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/memory-cache/-/memory-cache-0.2.0.tgz#7890b01d52c00c8ebc9d533e1f8eb17e3034871a"
+ integrity sha1-eJCwHVLADI68nVM+H46xfjA0hxo=
+
memory-pager@^1.0.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"