Merge remote-tracking branch 'origin/main' into feature/app-store-cli

This commit is contained in:
Hariom Balhara 2022-06-03 09:27:55 +05:30
commit c6ca411b54
73 changed files with 1095 additions and 954 deletions

View File

@ -15,7 +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
# To enable enterprise-only features, fill your license key in here.
# @see https://console.cal.com
CALCOM_LICENSE_KEY=
# ***********************************************************************************************************
@ -53,8 +54,8 @@ NEXTAUTH_SECRET=
# Used for cross-domain cookie authentication
NEXTAUTH_COOKIE_DOMAIN=.example.com
# Remove this var if you don't want Cal to collect anonymous usage
NEXT_PUBLIC_TELEMETRY_KEY=js.2pvs2bbpqq1zxna97wcml.oi2jzirnbj1ev4tc57c5r
# Set this to '1' if you don't want Cal to collect anonymous usage
CALCOM_TELEMETRY_DISABLED=
# ApiKey for cronjobs
CRON_API_KEY='0cc0e6c35519bba620c9360cfe3e68d0'
@ -76,7 +77,6 @@ NEXT_PUBLIC_HELPSCOUT_KEY=
# Inbox to send user feedback
SEND_FEEDBACK_EMAIL=
# This is used so we can bypass emails in auth flows for E2E testing
# Set it to "1" if you need to run E2E tests locally
NEXT_PUBLIC_IS_E2E=

View File

@ -1,7 +1,5 @@
name: E2E test - embed
name: E2E test
on:
push:
branches: [ tests/ci-embed ]
pull_request_target: # So we can test on forks
branches:
- main
@ -18,7 +16,7 @@ on:
jobs:
test:
timeout-minutes: 20
name: Testing Embeds
name: Embed and booking flow(for non-embed as well)
strategy:
matrix:
node: ["14.x"]

@ -1 +1 @@
Subproject commit ed2f42fb0195b1afa0bf2edbab1df2126038b273
Subproject commit 1180f3f2584f8697f7b18db3d809fe88639d90ac

@ -1 +1 @@
Subproject commit b6b26f47922a5404086bf34635338dc6afa9c1d3
Subproject commit 0331484a2d276e1c385266fed17e82737df6f103

View File

@ -0,0 +1,76 @@
import React from "react";
import Select from "react-select";
import { OptionProps } from "react-select";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { App } from "@calcom/types/App";
import { Button } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import { trpc } from "@lib/trpc";
interface AdditionalCalendarSelectorProps {
isLoading?: boolean;
}
const ImageOption = (optionProps: OptionProps<{ [key: string]: string; type: App["type"] }>) => {
const { data } = optionProps;
return (
<InstallAppButton
type={data.type}
render={(installProps) => {
return (
<Button {...installProps} className="w-full" color="minimal">
{/* eslint-disable @next/next/no-img-element */}
{data.image && (
<img className="float-left mr-3 inline h-5 w-5" src={data.image} alt={data.label} />
)}
<p>{data.label}</p>
</Button>
);
}}
/>
);
};
const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorProps): JSX.Element | null => {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.integrations", { variant: "calendar", onlyInstalled: true }]);
return (
<QueryCell
query={query}
success={({ data }) => {
const options = data.items.map((item) => ({
label: item.name,
slug: item.slug,
image: item.imageSrc,
type: item.type,
}));
return (
<Select
name={"additionalCalendar"}
placeholder={t("connect_additional_calendar")}
options={options}
styles={{
placeholder: (defaultStyles) => {
return {
...defaultStyles,
color: "#3E3E3E",
marginLeft: "3px",
};
},
}}
isSearchable={false}
className="mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 font-medium text-gray-700 sm:text-sm"
isLoading={isLoading}
components={{ Option: ImageOption }}
/>
);
}}
/>
);
};
export default AdditionalCalendarSelector;

View File

@ -5,6 +5,8 @@ import {
FlagIcon,
MailIcon,
ShieldCheckIcon,
PlusIcon,
CheckIcon,
} from "@heroicons/react/outline";
import { ChevronLeftIcon } from "@heroicons/react/solid";
import Link from "next/link";
@ -13,7 +15,7 @@ import React, { useEffect, useState } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { App as AppType } from "@calcom/types/App";
import { Button } from "@calcom/ui";
import { Button, SkeletonButton } from "@calcom/ui";
import Shell from "@components/Shell";
import Badge from "@components/ui/Badge";
@ -61,8 +63,8 @@ export default function App({
currency: "USD",
useGrouping: false,
}).format(price);
const [installedApp, setInstalledApp] = useState(false);
const [installedApp, setInstalledApp] = useState(0);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function getInstalledApp(slug: string) {
const queryParam = new URLSearchParams();
@ -73,9 +75,13 @@ export default function App({
headers: {
"Content-Type": "application/json",
},
}).then((data) => {
setIsLoading(false);
return data;
});
if (result.status === 200) {
setInstalledApp(true);
const res = await result.json();
setInstalledApp(res.count);
}
} catch (error) {
if (error instanceof Error) {
@ -111,19 +117,35 @@ export default function App({
</div>
<div className="mt-4 sm:mt-0 sm:text-right">
{isGlobal || installedApp ? (
<Button color="secondary" disabled title="This app is globally installed">
{t("installed")}
</Button>
) : (
<InstallAppButton
slug={slug}
render={(buttonProps) => (
<Button data-testid="install-app-button" {...buttonProps}>
{t("install_app")}
{!isLoading ? (
isGlobal || installedApp > 0 ? (
<div className="space-x-3">
<Button StartIcon={CheckIcon} color="secondary" disabled>
{installedApp > 0
? t("active_install", { count: installedApp })
: t("globally_install")}
</Button>
)}
/>
<InstallAppButton
slug={slug}
render={(buttonProps) => (
<Button StartIcon={PlusIcon} data-testid="install-app-button" {...buttonProps}>
{t("add_another")}
</Button>
)}
/>
</div>
) : (
<InstallAppButton
slug={slug}
render={(buttonProps) => (
<Button data-testid="install-app-button" {...buttonProps}>
{t("install_app")}
</Button>
)}
/>
)
) : (
<SkeletonButton width="24" height="10" />
)}
{price !== 0 && (
<small className="block text-right">

View File

@ -1,9 +1,9 @@
import classNames from "classnames";
import React, { useEffect, useState } from "react";
import Select from "react-select";
import Button from "@calcom/ui/Button";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useLocale } from "@lib/hooks/useLocale";
import { trpc } from "@lib/trpc";
interface Props {
@ -24,6 +24,22 @@ const DestinationCalendarSelector = ({
const query = trpc.useQuery(["viewer.connectedCalendars"]);
const [selectedOption, setSelectedOption] = useState<{ value: string; label: string } | null>(null);
// Extra styles to show prefixed text in react-select
const content = (hidePlaceholder = false) => {
if (!hidePlaceholder) {
return {
alignItems: "center",
display: "flex",
":before": {
content: `'${t("select_destination_calendar")}:'`,
display: "block",
marginRight: 8,
},
};
}
return {};
};
useEffect(() => {
const selected = query.data?.connectedCalendars
.map((connected) => connected.calendars ?? [])
@ -52,23 +68,29 @@ const DestinationCalendarSelector = ({
})) ?? [];
return (
<div className="relative" title={`${t("select_destination_calendar")}: ${selectedOption?.label || ""}`}>
{/* There's no easy way to customize the displayed value for a Select, so we fake it. */}
{!hidePlaceholder && (
<div className="pointer-events-none absolute z-10 w-full">
<Button
size="sm"
color="secondary"
className="m-[1px] w-[calc(100%_-_40px)] overflow-hidden overflow-ellipsis whitespace-nowrap rounded-sm border-none leading-5">
{t("select_destination_calendar")}: {selectedOption?.label || ""}
</Button>
</div>
)}
<Select
name={"primarySelectedCalendar"}
placeholder={!hidePlaceholder ? `${t("select_destination_calendar")}:` : undefined}
options={options}
styles={{
placeholder: (styles) => ({ ...styles, ...content(hidePlaceholder) }),
singleValue: (styles) => ({ ...styles, ...content(hidePlaceholder) }),
option: (defaultStyles, state) => ({
...defaultStyles,
backgroundColor: state.isSelected
? state.isFocused
? "var(--brand-color)"
: "var(--brand-color)"
: state.isFocused
? "var(--brand-color-dark-mode)"
: "var(--brand-text-color)",
}),
}}
isSearchable={false}
className="mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm"
className={classNames(
"mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm",
!hidePlaceholder && "font-medium"
)}
onChange={(option) => {
setSelectedOption(option);
if (!option) {

View File

@ -1,27 +0,0 @@
import React from "react";
import { SVGComponent } from "@lib/types/SVGComponent";
export default function EmptyScreen({
Icon,
headline,
description,
}: {
Icon: SVGComponent;
headline: 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-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 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

@ -1,4 +1,11 @@
import { CreditCardIcon, KeyIcon, LockClosedIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid";
import {
CreditCardIcon,
KeyIcon,
LockClosedIcon,
UserGroupIcon,
UserIcon,
CodeIcon,
} from "@heroicons/react/solid";
import React, { ComponentProps } from "react";
import ErrorBoundary from "@lib/ErrorBoundary";
@ -12,15 +19,20 @@ const tabs = [
href: "/settings/profile",
icon: UserIcon,
},
{
name: "teams",
href: "/settings/teams",
icon: UserGroupIcon,
},
{
name: "security",
href: "/settings/security",
icon: KeyIcon,
},
{
name: "teams",
href: "/settings/teams",
icon: UserGroupIcon,
name: "developer",
href: "/settings/developer",
icon: CodeIcon,
},
{
name: "billing",

View File

@ -37,7 +37,6 @@ import classNames from "@lib/classNames";
import { WEBAPP_URL } from "@lib/config/constants";
import { shouldShowOnboarding } from "@lib/getting-started";
import useMeQuery from "@lib/hooks/useMeQuery";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { trpc } from "@lib/trpc";
import CustomBranding from "@components/CustomBranding";
@ -415,16 +414,8 @@ type LayoutProps = {
};
export default function Shell(props: LayoutProps) {
const router = useRouter();
const { loading, session } = useRedirectToLoginIfUnauthenticated(props.isPublic);
const { isRedirectingToOnboarding } = useRedirectToOnboardingIfNeeded();
const telemetry = useTelemetry();
useEffect(() => {
telemetry.withJitsu((jitsu) => {
return jitsu.track(telemetryEventTypes.pageView, collectPageParameters(router.asPath));
});
}, [telemetry, router.asPath]);
const query = useMeQuery();
const user = query.data;

View File

@ -4,10 +4,10 @@ import { SkeletonText } from "@calcom/ui";
import { ShellSubHeading } from "@components/Shell";
function SkeletonLoader() {
function SkeletonLoader({ className }: { className?: string }) {
return (
<>
<ShellSubHeading title={<div className="h-6 w-32 rounded-sm bg-gray-100"></div>} />
<ShellSubHeading title={<div className="h-6 w-32 rounded-sm bg-gray-100"></div>} {...{ className }} />
<ul className="-mx-4 animate-pulse divide-y divide-neutral-200 rounded-sm border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
<SkeletonItem />
<SkeletonItem />

View File

@ -40,9 +40,7 @@ export default function SAMLLogin(props: Props) {
event.preventDefault();
// track Google logins. Without personal data/payload
telemetry.withJitsu((jitsu) =>
jitsu.track(telemetryEventTypes.googleLogin, collectPageParameters())
);
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
if (!props.hostedCal) {
await signIn("saml", {}, { tenant: props.samlTenantID, product: props.samlProductID });

View File

@ -78,9 +78,7 @@ export default function CancelBooking(props: Props) {
reason: cancellationReason,
};
telemetry.withJitsu((jitsu) =>
jitsu.track(telemetryEventTypes.bookingCancelled, collectPageParameters())
);
telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters());
const res = await fetch("/api/cancel", {
body: JSON.stringify(payload),

View File

@ -143,12 +143,13 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
useEffect(() => {
handleToggle24hClock(localStorage.getItem("timeOption.is24hClock") === "true");
telemetry.withJitsu((jitsu) =>
jitsu.track(
top !== window ? telemetryEventTypes.embedView : telemetryEventTypes.pageView,
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
telemetry.event(
telemetryEventTypes.embedView,
collectPageParameters("/availability", { isTeamBooking: document.URL.includes("team/") })
)
);
);
}
}, [telemetry]);
const changeDate = useCallback(
@ -252,62 +253,64 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
<h1 className="text-bookingdark mb-4 break-words text-xl font-semibold dark:text-white">
{eventType.title}
</h1>
{eventType?.description && (
<p className="text-bookinglight mb-2 dark:text-white">
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{eventType.description}
</p>
)}
{eventType.locations.length === 1 && (
<p className="text-bookinglight mb-2 dark:text-white">
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{locationKeyToString(eventType.locations[0], t)}
</p>
)}
{eventType.locations.length === 1 && (
<p className="text-bookinglight mb-2 dark:text-white">
{Object.values(AppStoreLocationType).includes(
eventType.locations[0].type as unknown as AppStoreLocationType
) ? (
<VideoCameraIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
) : (
<div className="flex flex-col space-y-4">
{eventType?.description && (
<p className="text-gray-600 dark:text-white">
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{eventType.description}
</p>
)}
{eventType.locations.length === 1 && (
<p className="text-gray-600 dark:text-white">
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
)}
{locationKeyToString(eventType.locations[0], t)}
</p>
)}
{eventType.locations.length === 1 && (
<p className="text-gray-600 dark:text-white">
{Object.values(AppStoreLocationType).includes(
eventType.locations[0].type as unknown as AppStoreLocationType
) ? (
<VideoCameraIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
) : (
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
)}
{locationKeyToString(eventType.locations[0], t)}
{locationKeyToString(eventType.locations[0], t)}
</p>
)}
<p className="text-gray-600 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
{eventType.length} {t("minutes")}
</p>
)}
<p className="text-bookinglight mb-2 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
{eventType.length} {t("minutes")}
</p>
{eventType.price > 0 && (
<div className="text-gray-600 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 dark:text-gray-400" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
</div>
)}
<div className="md:hidden">
{booking?.startTime && rescheduleUid && (
<div>
<p
className="mt-8 mb-2 text-gray-600 dark:text-white"
data-testid="former_time_p_mobile">
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-white">
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{typeof booking.startTime === "string" &&
parseDate(dayjs(booking.startTime), i18n)}
</p>
{eventType.price > 0 && (
<div className="text-gray-600 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 dark:text-gray-400" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
</div>
)}
<div className="md:hidden">
{booking?.startTime && rescheduleUid && (
<div>
<p
className="mt-8 text-gray-600 dark:text-white"
data-testid="former_time_p_mobile">
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-white">
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{typeof booking.startTime === "string" &&
parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
</div>
</div>
</div>
</div>
@ -343,88 +346,93 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
<h1 className="font-cal mb-4 break-words text-xl font-semibold text-gray-900 dark:text-white">
{eventType.title}
</h1>
{eventType?.description && (
<p className="text-bookinglight mb-3 dark:text-white">
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{eventType.description}
</p>
)}
{eventType.locations.length === 1 && (
<p className="text-bookinglight mb-2 dark:text-white">
{Object.values(AppStoreLocationType).includes(
eventType.locations[0].type as unknown as AppStoreLocationType
) ? (
<VideoCameraIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
) : (
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
)}
{locationKeyToString(eventType.locations[0], t)}
</p>
)}
{eventType.locations.length > 1 && (
<div className="text-bookinglight flex-warp mb-2 flex dark:text-white">
<div className="mr-[10px] ml-[2px] -mt-1 ">
<LocationMarkerIcon className="inline-block h-4 w-4 text-gray-400" />
<div className="flex flex-col space-y-4">
{eventType?.description && (
<div className="flex text-gray-600 dark:text-white">
<div>
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
</div>
<p>{eventType.description}</p>
</div>
<p>
{eventType.locations.map((el, i, arr) => {
return (
<span key={el.type}>
{locationKeyToString(el, t)}{" "}
{arr.length - 1 !== i && (
<span className="font-light"> {t("or_lowercase")} </span>
)}
</span>
);
})}
)}
{eventType.locations.length === 1 && (
<p className="text-gray-600 dark:text-white">
{Object.values(AppStoreLocationType).includes(
eventType.locations[0].type as unknown as AppStoreLocationType
) ? (
<VideoCameraIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
) : (
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
)}
{locationKeyToString(eventType.locations[0], t)}
</p>
</div>
)}
<p className="text-bookinglight mb-3 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
{eventType.length} {t("minutes")}
</p>
{!rescheduleUid && eventType.recurringEvent?.count && eventType.recurringEvent?.freq && (
<div className="mb-3 text-gray-600 dark:text-white">
<RefreshIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<p className="mb-1 -ml-2 inline px-2 py-1">
{t("every_for_freq", {
freq: t(
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
),
})}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-16 rounded-sm border-gray-300 bg-white text-gray-600 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500 dark:bg-gray-600 dark:text-white sm:text-sm"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline text-gray-600 dark:text-white">
{t(`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
count: recurringEventCount,
})}
</p>
</div>
)}
{eventType.price > 0 && (
<p className="mb-1 -ml-2 px-2 py-1 text-gray-600 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
)}
{eventType.locations.length > 1 && (
<div className="flex-warp flex text-gray-600 dark:text-white">
<div className="mr-[10px] ml-[2px] -mt-1 ">
<LocationMarkerIcon className="inline-block h-4 w-4 text-gray-400" />
</div>
<p>
{eventType.locations.map((el, i, arr) => {
return (
<span key={el.type}>
{locationKeyToString(el, t)}{" "}
{arr.length - 1 !== i && (
<span className="font-light"> {t("or_lowercase")} </span>
)}
</span>
);
})}
</p>
</div>
)}
<p className="text-gray-600 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
{eventType.length} {t("minutes")}
</p>
)}
<TimezoneDropdown />
{!rescheduleUid && eventType.recurringEvent?.count && eventType.recurringEvent?.freq && (
<div className="text-gray-600 dark:text-white">
<RefreshIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<p className="mb-1 -ml-2 inline px-2 py-1">
{t("every_for_freq", {
freq: t(
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
),
})}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 h-7 rounded-sm border-gray-300 bg-white text-gray-600 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500 dark:bg-gray-600 dark:text-white sm:text-sm"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline text-gray-600 dark:text-white">
{t(`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
count: recurringEventCount,
})}
</p>
</div>
)}
{eventType.price > 0 && (
<p className="-ml-2 px-2 py-1 text-gray-600 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
<TimezoneDropdown />
</div>
{previousPage === `${WEBAPP_URL}/${profile.slug}` && (
<div className="flex h-full flex-col justify-end">
<ArrowLeftIcon
@ -496,7 +504,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
function TimezoneDropdown() {
return (
<Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Collapsible.Trigger className="min-w-32 mb-1 -ml-2 px-2 py-1 text-left text-gray-600 dark:text-white">
<Collapsible.Trigger className="min-w-32 text-bookinglight mb-1 -ml-2 px-2 py-1 text-left dark:text-white">
<GlobeIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{timeZone()}
{isTimeOptionsOpen ? (

View File

@ -107,12 +107,13 @@ const BookingPage = ({
const telemetry = useTelemetry();
useEffect(() => {
telemetry.withJitsu((jitsu) =>
jitsu.track(
top !== window ? telemetryEventTypes.embedView : telemetryEventTypes.pageView,
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
telemetry.event(
telemetryEventTypes.embedView,
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
)
);
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -332,13 +333,10 @@ const BookingPage = ({
}
const bookEvent = (booking: BookingFormValues) => {
telemetry.withJitsu((jitsu) =>
jitsu.track(
top !== window ? telemetryEventTypes.embedBookingConfirmed : telemetryEventTypes.bookingConfirmed,
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
)
telemetry.event(
top !== window ? telemetryEventTypes.embedBookingConfirmed : telemetryEventTypes.bookingConfirmed,
{ isTeamBooking: document.URL.includes("team/") }
);
// "metadata" is a reserved key to allow for connecting external users without relying on the email address.
// <...url>&metadata[user_id]=123 will be send as a custom input field as the hidden type.

View File

@ -1,19 +1,20 @@
import { Fragment } from "react";
import { useMutation } from "react-query";
import { InstallAppButton } from "@calcom/app-store/components";
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 Switch from "@calcom/ui/Switch";
import { QueryCell } from "@lib/QueryCell";
import { useLocale } from "@lib/hooks/useLocale";
import { trpc } from "@lib/trpc";
import AdditionalCalendarSelector from "@components/AdditionalCalendarSelector";
import DestinationCalendarSelector from "@components/DestinationCalendarSelector";
import { List } from "@components/List";
import { ShellSubHeading } from "@components/Shell";
import SkeletonLoader from "@components/apps/SkeletonLoader";
import DisconnectIntegration from "./DisconnectIntegration";
import IntegrationListItem from "./IntegrationListItem";
@ -163,40 +164,6 @@ function ConnectedCalendarsList(props: Props) {
);
}
function CalendarList(props: Props) {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.integrations"]);
return (
<QueryCell
query={query}
success={({ data }) => (
<List>
{data.calendar.items.map((item) => (
<IntegrationListItem
key={item.title}
title={item.title}
imageSrc={item.imageSrc}
description={item.description}
actions={
<InstallAppButton
type={item.type}
render={(buttonProps) => (
<Button color="secondary" {...buttonProps}>
{t("connect")}
</Button>
)}
onChanged={() => props.onChanged()}
/>
}
/>
))}
</List>
)}
/>
);
}
export function CalendarListContainer(props: { heading?: false }) {
const { t } = useLocale();
const { heading = true } = props;
@ -207,39 +174,54 @@ export function CalendarListContainer(props: { heading?: false }) {
utils.invalidateQueries(["viewer.connectedCalendars"]),
]);
const query = trpc.useQuery(["viewer.connectedCalendars"]);
const installedCalendars = trpc.useQuery([
"viewer.integrations",
{ variant: "calendar", onlyInstalled: true },
]);
const mutation = trpc.useMutation("viewer.setDestinationCalendar");
return (
<>
{heading && (
<ShellSubHeading
className="mt-10 mb-0"
title={
<SubHeadingTitleWithConnections
title="Calendars"
numConnections={query.data?.connectedCalendars.length}
/>
}
subtitle={t("configure_how_your_event_types_interact")}
actions={
<div className="sm:min-w-80 block max-w-full">
<DestinationCalendarSelector
onChange={mutation.mutate}
isLoading={mutation.isLoading}
value={query.data?.destinationCalendar?.externalId}
/>
</div>
}
/>
)}
<ConnectedCalendarsList onChanged={onChanged} />
{!!query.data?.connectedCalendars.length && (
<ShellSubHeading
className="mt-6"
title={<SubHeadingTitleWithConnections title={t("connect_an_additional_calendar")} />}
/>
)}
<CalendarList onChanged={onChanged} />
</>
<QueryCell
query={query}
customLoader={<SkeletonLoader className="mt-10" />}
success={({ data }) => {
return (
<>
{(!!data.connectedCalendars.length || !!installedCalendars.data?.items.length) && (
<>
{heading && (
<ShellSubHeading
className="mt-10 mb-0"
title={
<SubHeadingTitleWithConnections
title="Calendars"
numConnections={data.connectedCalendars.length}
/>
}
subtitle={t("configure_how_your_event_types_interact")}
actions={
<div className="flex flex-col xl:flex-row xl:space-x-5">
<div className="sm:min-w-80 block max-w-full">
<DestinationCalendarSelector
onChange={mutation.mutate}
isLoading={mutation.isLoading}
value={data.destinationCalendar?.externalId}
/>
</div>
{!!data.connectedCalendars.length && (
<div className="sm:min-w-80 inline max-w-full">
<AdditionalCalendarSelector isLoading={mutation.isLoading} />
</div>
)}
</div>
}
/>
)}
<ConnectedCalendarsList onChanged={onChanged} />
</>
)}
</>
);
}}
/>
);
}

View File

@ -1,16 +1,15 @@
import classNames from "classnames";
import Image from "next/image";
import { PlusIcon } from "@heroicons/react/solid";
import { useState } from "react";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { QueryCell } from "@lib/QueryCell";
import { useLocale } from "@lib/hooks/useLocale";
import { trpc } from "@lib/trpc";
import { List, ListItem, ListItemText, ListItemTitle } from "@components/List";
import { List } from "@components/List";
import { ShellSubHeading } from "@components/Shell";
import SkeletonLoader from "@components/apps/SkeletonLoader";
import WebhookDialogForm from "@components/webhook/WebhookDialogForm";
import WebhookListItem, { TWebhook } from "@components/webhook/WebhookListItem";
@ -21,47 +20,35 @@ export type WebhookListContainerType = {
};
export default function WebhookListContainer(props: WebhookListContainerType) {
const { t } = useLocale();
const query = props.eventTypeId
? trpc.useQuery(["viewer.webhook.list", { eventTypeId: props.eventTypeId }], {
suspense: true,
})
: trpc.useQuery(["viewer.webhook.list"], {
suspense: true,
});
const query = trpc.useQuery(["viewer.webhook.list", { eventTypeId: props.eventTypeId }], {
suspense: true,
});
const [newWebhookModal, setNewWebhookModal] = useState(false);
const [editModalOpen, setEditModalOpen] = useState(false);
const [editing, setEditing] = useState<TWebhook | null>(null);
return (
<QueryCell
query={query}
customLoader={<SkeletonLoader />}
success={({ data }) => (
<>
<ShellSubHeading className="mt-10" title={props.title} subtitle={props.subtitle} />
<List>
<ListItem className={classNames("flex-col")}>
<div
className={classNames("flex w-full flex-1 items-center space-x-2 p-3 rtl:space-x-reverse")}>
<Image width={40} height={40} src="/apps/webhooks.svg" alt="Webhooks" />
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3">Webhooks</ListItemTitle>
<ListItemText component="p">{t("automation")}</ListItemText>
</div>
<div>
<Button
color="secondary"
onClick={() => setNewWebhookModal(true)}
data-testid="new_webhook">
{t("new_webhook")}
</Button>
</div>
</div>
</ListItem>
</List>
<div className="border-b border-gray-200 py-8 pl-2 pr-1">
<ShellSubHeading
className="mt-2"
title={props.title}
subtitle={props.subtitle}
actions={
<Button
color="secondary"
size="icon"
StartIcon={PlusIcon}
onClick={() => setNewWebhookModal(true)}
data-testid="new_webhook"
/>
}
/>
{data.length ? (
<List>
<List className="mt-6">
{data.map((item) => (
<WebhookListItem
key={item.id}
@ -97,7 +84,7 @@ export default function WebhookListContainer(props: WebhookListContainerType) {
)}
</DialogContent>
</Dialog>
</>
</div>
)}
/>
);

View File

@ -23,14 +23,14 @@ export default function WebhookListItem(props: { webhook: TWebhook; onEditWebhoo
});
return (
<ListItem className="-mt-px flex w-full p-4">
<ListItem className={classNames("flex w-full p-4", props.webhook.active ? "bg-white" : "bg-gray-100")}>
<div className="flex w-full justify-between">
<div className="flex max-w-full flex-col truncate">
<div className="flex space-y-1">
<span
className={classNames(
"truncate text-sm",
props.webhook.active ? "text-neutral-700" : "text-neutral-200"
props.webhook.active ? "text-neutral-700" : "text-neutral-400"
)}>
{props.webhook.subscriberUrl}
</span>
@ -41,8 +41,8 @@ export default function WebhookListItem(props: { webhook: TWebhook; onEditWebhoo
<span
key={ind}
className={classNames(
"w-max rounded-sm px-1 text-xs ",
props.webhook.active ? "bg-blue-100 text-blue-700" : "bg-blue-50 text-blue-200"
"w-max rounded-sm px-1 text-xs",
props.webhook.active ? "text-grey-200 bg-gray-200" : "bg-grey-50 text-neutral-400"
)}>
{t(`${eventTrigger.toLowerCase()}`)}
</span>

View File

@ -3,8 +3,7 @@ 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";
import EmptyScreen from "@calcom/ui/EmptyScreen";
type LicenseRequiredProps = {
as?: keyof JSX.IntrinsicElements | "";

View File

@ -11,6 +11,8 @@ import { QueryCell } from "@lib/QueryCell";
import { trpc } from "@lib/trpc";
import { List } from "@components/List";
import { ShellSubHeading } from "@components/Shell";
import SkeletonLoader from "@components/apps/SkeletonLoader";
import LicenseRequired from "../LicenseRequired";
@ -22,25 +24,28 @@ function ApiKeyListContainer() {
const [editModalOpen, setEditModalOpen] = useState(false);
const [apiKeyToEdit, setApiKeyToEdit] = useState<(TApiKeys & { neverExpires?: boolean }) | null>(null);
return (
<>
<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>
<div className="border-b border-gray-200 py-8 pl-2 pr-1">
<ShellSubHeading
className="mt-2"
title={t("api_keys")}
subtitle={t("api_keys_subtitle")}
actions={
<Button
color="secondary"
size="icon"
StartIcon={PlusIcon}
onClick={() => setNewApiKeyModal(true)}
/>
}
/>
<LicenseRequired>
<QueryCell
query={query}
customLoader={<SkeletonLoader />}
success={({ data }) => (
<>
{data.length > 0 && (
<List className="pb-6">
<List className="mt-6">
{data.map((item) => (
<ApiKeyListItem
key={item.id}
@ -80,7 +85,7 @@ function ApiKeyListContainer() {
)}
/>
</LicenseRequired>
</>
</div>
);
}

View File

@ -30,7 +30,7 @@ export default function ApiKeyListItem(props: { apiKey: TApiKeys; onEditApiKey:
},
});
return (
<ListItem className="-mt-px flex w-full p-4">
<ListItem className="flex w-full p-4">
<div className="flex w-full justify-between">
<div className="flex max-w-full flex-col truncate">
<div className="flex space-x-2">

View File

@ -74,7 +74,7 @@ export default function SAMLConfiguration({
const rawMetadata = samlConfigRef.current.value;
// track Google logins. Without personal data/payload
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.samlConfig, collectPageParameters()));
telemetry.event(telemetryEventTypes.samlConfig, collectPageParameters());
mutation.mutate({
encodedRawMetadata: Buffer.from(rawMetadata).toString("base64"),

View File

@ -66,7 +66,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
});
if (!rawPayment) throw Error("Payment not found");
if (!rawPayment) return { notFound: true };
const { data, booking: _booking, ...restPayment } = rawPayment;
const payment = {
@ -74,7 +74,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
data: data as unknown as PaymentData,
};
if (!_booking) throw Error("Booking not found");
if (!_booking) return { notFound: true };
const { startTime, eventType, ...restBooking } = _booking;
const booking = {
@ -82,7 +82,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
startTime: startTime.toString(),
};
if (!eventType) throw Error("Event not found");
if (!eventType) return { notFound: true };
const [user] = eventType.users;
if (!user) return { notFound: true };

View File

@ -7,7 +7,6 @@ import DynamicHelpscoutProvider from "@ee/lib/helpscout/providerDynamic";
import DynamicIntercomProvider from "@ee/lib/intercom/providerDynamic";
import usePublicPage from "@lib/hooks/usePublicPage";
import { createTelemetryClient, TelemetryProvider } from "@lib/telemetry";
import { trpc } from "./trpc";
@ -51,17 +50,15 @@ const AppProviders = (props: AppPropsWithChildren) => {
<CustomI18nextProvider {...props}>{props.children}</CustomI18nextProvider>
</SessionProvider>
);
const telemetryClient = useMemo(createTelemetryClient, []);
if (isPublicPage) {
return RemainingProviders;
}
return (
<TelemetryProvider value={telemetryClient}>
{isPublicPage ? (
RemainingProviders
) : (
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>{RemainingProviders}</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
)}
</TelemetryProvider>
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>{RemainingProviders}</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
);
};

View File

@ -1,17 +1,13 @@
import { jitsuClient, JitsuClient } from "@jitsu/sdk-js";
import React, { useContext } from "react";
import { NextApiRequest, NextApiResponse } from "next";
import { EventSinkOpts } from "next-collect";
import { useCollector } from "next-collect/client";
// it's ok to do this since we're importing only types which are harmless
// eslint-disable-next-line @next/next/no-server-import-in-page
import type { NextRequest, NextResponse } from "next/server";
declare global {
// eslint-disable-next-line no-var
var jitsu: JitsuClient | undefined;
}
/**
* Enumeration of all event types that are being sent
* to telemetry collection.
*/
export const telemetryEventTypes = {
pageView: "page_view",
apiCall: "api_call",
bookingConfirmed: "booking_confirmed",
bookingCancelled: "booking_cancelled",
importSubmitted: "import_submitted",
@ -23,93 +19,76 @@ export const telemetryEventTypes = {
embedBookingConfirmed: "embed_booking_confirmed",
};
/**
* Telemetry client
*/
export type TelemetryClient = {
/**
* Use it as: withJitsu((jitsu) => {return jitsu.track()}). If telemetry is disabled, the callback will ignored
*
* ATTENTION: always return the value of jitsu.track() or id() call. Otherwise unhandled rejection can happen,
* which is handled in Next.js with a popup.
*/
withJitsu: (callback: (jitsu: JitsuClient) => void | Promise<void>) => void;
};
const emptyClient: TelemetryClient = {
withJitsu: () => {
// empty
},
};
function useTelemetry(): TelemetryClient {
return useContext(TelemetryContext);
}
function isLocalhost(host: string) {
return "localhost" === host || "127.0.0.1" === host;
}
/**
* Collects page parameters and makes sure no sensitive data made it to telemetry
* @param route current next.js route
*/
export function collectPageParameters(
route?: string,
extraData: Record<string, unknown> = {}
): Record<string, unknown> {
const host = document.location.hostname;
const maskedHost = isLocalhost(host) ? "localhost" : "masked";
//starts with ''
const host = document.location.host;
const docPath = route ?? "";
return {
page_url: route,
page_title: "",
source_ip: "",
doc_encoding: document.characterSet,
url: document.location.protocol + "//" + host + (docPath ?? ""),
doc_host: maskedHost,
doc_search: "",
doc_path: docPath,
referer: "",
...extraData,
};
}
function createTelemetryClient(): TelemetryClient {
if (process.env.NEXT_PUBLIC_TELEMETRY_KEY) {
return {
withJitsu: (callback) => {
if (!process.env.NEXT_PUBLIC_TELEMETRY_KEY) {
//telemetry is disabled
return;
export const nextCollectBasicSettings: EventSinkOpts = {
drivers: [
process.env.CALCOM_TELEMETRY_DISABLED !== "1"
? {
type: "jitsu",
opts: {
key: "s2s.2pvs2bbpqq1zxna97wcml.esb6cikfrf7yn0qoh1nj1",
server: "https://t.calendso.com",
},
}
if (!window) {
console.warn("Jitsu has been called during SSR, this scenario isn't supported yet");
return;
} else if (!window["jitsu"]) {
window["jitsu"] = jitsuClient({
log_level: "ERROR",
tracking_host: "https://t.calendso.com",
key: process.env.NEXT_PUBLIC_TELEMETRY_KEY,
cookie_name: "__clnds",
capture_3rd_party_cookies: false,
});
}
const res = callback(window["jitsu"]);
if (res && typeof res["catch"] === "function") {
res.catch((e) => {
console.debug("Unable to send telemetry event", e);
});
}
},
};
} else {
return emptyClient;
}
}
: undefined,
process.env.TELEMETRY_DEBUG && { type: "echo", opts: { disableColor: true } },
],
eventTypes: [
{ "*.ttf": null },
{ "*.webmanifest": null },
{ "*.json": null },
{ "*.svg": null },
{ "*.map": null },
{ "*.png": null },
{ "*.gif": null },
{ "/api/collect-events": null },
{ "/api*": null },
{ "/img*": null },
{ "/favicon*": null },
{ "/*": telemetryEventTypes.pageView },
],
};
const TelemetryContext = React.createContext<TelemetryClient>(emptyClient);
export const extendEventData = (
req: NextRequest | NextApiRequest,
res: NextResponse | NextApiResponse,
original: any
) => {
const onVercel =
typeof req.headers?.get === "function"
? !!req.headers.get("x-vercel-id")
: !!(req.headers as any)?.["x-vercel-id"];
const pageUrl = original?.page_url || (req as any)?.page?.name || undefined;
return {
title: "",
ipAddress: "",
queryString: "",
page_url: pageUrl,
licenseConsent: !!process.env.NEXT_PUBLIC_LICENSE_CONSENT,
licensekey: process.env.CALCOM_LICENSE_KEY,
isTeamBooking:
original?.isTeamBooking === undefined
? pageUrl?.includes("team/") || undefined
: original?.isTeamBooking,
referrer: "",
onVercel,
isAuthorized:
!!req.cookies["next-auth.session-token"] || !!req.cookies["__Secure-next-auth.session-token"],
utc_time: new Date().toISOString(),
};
};
const TelemetryProvider = TelemetryContext.Provider;
export { TelemetryContext, TelemetryProvider, createTelemetryClient, useTelemetry };
export const useTelemetry = useCollector;

View File

@ -41,7 +41,6 @@
"@heroicons/react": "^1.0.6",
"@hookform/error-message": "^2.0.0",
"@hookform/resolvers": "^2.8.9",
"@jitsu/sdk-js": "^2.2.4",
"@metamask/providers": "^8.1.1",
"@next-auth/prisma-adapter": "^1.0.3",
"@next/bundle-analyzer": "12.1.6",
@ -81,6 +80,7 @@
"mime-types": "^2.1.35",
"next": "^12.1.6",
"next-auth": "^4.3.4",
"next-collect": "^0.1.0",
"next-i18next": "^11.0.0",
"next-mdx-remote": "^4.0.3",
"next-seo": "^4.26.0",

View File

@ -122,13 +122,12 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
const telemetry = useTelemetry();
useEffect(() => {
telemetry.withJitsu((jitsu) =>
jitsu.track(
top !== window ? telemetryEventTypes.embedView : telemetryEventTypes.pageView,
collectPageParameters("/[user]")
)
);
}, [telemetry]);
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/[user]"));
}
}, [telemetry, router.asPath]);
return (
<>
<Theme />

View File

@ -1,3 +1,4 @@
import { EventCollectionProvider } from "next-collect/client";
import { DefaultSeo } from "next-seo";
import Head from "next/head";
import superjson from "superjson";
@ -30,23 +31,26 @@ function MyApp(props: AppProps) {
pageStatus = "500";
}
return (
<ContractsProvider>
<AppProviders {...props}>
<DefaultSeo {...seoConfig.defaultNextSeo} />
<I18nLanguageHandler />
<Head>
<script dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}></script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
</Head>
{Component.requiresLicense ? (
<LicenseRequired>
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
<ContractsProvider>
<AppProviders {...props}>
<DefaultSeo {...seoConfig.defaultNextSeo} />
<I18nLanguageHandler />
<Head>
<script
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}></script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
</Head>
{Component.requiresLicense ? (
<LicenseRequired>
<Component {...pageProps} err={err} />
</LicenseRequired>
) : (
<Component {...pageProps} err={err} />
</LicenseRequired>
) : (
<Component {...pageProps} err={err} />
)}
</AppProviders>
</ContractsProvider>
)}
</AppProviders>
</ContractsProvider>
</EventCollectionProvider>
);
}

View File

@ -0,0 +1,9 @@
import { collectEvents } from "next-collect/server";
import { extendEventData, nextCollectBasicSettings } from "@lib/telemetry";
export default collectEvents({
...nextCollectBasicSettings,
cookieName: "__clnds",
extend: extendEventData,
});

View File

@ -32,12 +32,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
};
}
try {
const installedApp = await prisma.credential.findFirst({
const installedApp = await prisma.credential.findMany({
where,
});
if (installedApp && !!installedApp.key) {
res.status(200);
if (installedApp && !!installedApp.length) {
res.json({ count: installedApp.length });
} else {
res.status(404);
}

View File

@ -0,0 +1,9 @@
import { nextEventsCollectApi } from "next-collect/server";
import { extendEventData, nextCollectBasicSettings } from "@lib/telemetry";
export default nextEventsCollectApi({
...nextCollectBasicSettings,
cookieName: "__clnds",
extend: extendEventData,
});

View File

@ -1,21 +1,22 @@
import { ArrowRightIcon, ViewGridIcon } from "@heroicons/react/solid";
import Image from "next/image";
import React, { useEffect, useState } from "react";
import { JSONObject } from "superjson/dist/types";
import { AppConfiguration, InstallAppButton } from "@calcom/app-store/components";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { App } from "@calcom/types/App";
import type { App } from "@calcom/types/App";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import EmptyScreen from "@calcom/ui/EmptyScreen";
import { QueryCell } from "@lib/QueryCell";
import classNames from "@lib/classNames";
import { HttpError } from "@lib/core/http/error";
import { useLocale } from "@lib/hooks/useLocale";
import { trpc } from "@lib/trpc";
import AppsShell from "@components/AppsShell";
import { ClientSuspense } from "@components/ClientSuspense";
import { List, ListItem, ListItemText, ListItemTitle } from "@components/List";
import Shell, { ShellSubHeading } from "@components/Shell";
import SkeletonLoader from "@components/apps/SkeletonLoader";
@ -24,10 +25,8 @@ import DisconnectIntegration from "@components/integrations/DisconnectIntegratio
import DisconnectStripeIntegration from "@components/integrations/DisconnectStripeIntegration";
import IntegrationListItem from "@components/integrations/IntegrationListItem";
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
import WebhookListContainer from "@components/webhook/WebhookListContainer";
function ConnectOrDisconnectIntegrationButton(props: {
//
credentialIds: number[];
type: App["type"];
isGlobal?: boolean;
@ -95,122 +94,91 @@ function ConnectOrDisconnectIntegrationButton(props: {
);
}
function IntegrationsContainer() {
interface IntegrationsContainerProps {
variant: App["variant"];
className?: string;
}
const IntegrationsContainer = ({ variant, className = "" }: IntegrationsContainerProps): JSX.Element => {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.integrations"], { suspense: true });
const query = trpc.useQuery(["viewer.integrations", { variant, onlyInstalled: true }], { suspense: true });
return (
<QueryCell
query={query}
success={({ data }) => (
<>
<ShellSubHeading
title={
<SubHeadingTitleWithConnections
title={t("conferencing")}
numConnections={data.conferencing.numActive}
/>
}
/>
<List>
{data.conferencing.items.map((item) => (
<IntegrationListItem
key={item.title}
title={item.title}
imageSrc={item.imageSrc}
description={item.description}
actions={
<ConnectOrDisconnectIntegrationButton
credentialIds={item.credentialIds}
type={item.type}
isGlobal={item.isGlobal}
installed
/>
}
/>
))}
</List>
<ShellSubHeading
className="mt-10"
title={
<SubHeadingTitleWithConnections title={t("payment")} numConnections={data.payment.numActive} />
}
/>
<List>
{data.payment.items.map((item) => (
<IntegrationListItem
key={item.title}
imageSrc={item.imageSrc}
title={item.title}
description={item.description}
actions={
<ConnectOrDisconnectIntegrationButton
credentialIds={item.credentialIds}
type={item.type}
isGlobal={item.isGlobal}
installed={item.installed}
/>
}
/>
))}
</List>
<ShellSubHeading
className="mt-10"
title={
<SubHeadingTitleWithConnections title={"Others"} numConnections={data?.other?.numActive || 0} />
}
/>
<List>
{data.other.items.map((item) => (
<IntegrationListItem
key={item.title}
imageSrc={item.imageSrc}
title={item.title}
description={item.description}
actions={
<ConnectOrDisconnectIntegrationButton
credentialIds={item.credentialIds}
type={item.type}
isGlobal={item.isGlobal}
installed={item.installed}
/>
}>
<AppConfiguration type={item.type} credentialIds={item.credentialIds} />
</IntegrationListItem>
))}
</List>
</>
)}></QueryCell>
customLoader={<SkeletonLoader className={className} />}
success={({ data }) => {
return (
<>
{data.items.length > 0 && (
<div className={className}>
<ShellSubHeading
title={
<SubHeadingTitleWithConnections title={t(variant)} numConnections={data.items.length} />
}
/>
<List>
{data.items.map((item) => (
<IntegrationListItem
key={item.title}
title={item.title}
imageSrc={item.imageSrc}
description={item.description}
actions={
<ConnectOrDisconnectIntegrationButton
credentialIds={item.credentialIds}
type={item.type}
isGlobal={item.isGlobal}
installed
/>
}
/>
))}
</List>
</div>
)}
</>
);
}}
/>
);
}
};
function Web3Container() {
const { t } = useLocale();
const result = trpc.useQuery(["viewer.web3Integration"]);
const isWeb3Active = !!result.data?.isWeb3Active;
return (
<>
<ShellSubHeading title="Web3" subtitle={t("meet_people_with_the_same_tokens")} className="mt-10" />
<div className="lg:col-span-9 lg:pb-8">
<List>
<ListItem className={classNames("flex-col")}>
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-3")}>
<Image width={40} height={40} src="/apps/metamask.svg" alt="Embed" />
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3">
MetaMask (
<a className="text-blue-500" target="_blank" href="https://cal.com/web3" rel="noreferrer">
Read more
</a>
)
</ListItemTitle>
<ListItemText component="p">{t("only_book_people_and_allow")}</ListItemText>
</div>
<Web3ConnectBtn />
</div>
</ListItem>
</List>
</div>
{isWeb3Active && (
<>
<ShellSubHeading title="Web3" subtitle={t("meet_people_with_the_same_tokens")} className="mt-10" />
<div className="lg:col-span-9 lg:pb-8">
<List>
<ListItem className={classNames("flex-col")}>
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-3")}>
<Image width={40} height={40} src="/apps/metamask.svg" alt="Embed" />
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3">
MetaMask (
<a
className="text-blue-500"
target="_blank"
href="https://cal.com/web3"
rel="noreferrer">
Read more
</a>
)
</ListItemTitle>
<ListItemText component="p">{t("only_book_people_and_allow")}</ListItemText>
</div>
<Web3ConnectBtn />
</div>
</ListItem>
</List>
</div>
</>
)}
</>
);
}
@ -264,7 +232,7 @@ function Web3ConnectBtn() {
export default function IntegrationsPage() {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.integrations", { onlyInstalled: true }]);
return (
<Shell
heading={t("installed_apps")}
@ -272,12 +240,33 @@ export default function IntegrationsPage() {
large
customLoader={<SkeletonLoader />}>
<AppsShell>
<ClientSuspense fallback={<SkeletonLoader />}>
<IntegrationsContainer />
<CalendarListContainer />
<WebhookListContainer title={t("webhooks")} subtitle={t("receive_cal_meeting_data")} />
<Web3Container />
</ClientSuspense>
<QueryCell
query={query}
success={({ data }) => {
return data.items.length > 0 ? (
<>
<IntegrationsContainer variant="conferencing" />
<CalendarListContainer />
<IntegrationsContainer variant="payment" className="mt-8" />
<IntegrationsContainer variant="other" className="mt-8" />
<Web3Container />
</>
) : (
<EmptyScreen
Icon={ViewGridIcon}
headline={t("empty_installed_apps_headline")}
description={
<>
<span className="mb-6 block">{t("empty_installed_apps_description")}</span>
<Button href="/apps" EndIcon={ArrowRightIcon}>
{t("empty_installed_apps_button")}
</Button>
</>
}
/>
);
}}
/>
</AppsShell>
</Shell>
);

View File

@ -107,7 +107,7 @@ export default function Login({
className="space-y-6"
handleSubmit={async (values) => {
setErrorMessage(null);
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.login, collectPageParameters()));
telemetry.event(telemetryEventTypes.login, collectPageParameters());
const res = await signIn<"credentials">("credentials", {
...values,
callbackUrl,
@ -177,9 +177,7 @@ export default function Login({
onClick={async (e) => {
e.preventDefault();
// track Google logins. Without personal data/payload
telemetry.withJitsu((jitsu) =>
jitsu.track(telemetryEventTypes.googleLogin, collectPageParameters())
);
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
await signIn("google");
}}>
{t("signin_with_google")}

View File

@ -2,12 +2,12 @@ import { ClockIcon } from "@heroicons/react/outline";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import EmptyScreen from "@calcom/ui/EmptyScreen";
import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error";
import { inferQueryOutput, trpc } from "@lib/trpc";
import EmptyScreen from "@components/EmptyScreen";
import Shell from "@components/Shell";
import { NewScheduleButton } from "@components/availability/NewScheduleButton";
import { ScheduleListItem } from "@components/availability/ScheduleListItem";

View File

@ -6,12 +6,12 @@ import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/componen
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import EmptyScreen from "@calcom/ui/EmptyScreen";
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
import { inferQueryInput, inferQueryOutput, trpc } from "@lib/trpc";
import BookingsShell from "@components/BookingsShell";
import EmptyScreen from "@components/EmptyScreen";
import Shell from "@components/Shell";
import BookingListItem from "@components/booking/BookingListItem";
import SkeletonLoader from "@components/booking/SkeletonLoader";

View File

@ -114,9 +114,7 @@ export default function Type(props: inferSSRProps<typeof getServerSideProps>) {
reason: cancellationReason,
};
telemetry.withJitsu((jitsu) =>
jitsu.track(telemetryEventTypes.bookingCancelled, collectPageParameters())
);
telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters());
const res = await fetch("/api/cancel", {
body: JSON.stringify(payload),

View File

@ -31,6 +31,7 @@ import Dropdown, {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@calcom/ui/Dropdown";
import EmptyScreen from "@calcom/ui/EmptyScreen";
import { Tooltip } from "@calcom/ui/Tooltip";
import { withQuery } from "@lib/QueryCell";
@ -39,7 +40,6 @@ import { HttpError } from "@lib/core/http/error";
import { inferQueryOutput, trpc } from "@lib/trpc";
import { EmbedButton, EmbedDialog } from "@components/Embed";
import EmptyScreen from "@components/EmptyScreen";
import Shell from "@components/Shell";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
import CreateEventTypeButton from "@components/eventtype/CreateEventType";

View File

@ -322,12 +322,10 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
className="flex"
onSubmit={formMethods.handleSubmit(async (values) => {
// track the number of imports. Without personal data/payload
telemetry.withJitsu((jitsu) =>
jitsu.track(telemetryEventTypes.importSubmitted, {
...collectPageParameters(),
selectedImport,
})
);
telemetry.event(telemetryEventTypes.importSubmitted, {
...collectPageParameters(),
selectedImport,
});
setSubmitting(true);
const response = await fetch(`/api/import/${selectedImport}`, {
method: "POST",

View File

@ -14,7 +14,7 @@ import Shell from "@components/Shell";
function AdminView() {
const { t } = useLocale();
const usernameRef = useRef<HTMLInputElement>(null!);
const usernameRef = useRef<HTMLInputElement>(null);
return (
<div className="divide-y divide-gray-200 lg:col-span-9">
@ -23,7 +23,7 @@ function AdminView() {
className="mb-6 w-full sm:w-1/2"
onSubmit={(e) => {
e.preventDefault();
const enteredUsername = usernameRef.current.value.toLowerCase();
const enteredUsername = usernameRef.current?.value.toLowerCase();
signIn("impersonation-auth", { username: enteredUsername }).then((res) => {
console.log(res);
});
@ -55,11 +55,9 @@ export default function Admin() {
const { t } = useLocale();
return (
<Shell heading={t("profile")} subtitle={t("edit_profile_info_description")}>
<SettingsShell>
<AdminView />
</SettingsShell>
</Shell>
<SettingsShell heading={t("admin")}>
<AdminView />
</SettingsShell>
);
}

View File

@ -0,0 +1,16 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import ApiKeyListContainer from "@ee/components/apiKeys/ApiKeyListContainer";
import SettingsShell from "@components/SettingsShell";
import WebhookListContainer from "@components/webhook/WebhookListContainer";
export default function Settings() {
const { t } = useLocale();
return (
<SettingsShell heading={t("developer")} subtitle={t("manage_developer_settings")}>
<WebhookListContainer title={t("webhooks")} subtitle={t("receive_cal_meeting_data")} />
<ApiKeyListContainer />
</SettingsShell>
);
}

View File

@ -2,7 +2,6 @@ import { IdentityProvider } from "@prisma/client";
import React from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import ApiKeyListContainer from "@ee/components/apiKeys/ApiKeyListContainer";
import SAMLConfiguration from "@ee/components/saml/Configuration";
import { identityProviderNameMap } from "@lib/auth";
@ -37,7 +36,6 @@ export default function Security() {
) : (
<div className="space-y-2 divide-y">
<ChangePasswordSection />
<ApiKeyListContainer />
<TwoFactorAuthSection twoFactorEnabled={user?.twoFactorEnabled || false} />
<DisableUserImpersonation disableImpersonation={user?.disableImpersonation ?? true} />
</div>

View File

@ -7,11 +7,11 @@ import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import EmptyScreen from "@calcom/ui/EmptyScreen";
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 TeamCreateModal from "@components/team/TeamCreateModal";

View File

@ -179,12 +179,10 @@ export default function Success(props: SuccessProps) {
const isCancelled = status === "CANCELLED" || status === "REJECTED";
const telemetry = useTelemetry();
useEffect(() => {
telemetry.withJitsu((jitsu) =>
jitsu.track(
top !== window ? telemetryEventTypes.embedView : telemetryEventTypes.pageView,
collectPageParameters("/success")
)
);
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/success"));
}
}, [telemetry]);
useEffect(() => {

View File

@ -3,6 +3,7 @@ import { UserPlan } from "@prisma/client";
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useEffect } from "react";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
@ -32,17 +33,15 @@ function TeamPage({ team }: TeamPageProps) {
useExposePlanGlobally("PRO");
const isEmbed = useIsEmbed();
const telemetry = useTelemetry();
const router = useRouter();
useEffect(() => {
telemetry.withJitsu((jitsu) =>
jitsu.track(
telemetryEventTypes.pageView,
collectPageParameters("/team/[slug]", {
isTeamBooking: true,
})
)
telemetry.event(
telemetryEventTypes.pageView,
collectPageParameters("/team/[slug]", { isTeamBooking: true })
);
}, [telemetry]);
}, [telemetry, router.asPath]);
const eventTypes = (
<ul className="space-y-3">
{team.eventTypes.map((type) => (

View File

@ -145,12 +145,12 @@ export async function login(
}
export async function getPaymentCredential(page: Page) {
await page.goto("/apps/installed");
await page.goto("/apps/stripe");
/** We start the Stripe flow */
await Promise.all([
page.waitForNavigation({ url: "https://connect.stripe.com/oauth/v2/authorize?*" }),
page.click('li:has-text("Stripe") >> [data-testid="integration-connection-button"]'),
page.click('[data-testid="install-app-button"]'),
]);
await Promise.all([

View File

@ -24,7 +24,7 @@ test.describe("Integrations", () => {
const user = await users.create();
const [eventType] = user.eventTypes;
await user.login();
await page.goto("/apps/installed");
await page.goto("/settings/developer");
// --- add webhook
await page.click('[data-testid="new_webhook"]');

View File

@ -70,6 +70,12 @@ export async function waitFor(fn: () => Promise<unknown> | unknown, opts: { time
}
export async function selectFirstAvailableTimeSlotNextMonth(page: Page) {
// Let current month dates fully render.
// There is a bug where if we don't let current month fully render and quickly click go to next month, current month get's rendered
// This doesn't seem to be replicable with the speed of a person, only during automation.
// It would also allow correct snapshot to be taken for current month.
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
await page.click('[data-testid="incrementMonth"]');
// @TODO: Find a better way to make test wait for full month change render to end
// so it can click up on the right day, also when resolve remove other todos
@ -82,6 +88,12 @@ export async function selectFirstAvailableTimeSlotNextMonth(page: Page) {
}
export async function selectSecondAvailableTimeSlotNextMonth(page: Page) {
// Let current month dates fully render.
// There is a bug where if we don't let current month fully render and quickly click go to next month, current month get's rendered
// This doesn't seem to be replicable with the speed of a person, only during automation.
// It would also allow correct snapshot to be taken for current month.
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
await page.click('[data-testid="incrementMonth"]');
// @TODO: Find a better way to make test wait for full month change render to end
// so it can click up on the right day, also when resolve remove other todos
@ -95,7 +107,9 @@ export async function selectSecondAvailableTimeSlotNextMonth(page: Page) {
export async function bookFirstEvent(page: Page) {
// Click first event type
await page.click('[data-testid="event-type-link"]');
await selectFirstAvailableTimeSlotNextMonth(page);
await bookTimeSlot(page);

View File

@ -352,6 +352,8 @@
"loading": "Loading...",
"deleting": "Deleting...",
"standard_iframe": "Standard iframe",
"developer": "Developer",
"manage_developer_settings": "Manage your developer settings.",
"iframe_embed": "iframe Embed",
"embed_calcom": "The easiest way to embed Cal.com on your website.",
"integrate_using_embed_or_webhooks": "Integrate with your website using our embed options, or get real-time booking information using custom webhooks.",
@ -670,13 +672,16 @@
"next_step": "Skip step",
"prev_step": "Prev step",
"installed": "Installed",
"active_install": "{{count}} active install",
"active_install_plural": "{{count}} active installs",
"globally_install": "Globally installed",
"disconnect": "Disconnect",
"embed_your_calendar": "Embed your calendar within your webpage",
"connect_your_favourite_apps": "Connect your favourite apps.",
"automation": "Automation",
"configure_how_your_event_types_interact": "Configure how your event types should interact with your calendars.",
"select_destination_calendar": "Create events on",
"connect_an_additional_calendar": "Connect an additional calendar",
"connect_additional_calendar": "Connect additional calendar",
"conferencing": "Conferencing",
"calendar": "Calendar",
"not_installed": "Not installed",
@ -716,6 +721,9 @@
"trending_apps": "Trending Apps",
"all_apps": "All Apps",
"installed_apps": "Installed Apps",
"empty_installed_apps_headline": "No apps installed",
"empty_installed_apps_description": "Apps enable you to enhance your workflow and improve your scheduling life significantly.",
"empty_installed_apps_button": "Explore the App Store",
"manage_your_connected_apps": "Manage your installed apps or change settings",
"browse_apps": "Browse Apps",
"features": "Features",
@ -776,7 +784,6 @@
"api_keys": "API Keys",
"api_key_modal_subtitle": "API keys allow you to make API calls for your own account.",
"api_keys_subtitle": "Generate API keys to use for accessing your own account.",
"generate_new_api_key": "Generate new API key",
"create_api_key": "Create an API key",
"personal_note": "Name this key",
"personal_note_placeholder": "E.g. Development",

View File

@ -466,10 +466,8 @@ const loggedInViewerRouter = createProtectedRouter()
}
let nextCursor: typeof skip | null = skip;
if (bookingsFetched > take) {
bookings.shift();
nextCursor += bookings.length;
nextCursor += bookingsFetched;
} else {
nextCursor = null;
}
@ -612,15 +610,20 @@ const loggedInViewerRouter = createProtectedRouter()
},
})
.query("integrations", {
async resolve({ ctx }) {
input: z.object({
variant: z.string().optional(),
onlyInstalled: z.boolean().optional(),
}),
async resolve({ ctx, input }) {
const { user } = ctx;
const { variant, onlyInstalled } = input;
const { credentials } = user;
function countActive(items: { credentialIds: unknown[] }[]) {
return items.reduce((acc, item) => acc + item.credentialIds.length, 0);
}
const apps = getApps(credentials).map(
let apps = getApps(credentials).map(
({ credentials: _, credential: _1 /* don't leak to frontend */, ...app }) => ({
...app,
credentialIds: credentials
@ -636,28 +639,17 @@ const loggedInViewerRouter = createProtectedRouter()
.map((c) => c.id),
})
);
// `flatMap()` these work like `.filter()` but infers the types correctly
const conferencing = apps.flatMap((item) => (item.variant === "conferencing" ? [item] : []));
const payment = apps.flatMap((item) => (item.variant === "payment" ? [item] : []));
const other = apps.flatMap((item) => (item.variant.startsWith("other") ? [item] : []));
const calendar = apps.flatMap((item) => (item.variant === "calendar" ? [item] : []));
if (variant) {
// `flatMap()` these work like `.filter()` but infers the types correctly
apps = apps
// variant check
.flatMap((item) => (item.variant.startsWith(variant) ? [item] : []));
}
if (onlyInstalled) {
apps = apps.flatMap((item) => (item.credentialIds.length > 0 || item.isGlobal ? [item] : []));
}
return {
conferencing: {
items: conferencing,
numActive: countActive(conferencing),
},
calendar: {
items: calendar,
numActive: countActive(calendar),
},
payment: {
items: payment,
numActive: countActive(payment),
},
other: {
items: other,
numActive: countActive(other),
},
items: apps,
};
},
})

View File

@ -175,13 +175,17 @@ export const viewerTeamsRouter = createProtectedRouter()
}),
async resolve({ ctx, input }) {
if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
// Only a team owner can remove another team owner.
if (
(await isTeamOwner(input.memberId, input.teamId)) &&
!(await isTeamOwner(ctx.user?.id, input.teamId))
)
throw new TRPCError({ code: "UNAUTHORIZED" });
if (ctx.user?.id === input.memberId)
throw new TRPCError({
code: "FORBIDDEN",
message: "You can not remove yourself from a team you own.",
});
await ctx.prisma.membership.delete({
where: {
userId_teamId: { userId: input.memberId, teamId: input.teamId },
@ -351,7 +355,9 @@ export const viewerTeamsRouter = createProtectedRouter()
}),
async resolve({ ctx, input }) {
if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
// Only owners can award owner role.
if (input.role === MembershipRole.OWNER && !(await isTeamOwner(ctx.user?.id, input.teamId)))
throw new TRPCError({ code: "UNAUTHORIZED" });
const memberships = await ctx.prisma.membership.findMany({
where: {
teamId: input.teamId,

@ -1 +1 @@
Subproject commit 22ad56ec9413ff5ee057fc402d999cdcbca1936a
Subproject commit f4f5f9b8dc94a96b74ee699a4750e0464c91204c

View File

@ -1,6 +1,7 @@
import { GetStaticPropsContext } from "next";
export const AppSetupPageMap = {
"apple-calendar": import("../../applecalendar/pages/setup/_getStaticProps"),
zapier: import("../../zapier/pages/setup/_getStaticProps"),
};

View File

@ -3,6 +3,7 @@ import dynamic from "next/dynamic";
import { DynamicComponent } from "../../_components/DynamicComponent";
export const AppSetupMap = {
"apple-calendar": dynamic(() => import("../../applecalendar/pages/setup")),
zapier: dynamic(() => import("../../zapier/pages/setup")),
};

View File

@ -41,6 +41,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(500).json({ message: "Could not add this caldav account" });
}
return res.status(200).json({});
return res.status(200).json({ url: "/apps/installed" });
}
if (req.method === "GET") {
return res.status(200).json({ url: "/apps/apple-calendar/setup" });
}
}

View File

@ -1,104 +0,0 @@
import { useState } from "react";
import { useForm } from "react-hook-form";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogProps,
} from "@calcom/ui/Dialog";
import { Form, TextField } from "@calcom/ui/form/fields";
export const ADD_INTEGRATION_FORM_TITLE = "addAppleIntegration";
function AddIntegrationModal(props: DialogProps) {
const form = useForm({
defaultValues: {
username: "",
password: "",
},
});
const [errorMessage, setErrorMessage] = useState("");
return (
<Dialog name={ADD_INTEGRATION_FORM_TITLE} {...props}>
<DialogContent>
<DialogHeader
title="Connect to Apple Server"
subtitle={
<>
Generate an app specific password to use with Cal.com at{" "}
<a
className="text-indigo-400"
href="https://appleid.apple.com/account/manage"
target="_blank"
rel="noopener noreferrer">
https://appleid.apple.com/account/manage
</a>
. Your credentials will be stored and encrypted.
</>
}
/>
<Form
form={form}
handleSubmit={async (values) => {
setErrorMessage("");
const res = await fetch("/api/integrations/applecalendar/add", {
method: "POST",
body: JSON.stringify(values),
headers: {
"Content-Type": "application/json",
},
});
const json = await res.json();
if (!res.ok) {
setErrorMessage(json?.message || "Something went wrong");
} else {
props.onOpenChange?.(false);
}
}}>
<fieldset className="space-y-2" disabled={form.formState.isSubmitting}>
<TextField
required
type="text"
{...form.register("username")}
label="Username"
placeholder="rickroll"
/>
<TextField
required
type="password"
{...form.register("password")}
label="Password"
placeholder="•••••••••••••"
autoComplete="password"
/>
</fieldset>
{errorMessage && <Alert severity="error" title={errorMessage} className="my-4" />}
<DialogFooter>
<DialogClose
onClick={() => {
props.onOpenChange?.(false);
}}
asChild>
<Button type="button" color="secondary" tabIndex={-1}>
Cancel
</Button>
</DialogClose>
<Button type="submit" loading={form.formState.isSubmitting}>
Save
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
}
export default AddIntegrationModal;

View File

@ -1,20 +1,18 @@
import { useState } from "react";
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import { InstallAppButtonProps } from "../../types";
import AddIntegration from "./AddIntegration";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
const mutation = useAddAppMutation("apple_calendar");
return (
<>
{props.render({
onClick() {
setIsModalOpen(true);
mutation.mutate("");
},
disabled: isModalOpen,
loading: mutation.isLoading,
})}
<AddIntegration open={isModalOpen} onOpenChange={setIsModalOpen} />
</>
);
}

View File

@ -1,2 +1 @@
export { default as AddIntegration } from "./AddIntegration";
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -0,0 +1,7 @@
import { GetStaticPropsContext } from "next";
export const getStaticProps = async (ctx: GetStaticPropsContext) => {
return {
props: {},
};
};

View File

@ -0,0 +1,100 @@
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { Toaster } from "react-hot-toast";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import { Form, TextField } from "@calcom/ui/form/fields";
export default function AppleCalendarSetup() {
const { t } = useLocale();
const router = useRouter();
const form = useForm({
defaultValues: {
username: "",
password: "",
},
});
const [errorMessage, setErrorMessage] = useState("");
return (
<div className="flex h-screen bg-gray-200">
<div className="m-auto rounded bg-white p-5 md:w-[560px] md:p-10">
<div className="flex flex-col space-y-5 md:flex-row md:space-y-0 md:space-x-5">
<div>
{/* eslint-disable @next/next/no-img-element */}
<img
src="/api/app-store/applecalendar/icon.svg"
alt="Apple Calendar"
className="h-12 w-12 max-w-2xl"
/>
</div>
<div>
<h1 className="text-gray-600">Connect to Apple Server</h1>
<div className="mt-1 text-sm">
Generate an app specific password to use with Cal.com at{" "}
<a
className="text-indigo-400"
href="https://appleid.apple.com/account/manage"
target="_blank"
rel="noopener noreferrer">
https://appleid.apple.com/account/manage
</a>
. Your credentials will be stored and encrypted.
</div>
<div className="my-2 mt-3">
<Form
form={form}
handleSubmit={async (values) => {
setErrorMessage("");
const res = await fetch("/api/integrations/applecalendar/add", {
method: "POST",
body: JSON.stringify(values),
headers: {
"Content-Type": "application/json",
},
});
const json = await res.json();
if (!res.ok) {
setErrorMessage(json?.message || "Something went wrong");
} else {
router.push(json.url);
}
}}>
<fieldset className="space-y-2" disabled={form.formState.isSubmitting}>
<TextField
required
type="text"
{...form.register("username")}
label="Username"
placeholder="rickroll"
/>
<TextField
required
type="password"
{...form.register("password")}
label="Password"
placeholder="•••••••••••••"
autoComplete="password"
/>
</fieldset>
{errorMessage && <Alert severity="error" title={errorMessage} className="my-4" />}
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button type="submit" loading={form.formState.isSubmitting}>
Save
</Button>
</div>
</Form>
</div>
</div>
</div>
</div>
<Toaster position="bottom-right" />
</div>
);
}

View File

@ -13,12 +13,15 @@ interface IWipeMyCalActionButtonProps {
const WipeMyCalActionButton = (props: IWipeMyCalActionButtonProps) => {
const { trpc, bookingsEmpty, bookingStatus } = props;
const [openDialog, setOpenDialog] = useState(false);
const { isSuccess, isLoading, data } = trpc.useQuery(["viewer.integrations"]);
const { isSuccess, isLoading, data } = trpc.useQuery([
"viewer.integrations",
{ variant: "other", onlyInstalled: undefined },
]);
if (bookingStatus !== "upcoming" || bookingsEmpty) {
return <></>;
}
const wipeMyCalCredentials: { credentialIds: number[] } = data?.other?.items.find(
const wipeMyCalCredentials: { credentialIds: number[] } = data?.items.find(
(item: { type: string }) => item.type === "wipemycal_other"
);

View File

@ -23,12 +23,12 @@ export default function ZapierSetup(props: IZapierSetupProps) {
const [newApiKey, setNewApiKey] = useState("");
const { t } = useLocale();
const utils = trpc.useContext();
const integrations = trpc.useQuery(["viewer.integrations"]);
const integrations = trpc.useQuery(["viewer.integrations", { variant: "other" }]);
// @ts-ignore
const oldApiKey = trpc.useQuery(["viewer.apiKeys.findKeyOfType", { appId: ZAPIER }]);
const deleteApiKey = trpc.useMutation("viewer.apiKeys.delete");
const zapierCredentials: { credentialIds: number[] } | undefined = integrations.data?.other?.items.find(
const zapierCredentials: { credentialIds: number[] } | undefined = integrations.data?.items.find(
(item: { type: string }) => item.type === "zapier_other"
);
const [credentialId] = zapierCredentials?.credentialIds || [false];
@ -49,7 +49,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
if (integrations.isLoading) {
return (
<div className="absolute z-50 flex items-center w-full h-screen bg-gray-200">
<div className="flex absolute z-50 h-screen w-full items-center bg-gray-200">
<Loader />
</div>
);
@ -58,7 +58,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
return (
<div className="flex h-screen bg-gray-200">
{showContent ? (
<div className="p-10 m-auto bg-white rounded">
<div className="m-auto rounded bg-white p-10">
<div className="flex flex-row">
<div className="mr-5">
<Icon />
@ -76,7 +76,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
<>
<div className="mt-1 text-xl">{t("your_unique_api_key")}</div>
<div className="flex my-2 mt-3">
<div className="w-full p-3 pr-5 mr-1 bg-gray-100 rounded">{newApiKey}</div>
<div className="mr-1 w-full rounded bg-gray-100 p-3 pr-5">{newApiKey}</div>
<Tooltip content="copy to clipboard">
<Button
onClick={() => {
@ -85,7 +85,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
}}
type="button"
className="px-4 text-base ">
<ClipboardCopyIcon className="w-5 h-5 mr-2 text-neutral-100" />
<ClipboardCopyIcon className="mr-2 h-5 w-5 text-neutral-100" />
{t("copy")}
</Button>
</Tooltip>
@ -104,8 +104,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
{t("zapier_invite_link")}
</a>
</li>
)
}
)}
<Trans i18nKey="zapier_setup_instructions">
<li>Log into your Zapier account and create a new Zap.</li>
<li>Select Cal.com as your Trigger app. Also choose a Trigger event.</li>

View File

@ -12,7 +12,7 @@ async function checkLicense(license: string): Promise<boolean> {
return cachedResponse;
} else {
try {
const response = await fetch(url);
const response = await fetch(url, { mode: "cors" });
const data = await response.json();
cache.put(url, data.valid, CACHING_TIME);
return data.valid;

View File

@ -6,13 +6,14 @@ require("dotenv").config({ path: "../../../../../.env" });
const outputDir = path.join("../results");
const testDir = path.join("../tests");
const quickMode = process.env.QUICK === "true";
const CI = process.env.CI;
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
retries: quickMode ? 0 : 1,
forbidOnly: !!CI,
retries: quickMode && !CI ? 0 : 1,
workers: 1,
timeout: 60_000,
reporter: [
[process.env.CI ? "github" : "list"],
[CI ? "github" : "list"],
[
"html",
{ outputFolder: path.join(__dirname, "..", "reports", "playwright-html-report"), open: "never" },
@ -33,13 +34,13 @@ const config: PlaywrightTestConfig = {
command: "yarn run-p 'embed-dev' 'embed-web-start'",
port: 3100,
timeout: 60_000,
reuseExistingServer: !process.env.CI,
reuseExistingServer: !CI,
},
use: {
baseURL: "http://localhost:3100",
locale: "en-US",
trace: "retain-on-failure",
headless: !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS,
headless: !!CI || !!process.env.PLAYWRIGHT_HEADLESS,
},
projects: [
{

View File

@ -60,8 +60,11 @@ export const getEmbedIframe = async ({ page, pathname }: { page: Page; pathname:
async function selectFirstAvailableTimeSlotNextMonth(frame: Frame, page: Page) {
await frame.click('[data-testid="incrementMonth"]');
// @TODO: Find a better way to make test wait for full month change render to end
// so it can click up on the right day, also when resolve remove other todos
// so it can click up on the right day, also when done, resolve other todos as well
// The problem is that the Month Text changes instantly but we don't know when the corresponding dates are visible
// Waiting for full month increment
await frame.waitForTimeout(1000);
expect(await page.screenshot()).toMatchSnapshot("availability-page-2.png");
@ -78,7 +81,14 @@ export async function bookFirstEvent(username: string, frame: Frame, page: Page)
return !!url.pathname.match(new RegExp(`/${username}/.*$`));
},
});
// Let current month dates fully render.
// There is a bug where if we don't let current month fully render and quickly click go to next month, current month get's rendered
// This doesn't seem to be replicable with the speed of a person, only during automation.
// It would also allow correct snapshot to be taken for current month.
await frame.waitForTimeout(1000);
expect(await page.screenshot()).toMatchSnapshot("availability-page-1.png");
await selectFirstAvailableTimeSlotNextMonth(frame, page);
await frame.waitForNavigation({
url(url) {

View File

@ -15,7 +15,8 @@
],
"types": "./dist/index.d.ts",
"devDependencies": {
"eslint": "^8.15.0"
"eslint": "^8.15.0",
"typescript": "^4.6.4"
},
"dependencies": {
"@calcom/embed-core": "^1.1.0"

View File

@ -8,9 +8,10 @@ export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL || "https://cal.c
// As website isn't setup for preview environments, use the webapp url instead
export const CAL_URL = new URL(WEBAPP_URL).hostname.endsWith(".vercel.app") ? WEBAPP_URL : WEBSITE_URL;
export const CONSOLE_URL = WEBAPP_URL.startsWith("http://localhost")
? "http://localhost:3004"
: `https://console.cal.${process.env.VERCEL_ENV === "production" ? "com" : "dev"}`;
export const CONSOLE_URL =
new URL(WEBAPP_URL).hostname.endsWith(".cal.dev") || process.env.NODE_ENV !== "production"
? `https://console.cal.dev`
: `https://console.cal.com`;
export const EMBED_LIB_URL = process.env.NEXT_PUBLIC_EMBED_LIB_URL || `${WEBAPP_URL}/embed/embed.js`;
export const IS_PRODUCTION = process.env.NODE_ENV === "production";
export const TRIAL_LIMIT_DAYS = 14;

View File

@ -0,0 +1,3 @@
insert into "App" ("slug", "dirName", "updatedAt", "categories")
values ('daily-video', 'dailyvideo', CURRENT_TIMESTAMP, '{video}')
on conflict do nothing

View File

@ -12,6 +12,7 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_LICENSE_CONSENT: "agree" | undefined;
/** Needed to enable enterprise-only features */
readonly CALCOM_LICENSE_KEY: string | undefined;
readonly CALCOM_TELEMETRY_DISABLED: string | undefined;
readonly CALENDSO_ENCRYPTION_KEY: string | undefined;
readonly DATABASE_URL: string | undefined;
readonly GOOGLE_API_CREDENTIALS: string | undefined;
@ -22,7 +23,6 @@ declare namespace NodeJS {
/** @deprecated use `NEXT_PUBLIC_WEBSITE_URL` */
readonly NEXT_PUBLIC_APP_URL: string | undefined;
readonly NEXTAUTH_SECRET: string | undefined;
readonly NEXT_PUBLIC_TELEMETRY_KEY: string | undefined;
readonly MS_GRAPH_CLIENT_ID: string | undefined;
readonly MS_GRAPH_CLIENT_SECRET: string | undefined;
readonly ZOOM_CLIENT_ID: string | undefined;

View File

@ -52,7 +52,7 @@ export function Dialog(props: DialogProps) {
return (
<DialogPrimitive.Root {...dialogProps}>
<DialogPrimitive.Overlay className="fixed inset-0 z-40 transition-opacity bg-black bg-opacity-50 fadeIn" />
<DialogPrimitive.Overlay className="fadeIn fixed inset-0 z-40 bg-black bg-opacity-50 transition-opacity" />
{children}
</DialogPrimitive.Root>
);
@ -64,7 +64,7 @@ type DialogContentProps = React.ComponentProps<typeof DialogPrimitive["Content"]
export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
({ children, ...props }, forwardedRef) => (
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className="fixed inset-0 z-40 transition-opacity bg-gray-500 bg-opacity-75 fadeIn" />
<DialogPrimitive.Overlay className="fadeIn fixed inset-0 z-40 bg-gray-500 bg-opacity-75 transition-opacity" />
{/*zIndex one less than Toast */}
<DialogPrimitive.Content
{...props}
@ -93,7 +93,7 @@ type DialogHeaderProps = {
export function DialogHeader(props: DialogHeaderProps) {
return (
<div className="mb-8">
<h3 className="text-xl text-gray-900 leading-16 font-cal" id="modal-title">
<h3 className="leading-16 font-cal text-xl text-gray-900" id="modal-title">
{props.title}
</h3>
{props.subtitle && <div className="text-sm text-gray-400">{props.subtitle}</div>}
@ -104,7 +104,7 @@ export function DialogHeader(props: DialogHeaderProps) {
export function DialogFooter(props: { children: ReactNode }) {
return (
<div>
<div className="flex justify-end mt-5 space-x-2 rtl:space-x-reverse">{props.children}</div>
<div className="mt-5 flex justify-end space-x-2 rtl:space-x-reverse">{props.children}</div>
</div>
);
}

View File

@ -13,7 +13,7 @@ export default function EmptyScreen({
}) {
return (
<>
<div className="min-h-80 my-6 flex flex-col items-center justify-center rounded-sm border border-dashed">
<div className="min-h-80 flex my-6 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-gray-600 dark:bg-white">
<Icon className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
</div>

View File

@ -27,9 +27,17 @@ const SkeletonText: React.FC<SkeletonBaseProps> = ({ width, height }) => {
return <div className={`rounded-md bg-gray-200 w-${width} h-${height} ${classNames}`} />;
};
const SkeletonButton: React.FC<SkeletonBaseProps> = ({ width, height }) => {
return (
<SkeletonContainer>
<div className={`w-${width} h-${height} bg-gray-200 ${classNames}`} />
</SkeletonContainer>
);
};
const SkeletonContainer: React.FC<SkeletonContainer> = ({ children, as }) => {
const Component = as || "div";
return <Component className="animate-pulse">{children}</Component>;
};
export { SkeletonAvatar, SkeletonText, SkeletonContainer };
export { SkeletonAvatar, SkeletonText, SkeletonButton, SkeletonContainer };

View File

@ -25,6 +25,8 @@
"^build",
"@calcom/prisma#build",
"$CALENDSO_ENCRYPTION_KEY",
"$CALCOM_LICENSE_KEY",
"$CALCOM_TELEMETRY_DISABLED",
"$CRON_API_KEY",
"$DAILY_API_KEY",
"$DAILY_SCALE_PLAN",
@ -48,7 +50,6 @@
"$NEXT_PUBLIC_STRIPE_PRO_PLAN_PRICE",
"$NEXT_PUBLIC_STRIPE_PRO_PLAN_PRODUCT",
"$NEXT_PUBLIC_STRIPE_PUBLIC_KEY",
"$NEXT_PUBLIC_TELEMETRY_KEY",
"$NEXT_PUBLIC_WEBAPP_URL",
"$NEXT_PUBLIC_WEBSITE_URL",
"$NEXT_PUBLIC_ZENDESK_KEY",

289
yarn.lock
View File

@ -116,28 +116,7 @@
json5 "^2.1.2"
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==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.16.7"
"@babel/generator" "^7.17.12"
"@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/template" "^7.16.7"
"@babel/traverse" "^7.17.12"
"@babel/types" "^7.17.12"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.1"
semver "^6.3.0"
"@babel/core@^7.12.16":
"@babel/core@^7.11.6", "@babel/core@^7.12.16":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876"
integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==
@ -215,15 +194,6 @@
"@jridgewell/gen-mapping" "^0.1.0"
jsesc "^2.5.1"
"@babel/generator@^7.17.12", "@babel/generator@^7.7.2":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.12.tgz#5970e6160e9be0428e02f4aba62d8551ec366cc8"
integrity sha512-V49KtZiiiLjH/CnIW6OjJdrenrGoyh6AmKQ3k2AZFKozC1h846Q4NYlZ5nqAigPDUXfGzC88+LOUuG8yKd2kCw==
dependencies:
"@babel/types" "^7.17.12"
"@jridgewell/gen-mapping" "^0.3.0"
jsesc "^2.5.1"
"@babel/generator@^7.17.9":
version "7.17.9"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc"
@ -233,7 +203,7 @@
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/generator@^7.18.2":
"@babel/generator@^7.18.2", "@babel/generator@^7.7.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d"
integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==
@ -343,20 +313,6 @@
"@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==
dependencies:
"@babel/helper-environment-visitor" "^7.16.7"
"@babel/helper-module-imports" "^7.16.7"
"@babel/helper-simple-access" "^7.17.7"
"@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/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"
@ -465,11 +421,6 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78"
integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==
"@babel/parser@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.12.tgz#36c2ed06944e3691ba82735fc4cf62d12d491a23"
integrity sha512-FLzHmN9V3AJIrWfOpvRlZCeVg/WLdicSnTMsLur6uDj9TT8ymUlG9XxURdW/XvuygK+2CW0poOJABdA4m/YKxA==
"@babel/parser@^7.17.9":
version "7.17.9"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef"
@ -696,22 +647,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"
@ -728,7 +663,7 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2":
"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.7.2":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.2.tgz#b77a52604b5cc836a9e1e08dca01cba67a12d2e8"
integrity sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==
@ -769,14 +704,6 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.12.tgz#1210690a516489c0200f355d87619157fbbd69a0"
integrity sha512-rH8i29wcZ6x9xjzI5ILHL/yZkbQnCERdHlogKuIb4PUr7do4iT8DPekrTbBLWTnRQm6U0GYABbTMSzijmEqlAg==
dependencies:
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.18.0", "@babel/types@^7.18.2":
version "7.18.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354"
@ -997,6 +924,21 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/eslintrc@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.3.2"
globals "^13.15.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@ethereumjs/common@^2.5.0", "@ethereumjs/common@^2.6.3":
version "2.6.3"
resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.3.tgz#39ddece7300b336276bad6c02f6a9f1a082caa05"
@ -1264,15 +1206,10 @@
resolved "https://registry.yarnpkg.com/@glidejs/glide/-/glide-3.5.2.tgz#7012c5920ecf202bbda44d8526fc979984b6dd54"
integrity sha512-7jGciNJ2bQ4eZLSNlSZ+VAyW63kALf420CvkEpK4lEsUfWJq9odqimci0YCiyNyMUFB+pWHwLYyNc57dijYsCg==
"@headlessui/react@^1.4.1":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.1.tgz#d822792e589aac005462491dd62f86095e0c3bef"
integrity sha512-gMd6uIs1U4Oz718Z5gFoV0o/vD43/4zvbyiJN9Dt7PK9Ubxn+TmJwTmYwyNJc5KxxU1t0CmgTNgwZX9+4NjCnQ==
"@headlessui/react@^1.5.0":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.3.tgz#6e52477ea3aee7d99f153b98c1b41765ed77db3d"
integrity sha512-WNu/ypGzl0JmJ+sD34KtdycEu2n7EZjKFx2rq6fivsszPdoEyOVZ/GYQMJ437dfAJI0/ZxoRYfrOVduZHjlokQ==
"@headlessui/react@^1.4.1", "@headlessui/react@^1.5.0":
version "1.6.4"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.4.tgz#c73084e23386bef5fb86cd16da3352c3a844bb4c"
integrity sha512-0yqz1scwbFtwljmbbKjXsSGl5ABEYNICVHZnMCWo0UtOZodo2Tpu94uOVgCRjRZ77l2WcTi2S0uidINDvG7lsA==
"@heroicons/react@^1.0.4", "@heroicons/react@^1.0.6":
version "1.0.6"
@ -1284,7 +1221,12 @@
resolved "https://registry.yarnpkg.com/@hookform/error-message/-/error-message-2.0.0.tgz#9b1b037fd816ea9b1531c06aa7fab5f5154aa740"
integrity sha512-Y90nHzjgL2MP7GFy75kscdvxrCTjtyxGmOLLxX14nd08OXRIh9lMH/y9Kpdo0p1IPowJBiZMHyueg7p+yrqynQ==
"@hookform/resolvers@^2.8.1", "@hookform/resolvers@^2.8.9":
"@hookform/resolvers@^2.8.1":
version "2.8.10"
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-2.8.10.tgz#b66d7a7848b1b1dd5b976a73fff36bb366666e7d"
integrity sha512-DDFtNlugsbwAhCJHYp3NcN5LvJrwSsCLPi41Wo5O8UAIbUFnBfY/jW+zKnlX57BZ4jE0j/g6R9rB3JlO89ad0g==
"@hookform/resolvers@^2.8.9":
version "2.8.9"
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-2.8.9.tgz#0177a6b2b5b0dfa7860625f9a1b71803d467e78a"
integrity sha512-IXwGpjewxScF4N2kuyYDip6ABqH4lCg9n1f1mp0vbmKik+u+nestpbtdEs6U1WQZxwaoK/2APv1+MEr4czX7XA==
@ -2027,11 +1969,6 @@
"@babel/runtime" "^7.7.2"
regenerator-runtime "^0.13.3"
"@jitsu/sdk-js@^2.2.4":
version "2.5.5"
resolved "https://registry.yarnpkg.com/@jitsu/sdk-js/-/sdk-js-2.5.5.tgz#92ee905cf7c571330a6665e7e91d9d5c2cbd536b"
integrity sha512-aIHRNZE++csy37HSt9CqZXAWq0BjItsXr2irUmtBl30KBKFiSWDMHfIr12EdOEVMsrKla9Uyb/9uppHnrKMaGw==
"@jridgewell/gen-mapping@^0.1.0":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
@ -3205,7 +3142,12 @@
dependencies:
prop-types "^15.7.2"
"@stripe/stripe-js@^1.17.1", "@stripe/stripe-js@^1.29.0":
"@stripe/stripe-js@^1.17.1":
version "1.31.0"
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.31.0.tgz#2920bdddc3eabb5734f9ca794824e25b378533ac"
integrity sha512-drlXsNvd/s9S1+Ghja0aDB81N3Wr/6iRAZ7MOzKv7AtKxVdvGpOwESw3z1lHl/Wx/rvPpA7whKdB3W2CnVSArw==
"@stripe/stripe-js@^1.29.0":
version "1.29.0"
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.29.0.tgz#f41e46aee711d1eabcb3bbc77376016a250ec962"
integrity sha512-OsUxk0VLlum8E2d6onlEdKuQcvLMs7qTrOXCnl/BGV3fAm65qr6h3e1IZ5AX4lgUlPRrzRcddSOA5DvkKKYLvg==
@ -3217,7 +3159,14 @@
dependencies:
defer-to-connect "^1.0.1"
"@tailwindcss/forms@^0.5.0", "@tailwindcss/forms@^0.5.1":
"@tailwindcss/forms@^0.5.0":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.2.tgz#4ef45f9916dcb37838cbe7fecdcc4ba7a7c2ab59"
integrity sha512-pSrFeJB6Bg1Mrg9CdQW3+hqZXAKsBrSG9MAfFLKy1pVA4Mb4W7C0k7mEhlmS2Dfo/otxrQOET7NJiJ9RrS563w==
dependencies:
mini-svg-data-uri "^1.2.3"
"@tailwindcss/forms@^0.5.1":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.1.tgz#7fe86b9b67e6d91cb902e2d3f4ebe561cc057a13"
integrity sha512-QSwsFORnC2BAP0lRzQkz1pw+EzIiiPdk4e27vGQjyXkwJPeC7iLIRVndJzf9CJVbcrrIcirb/TfxF3gRTyFEVA==
@ -3732,9 +3681,9 @@
integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==
"@types/prettier@^2.1.5":
version "2.6.1"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.1.tgz#76e72d8a775eef7ce649c63c8acae1a0824bbaed"
integrity sha512-XFjFHmaLVifrAKaZ+EKghFHtHSUonyw8P2Qmy2/+osBnrKbH9UYtlK10zg8/kCt47MFilll/DEDKy3DHfJ0URw==
version "2.6.3"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a"
integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==
"@types/prop-types@*":
version "15.7.5"
@ -4470,6 +4419,11 @@ arr-flatten@^1.1.0:
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
arr-rotate@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/arr-rotate/-/arr-rotate-1.0.0.tgz#c11877d06a0a42beb39ab8956a06779d9b71d248"
integrity sha512-yOzOZcR9Tn7enTF66bqKorGGH0F36vcPaSWg8fO0c0UYb3LX3VMXj5ZxEqQLNOecAhlRJ7wYZja5i4jTlnbIfQ==
arr-union@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
@ -5935,7 +5889,7 @@ code-excerpt@^4.0.0:
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==
collect-v8-coverage@^1.0.0:
version "1.0.1"
@ -6080,7 +6034,7 @@ confusing-browser-globals@1.0.10:
console-control-strings@^1.0.0, console-control-strings@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
constant-case@^1.1.0:
version "1.1.2"
@ -6143,7 +6097,7 @@ cookie@0.4.2, cookie@^0.4.1:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
cookie@~0.5.0:
cookie@^0.5.0, cookie@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
@ -6553,7 +6507,7 @@ decompress@^4.2.1:
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==
deep-extend@0.6.0, deep-extend@^0.6.0:
version "0.6.0"
@ -6658,7 +6612,7 @@ delayed-stream@~1.0.0:
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
denque@^2.0.1:
version "2.0.1"
@ -6928,9 +6882,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.4.118:
version "1.4.132"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.132.tgz#b64599eb018221e52e2e4129de103b03a413c55d"
integrity sha512-JYdZUw/1068NWN+SwXQ7w6Ue0bWYGihvSUNNQwurvcDV/SM7vSiGZ3NuFvFgoEiCs4kB8xs3cX2an3wB7d4TBw==
version "1.4.144"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.144.tgz#9a5d1f41452ecc65b686d529ae919248da44f406"
integrity sha512-R3RV3rU1xWwFJlSClVWDvARaOk6VUO/FubHLodIASDB3Mc2dzuWvNdfOgH9bwHUTqT79u92qw60NWfwUdzAqdg==
electron-to-chromium@^1.4.76, electron-to-chromium@^1.4.84:
version "1.4.103"
@ -7652,7 +7606,7 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
eslint@8.15.0, eslint@^8.10.0, eslint@^8.15.0:
eslint@8.15.0, eslint@^8.15.0:
version "8.15.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.15.0.tgz#fea1d55a7062da48d82600d2e0974c55612a11e9"
integrity sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==
@ -7739,6 +7693,47 @@ eslint@^7.24.0:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
eslint@^8.10.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.16.0.tgz#6d936e2d524599f2a86c708483b4c372c5d3bbae"
integrity sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==
dependencies:
"@eslint/eslintrc" "^1.3.0"
"@humanwhocodes/config-array" "^0.9.2"
ajv "^6.10.0"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.3.2"
doctrine "^3.0.0"
escape-string-regexp "^4.0.0"
eslint-scope "^7.1.1"
eslint-utils "^3.0.0"
eslint-visitor-keys "^3.3.0"
espree "^9.3.2"
esquery "^1.4.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1"
functional-red-black-tree "^1.0.1"
glob-parent "^6.0.1"
globals "^13.15.0"
ignore "^5.2.0"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
js-yaml "^4.1.0"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1"
lodash.merge "^4.6.2"
minimatch "^3.1.2"
natural-compare "^1.4.0"
optionator "^0.9.1"
regexpp "^3.2.0"
strip-ansi "^6.0.1"
strip-json-comments "^3.1.0"
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
espree@^7.3.0, espree@^7.3.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
@ -8336,7 +8331,7 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
figures@^3.0.0:
figures@^3.0.0, figures@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
@ -8702,7 +8697,7 @@ functions-have-names@^1.2.2:
gauge@~2.7.3:
version "2.7.4"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==
dependencies:
aproba "^1.0.3"
console-control-strings "^1.0.0"
@ -8955,6 +8950,13 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globals@^13.15.0:
version "13.15.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac"
integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==
dependencies:
type-fest "^0.20.2"
globals@^13.6.0, globals@^13.9.0:
version "13.13.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.13.0.tgz#ac32261060d8070e2719dd6998406e27d2b5727b"
@ -9256,7 +9258,7 @@ has-tostringtag@^1.0.0:
has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
has-value@^0.3.1:
version "0.3.1"
@ -9649,7 +9651,7 @@ image-q@^4.0.0:
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
immutable@^3.x.x:
version "3.8.2"
@ -9725,6 +9727,15 @@ ini@~1.3.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
ink-select-input@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ink-select-input/-/ink-select-input-4.2.1.tgz#121108ccbcb42aa619f9f0baedb796c24c971a2a"
integrity sha512-WvlrYdwmdnD6/nE/9mNhaaanTQOKmwy/hT/vuAqbDec3PUQBQ8Pkwszii/8eGvDTx5bGiUHu18P9D5IoB/ERaw==
dependencies:
arr-rotate "^1.0.0"
figures "^3.2.0"
lodash.isequal "^4.5.0"
ink-testing-library@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ink-testing-library/-/ink-testing-library-2.1.0.tgz#b5ffd1ef1049550ae4d2f008b8770e7ece6e0313"
@ -10074,7 +10085,7 @@ is-extglob@^2.1.0, is-extglob@^2.1.1:
is-fullwidth-code-point@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==
dependencies:
number-is-nan "^1.0.0"
@ -11750,7 +11761,7 @@ libphonenumber-js@^1.9.52, libphonenumber-js@^1.9.53:
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==
dependencies:
immediate "~3.0.5"
@ -11927,7 +11938,7 @@ lodash.isboolean@^3.0.3:
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.isinteger@^4.0.4:
version "4.0.4"
@ -12083,7 +12094,7 @@ lru-memoizer@^2.1.2, lru-memoizer@^2.1.4:
lru_map@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==
luxon@^1.21.3:
version "1.28.0"
@ -13259,6 +13270,13 @@ next-auth@^4.3.3, next-auth@^4.3.4:
preact-render-to-string "^5.1.19"
uuid "^8.3.2"
next-collect@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/next-collect/-/next-collect-0.1.0.tgz#54a52e07ee083050690df9400a280d99ca975c80"
integrity sha512-U04EqryvShhmZ8N8hhbH5wrd594BEs2sdtoTgDZSBM6IOni1bVNamKnjRHy1NMoKNtzg/1aUap9oGCNViJ04Wg==
dependencies:
cookie "^0.5.0"
next-i18next@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/next-i18next/-/next-i18next-11.0.0.tgz#2857d13c58a5ed976fe57c44286f1520b07f7c96"
@ -13572,7 +13590,7 @@ npmlog@^4.1.2:
number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
number-to-bn@1.7.0:
version "1.7.0"
@ -14547,12 +14565,12 @@ postcss@8.4.5:
picocolors "^1.0.0"
source-map-js "^1.0.1"
postcss@^8.3.6, postcss@^8.4.13:
version "8.4.13"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
postcss@^8.3.6, postcss@^8.4.8:
version "8.4.14"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
dependencies:
nanoid "^3.3.3"
nanoid "^3.3.4"
picocolors "^1.0.0"
source-map-js "^1.0.2"
@ -14565,12 +14583,12 @@ postcss@^8.4.12:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.8:
version "8.4.14"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
postcss@^8.4.13:
version "8.4.13"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
dependencies:
nanoid "^3.3.4"
nanoid "^3.3.3"
picocolors "^1.0.0"
source-map-js "^1.0.2"
@ -15021,9 +15039,9 @@ react-colorful@^5.5.1:
integrity sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg==
react-confetti@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.0.1.tgz#d4f57b5a021dd908a6243b8f63b6009b00818d10"
integrity sha512-ZpOTBrqSNhWE4rRXCZ6E6U+wGd7iYHF5MGrqwikoiBpgBq9Akdu0DcLW+FdFnLjyZYC+VfAiV2KeFgYRMyMrkA==
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.1.0.tgz#03dc4340d955acd10b174dbf301f374a06e29ce6"
integrity sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==
dependencies:
tween-functions "^1.2.0"
@ -15097,7 +15115,12 @@ react-fit@^1.4.0:
prop-types "^15.6.0"
tiny-warning "^1.0.0"
react-hook-form@^7.16.2, react-hook-form@^7.31.1:
react-hook-form@^7.16.2:
version "7.31.3"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.31.3.tgz#b61bafb9a7435f91695351a7a9f714d8c4df0121"
integrity sha512-NVZdCWViIWXXXlQ3jxVQH0NuNfwPf8A/0KvuCxrM9qxtP1qYosfR2ZudarziFrVOC7eTUbWbm1T4OyYCwv9oSQ==
react-hook-form@^7.31.1:
version "7.31.1"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.31.1.tgz#16c357dd366bc226172e6acbb5a1672873bbfb28"
integrity sha512-QjtjZ8r8KtEBWWpcXLyQordCraTFxILtyQpaz5KLLxN2YzcC+FZ9LLtOnNGuOnzZo9gCoB+viK3ZHV9Mb2htmQ==
@ -16607,7 +16630,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
stringify-entities@^4.0.0, stringify-entities@^4.0.2:
stringify-entities@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.2.tgz#13d113dc7449dc8ae4cb22c28883ee3fff8753e3"
integrity sha512-MTxTVcEkorNtBbNpoFJPEh0kKdM6+QbMjLbaxmvaPMmayOXdr/AIVIIJX7FReUVweRBFJfZepK4A4AKgwuFpMQ==
@ -16615,6 +16638,14 @@ stringify-entities@^4.0.0, stringify-entities@^4.0.2:
character-entities-html4 "^2.0.0"
character-entities-legacy "^3.0.0"
stringify-entities@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.3.tgz#cfabd7039d22ad30f3cc435b0ca2c1574fc88ef8"
integrity sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==
dependencies:
character-entities-html4 "^2.0.0"
character-entities-legacy "^3.0.0"
stringify-object@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"