Merge remote-tracking branch 'origin/main' into feature/app-store-cli
This commit is contained in:
commit
c6ca411b54
|
@ -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=
|
||||
|
|
|
@ -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"]
|
||||
|
|
2
apps/api
2
apps/api
|
@ -1 +1 @@
|
|||
Subproject commit ed2f42fb0195b1afa0bf2edbab1df2126038b273
|
||||
Subproject commit 1180f3f2584f8697f7b18db3d809fe88639d90ac
|
|
@ -1 +1 @@
|
|||
Subproject commit b6b26f47922a5404086bf34635338dc6afa9c1d3
|
||||
Subproject commit 0331484a2d276e1c385266fed17e82737df6f103
|
|
@ -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;
|
|
@ -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">
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 | "";
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { collectEvents } from "next-collect/server";
|
||||
|
||||
import { extendEventData, nextCollectBasicSettings } from "@lib/telemetry";
|
||||
|
||||
export default collectEvents({
|
||||
...nextCollectBasicSettings,
|
||||
cookieName: "__clnds",
|
||||
extend: extendEventData,
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { nextEventsCollectApi } from "next-collect/server";
|
||||
|
||||
import { extendEventData, nextCollectBasicSettings } from "@lib/telemetry";
|
||||
|
||||
export default nextEventsCollectApi({
|
||||
...nextCollectBasicSettings,
|
||||
cookieName: "__clnds",
|
||||
extend: extendEventData,
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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"]');
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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
|
|
@ -1,6 +1,7 @@
|
|||
import { GetStaticPropsContext } from "next";
|
||||
|
||||
export const AppSetupPageMap = {
|
||||
"apple-calendar": import("../../applecalendar/pages/setup/_getStaticProps"),
|
||||
zapier: import("../../zapier/pages/setup/_getStaticProps"),
|
||||
};
|
||||
|
||||
|
|
|
@ -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")),
|
||||
};
|
||||
|
||||
|
|
|
@ -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" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export { default as AddIntegration } from "./AddIntegration";
|
||||
export { default as InstallAppButton } from "./InstallAppButton";
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { GetStaticPropsContext } from "next";
|
||||
|
||||
export const getStaticProps = async (ctx: GetStaticPropsContext) => {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
insert into "App" ("slug", "dirName", "updatedAt", "categories")
|
||||
values ('daily-video', 'dailyvideo', CURRENT_TIMESTAMP, '{video}')
|
||||
on conflict do nothing
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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
289
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue
Block a user