License server (#2379)
* WIP License server * WIP * Moves locations to App Store and Core * LocationType fixes * Runs db migrations post-deploy * WIP * WIP * Cleanup * WIP * WIP * Decouples translations from NavTabs * Adds admin submodule * Adds admin submodule * Sync dependencies * WIP * WIP * Updates submodules * Renames package * Updates submodules * Adds scripts for console * Updates license checker URL * Updates admin * Adds staging/prod admin console links * Update yarn.lock * Update NavTabs.tsx * WIP * Update admin * WIP * Adds hint to InputField * Update admin * Adds turbo admin dependecies * Update admin * Prevents redirection on form submit * Form warning fixes * Update admin * Form fixes * Update yarn.lock * Update admin * Update admin * Update admin * Adds withLicenseRequired HOC * Adds LicenseRequired to EE components * Admin deploy fix? * Updates submodules * Use relative inside lib * type fixes * Fixes turbo race condition * Relocates admin to console * Relocates admin to console * Update console * Update api * Update turbo.json * Update ErrorBoundary.tsx * Update defaultEvents.ts * Update checkLicense.ts * Update yarn.lock * Skip on E2E Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
6940c8d6cc
commit
9df4867fca
|
@ -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
|
||||
|
|
2
apps/api
2
apps/api
|
@ -1 +1 @@
|
|||
Subproject commit 14aca7ef2b81421bdf4c95020bf54255abe34dcb
|
||||
Subproject commit 76b3f1ba3a6a7aad37b32ce4afee3c26420b18f4
|
|
@ -1 +1 @@
|
|||
Subproject commit dfa050650c1507f24ec81d972c0c697ec07934d9
|
||||
Subproject commit 3c6d61a703f1b1ce8739f0fe86fcd78b67085a22
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<NavTabs tabs={tabs} linkProps={{ shallow: true }} />
|
||||
|
|
|
@ -9,17 +9,17 @@ export default function EmptyScreen({
|
|||
}: {
|
||||
Icon: SVGComponent;
|
||||
headline: string;
|
||||
description: string;
|
||||
description: string | React.ReactElement;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className="min-h-80 my-6 flex flex-col items-center justify-center rounded-sm border border-dashed">
|
||||
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-white">
|
||||
<Icon className="inline-block h-10 w-10 bg-white" />
|
||||
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white">
|
||||
<Icon className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
|
||||
</div>
|
||||
<div className="max-w-[420px] text-center">
|
||||
<h2 className="mt-6 mb-1 text-lg font-medium">{headline}</h2>
|
||||
<p className="text-sm leading-6 text-gray-600">{description}</p>
|
||||
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{headline}</h2>
|
||||
<p className="text-sm leading-6 text-gray-600 dark:text-gray-300">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -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<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
|
@ -77,7 +80,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
|||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<span>{tab.name}</span>
|
||||
<span>{t(tab.name)}</span>
|
||||
</a>
|
||||
</Link>
|
||||
</Component>
|
||||
|
|
|
@ -1,48 +1,54 @@
|
|||
import { CreditCardIcon, KeyIcon, LockClosedIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid";
|
||||
import React from "react";
|
||||
import React, { ComponentProps } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import ErrorBoundary from "@lib/ErrorBoundary";
|
||||
|
||||
import NavTabs from "./NavTabs";
|
||||
import Shell from "./Shell";
|
||||
|
||||
export default function SettingsShell({ children }: { children: React.ReactNode }) {
|
||||
const { t } = useLocale();
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: t("profile"),
|
||||
href: "/settings/profile",
|
||||
icon: UserIcon,
|
||||
},
|
||||
{
|
||||
name: t("security"),
|
||||
href: "/settings/security",
|
||||
icon: KeyIcon,
|
||||
},
|
||||
{
|
||||
name: t("teams"),
|
||||
href: "/settings/teams",
|
||||
icon: UserGroupIcon,
|
||||
},
|
||||
{
|
||||
name: t("billing"),
|
||||
href: "/settings/billing",
|
||||
icon: CreditCardIcon,
|
||||
},
|
||||
{
|
||||
name: t("admin"),
|
||||
href: "/settings/admin",
|
||||
icon: LockClosedIcon,
|
||||
adminRequired: true,
|
||||
},
|
||||
];
|
||||
const tabs = [
|
||||
{
|
||||
name: "profile",
|
||||
href: "/settings/profile",
|
||||
icon: UserIcon,
|
||||
},
|
||||
{
|
||||
name: "security",
|
||||
href: "/settings/security",
|
||||
icon: KeyIcon,
|
||||
},
|
||||
{
|
||||
name: "teams",
|
||||
href: "/settings/teams",
|
||||
icon: UserGroupIcon,
|
||||
},
|
||||
{
|
||||
name: "billing",
|
||||
href: "/settings/billing",
|
||||
icon: CreditCardIcon,
|
||||
},
|
||||
{
|
||||
name: "admin",
|
||||
href: "/settings/admin",
|
||||
icon: LockClosedIcon,
|
||||
adminRequired: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default function SettingsShell({
|
||||
children,
|
||||
...rest
|
||||
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
||||
return (
|
||||
<>
|
||||
<Shell {...rest}>
|
||||
<div className="sm:mx-auto">
|
||||
<NavTabs tabs={tabs} />
|
||||
</div>
|
||||
<main className="max-w-4xl">{children}</main>
|
||||
</>
|
||||
<main className="max-w-4xl">
|
||||
<>
|
||||
<ErrorBoundary>{children}</ErrorBoundary>
|
||||
</>
|
||||
</main>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import LicenseBanner from "@ee/components/LicenseBanner";
|
|||
import TrialBanner from "@ee/components/TrialBanner";
|
||||
import HelpMenuItem from "@ee/components/support/HelpMenuItem";
|
||||
|
||||
import ErrorBoundary from "@lib/ErrorBoundary";
|
||||
import classNames from "@lib/classNames";
|
||||
import { WEBAPP_URL } from "@lib/config/constants";
|
||||
import { shouldShowOnboarding } from "@lib/getting-started";
|
||||
|
@ -349,7 +350,7 @@ const Layout = ({
|
|||
"px-4 sm:px-6 md:px-8",
|
||||
props.flexChildrenContainer && "flex flex-1 flex-col"
|
||||
)}>
|
||||
{!props.isLoading ? props.children : props.customLoader}
|
||||
<ErrorBoundary>{!props.isLoading ? props.children : props.customLoader}</ErrorBoundary>
|
||||
</div>
|
||||
{/* show bottom navigation for md and smaller (tablet and phones) */}
|
||||
{status === "authenticated" && (
|
||||
|
|
|
@ -18,7 +18,7 @@ export default function ModalContainer(props: Props) {
|
|||
<DialogContent>
|
||||
<div
|
||||
className={classNames(
|
||||
"inline-block transform bg-white text-left align-bottom transition-all sm:align-middle",
|
||||
"inline-block w-full transform bg-white text-left align-bottom transition-all sm:align-middle",
|
||||
{
|
||||
"sm:w-full sm:max-w-lg ": !props.wide,
|
||||
"sm:w-4xl sm:max-w-4xl": props.wide,
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { ExclamationIcon } from "@heroicons/react/solid";
|
||||
import { useSession } from "next-auth/react";
|
||||
import React, { AriaRole, ComponentType, FC, Fragment } from "react";
|
||||
|
||||
import { CONSOLE_URL } from "@calcom/lib/constants";
|
||||
|
||||
import EmptyScreen from "@components/EmptyScreen";
|
||||
|
||||
type LicenseRequiredProps = {
|
||||
as?: keyof JSX.IntrinsicElements | "";
|
||||
className?: string;
|
||||
role?: AriaRole | undefined;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* This component will only render it's children if the installation has a valid
|
||||
* license.
|
||||
*/
|
||||
const LicenseRequired: FC<LicenseRequiredProps> = ({ children, as = "", ...rest }) => {
|
||||
const session = useSession();
|
||||
const Component = as || Fragment;
|
||||
return (
|
||||
<Component {...rest}>
|
||||
{session.data?.hasValidLicense ? (
|
||||
children
|
||||
) : (
|
||||
<EmptyScreen
|
||||
Icon={ExclamationIcon}
|
||||
headline="This is an enterprise feature"
|
||||
description={
|
||||
<>
|
||||
To enable this feature, get a deployment key at{" "}
|
||||
<a href={CONSOLE_URL} target="_blank" rel="noopener noreferrer" className="underline">
|
||||
Cal.com console
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
export function withLicenseRequired<T>(Component: ComponentType<T>) {
|
||||
// eslint-disable-next-line react/display-name
|
||||
return (hocProps: T) => {
|
||||
return (
|
||||
<LicenseRequired>
|
||||
<Component {...(hocProps as T)} />;
|
||||
</LicenseRequired>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default LicenseRequired;
|
|
@ -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<TApiKeys, "userId" | "createdAt" | "lastUsedAt"> & { neverExpires: boolean };
|
||||
defaultValues?: Omit<TApiKeys, "userId" | "createdAt" | "lastUsedAt"> & { neverExpires?: boolean };
|
||||
handleClose: () => void;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
|
@ -49,7 +50,7 @@ export default function ApiKeyDialogForm(props: {
|
|||
const watchNeverExpires = form.watch("neverExpires");
|
||||
|
||||
return (
|
||||
<>
|
||||
<LicenseRequired>
|
||||
{successfulNewApiKeyModal ? (
|
||||
<>
|
||||
<div className="mb-10">
|
||||
|
@ -92,12 +93,12 @@ export default function ApiKeyDialogForm(props: {
|
|||
</DialogFooter>
|
||||
</>
|
||||
) : (
|
||||
<Form<Omit<TApiKeys, "userId" | "createdAt" | "lastUsedAt"> & { neverExpires: boolean }>
|
||||
<Form<Omit<TApiKeys, "userId" | "createdAt" | "lastUsedAt"> & { neverExpires?: boolean }>
|
||||
form={form}
|
||||
handleSubmit={async (event) => {
|
||||
const apiKey = await utils.client.mutation("viewer.apiKeys.create", event);
|
||||
setApiKey(apiKey);
|
||||
setApiKeyDetails({ ...event });
|
||||
setApiKeyDetails({ ...event, neverExpires: !!event.neverExpires });
|
||||
await utils.invalidateQueries(["viewer.apiKeys.list"]);
|
||||
setSuccessfulNewApiKeyModal(true);
|
||||
}}
|
||||
|
@ -146,6 +147,6 @@ export default function ApiKeyDialogForm(props: {
|
|||
</DialogFooter>
|
||||
</Form>
|
||||
)}
|
||||
</>
|
||||
</LicenseRequired>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<QueryCell
|
||||
query={query}
|
||||
success={({ data }) => (
|
||||
<>
|
||||
<div className="flex flex-col justify-between truncate pl-2 pr-1 sm:flex-row">
|
||||
<div className="mt-9">
|
||||
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">{t("api_keys")}</h2>
|
||||
<p className="mt-1 mb-5 text-sm text-gray-500">{t("api_keys_subtitle")}</p>
|
||||
</div>
|
||||
<div className="mb-9 sm:self-center">
|
||||
<Button StartIcon={PlusIcon} color="secondary" onClick={() => setNewApiKeyModal(true)}>
|
||||
{t("generate_new_api_key")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.length > 0 && (
|
||||
<List className="pb-6">
|
||||
{data.map((item: any) => (
|
||||
<ApiKeyListItem
|
||||
key={item.id}
|
||||
apiKey={item}
|
||||
onEditApiKey={() => {
|
||||
setApiKeyToEdit(item);
|
||||
setEditModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
|
||||
{/* New api key dialog */}
|
||||
<Dialog open={newApiKeyModal} onOpenChange={(isOpen) => !isOpen && setNewApiKeyModal(false)}>
|
||||
<DialogContent>
|
||||
<ApiKeyDialogForm title={t("create_api_key")} handleClose={() => setNewApiKeyModal(false)} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/* Edit api key dialog */}
|
||||
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
|
||||
<DialogContent>
|
||||
{apiKeyToEdit && (
|
||||
<ApiKeyDialogForm
|
||||
title={t("edit_api_key")}
|
||||
key={apiKeyToEdit.id}
|
||||
handleClose={() => setEditModalOpen(false)}
|
||||
defaultValues={apiKeyToEdit}
|
||||
/>
|
||||
<>
|
||||
<div className="flex flex-col justify-between truncate pl-2 pr-1 sm:flex-row">
|
||||
<div className="mt-9">
|
||||
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">{t("api_keys")}</h2>
|
||||
<p className="mt-1 mb-5 text-sm text-gray-500">{t("api_keys_subtitle")}</p>
|
||||
</div>
|
||||
<div className="mb-9 sm:self-center">
|
||||
<Button StartIcon={PlusIcon} color="secondary" onClick={() => setNewApiKeyModal(true)}>
|
||||
{t("generate_new_api_key")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<LicenseRequired>
|
||||
<QueryCell
|
||||
query={query}
|
||||
success={({ data }) => (
|
||||
<>
|
||||
{data.length > 0 && (
|
||||
<List className="pb-6">
|
||||
{data.map((item) => (
|
||||
<ApiKeyListItem
|
||||
key={item.id}
|
||||
apiKey={item}
|
||||
onEditApiKey={() => {
|
||||
setApiKeyToEdit(item);
|
||||
setEditModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* New api key dialog */}
|
||||
<Dialog open={newApiKeyModal} onOpenChange={(isOpen) => !isOpen && setNewApiKeyModal(false)}>
|
||||
<DialogContent>
|
||||
<ApiKeyDialogForm
|
||||
title={t("create_api_key")}
|
||||
handleClose={() => setNewApiKeyModal(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/* Edit api key dialog */}
|
||||
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
|
||||
<DialogContent>
|
||||
{apiKeyToEdit && (
|
||||
<ApiKeyDialogForm
|
||||
title={t("edit_api_key")}
|
||||
key={apiKeyToEdit.id}
|
||||
handleClose={() => setEditModalOpen(false)}
|
||||
defaultValues={apiKeyToEdit}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</LicenseRequired>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApiKeyListContainer;
|
||||
|
|
|
@ -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 ? (
|
||||
<>
|
||||
<LicenseRequired>
|
||||
<hr className="mt-8" />
|
||||
<div className="mt-6">
|
||||
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">
|
||||
|
@ -157,7 +159,7 @@ export default function SAMLConfiguration({
|
|||
</div>
|
||||
<hr className="mt-4" />
|
||||
</form>
|
||||
</>
|
||||
</LicenseRequired>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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<PaymentPageProps> = (props) => {
|
|||
<span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
<div
|
||||
<LicenseRequired
|
||||
as="div"
|
||||
className={classNames(
|
||||
"main inline-block transform overflow-hidden rounded-lg border border-neutral-200 bg-white px-8 pt-5 pb-4 text-left align-bottom transition-all dark:border-neutral-700 dark:bg-gray-800 sm:w-full sm:max-w-lg sm:py-6 sm:align-middle",
|
||||
isEmbed ? "" : "sm:my-8"
|
||||
|
@ -151,7 +153,7 @@ const PaymentPage: FC<PaymentPageProps> = (props) => {
|
|||
<a href="https://cal.com/signup">{t("create_booking_link_with_calcom")}</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</LicenseRequired>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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 (
|
||||
<div className="flex max-h-[500px] min-h-[500px] flex-row space-x-8 rtl:space-x-reverse">
|
||||
<div className="min-w-64 w-64 space-y-5 p-5 pr-0">
|
||||
<div className="flex">
|
||||
<Avatar
|
||||
imageSrc={WEBSITE_URL + "/" + props.member?.username + "/avatar.png"}
|
||||
alt={props.member?.name || ""}
|
||||
className="h-14 w-14 rounded-full"
|
||||
/>
|
||||
<div className="ml-3 inline-block pt-1">
|
||||
<span className="text-lg font-bold text-neutral-700">{props.member?.name}</span>
|
||||
<span className="-mt-1 block text-sm text-gray-400">{props.member?.email}</span>
|
||||
<LicenseRequired>
|
||||
<div className="flex max-h-[500px] min-h-[500px] flex-row space-x-8 rtl:space-x-reverse">
|
||||
<div className="min-w-64 w-64 space-y-5 p-5 pr-0">
|
||||
<div className="flex">
|
||||
<Avatar
|
||||
imageSrc={WEBSITE_URL + "/" + props.member?.username + "/avatar.png"}
|
||||
alt={props.member?.name || ""}
|
||||
className="h-14 w-14 rounded-full"
|
||||
/>
|
||||
<div className="ml-3 inline-block pt-1">
|
||||
<span className="text-lg font-bold text-neutral-700">{props.member?.name}</span>
|
||||
<span className="-mt-1 block text-sm text-gray-400">{props.member?.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold text-gray-600">Date</span>
|
||||
<DatePicker
|
||||
date={selectedDate.toDate()}
|
||||
onDatesChange={(newDate) => {
|
||||
setSelectedDate(dayjs(newDate));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold text-gray-600">Timezone</span>
|
||||
<TimezoneSelect
|
||||
id="timeZone"
|
||||
value={selectedTimeZone}
|
||||
onChange={(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"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold text-gray-600">Slot Length</span>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 15, label: "15 minutes" },
|
||||
{ value: 30, label: "30 minutes" },
|
||||
{ value: 60, label: "60 minutes" },
|
||||
]}
|
||||
isSearchable={false}
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container focus:ring-primary-500 focus:border-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
|
||||
value={{ value: frequency, label: `${frequency} minutes` }}
|
||||
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold text-gray-600">Date</span>
|
||||
<DatePicker
|
||||
date={selectedDate.toDate()}
|
||||
onDatesChange={(newDate) => {
|
||||
setSelectedDate(dayjs(newDate));
|
||||
}}
|
||||
{props.team && props.member && (
|
||||
<TeamAvailabilityTimes
|
||||
className="overflow-scroll"
|
||||
teamId={props.team.id}
|
||||
memberId={props.member.id}
|
||||
frequency={frequency}
|
||||
selectedDate={selectedDate}
|
||||
selectedTimeZone={selectedTimeZone}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold text-gray-600">Timezone</span>
|
||||
<TimezoneSelect
|
||||
id="timeZone"
|
||||
value={selectedTimeZone}
|
||||
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
|
||||
className="mt-1 block w-full rounded-sm border border-gray-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold text-gray-600">Slot Length</span>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 15, label: "15 minutes" },
|
||||
{ value: 30, label: "30 minutes" },
|
||||
{ value: 60, label: "60 minutes" },
|
||||
]}
|
||||
isSearchable={false}
|
||||
className="block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
|
||||
value={{ value: frequency, label: `${frequency} minutes` }}
|
||||
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{props.team && props.member && (
|
||||
<TeamAvailabilityTimes
|
||||
className="overflow-auto"
|
||||
teamId={props.team.id}
|
||||
memberId={props.member.id}
|
||||
frequency={frequency}
|
||||
selectedDate={selectedDate}
|
||||
selectedTimeZone={selectedTimeZone}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</LicenseRequired>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 && <Alert className="-mt-24 border" severity="error" title={errorMessage} />}
|
||||
{isLoading && <Loader />}
|
||||
{isFreeUser ? (
|
||||
<Alert
|
||||
className="-mt-24 border"
|
||||
severity="warning"
|
||||
title="This is a pro feature. Upgrade to pro to see your team's availability."
|
||||
/>
|
||||
) : (
|
||||
TeamAvailability
|
||||
)}
|
||||
<LicenseRequired>
|
||||
{!!errorMessage && <Alert className="-mt-24 border" severity="error" title={errorMessage} />}
|
||||
{isLoading && <Loader />}
|
||||
{isFreeUser ? (
|
||||
<Alert
|
||||
className="-mt-24 border"
|
||||
severity="warning"
|
||||
title="This is a pro feature. Upgrade to pro to see your team's availability."
|
||||
/>
|
||||
) : (
|
||||
TeamAvailability
|
||||
)}
|
||||
</LicenseRequired>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
<h2>Something went wrong.</h2>
|
||||
<details style={{ whiteSpace: "pre-wrap" }}>
|
||||
{this.state.error && this.state.error.toString()}
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Normally, just render children
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
|
@ -16,7 +16,8 @@ const I18nextAdapter = appWithTranslation<NextJsAppProps & { children: React.Rea
|
|||
));
|
||||
|
||||
// Workaround for https://github.com/vercel/next.js/issues/8592
|
||||
export type AppProps = NextAppProps & {
|
||||
export type AppProps = Omit<NextAppProps, "Component"> & {
|
||||
Component: NextAppProps["Component"] & { requiresLicense?: boolean };
|
||||
/** Will be defined only is there was an error */
|
||||
err?: Error;
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) {
|
|||
<script dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
</Head>
|
||||
<Component {...pageProps} err={err} />
|
||||
{Component.requiresLicense ? (
|
||||
<LicenseRequired>
|
||||
<Component {...pageProps} err={err} />
|
||||
</LicenseRequired>
|
||||
) : (
|
||||
<Component {...pageProps} err={err} />
|
||||
)}
|
||||
</AppProviders>
|
||||
</ContractsProvider>
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 (
|
||||
<Shell heading={t("billing")} subtitle={t("manage_your_billing_info")}>
|
||||
<SettingsShell>
|
||||
<SettingsShell heading={t("billing")} subtitle={t("manage_your_billing_info")}>
|
||||
<>
|
||||
<div className="py-6 lg:col-span-9 lg:pb-8">
|
||||
{data?.plan && ["FREE", "TRIAL"].includes(data.plan) && (
|
||||
<Card
|
||||
|
@ -72,7 +71,7 @@ export default function Billing() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
</>
|
||||
</SettingsShell>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Shell heading={t("profile")} subtitle={t("edit_profile_info_description")}>
|
||||
<SettingsShell>
|
||||
<WithQuery success={({ data }) => <SettingsView {...props} localeProp={data.locale} />} />
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
<SettingsShell heading={t("profile")} subtitle={t("edit_profile_info_description")}>
|
||||
<WithQuery success={({ data }) => <SettingsView {...props} localeProp={data.locale} />} />
|
||||
</SettingsShell>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<Shell heading={t("security")} subtitle={t("manage_account_security")}>
|
||||
<SettingsShell>
|
||||
<SettingsShell heading={t("security")} subtitle={t("manage_account_security")}>
|
||||
<>
|
||||
{user && user.identityProvider !== IdentityProvider.CAL ? (
|
||||
<>
|
||||
<div className="mt-6">
|
||||
|
@ -45,7 +44,7 @@ export default function Security() {
|
|||
)}
|
||||
|
||||
<SAMLConfiguration teamsView={false} teamId={null} />
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
</>
|
||||
</SettingsShell>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Shell heading={t("teams")} subtitle={t("create_manage_teams_collaborative")}>
|
||||
<SettingsShell>
|
||||
<SettingsShell heading={t("teams")} subtitle={t("create_manage_teams_collaborative")}>
|
||||
<>
|
||||
{!!errorMessage && <Alert severity="error" title={errorMessage} />}
|
||||
{isFreePlan && (
|
||||
<Alert
|
||||
|
@ -87,7 +86,11 @@ export default function Teams() {
|
|||
/>
|
||||
)}
|
||||
{teams.length > 0 && <TeamList teams={teams}></TeamList>}
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
</>
|
||||
</SettingsShell>
|
||||
);
|
||||
}
|
||||
|
||||
Teams.requiresLicense = false;
|
||||
|
||||
export default Teams;
|
||||
|
|
|
@ -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\"",
|
||||
|
|
|
@ -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<boolean> {
|
||||
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;
|
|
@ -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);
|
||||
|
|
|
@ -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 = [
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
},
|
||||
|
|
124
yarn.lock
124
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"
|
||||
|
|
Loading…
Reference in New Issue
Block a user