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:
Omar López 2022-05-26 11:07:14 -06:00 committed by GitHub
parent 6940c8d6cc
commit 9df4867fca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 502 additions and 285 deletions

View File

@ -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

@ -1 +1 @@
Subproject commit 14aca7ef2b81421bdf4c95020bf54255abe34dcb
Subproject commit 76b3f1ba3a6a7aad37b32ce4afee3c26420b18f4

@ -1 +1 @@
Subproject commit dfa050650c1507f24ec81d972c0c697ec07934d9
Subproject commit 3c6d61a703f1b1ce8739f0fe86fcd78b67085a22

View File

@ -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 (
<>

View File

@ -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 }} />

View File

@ -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>
</>

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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" && (

View File

@ -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,

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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}
</>
);

View File

@ -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">
&#8203;
</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>

View File

@ -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>
);
}

View File

@ -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);

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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",

View File

@ -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>
);

View File

@ -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,

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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\"",

View File

@ -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;

View File

@ -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);

View File

@ -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 = [
{

View File

@ -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);

View File

@ -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"
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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
View File

@ -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"