Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7aa3c7163 | ||
|
|
e899a92071 | ||
|
|
ebdbe407cd | ||
|
|
e7f917bab9 | ||
|
|
ba9688a04a | ||
|
|
f0e15d77dc | ||
|
|
30b72b687e | ||
|
|
bbf50dbc53 | ||
|
|
bb547a0111 | ||
|
|
1758288948 | ||
|
|
11789c725a | ||
|
|
73bfe0aebe | ||
|
|
24140f0f29 | ||
|
|
e504f5cd9e |
@@ -0,0 +1,27 @@
|
||||
name: "Apply issue labels to PR"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
label_on_pr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Apply labels from linked issue to PR
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_DATA: ${{ toJson(github.event.pull_request) }}
|
||||
run: node ./.github/workflows/scripts/apply-issue-labels-to-pr.ts
|
||||
@@ -26,6 +26,6 @@ jobs:
|
||||
localization_branch_name: main
|
||||
create_pull_request: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
const https = require("https");
|
||||
|
||||
async function applyLabelFromLinkedIssueToPR(pr, token) {
|
||||
|
||||
// Get the labels from issues linked to the PR
|
||||
const query = `
|
||||
query GetLinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $prNumber) {
|
||||
closingIssuesReferences(first: 10) {
|
||||
nodes {
|
||||
number
|
||||
labels(first: 10) {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const graphqlData = JSON.stringify({
|
||||
query,
|
||||
variables: {
|
||||
owner: pr.base.repo.owner.login,
|
||||
repo: pr.base.repo.name,
|
||||
prNumber: pr.number,
|
||||
},
|
||||
});
|
||||
|
||||
const requestOptions = {
|
||||
hostname: "api.github.com",
|
||||
path: "/graphql",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": graphqlData.length,
|
||||
"Authorization": "Bearer " + token,
|
||||
"User-Agent": "Node.js",
|
||||
},
|
||||
};
|
||||
|
||||
// Use the native Node.js request library to make the request
|
||||
let linkedIssues;
|
||||
linkedIssues = await new Promise((resolve, reject) => {
|
||||
const request = https.request(requestOptions, (response) => {
|
||||
let responseBody = "";
|
||||
response.on("data", (chunk) => {
|
||||
responseBody += chunk;
|
||||
});
|
||||
response.on("end", () => {
|
||||
resolve(
|
||||
JSON.parse(responseBody)
|
||||
?.data
|
||||
?.repository
|
||||
?.pullRequest
|
||||
?.closingIssuesReferences
|
||||
?.nodes
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
request.write(graphqlData);
|
||||
request.end();
|
||||
});
|
||||
|
||||
if (!linkedIssues || linkedIssues.length === 0) {
|
||||
console.log("No issue linked.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over linked issues and apply labels to PR
|
||||
for (const issue of linkedIssues) {
|
||||
const labels = issue?.labels?.nodes?.map((label) => label.name);
|
||||
|
||||
if (!labels || labels.length === 0) {
|
||||
console.log(`No labels found on the linked issue #${issue.number}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const labelsData = JSON.stringify({
|
||||
labels: labels,
|
||||
});
|
||||
|
||||
const requestOptions = {
|
||||
hostname: "api.github.com",
|
||||
path: `/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/issues/${pr.number}/labels`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": labelsData.length,
|
||||
"Authorization": "Bearer " + token,
|
||||
"User-Agent": "Node.js",
|
||||
},
|
||||
};
|
||||
|
||||
let labelResult;
|
||||
labelResult = await new Promise((resolve, reject) => {
|
||||
const request = https.request(requestOptions, (response) => {
|
||||
let responseBody = "";
|
||||
response.on("data", (chunk) => {
|
||||
responseBody += chunk;
|
||||
});
|
||||
response.on("data", () => {});
|
||||
response.on("end", () => {
|
||||
resolve(JSON.parse(responseBody));
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
request.write(labelsData);
|
||||
request.end();
|
||||
});
|
||||
|
||||
if (labelResult.message) {
|
||||
console.log(`Error labelling PR: ${labelResult.message}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Applied labels: ${labels.join(", ")} to PR #${
|
||||
pr.number
|
||||
} from linked issue #${issue.number}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass the PR data and GitHub token to the script
|
||||
(async () => {
|
||||
if (!process.env.PR_DATA) {
|
||||
console.log("No PR data found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const prData = JSON.parse(process.env.PR_DATA);
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
|
||||
await applyLabelFromLinkedIssueToPR(prData, token);
|
||||
})();
|
||||
@@ -10,11 +10,12 @@ import type { EventNameObjectType } from "@calcom/core/event";
|
||||
import { getEventName } from "@calcom/core/event";
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
|
||||
import { APP_NAME, CAL_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Badge, Button, Checkbox, Label, SettingsToggle, showToast, TextField, Tooltip } from "@calcom/ui";
|
||||
import { Button, Checkbox, Label, SettingsToggle, showToast, TextField, Tooltip } from "@calcom/ui";
|
||||
import { FiEdit, FiCopy } from "@calcom/ui/components/icon";
|
||||
|
||||
import RequiresConfirmationController from "./RequiresConfirmationController";
|
||||
@@ -183,9 +184,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
setRedirectUrlVisible(e);
|
||||
onChange(e ? value : "");
|
||||
}}>
|
||||
{/* Textfield has some margin by default we remove that so we can keep consitant aligment */}
|
||||
<div className="lg:-ml-2">
|
||||
{/* Textfield has some margin by default we remove that so we can keep consistent alignment */}
|
||||
<div className="lg:-ml-2 lg:-mb-2">
|
||||
<TextField
|
||||
className="w-full"
|
||||
label={t("redirect_success_booking")}
|
||||
labelSrOnly
|
||||
placeholder={t("external_redirect_url")}
|
||||
@@ -194,21 +196,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
defaultValue={eventType.successRedirectUrl || ""}
|
||||
{...formMethods.register("successRedirectUrl")}
|
||||
/>
|
||||
<div className="mt-2 flex">
|
||||
<Checkbox
|
||||
description={t("disable_success_page")}
|
||||
// Disable if it's not Self Hosted or if the redirect url is not set
|
||||
disabled={!IS_SELF_HOSTED || !formMethods.watch("successRedirectUrl")}
|
||||
{...formMethods.register("metadata.disableSuccessPage")}
|
||||
/>
|
||||
{/*TODO: Extract it out into a component when used more than once*/}
|
||||
{!IS_SELF_HOSTED && (
|
||||
<Link href="https://cal.com/pricing" target="_blank">
|
||||
<Badge variant="orange" className="ml-2">
|
||||
Platform Only
|
||||
</Badge>
|
||||
</Link>
|
||||
)}
|
||||
<div
|
||||
className={classNames(
|
||||
"p-1 text-sm text-orange-600",
|
||||
formMethods.getValues("successRedirectUrl") ? "block" : "hidden"
|
||||
)}>
|
||||
{t("redirect_url_warning")}
|
||||
</div>
|
||||
</div>
|
||||
</SettingsToggle>
|
||||
|
||||
@@ -229,14 +229,6 @@ export const EventSetupTab = (
|
||||
|
||||
return (
|
||||
<li
|
||||
onClick={() => {
|
||||
locationFormMethods.setValue("locationType", location.type);
|
||||
locationFormMethods.unregister("locationLink");
|
||||
locationFormMethods.unregister("locationAddress");
|
||||
locationFormMethods.unregister("locationPhoneNumber");
|
||||
setEditingLocationType(location.type);
|
||||
openLocationModal(location.type);
|
||||
}}
|
||||
key={`${location.type}${index}`}
|
||||
className="mb-2 rounded-md border border-gray-300 py-1.5 px-2 hover:cursor-pointer">
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.7.7",
|
||||
"version": "2.7.8",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { COMPANY_NAME, DEVELOPER_DOCS, DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
import { FiBookOpen, FiCheck, FiChevronRight, FiFileText } from "@calcom/ui/components/icon";
|
||||
@@ -99,7 +99,7 @@ export default function Custom404() {
|
||||
<h1 className="font-cal mt-2 text-3xl font-extrabold text-gray-900">
|
||||
{t("signup_requires")}
|
||||
</h1>
|
||||
<p className="mt-4">{t("signup_requires_description", { companyName: COMPANY_NAME })}</p>
|
||||
<p className="mt-4">{t("signup_requires_description", { companyName: "Cal.com" })}</p>
|
||||
</div>
|
||||
<div className="mt-12">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">
|
||||
@@ -136,7 +136,7 @@ export default function Custom404() {
|
||||
<ul role="list" className="divide-y divide-gray-200 border-gray-200">
|
||||
<li className="px-4 py-2">
|
||||
<Link
|
||||
href={`${DEVELOPER_DOCS}/self-hosting/installation`}
|
||||
href="https://cal.com/self-hosting/installation"
|
||||
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
|
||||
@@ -205,7 +205,7 @@ export default function Custom404() {
|
||||
</ul>
|
||||
<div className="mt-8">
|
||||
<Link
|
||||
href={`${WEBSITE_URL}/enterprise`}
|
||||
href="https://cal.com/sales"
|
||||
className="text-base font-medium text-black hover:text-gray-500">
|
||||
{t("contact_sales")}
|
||||
<span aria-hidden="true"> →</span>
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
getGroupName,
|
||||
getUsernameList,
|
||||
} from "@calcom/lib/defaultEvents";
|
||||
import getBooking from "@calcom/lib/getBooking";
|
||||
import type { GetBookingType } from "@calcom/lib/getBooking";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import prisma, { bookEventTypeSelect } from "@calcom/prisma";
|
||||
@@ -22,8 +24,6 @@ import {
|
||||
userMetadata as userMetadataSchema,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { GetBookingType } from "@lib/getBooking";
|
||||
import getBooking from "@lib/getBooking";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import BookingPage from "@components/booking/pages/BookingPage";
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { sendOrganizerRequestReminderEmail } from "@calcom/emails";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
@@ -48,8 +49,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
eventType: {
|
||||
select: {
|
||||
recurringEvent: true,
|
||||
bookingFields: true,
|
||||
},
|
||||
},
|
||||
responses: true,
|
||||
uid: true,
|
||||
destinationCalendar: true,
|
||||
},
|
||||
@@ -96,6 +99,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
title: booking.title,
|
||||
description: booking.description || undefined,
|
||||
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||
...getCalEventResponses({
|
||||
bookingFields: booking.eventType?.bookingFields ?? null,
|
||||
booking,
|
||||
}),
|
||||
location: booking.location ?? "",
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { GetServerSidePropsContext } from "next";
|
||||
import { useSession } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RRule } from "rrule";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
formatToLocalizedTimezone,
|
||||
} from "@calcom/lib/date-fns";
|
||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
||||
import { getBookingWithResponses } from "@calcom/lib/getBooking";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||
@@ -51,7 +52,6 @@ import { Button, EmailInput, HeadSeo, Badge } from "@calcom/ui";
|
||||
import { FiX, FiExternalLink, FiChevronLeft, FiCheck, FiCalendar } from "@calcom/ui/components/icon";
|
||||
|
||||
import { timeZone } from "@lib/clock";
|
||||
import { getBookingWithResponses } from "@lib/getBooking";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import CancelBooking from "@components/booking/CancelBooking";
|
||||
@@ -59,114 +59,6 @@ import EventReservationSchema from "@components/schemas/EventReservationSchema";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
function redirectToExternalUrl(url: string) {
|
||||
window.parent.location.href = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to external URL with query params from current URL.
|
||||
* Query Params and Hash Fragment if present in external URL are kept intact.
|
||||
*/
|
||||
function RedirectionToast({ url }: { url: string }) {
|
||||
const [timeRemaining, setTimeRemaining] = useState(10);
|
||||
const [isToastVisible, setIsToastVisible] = useState(true);
|
||||
|
||||
const { t } = useLocale();
|
||||
const timerRef = useRef<number | null>(null);
|
||||
const router = useRouter();
|
||||
const { cancel: isCancellationMode } = querySchema.parse(router.query);
|
||||
const urlWithSuccessParamsRef = useRef<string | null>();
|
||||
if (isCancellationMode && timerRef.current) {
|
||||
setIsToastVisible(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isToastVisible && timerRef.current) {
|
||||
window.clearInterval(timerRef.current);
|
||||
}
|
||||
}, [isToastVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
const parsedExternalUrl = new URL(url);
|
||||
|
||||
const parsedSuccessUrl = new URL(document.URL);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
/* @ts-ignore */ //https://stackoverflow.com/questions/49218765/typescript-and-iterator-type-iterableiteratort-is-not-an-array-type
|
||||
for (const [name, value] of parsedExternalUrl.searchParams.entries()) {
|
||||
parsedSuccessUrl.searchParams.set(name, value);
|
||||
}
|
||||
|
||||
const urlWithSuccessParams =
|
||||
parsedExternalUrl.origin +
|
||||
parsedExternalUrl.pathname +
|
||||
"?" +
|
||||
parsedSuccessUrl.searchParams.toString() +
|
||||
parsedExternalUrl.hash;
|
||||
urlWithSuccessParamsRef.current = urlWithSuccessParams;
|
||||
|
||||
timerRef.current = window.setInterval(() => {
|
||||
if (timeRemaining > 0) {
|
||||
setTimeRemaining((timeRemaining) => {
|
||||
return timeRemaining - 1;
|
||||
});
|
||||
} else {
|
||||
redirectToExternalUrl(urlWithSuccessParams);
|
||||
window.clearInterval(timerRef.current as number);
|
||||
}
|
||||
}, 1000);
|
||||
return () => {
|
||||
window.clearInterval(timerRef.current as number);
|
||||
};
|
||||
}, [timeRemaining, url]);
|
||||
|
||||
if (!isToastVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative z-[60] pb-2 sm:pb-5">
|
||||
<div className="mx-auto w-full sm:max-w-7xl sm:px-2 lg:px-8">
|
||||
<div className="border border-green-600 bg-green-500 p-2 sm:p-3">
|
||||
<div className="flex flex-wrap items-center justify-between">
|
||||
<div className="flex w-0 flex-1 items-center">
|
||||
<p className="truncate font-medium text-white sm:mx-3">
|
||||
<span className="md:hidden">Redirecting to {url} ...</span>
|
||||
<span className="hidden md:inline">
|
||||
{t("you_are_being_redirected", { url, seconds: timeRemaining })}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="order-3 mt-2 w-full flex-shrink-0 sm:order-2 sm:mt-0 sm:w-auto">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (urlWithSuccessParamsRef.current) {
|
||||
redirectToExternalUrl(urlWithSuccessParamsRef.current);
|
||||
}
|
||||
}}
|
||||
className="flex w-full items-center justify-center rounded-sm border border-transparent bg-white px-4 py-2 text-sm font-medium text-green-600 hover:bg-green-50">
|
||||
{t("continue")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="order-2 flex-shrink-0 sm:order-3 sm:ml-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsToastVisible(false);
|
||||
}}
|
||||
className="-mr-1 flex rounded-md p-2 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-white">
|
||||
<FiX className="h-6 w-6 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type SuccessProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
const stringToBoolean = z
|
||||
@@ -176,13 +68,14 @@ const stringToBoolean = z
|
||||
|
||||
const querySchema = z.object({
|
||||
uid: z.string(),
|
||||
allRemainingBookings: stringToBoolean,
|
||||
email: z.string().optional(),
|
||||
eventTypeSlug: z.string().optional(),
|
||||
cancel: stringToBoolean,
|
||||
allRemainingBookings: stringToBoolean,
|
||||
changes: stringToBoolean,
|
||||
reschedule: stringToBoolean,
|
||||
isSuccessBookingPage: stringToBoolean,
|
||||
formerTime: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
seatReferenceUid: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -404,9 +297,6 @@ export default function Success(props: SuccessProps) {
|
||||
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
|
||||
<main className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "" : "max-w-3xl")}>
|
||||
<div className={classNames("overflow-y-auto", isEmbed ? "" : "z-50 ")}>
|
||||
{isSuccessBookingPage && !isCancellationMode && eventType.successRedirectUrl ? (
|
||||
<RedirectionToast url={eventType.successRedirectUrl} />
|
||||
) : null}{" "}
|
||||
<div
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "text-center" : "",
|
||||
@@ -981,13 +871,6 @@ const getEventTypesFromDB = async (id: number) => {
|
||||
};
|
||||
};
|
||||
|
||||
const schema = z.object({
|
||||
uid: z.string(),
|
||||
email: z.string().optional(),
|
||||
eventTypeSlug: z.string().optional(),
|
||||
cancel: stringToBoolean,
|
||||
});
|
||||
|
||||
const handleSeatsEventTypeOnBooking = (
|
||||
eventType: {
|
||||
seatsPerTimeSlot?: number | null;
|
||||
@@ -1017,9 +900,9 @@ const handleSeatsEventTypeOnBooking = (
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const ssr = await ssrInit(context);
|
||||
const parsedQuery = schema.safeParse(context.query);
|
||||
const parsedQuery = querySchema.safeParse(context.query);
|
||||
if (!parsedQuery.success) return { notFound: true };
|
||||
const { uid, email, eventTypeSlug, cancel } = parsedQuery.data;
|
||||
const { uid, email, eventTypeSlug, cancel, isSuccessBookingPage } = parsedQuery.data;
|
||||
|
||||
const bookingInfoRaw = await prisma.booking.findFirst({
|
||||
where: {
|
||||
@@ -1116,7 +999,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
|
||||
};
|
||||
|
||||
if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl && !cancel) {
|
||||
if (eventType.successRedirectUrl && isSuccessBookingPage && !cancel) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: eventType.successRedirectUrl,
|
||||
|
||||
@@ -5,12 +5,12 @@ import type { LocationObject } from "@calcom/core/location";
|
||||
import { privacyFilteredLocations } from "@calcom/core/location";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import { getWorkingHours } from "@calcom/lib/availability";
|
||||
import type { GetBookingType } from "@calcom/lib/getBooking";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import { availiblityPageEventTypeSelect } from "@calcom/prisma";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { GetBookingType } from "@lib/getBooking";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
PopularEventsTable,
|
||||
} from "@calcom/features/insights/components";
|
||||
import { FiltersProvider } from "@calcom/features/insights/context/FiltersProvider";
|
||||
import { useFilterContext } from "@calcom/features/insights/context/provider";
|
||||
import { Filters } from "@calcom/features/insights/filters";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { UpgradeTip } from "@calcom/features/tips";
|
||||
@@ -20,15 +19,11 @@ import { FiRefreshCcw, FiUserPlus, FiUsers } from "@calcom/ui/components/icon";
|
||||
|
||||
const Heading = () => {
|
||||
const { t } = useLocale();
|
||||
const {
|
||||
filter: { selectedTeamName },
|
||||
} = useFilterContext();
|
||||
|
||||
return (
|
||||
<div className="min-w-52">
|
||||
<h3 className="font-cal max-w-28 sm:max-w-72 md:max-w-80 hidden truncate text-xl font-semibold tracking-wide text-black md:block xl:max-w-full">
|
||||
{t("analytics_for_organisation", {
|
||||
organisationName: selectedTeamName,
|
||||
})}
|
||||
<div className="min-w-52 hidden md:block">
|
||||
<h3 className="font-cal max-w-28 sm:max-w-72 md:max-w-80 truncate text-xl font-semibold tracking-wide text-black xl:max-w-full">
|
||||
{t("analytics_for_organisation")}
|
||||
</h3>
|
||||
<p className="hidden text-sm text-gray-500 md:block">{t("subtitle_analytics")}</p>
|
||||
</div>
|
||||
@@ -80,7 +75,7 @@ export default function InsightsPage() {
|
||||
<></>
|
||||
) : (
|
||||
<FiltersProvider>
|
||||
<div className="mb-4 mt-0 ml-auto flex w-full flex-wrap justify-between md:-mt-8">
|
||||
<div className="mb-8 mt-0 ml-auto flex w-full flex-wrap justify-between md:-mt-8">
|
||||
<Heading />
|
||||
<Filters />
|
||||
</div>
|
||||
@@ -99,7 +94,7 @@ export default function InsightsPage() {
|
||||
<LeastBookedTeamMembersTable />
|
||||
</div>
|
||||
<small className="block text-center text-gray-600">
|
||||
{t("looking_for_more_analytics")}
|
||||
{t("looking_for_more_insights")}
|
||||
<a
|
||||
className="text-blue-500 hover:underline"
|
||||
href="mailto:updates@cal.com?subject=Feature%20Request%3A%20More%20Analytics&body=Hey%20Cal.com%20Team%2C%20I%20love%20the%20analytics%20page%20but%20I%20am%20looking%20for%20...">
|
||||
|
||||
@@ -4,13 +4,13 @@ import type { LocationObject } from "@calcom/core/location";
|
||||
import { privacyFilteredLocations } from "@calcom/core/location";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import { getWorkingHours } from "@calcom/lib/availability";
|
||||
import getBooking from "@calcom/lib/getBooking";
|
||||
import type { GetBookingType } from "@calcom/lib/getBooking";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||
import type { GetBookingType } from "@lib/getBooking";
|
||||
import getBooking from "@lib/getBooking";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ import type { LocationObject } from "@calcom/app-store/locations";
|
||||
import { privacyFilteredLocations } from "@calcom/app-store/locations";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import type { GetBookingType } from "@calcom/lib/getBooking";
|
||||
import getBooking from "@calcom/lib/getBooking";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { customInputSchema, eventTypeBookingFields, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
|
||||
import type { GetBookingType } from "@lib/getBooking";
|
||||
import getBooking from "@lib/getBooking";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import BookingPage from "@components/booking/pages/BookingPage";
|
||||
|
||||
@@ -404,7 +404,7 @@
|
||||
"add_time_availability": "Add new time slot",
|
||||
"add_an_extra_layer_of_security": "Add an extra layer of security to your account in case your password is stolen.",
|
||||
"2fa": "Two-Factor Authentication",
|
||||
"2fa_disabled":"Two-Factor authentication can only be enabled for email and password authentication",
|
||||
"2fa_disabled": "Two-Factor authentication can only be enabled for email and password authentication",
|
||||
"enable_2fa": "Enable two-factor authentication",
|
||||
"disable_2fa": "Disable two-factor authentication",
|
||||
"disable_2fa_recommendation": "If you need to disable 2FA, we recommend re-enabling it as soon as possible.",
|
||||
@@ -1673,7 +1673,7 @@
|
||||
"default_conferencing_bulk_title": "Bulk update existing event types",
|
||||
"requires_at_least_one_schedule": "You are required to have at least one schedule",
|
||||
"default_conferencing_bulk_description": "Update the locations for the selected event types",
|
||||
"looking_for_more_analytics": "Looking for more analytics?",
|
||||
"looking_for_more_insights": "Looking for more Insights?",
|
||||
"add_filter": "Add filter",
|
||||
"select_user": "Select User",
|
||||
"select_event_type": "Select Event Type",
|
||||
@@ -1689,10 +1689,12 @@
|
||||
"events_rescheduled": "Events Rescheduled",
|
||||
"from_last_period": "from last period",
|
||||
"from_to_date_period": "From: {{startDate}} To: {{endDate}}",
|
||||
"analytics_for_organisation": "Insights for {{organisationName}}",
|
||||
"analytics_for_organisation": "Insights",
|
||||
"subtitle_analytics": "Learn more about your team's activity",
|
||||
"redirect_url_warning": "Adding a redirect will disable the success page. Make sure to mention \"Booking Confirmed\" on your custom success page.",
|
||||
"event_trends": "Event Trends",
|
||||
"clear_filters": "Clear Filters",
|
||||
"insights": "Insights",
|
||||
"insights_no_data_found_for_filter": "No data found for the selected filter or selected dates.",
|
||||
"email_invite_team": "{{email}} has been invited"
|
||||
}
|
||||
|
||||
@@ -148,8 +148,6 @@ select:focus {
|
||||
--tw-ring-color: var(--brand-color);
|
||||
border-color: var(--brand-color);
|
||||
}
|
||||
/* https://stackoverflow.com/a/69308190 */
|
||||
input[type='text']:focus { box-shadow: none; }
|
||||
|
||||
|
||||
@layer components {
|
||||
|
||||
@@ -40,6 +40,7 @@ export const EventTypeAddonMap = {
|
||||
fathom: dynamic(() => import("./fathom/components/EventTypeAppCardInterface")),
|
||||
ga4: dynamic(() => import("./ga4/components/EventTypeAppCardInterface")),
|
||||
giphy: dynamic(() => import("./giphy/components/EventTypeAppCardInterface")),
|
||||
"google-tag-manager": dynamic(() => import("./google-tag-manager/components/EventTypeAppCardInterface")),
|
||||
plausible: dynamic(() => import("./plausible/components/EventTypeAppCardInterface")),
|
||||
qr_code: dynamic(() => import("./qr_code/components/EventTypeAppCardInterface")),
|
||||
rainbow: dynamic(() => import("./rainbow/components/EventTypeAppCardInterface")),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { appKeysSchema as dailyvideo_zod_ts } from "./dailyvideo/zod";
|
||||
import { appKeysSchema as fathom_zod_ts } from "./fathom/zod";
|
||||
import { appKeysSchema as ga4_zod_ts } from "./ga4/zod";
|
||||
import { appKeysSchema as giphy_zod_ts } from "./giphy/zod";
|
||||
import { appKeysSchema as google_tag_manager_zod_ts } from "./google-tag-manager/zod";
|
||||
import { appKeysSchema as googlecalendar_zod_ts } from "./googlecalendar/zod";
|
||||
import { appKeysSchema as hubspot_zod_ts } from "./hubspot/zod";
|
||||
import { appKeysSchema as larkcalendar_zod_ts } from "./larkcalendar/zod";
|
||||
@@ -30,6 +31,7 @@ export const appKeysSchemas = {
|
||||
fathom: fathom_zod_ts,
|
||||
ga4: ga4_zod_ts,
|
||||
giphy: giphy_zod_ts,
|
||||
"google-tag-manager": google_tag_manager_zod_ts,
|
||||
googlecalendar: googlecalendar_zod_ts,
|
||||
hubspot: hubspot_zod_ts,
|
||||
larkcalendar: larkcalendar_zod_ts,
|
||||
|
||||
@@ -17,6 +17,7 @@ import facetime_config_json from "./facetime/config.json";
|
||||
import fathom_config_json from "./fathom/config.json";
|
||||
import ga4_config_json from "./ga4/config.json";
|
||||
import { metadata as giphy__metadata_ts } from "./giphy/_metadata";
|
||||
import google_tag_manager_config_json from "./google-tag-manager/config.json";
|
||||
import { metadata as googlecalendar__metadata_ts } from "./googlecalendar/_metadata";
|
||||
import { metadata as googlevideo__metadata_ts } from "./googlevideo/_metadata";
|
||||
import { metadata as hubspot__metadata_ts } from "./hubspot/_metadata";
|
||||
@@ -39,6 +40,7 @@ import sendgrid_config_json from "./sendgrid/config.json";
|
||||
import signal_config_json from "./signal/config.json";
|
||||
import sirius_video_config_json from "./sirius_video/config.json";
|
||||
import { metadata as stripepayment__metadata_ts } from "./stripepayment/_metadata";
|
||||
import sylapsvideo_config_json from "./sylapsvideo/config.json";
|
||||
import { metadata as tandemvideo__metadata_ts } from "./tandemvideo/_metadata";
|
||||
import telegram_config_json from "./telegram/config.json";
|
||||
import basic_config_json from "./templates/basic/config.json";
|
||||
@@ -75,6 +77,7 @@ export const appStoreMetadata = {
|
||||
fathom: fathom_config_json,
|
||||
ga4: ga4_config_json,
|
||||
giphy: giphy__metadata_ts,
|
||||
"google-tag-manager": google_tag_manager_config_json,
|
||||
googlecalendar: googlecalendar__metadata_ts,
|
||||
googlevideo: googlevideo__metadata_ts,
|
||||
hubspot: hubspot__metadata_ts,
|
||||
@@ -97,6 +100,7 @@ export const appStoreMetadata = {
|
||||
signal: signal_config_json,
|
||||
sirius_video: sirius_video_config_json,
|
||||
stripepayment: stripepayment__metadata_ts,
|
||||
sylapsvideo: sylapsvideo_config_json,
|
||||
tandemvideo: tandemvideo__metadata_ts,
|
||||
telegram: telegram_config_json,
|
||||
basic: basic_config_json,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { appDataSchema as dailyvideo_zod_ts } from "./dailyvideo/zod";
|
||||
import { appDataSchema as fathom_zod_ts } from "./fathom/zod";
|
||||
import { appDataSchema as ga4_zod_ts } from "./ga4/zod";
|
||||
import { appDataSchema as giphy_zod_ts } from "./giphy/zod";
|
||||
import { appDataSchema as google_tag_manager_zod_ts } from "./google-tag-manager/zod";
|
||||
import { appDataSchema as googlecalendar_zod_ts } from "./googlecalendar/zod";
|
||||
import { appDataSchema as hubspot_zod_ts } from "./hubspot/zod";
|
||||
import { appDataSchema as larkcalendar_zod_ts } from "./larkcalendar/zod";
|
||||
@@ -30,6 +31,7 @@ export const appDataSchemas = {
|
||||
fathom: fathom_zod_ts,
|
||||
ga4: ga4_zod_ts,
|
||||
giphy: giphy_zod_ts,
|
||||
"google-tag-manager": google_tag_manager_zod_ts,
|
||||
googlecalendar: googlecalendar_zod_ts,
|
||||
hubspot: hubspot_zod_ts,
|
||||
larkcalendar: larkcalendar_zod_ts,
|
||||
|
||||
@@ -17,6 +17,7 @@ export const apiHandlers = {
|
||||
fathom: import("./fathom/api"),
|
||||
ga4: import("./ga4/api"),
|
||||
giphy: import("./giphy/api"),
|
||||
"google-tag-manager": import("./google-tag-manager/api"),
|
||||
googlecalendar: import("./googlecalendar/api"),
|
||||
googlevideo: import("./googlevideo/api"),
|
||||
hubspot: import("./hubspot/api"),
|
||||
@@ -39,6 +40,7 @@ export const apiHandlers = {
|
||||
signal: import("./signal/api"),
|
||||
sirius_video: import("./sirius_video/api"),
|
||||
stripepayment: import("./stripepayment/api"),
|
||||
sylapsvideo: import("./sylapsvideo/api"),
|
||||
tandemvideo: import("./tandemvideo/api"),
|
||||
telegram: import("./telegram/api"),
|
||||
basic: import("./templates/basic/api"),
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createDefaultInstallation } from "@calcom/app-store/_utils/installation";
|
||||
import type { AppDeclarativeHandler } from "@calcom/types/AppHandler";
|
||||
|
||||
import appConfig from "../config.json";
|
||||
|
||||
const handler: AppDeclarativeHandler = {
|
||||
appType: appConfig.type,
|
||||
variant: appConfig.variant,
|
||||
slug: appConfig.slug,
|
||||
supportsMultipleInstalls: false,
|
||||
handlerType: "add",
|
||||
createCredential: ({ appType, user, slug }) =>
|
||||
createDefaultInstallation({ appType, userId: user.id, slug, key: {} }),
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as add } from "./add";
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
|
||||
import AppCard from "@calcom/app-store/_components/AppCard";
|
||||
import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
|
||||
import { TextField } from "@calcom/ui";
|
||||
|
||||
import type { appDataSchema } from "../zod";
|
||||
|
||||
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app }) {
|
||||
const [getAppData, setAppData] = useAppContextWithSchema<typeof appDataSchema>();
|
||||
const trackingId = getAppData("trackingId");
|
||||
const [enabled, setEnabled] = useState(getAppData("enabled"));
|
||||
|
||||
return (
|
||||
<AppCard
|
||||
setAppData={setAppData}
|
||||
app={app}
|
||||
switchOnClick={(e) => {
|
||||
if (!e) {
|
||||
setEnabled(false);
|
||||
} else {
|
||||
setEnabled(true);
|
||||
}
|
||||
}}
|
||||
switchChecked={enabled}>
|
||||
<TextField
|
||||
name="Tracking ID"
|
||||
value={trackingId}
|
||||
onChange={(e) => {
|
||||
setAppData("trackingId", e.target.value);
|
||||
}}
|
||||
/>
|
||||
</AppCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventTypeAppCard;
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Google Tag Manager",
|
||||
"slug": "google-tag-manager",
|
||||
"type": "google-tag-manager_analytics",
|
||||
"imageSrc": "icon.svg",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://tagmanager.google.com",
|
||||
"variant": "analytics",
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"publisher": "Black Lemon",
|
||||
"email": "support@blacklemon.dk",
|
||||
"description": "App to install Google Tag Manager",
|
||||
"extendsFeature": "EventType",
|
||||
"appData": {
|
||||
"tag": {
|
||||
"scripts": [
|
||||
{
|
||||
"content":"(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-{TRACKING_ID}');"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"isTemplate": false,
|
||||
"__createdUsingCli": true,
|
||||
"__template": "booking-pages-tag"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * as api from "./api";
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/google-tag-manager",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "App to install Google Tag Manager"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.87 122.89"><defs><style>.cls-1{fill:#8ab4f8;}.cls-2{fill:#4285f4;}.cls-3{fill:#246fdb;}</style></defs><title>google-tag-manager</title><polygon class="cls-1" points="72.13 118.22 50.8 96.86 96.63 50.56 118.35 72.28 72.13 118.22 72.13 118.22"/><path class="cls-2" d="M72.29,26.23,50.56,4.5,4.5,50.56a15.35,15.35,0,0,0,0,21.71h0l46.06,46.06,21.31-21.4L37.07,61.45,72.29,26.23Z"/><path class="cls-1" d="M118.35,50.56,72.29,4.5A15.36,15.36,0,0,0,50.56,26.22L96.65,72.28a15.36,15.36,0,0,0,21.72-21.72Z"/><path class="cls-3" d="M76.48,107.66a15.23,15.23,0,1,0-15.22,15.23,15.22,15.22,0,0,0,15.22-15.23Z"/></svg>
|
||||
|
After Width: | Height: | Size: 695 B |
@@ -0,0 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
trackingId: z.string(),
|
||||
})
|
||||
);
|
||||
|
||||
export const appKeysSchema = z.object({});
|
||||
@@ -20,6 +20,7 @@ import * as plausible from "./plausible";
|
||||
import * as salesforce from "./salesforce";
|
||||
import * as sendgrid from "./sendgrid";
|
||||
import * as stripepayment from "./stripepayment";
|
||||
import * as sylapsvideo from "./sylapsvideo";
|
||||
import * as tandemvideo from "./tandemvideo";
|
||||
import * as vital from "./vital";
|
||||
import * as wipemycalother from "./wipemycalother";
|
||||
@@ -38,6 +39,7 @@ const appStore = {
|
||||
hubspot,
|
||||
huddle01video,
|
||||
jitsivideo,
|
||||
sylapsvideo,
|
||||
larkcalendar,
|
||||
office365calendar,
|
||||
office365video,
|
||||
|
||||
+13
-1
@@ -1,3 +1,4 @@
|
||||
import type { ChangeEvent } from "react";
|
||||
import type { Settings, Widgets, SelectWidgetProps } from "react-awesome-query-builder";
|
||||
// Figure out why routing-forms/env.d.ts doesn't work
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -94,7 +95,18 @@ const widgets: Widgets & { [key in keyof Widgets]: Widgets[key] & { type: string
|
||||
if (!props) {
|
||||
return <div />;
|
||||
}
|
||||
return <EmailField type="email" {...props} />;
|
||||
|
||||
return (
|
||||
<EmailField
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const val = e.target.value;
|
||||
props.setValue(val);
|
||||
}}
|
||||
containerClassName="w-full"
|
||||
className="dark:placeholder:text-darkgray-600 focus:border-brand dark:border-darkgray-300 dark:text-darkgray-900 block w-full rounded-md border-gray-300 text-sm focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:bg-transparent dark:selection:bg-green-500 disabled:dark:text-gray-500"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
items:
|
||||
- /api/app-store/sylaps/sylaps1.png
|
||||
---
|
||||
|
||||
Free Audio and Video Conferencing, Online Collaboration, Screen Sharing on web browser, mobile and desktop.
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
||||
|
||||
/**
|
||||
* This is an example endpoint for an app, these will run under `/api/integrations/[...args]`
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!req.session?.user?.id) {
|
||||
return res.status(401).json({ message: "You must be logged in to do this" });
|
||||
}
|
||||
const appType = "sylaps_video";
|
||||
try {
|
||||
const alreadyInstalled = await prisma.credential.findFirst({
|
||||
where: {
|
||||
type: appType,
|
||||
userId: req.session.user.id,
|
||||
},
|
||||
});
|
||||
if (alreadyInstalled) {
|
||||
throw new Error("Already installed");
|
||||
}
|
||||
const installation = await prisma.credential.create({
|
||||
data: {
|
||||
type: appType,
|
||||
key: {},
|
||||
userId: req.session.user.id,
|
||||
appId: "sylaps",
|
||||
},
|
||||
});
|
||||
if (!installation) {
|
||||
throw new Error("Unable to create user credential for sylaps");
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
return res.status(500);
|
||||
}
|
||||
return res.status(200).json({ url: getInstalledAppPath({ variant: "conferencing", slug: "sylaps" }) });
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as add } from "./add";
|
||||
@@ -0,0 +1 @@
|
||||
.gitkeep
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Sylaps",
|
||||
"title": "Sylaps",
|
||||
"slug": "sylaps",
|
||||
"type": "sylaps_video",
|
||||
"imageSrc": "/api/app-store/sylaps/icon.svg",
|
||||
"logo": "/api/app-store/sylaps/icon.svg",
|
||||
"url": "https://cal.com/apps/sylaps",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
"publisher": "Sylaps Inc",
|
||||
"email": "support@sylaps.com",
|
||||
"description": "Free Audio and Video Conferencing, Online Collaboration, Screen Sharing on web browser, mobile and desktop.",
|
||||
"__createdUsingCli": true,
|
||||
"appData": {
|
||||
"location": {
|
||||
"linkType": "dynamic",
|
||||
"type": "integrations:sylaps_video",
|
||||
"label": "Sylaps"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * as lib from "./lib";
|
||||
export * as api from "./api";
|
||||
@@ -0,0 +1,34 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
const SylapsApiAdapter = (): VideoApiAdapter => {
|
||||
return {
|
||||
getAvailability: () => {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
createMeeting: async (): Promise<VideoCallData> => {
|
||||
const meetingID = uuidv4();
|
||||
return Promise.resolve({
|
||||
type: "sylaps_video",
|
||||
id: meetingID,
|
||||
password: "",
|
||||
url: "https://sylaps.com/r/" + meetingID,
|
||||
});
|
||||
},
|
||||
deleteMeeting: async (): Promise<void> => {
|
||||
Promise.resolve();
|
||||
},
|
||||
updateMeeting: (bookingRef: PartialReference): Promise<VideoCallData> => {
|
||||
return Promise.resolve({
|
||||
type: "sylaps_video",
|
||||
id: bookingRef.meetingId as string,
|
||||
password: bookingRef.meetingPassword as string,
|
||||
url: bookingRef.meetingUrl as string,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default SylapsApiAdapter;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as VideoApiAdapter } from "./VideoApiAdapter";
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/sylapsvideo",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"description": "Free Audio and Video Conferencing, Online Collaboration, Screen Sharing on web browser, mobile and desktop.",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 318 318" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><use xlink:href="#a" x="255" width="318" height="318" transform="matrix(1.08202 0 0 1.08518 -289.504 -13.007)"/><path d="M126.988 77.707c6.621-7.672 13.161-15.177 19.543-22.814.431-.516.036-2.425-.599-3.021-6.041-5.662-11.83-11.695-18.427-16.64C109.3 21.586 89.027 17.757 67.314 25.074 36.697 35.392 18.186 66.19 22.801 98.267c4.15 28.837 24.33 51.186 52.908 56.744 28.895 5.62 54.399-2.534 74.886-23.504 11.478-11.749 21.051-25.341 31.726-37.896 7.402-8.705 14.589-17.75 23.007-25.393 10.772-9.779 23.805-13.01 37.68-6.757 14.16 6.382 20.803 21.031 17.142 36.146-3.611 14.908-16.076 24.446-31.371 23.875-7.912-.296-14.583-3.867-20.539-8.703-5.03-4.084-9.701-8.611-14.855-13.233l-19.731 24.625c9.626 10.85 19.982 20.108 32.778 26.305 12.635 6.119 25.924 7.785 39.613 4.901 34.945-7.361 57.368-39.123 52.927-74.668-3.626-29.021-24.928-52.216-53.51-57.637-25.757-4.886-48.429 2.398-68.468 18.706-11.754 9.566-20.742 21.653-29.963 33.515-7.215 9.283-14.273 18.705-21.855 27.681-6.101 7.223-13.527 12.849-22.954 15.37-19.787 5.29-38.579-5.736-41.277-25.005-3.016-21.536 13.96-40.408 36.739-35.904 7.873 1.557 14.12 6.308 20.005 11.469 3.141 2.753 6.078 5.739 9.299 8.803Z" style="fill:#fff;fill-rule:nonzero" transform="matrix(.81681 0 0 .8192 28.171 86.549)"/><defs><image id="a" width="318" height="318" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAT4AAAE+CAYAAAAUOHwwAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAELUlEQVR4nO3UQRGAQBDAsIXnScU0UsAFN0MTBX31mJlZ1/0MQMS5OwDga8YH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8QI7xATnGB+QYH5BjfECO8QE5xgfkGB+QY3xAjvEBOcYH5BgfkGN8AAB/9wLC9QTMTO4oPgAAAABJRU5ErkJggg=="/></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
@@ -1,6 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { getHumanReadableLocationValue } from "@calcom/core/location";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import findValidApiKey from "@calcom/features/ee/api-keys/lib/findValidApiKey";
|
||||
import { defaultHandler, defaultResponder, getTranslation } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
@@ -31,6 +32,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
title: true,
|
||||
description: true,
|
||||
customInputs: true,
|
||||
responses: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
location: true,
|
||||
@@ -53,6 +55,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
price: true,
|
||||
currency: true,
|
||||
length: true,
|
||||
bookingFields: true,
|
||||
},
|
||||
},
|
||||
attendees: {
|
||||
@@ -68,7 +71,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const t = await getTranslation(bookings[0].user?.locale ?? "en", "common");
|
||||
|
||||
const updatedBookings = bookings.map((booking) => {
|
||||
return { ...booking, location: getHumanReadableLocationValue(booking.location || "", t) };
|
||||
return {
|
||||
...booking,
|
||||
...getCalEventResponses({
|
||||
bookingFields: booking.eventType?.bookingFields ?? null,
|
||||
booking,
|
||||
}),
|
||||
location: getHumanReadableLocationValue(booking.location || "", t),
|
||||
};
|
||||
});
|
||||
|
||||
res.status(201).json(updatedBookings);
|
||||
|
||||
@@ -30,6 +30,7 @@ const config = {
|
||||
"no",
|
||||
"uk",
|
||||
"da",
|
||||
"tr",
|
||||
],
|
||||
},
|
||||
reloadOnPrerender: process.env.NODE_ENV !== "production",
|
||||
|
||||
@@ -2,23 +2,45 @@ import type z from "zod";
|
||||
|
||||
import { SystemField } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import type { bookingResponsesDbSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
|
||||
import type { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||
import { getBookingWithResponses } from "@calcom/lib/getBooking";
|
||||
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import type { EventType, Prisma } from ".prisma/client";
|
||||
|
||||
export const getCalEventResponses = ({
|
||||
bookingFields,
|
||||
booking,
|
||||
responses,
|
||||
}: {
|
||||
// If the eventType has been deleted and a booking is Accepted later on, then bookingFields will be null and we can't know the label of fields. So, we should store the label as well in the DB
|
||||
// Also, it is no longer straightforward to identify if a field is system field or not
|
||||
bookingFields: z.infer<typeof eventTypeBookingFields> | null;
|
||||
responses: z.infer<typeof bookingResponsesDbSchema>;
|
||||
bookingFields: z.infer<typeof eventTypeBookingFields> | EventType["bookingFields"] | null;
|
||||
booking?: Prisma.BookingGetPayload<{
|
||||
select: {
|
||||
description: true;
|
||||
customInputs: true;
|
||||
attendees: {
|
||||
select: {
|
||||
email: true;
|
||||
name: true;
|
||||
};
|
||||
};
|
||||
location: true;
|
||||
responses: true;
|
||||
};
|
||||
}>;
|
||||
responses?: z.infer<typeof bookingResponsesDbSchema>;
|
||||
}) => {
|
||||
const calEventUserFieldsResponses = {} as NonNullable<CalendarEvent["userFieldsResponses"]>;
|
||||
const calEventResponses = {} as NonNullable<CalendarEvent["responses"]>;
|
||||
const parsedBookingFields = bookingFields ? eventTypeBookingFields.parse(bookingFields) : null;
|
||||
const backwardCompatibleResponses =
|
||||
responses ?? (booking ? getBookingWithResponses(booking).responses : null);
|
||||
if (!backwardCompatibleResponses) throw new Error("Couldn't get responses");
|
||||
|
||||
if (bookingFields) {
|
||||
bookingFields.forEach((field) => {
|
||||
if (parsedBookingFields) {
|
||||
parsedBookingFields.forEach((field) => {
|
||||
const label = field.label || field.defaultLabel;
|
||||
if (!label) {
|
||||
throw new Error('Missing label for booking field "' + field.name + '"');
|
||||
@@ -26,17 +48,17 @@ export const getCalEventResponses = ({
|
||||
if (field.editable === "user" || field.editable === "user-readonly") {
|
||||
calEventUserFieldsResponses[field.name] = {
|
||||
label,
|
||||
value: responses[field.name],
|
||||
value: backwardCompatibleResponses[field.name],
|
||||
};
|
||||
}
|
||||
calEventResponses[field.name] = {
|
||||
label,
|
||||
value: responses[field.name],
|
||||
value: backwardCompatibleResponses[field.name],
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// Alternative way to generate for a booking of whose eventType has been deleted
|
||||
for (const [name, value] of Object.entries(responses)) {
|
||||
for (const [name, value] of Object.entries(backwardCompatibleResponses)) {
|
||||
const isSystemField = SystemField.safeParse(name);
|
||||
|
||||
// Use name for Label because we don't have access to the label. This will not be needed once we start storing the label along with the response
|
||||
@@ -55,5 +77,5 @@ export const getCalEventResponses = ({
|
||||
};
|
||||
}
|
||||
}
|
||||
return { calEventUserFieldsResponses, calEventResponses };
|
||||
return { userFieldsResponses: calEventUserFieldsResponses, responses: calEventResponses };
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import { cancelScheduledJobs } from "@calcom/app-store/zapier/lib/nodeScheduler"
|
||||
import { deleteMeeting, updateMeeting } from "@calcom/core/videoClient";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { sendCancelledEmails, sendCancelledSeatEmails } from "@calcom/emails";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { deleteScheduledEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager";
|
||||
import { sendCancelledReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import { deleteScheduledSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
|
||||
@@ -67,6 +68,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine
|
||||
currency: true,
|
||||
length: true,
|
||||
seatsPerTimeSlot: true,
|
||||
bookingFields: true,
|
||||
seatsShowAttendees: true,
|
||||
hosts: {
|
||||
select: {
|
||||
@@ -91,6 +93,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine
|
||||
workflowReminders: true,
|
||||
scheduledJobs: true,
|
||||
seatsReferences: true,
|
||||
responses: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -173,6 +176,10 @@ async function handler(req: CustomRequest) {
|
||||
type: (bookingToDelete?.eventType?.title as string) || bookingToDelete?.title,
|
||||
description: bookingToDelete?.description || "",
|
||||
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
|
||||
...getCalEventResponses({
|
||||
bookingFields: bookingToDelete.eventType?.bookingFields ?? null,
|
||||
booking: bookingToDelete,
|
||||
}),
|
||||
startTime: bookingToDelete?.startTime ? dayjs(bookingToDelete.startTime).format() : "",
|
||||
endTime: bookingToDelete?.endTime ? dayjs(bookingToDelete.endTime).format() : "",
|
||||
organizer: {
|
||||
@@ -509,6 +516,10 @@ async function handler(req: CustomRequest) {
|
||||
title: bookingToDelete.title,
|
||||
description: bookingToDelete.description ?? "",
|
||||
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
|
||||
...getCalEventResponses({
|
||||
booking: bookingToDelete,
|
||||
bookingFields: bookingToDelete.eventType?.bookingFields ?? null,
|
||||
}),
|
||||
startTime: bookingToDelete.startTime.toISOString(),
|
||||
endTime: bookingToDelete.endTime.toISOString(),
|
||||
organizer: {
|
||||
|
||||
@@ -404,10 +404,11 @@ function getBookingData({
|
||||
const reqBody = bookingDataSchema.parse(req.body);
|
||||
if ("responses" in reqBody) {
|
||||
const responses = reqBody.responses;
|
||||
const { calEventUserFieldsResponses, calEventResponses } = getCalEventResponses({
|
||||
bookingFields: eventType.bookingFields,
|
||||
responses,
|
||||
});
|
||||
const { userFieldsResponses: calEventUserFieldsResponses, responses: calEventResponses } =
|
||||
getCalEventResponses({
|
||||
bookingFields: eventType.bookingFields,
|
||||
responses,
|
||||
});
|
||||
return {
|
||||
...reqBody,
|
||||
name: responses.name,
|
||||
@@ -742,6 +743,7 @@ async function handler(
|
||||
const uid = translator.fromUUID(uuidv5(seed, uuidv5.URL));
|
||||
|
||||
const bookingLocation = getLocationValueForDB(locationBodyString, eventType.locations);
|
||||
|
||||
const customInputs = getCustomInputsResponses(reqBody, eventType.customInputs);
|
||||
const teamMemberPromises =
|
||||
users.length > 1
|
||||
|
||||
@@ -7,6 +7,7 @@ import type Stripe from "stripe";
|
||||
import stripe from "@calcom/app-store/stripepayment/lib/server";
|
||||
import EventManager from "@calcom/core/EventManager";
|
||||
import { sendScheduledEmails } from "@calcom/emails";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation";
|
||||
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
@@ -65,6 +66,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||
paid: true,
|
||||
destinationCalendar: true,
|
||||
status: true,
|
||||
responses: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -114,6 +116,10 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||
...getCalEventResponses({
|
||||
booking: booking,
|
||||
bookingFields: booking.eventType?.bookingFields || null,
|
||||
}),
|
||||
organizer: {
|
||||
email: user.email,
|
||||
name: user.name!,
|
||||
|
||||
@@ -6,34 +6,45 @@ import { trpc } from "@calcom/trpc";
|
||||
import { useFilterContext } from "../context/provider";
|
||||
import { valueFormatter } from "../lib/valueFormatter";
|
||||
import { CardInsights } from "./Card";
|
||||
import { LoadingInsight } from "./LoadingInsights";
|
||||
|
||||
export const AverageEventDurationChart = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter } = useFilterContext();
|
||||
const { dateRange, selectedUserId } = filter;
|
||||
const { dateRange, selectedMemberUserId } = filter;
|
||||
const [startDate, endDate] = dateRange;
|
||||
const { selectedTeamId: teamId } = filter;
|
||||
const { selectedTeamId: teamId, selectedUserId } = filter;
|
||||
|
||||
const { data, isSuccess } = trpc.viewer.insights.averageEventDuration.useQuery({
|
||||
const { data, isSuccess, isLoading } = trpc.viewer.insights.averageEventDuration.useQuery({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
teamId,
|
||||
teamId: teamId ?? undefined,
|
||||
memberUserId: selectedMemberUserId ?? undefined,
|
||||
userId: selectedUserId ?? undefined,
|
||||
});
|
||||
|
||||
if (!isSuccess || data?.length == 0 || !startDate || !endDate || !teamId) return null;
|
||||
if (isLoading) return <LoadingInsight />;
|
||||
|
||||
if (!isSuccess || !startDate || !endDate || (!teamId && !selectedUserId)) return null;
|
||||
const isNoData = (data && data.length === 0) || data.every((item) => item["Average"] === 0);
|
||||
return (
|
||||
<CardInsights>
|
||||
<Title>{t("average_event_duration")}</Title>
|
||||
<LineChart
|
||||
className="mt-4 h-80"
|
||||
data={data}
|
||||
index="Date"
|
||||
categories={["Average"]}
|
||||
colors={["blue"]}
|
||||
valueFormatter={valueFormatter}
|
||||
/>
|
||||
{isNoData && (
|
||||
<div className="flex h-60 text-center">
|
||||
<p className="m-auto text-sm font-light">{t("insights_no_data_found_for_filter")}</p>
|
||||
</div>
|
||||
)}
|
||||
{data && data.length > 0 && !isNoData && (
|
||||
<LineChart
|
||||
className="mt-4 h-80"
|
||||
data={data}
|
||||
index="Date"
|
||||
categories={["Average"]}
|
||||
colors={["blue"]}
|
||||
valueFormatter={valueFormatter}
|
||||
/>
|
||||
)}
|
||||
</CardInsights>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,23 +2,26 @@ import { Grid } from "@tremor/react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
|
||||
|
||||
import { useFilterContext } from "../context/provider";
|
||||
import { CardInsights } from "./Card";
|
||||
import { KPICard } from "./KPICard";
|
||||
|
||||
export const BookingKPICards = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter } = useFilterContext();
|
||||
const { dateRange, selectedEventTypeId, selectedUserId } = filter;
|
||||
const { dateRange, selectedEventTypeId, selectedUserId, selectedMemberUserId } = filter;
|
||||
const [startDate, endDate] = dateRange;
|
||||
|
||||
const { selectedTeamId: teamId } = filter;
|
||||
|
||||
const { data, isSuccess } = trpc.viewer.insights.eventsByStatus.useQuery({
|
||||
const { data, isSuccess, isLoading } = trpc.viewer.insights.eventsByStatus.useQuery({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
teamId,
|
||||
eventTypeId: selectedEventTypeId ?? undefined,
|
||||
memberUserId: selectedMemberUserId ?? undefined,
|
||||
userId: selectedUserId ?? undefined,
|
||||
});
|
||||
|
||||
@@ -44,18 +47,41 @@ export const BookingKPICards = () => {
|
||||
},
|
||||
];
|
||||
|
||||
if (!isSuccess || !startDate || !endDate || !teamId || data?.empty) return null;
|
||||
if (isLoading) {
|
||||
return <LoadingKPICards categories={categories} />;
|
||||
}
|
||||
|
||||
if (!isSuccess || !startDate || !endDate || (!teamId && !selectedUserId)) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid numColsSm={2} numColsLg={4} className="gap-x-6 gap-y-6">
|
||||
{categories.map((item) => (
|
||||
<KPICard
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
value={data[item.index].count}
|
||||
previousMetricData={data[item.index]}
|
||||
previousDateRange={data.previousRange}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadingKPICards = (props: { categories: { title: string; index: string }[] }) => {
|
||||
const { categories } = props;
|
||||
return (
|
||||
<Grid numColsSm={2} numColsLg={4} className="gap-x-6 gap-y-6">
|
||||
{categories.map((item) => (
|
||||
<KPICard
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
value={data[item.index].count}
|
||||
previousMetricData={data[item.index]}
|
||||
previousDateRange={data.previousRange}
|
||||
/>
|
||||
<CardInsights key={item.title}>
|
||||
<SkeletonContainer className="flex w-full flex-col">
|
||||
<SkeletonText className="mt-2 h-4 w-32" />
|
||||
<SkeletonText className="mt-2 h-6 w-16" />
|
||||
<SkeletonText className="mt-4 h-6 w-44" />
|
||||
</SkeletonContainer>
|
||||
</CardInsights>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -6,23 +6,37 @@ import { trpc } from "@calcom/trpc";
|
||||
import { useFilterContext } from "../context/provider";
|
||||
import { valueFormatter } from "../lib/valueFormatter";
|
||||
import { CardInsights } from "./Card";
|
||||
import { LoadingInsight } from "./LoadingInsights";
|
||||
|
||||
export const BookingStatusLineChart = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter } = useFilterContext();
|
||||
const { selectedTeamId, selectedTimeView = "week", dateRange, selectedEventTypeId } = filter;
|
||||
const {
|
||||
selectedTeamId,
|
||||
selectedUserId,
|
||||
selectedTimeView = "week",
|
||||
dateRange,
|
||||
selectedEventTypeId,
|
||||
} = filter;
|
||||
const [startDate, endDate] = dateRange;
|
||||
|
||||
if (!startDate || !endDate) return null;
|
||||
|
||||
const { data: eventsTimeLine, isSuccess } = trpc.viewer.insights.eventsTimeline.useQuery({
|
||||
const {
|
||||
data: eventsTimeLine,
|
||||
isSuccess,
|
||||
isLoading,
|
||||
} = trpc.viewer.insights.eventsTimeline.useQuery({
|
||||
timeView: selectedTimeView,
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
teamId: selectedTeamId || -1,
|
||||
teamId: selectedTeamId ?? undefined,
|
||||
eventTypeId: selectedEventTypeId ?? undefined,
|
||||
userId: selectedUserId ?? undefined,
|
||||
});
|
||||
|
||||
if (isLoading) return <LoadingInsight />;
|
||||
|
||||
if (!isSuccess) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Card } from "@tremor/react";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
|
||||
interface ICardProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
@@ -9,7 +11,7 @@ export const CardInsights = (props: ICardProps) => {
|
||||
const { children, className = "", ...rest } = props;
|
||||
|
||||
return (
|
||||
<Card className={`shadow-none ring-gray-300 ${className}`} {...rest}>
|
||||
<Card className={classNames("shadow-none ring-gray-200", className)} {...rest}>
|
||||
{children}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { trpc } from "@calcom/trpc";
|
||||
|
||||
import { useFilterContext } from "../context/provider";
|
||||
import { CardInsights } from "./Card";
|
||||
import { LoadingInsight } from "./LoadingInsights";
|
||||
import { TotalBookingUsersTable } from "./TotalBookingUsersTable";
|
||||
|
||||
export const LeastBookedTeamMembersTable = () => {
|
||||
@@ -13,13 +14,15 @@ export const LeastBookedTeamMembersTable = () => {
|
||||
const { dateRange, selectedEventTypeId, selectedTeamId: teamId } = filter;
|
||||
const [startDate, endDate] = dateRange;
|
||||
|
||||
const { data, isSuccess } = trpc.viewer.insights.membersWithLeastBookings.useQuery({
|
||||
const { data, isSuccess, isLoading } = trpc.viewer.insights.membersWithLeastBookings.useQuery({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
teamId,
|
||||
eventTypeId: selectedEventTypeId ?? undefined,
|
||||
});
|
||||
|
||||
if (isLoading) return <LoadingInsight />;
|
||||
|
||||
if (!isSuccess || !startDate || !endDate || !teamId) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { SkeletonText } from "@calcom/ui";
|
||||
|
||||
import { CardInsights } from "./Card";
|
||||
|
||||
export const LoadingInsight = () => {
|
||||
return (
|
||||
<CardInsights>
|
||||
<SkeletonText className="w-32" />
|
||||
<div className="m-auto flex h-80 flex-col items-center justify-center">
|
||||
<svg
|
||||
className={classNames("mx-4 h-8 w-8 animate-spin", "text-black dark:text-white")}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</CardInsights>
|
||||
);
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import { trpc } from "@calcom/trpc";
|
||||
|
||||
import { useFilterContext } from "../context/provider";
|
||||
import { CardInsights } from "./Card";
|
||||
import { LoadingInsight } from "./LoadingInsights";
|
||||
import { TotalBookingUsersTable } from "./TotalBookingUsersTable";
|
||||
|
||||
export const MostBookedTeamMembersTable = () => {
|
||||
@@ -14,13 +15,15 @@ export const MostBookedTeamMembersTable = () => {
|
||||
const [startDate, endDate] = dateRange;
|
||||
const { selectedTeamId: teamId } = filter;
|
||||
|
||||
const { data, isSuccess } = trpc.viewer.insights.membersWithMostBookings.useQuery({
|
||||
const { data, isSuccess, isLoading } = trpc.viewer.insights.membersWithMostBookings.useQuery({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
teamId,
|
||||
eventTypeId: selectedEventTypeId ?? undefined,
|
||||
});
|
||||
|
||||
if (isLoading) return <LoadingInsight />;
|
||||
|
||||
if (!isSuccess || !startDate || !endDate || !teamId) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,22 +5,26 @@ import { trpc } from "@calcom/trpc";
|
||||
|
||||
import { useFilterContext } from "../context/provider";
|
||||
import { CardInsights } from "./Card";
|
||||
import { LoadingInsight } from "./LoadingInsights";
|
||||
|
||||
export const PopularEventsTable = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter } = useFilterContext();
|
||||
const { dateRange, selectedUserId } = filter;
|
||||
const { dateRange, selectedMemberUserId, selectedUserId } = filter;
|
||||
const [startDate, endDate] = dateRange;
|
||||
const { selectedTeamId: teamId } = filter;
|
||||
|
||||
const { data, isSuccess } = trpc.viewer.insights.popularEventTypes.useQuery({
|
||||
const { data, isSuccess, isLoading } = trpc.viewer.insights.popularEventTypes.useQuery({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
teamId,
|
||||
teamId: teamId ?? undefined,
|
||||
userId: selectedUserId ?? undefined,
|
||||
memberUserId: selectedMemberUserId ?? undefined,
|
||||
});
|
||||
|
||||
if (!isSuccess || !startDate || !endDate || !teamId || data?.length === 0) return null;
|
||||
if (isLoading) return <LoadingInsight />;
|
||||
|
||||
if (!isSuccess || !startDate || !endDate || (!teamId && !selectedUserId)) return null;
|
||||
|
||||
return (
|
||||
<CardInsights>
|
||||
@@ -30,7 +34,7 @@ export const PopularEventsTable = () => {
|
||||
{data.map((item) => (
|
||||
<TableRow key={item.eventTypeId}>
|
||||
<TableCell>{item.eventTypeName}</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Text>
|
||||
<strong>{item.count}</strong>
|
||||
</Text>
|
||||
@@ -39,6 +43,11 @@ export const PopularEventsTable = () => {
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{data.length === 0 && (
|
||||
<div className="flex h-60 text-center">
|
||||
<p className="m-auto text-sm font-light">{t("insights_no_data_found_for_filter")}</p>
|
||||
</div>
|
||||
)}
|
||||
</CardInsights>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ export const TotalBookingUsersTable = ({
|
||||
<strong>{item.user.name}</strong>
|
||||
</p>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Text>
|
||||
<strong>{item.count}</strong>
|
||||
</Text>
|
||||
|
||||
@@ -10,17 +10,24 @@ import { FilterProvider } from "./provider";
|
||||
export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
// useRouter to get initial values from query params
|
||||
const router = useRouter();
|
||||
const { startTime, endTime, teamId, userId, eventTypeId, filter } = router.query;
|
||||
const { startTime, endTime, teamId, userId, eventTypeId, filter, memberUserId } = router.query;
|
||||
const querySchema = z.object({
|
||||
startTime: z.string().optional(),
|
||||
endTime: z.string().optional(),
|
||||
teamId: z.coerce.number().optional(),
|
||||
userId: z.coerce.number().optional(),
|
||||
memberUserId: z.coerce.number().optional(),
|
||||
eventTypeId: z.coerce.number().optional(),
|
||||
filter: z.enum(["event-type", "user"]).optional(),
|
||||
});
|
||||
|
||||
let startTimeParsed, endTimeParsed, teamIdParsed, userIdParsed, eventTypeIdParsed, filterParsed;
|
||||
let startTimeParsed,
|
||||
endTimeParsed,
|
||||
teamIdParsed: number | undefined,
|
||||
userIdParsed,
|
||||
eventTypeIdParsed,
|
||||
filterParsed,
|
||||
memberUserIdParsed;
|
||||
|
||||
const safe = querySchema.safeParse({
|
||||
startTime,
|
||||
@@ -29,6 +36,7 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
userId,
|
||||
eventTypeId,
|
||||
filter,
|
||||
memberUserId,
|
||||
});
|
||||
|
||||
if (!safe.success) {
|
||||
@@ -40,6 +48,7 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
userIdParsed = safe.data.userId;
|
||||
eventTypeIdParsed = safe.data.eventTypeId;
|
||||
filterParsed = safe.data.filter;
|
||||
memberUserIdParsed = safe.data.memberUserId;
|
||||
}
|
||||
|
||||
// TODO: Sync insight filters with URL parameters
|
||||
@@ -48,6 +57,9 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
const [selectedUserId, setSelectedUserId] = useState<FilterContextType["filter"]["selectedUserId"]>(
|
||||
userIdParsed || null
|
||||
);
|
||||
const [selectedMemberUserId, setSelectedMemberUserId] = useState<
|
||||
FilterContextType["filter"]["selectedMemberUserId"]
|
||||
>(memberUserIdParsed || null);
|
||||
const [selectedTeamId, setSelectedTeamId] = useState<FilterContextType["filter"]["selectedTeamId"]>(
|
||||
teamIdParsed || null
|
||||
);
|
||||
@@ -64,21 +76,24 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
endTimeParsed ? dayjs(endTimeParsed) : dayjs(),
|
||||
"t",
|
||||
]);
|
||||
|
||||
return (
|
||||
<FilterProvider
|
||||
value={{
|
||||
filter: {
|
||||
dateRange,
|
||||
selectedTimeView,
|
||||
selectedUserId,
|
||||
selectedMemberUserId,
|
||||
selectedTeamId,
|
||||
selectedUserId,
|
||||
selectedTeamName,
|
||||
selectedEventTypeId,
|
||||
selectedFilter,
|
||||
},
|
||||
setSelectedFilter: (filter) => {
|
||||
setSelectedFilter(filter);
|
||||
const userId = filter?.[0] === "user" ? selectedUserId : undefined;
|
||||
const userId =
|
||||
filter?.[0] === "user" ? selectedMemberUserId : selectedUserId ? selectedUserId : undefined;
|
||||
const eventTypeId = filter?.[0] === "event-type" ? selectedEventTypeId : undefined;
|
||||
router.push({
|
||||
query: {
|
||||
@@ -100,24 +115,38 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
});
|
||||
},
|
||||
setSelectedTimeView: (selectedTimeView) => setSelectedTimeView(selectedTimeView),
|
||||
setSelectedUserId: (selectedUserId) => {
|
||||
setSelectedUserId(selectedUserId);
|
||||
setSelectedMemberUserId: (selectedMemberUserId) => {
|
||||
setSelectedMemberUserId(selectedMemberUserId);
|
||||
router.push({
|
||||
query: {
|
||||
...router.query,
|
||||
userId: selectedUserId,
|
||||
memberUserId: selectedMemberUserId,
|
||||
},
|
||||
});
|
||||
},
|
||||
setSelectedTeamId: (selectedTeamId) => {
|
||||
setSelectedTeamId(selectedTeamId);
|
||||
router.push({
|
||||
setSelectedUserId(null);
|
||||
const { teamId, eventTypeId, memberUserId, ...rest } = router.query;
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
...rest,
|
||||
teamId: selectedTeamId,
|
||||
},
|
||||
});
|
||||
},
|
||||
setSelectedUserId: (selectedUserId) => {
|
||||
setSelectedUserId(selectedUserId);
|
||||
setSelectedTeamId(null);
|
||||
setSelectedTeamName(null);
|
||||
const { teamId, eventTypeId, memberUserId, ...rest } = router.query;
|
||||
router.replace({
|
||||
query: {
|
||||
...rest,
|
||||
userId: selectedUserId,
|
||||
},
|
||||
});
|
||||
},
|
||||
setSelectedTeamName: (selectedTeamName) => setSelectedTeamName(selectedTeamName),
|
||||
setSelectedEventTypeId: (selectedEventTypeId) => {
|
||||
setSelectedEventTypeId(selectedEventTypeId);
|
||||
@@ -128,6 +157,30 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
},
|
||||
});
|
||||
},
|
||||
clearFilters: () => {
|
||||
setSelectedTeamName(null);
|
||||
setSelectedEventTypeId(null);
|
||||
setSelectedMemberUserId(null);
|
||||
setSelectedFilter(null);
|
||||
const { teamId, userId, ...rest } = router.query;
|
||||
const query: { teamId?: number; userId?: number } = {};
|
||||
const parsedTeamId = Number(Array.isArray(teamId) ? teamId[0] : teamId);
|
||||
const parsedUserId = Number(Array.isArray(userId) ? userId[0] : userId);
|
||||
|
||||
if ((teamId && !userId) || (userId && teamId)) {
|
||||
query.teamId = parsedTeamId;
|
||||
setSelectedTeamId(parsedTeamId);
|
||||
setSelectedUserId(null);
|
||||
} else if (userId && !teamId) {
|
||||
query.userId = parsedUserId;
|
||||
setSelectedUserId(parsedUserId);
|
||||
setSelectedTeamId(null);
|
||||
}
|
||||
|
||||
router.push({
|
||||
query,
|
||||
});
|
||||
},
|
||||
}}>
|
||||
{children}
|
||||
</FilterProvider>
|
||||
|
||||
@@ -10,6 +10,7 @@ export type FilterContextType = {
|
||||
selectedTeamId: number | null;
|
||||
selectedTeamName: string | null;
|
||||
selectedUserId: number | null;
|
||||
selectedMemberUserId: number | null;
|
||||
selectedEventTypeId: number | null;
|
||||
};
|
||||
setDateRange: ([start, end, range]: [Dayjs, Dayjs, null | string]) => void;
|
||||
@@ -17,8 +18,10 @@ export type FilterContextType = {
|
||||
setSelectedTeamId: (teamId: number | null) => void;
|
||||
setSelectedTeamName: (teamName: string | null) => void;
|
||||
setSelectedUserId: (userId: number | null) => void;
|
||||
setSelectedMemberUserId: (userId: number | null) => void;
|
||||
setSelectedEventTypeId: (eventTypeId: number | null) => void;
|
||||
setSelectedTimeView: (timeView: "year" | "week" | "month") => void;
|
||||
clearFilters: () => void;
|
||||
};
|
||||
|
||||
export const FilterContext = React.createContext<FilterContextType | null>(null);
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
.custom-date > .tremor-DateRangePicker-root > .tremor-DateRangePicker-button {
|
||||
box-shadow: none;
|
||||
max-width: 288px;
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarButton, .tremor-DateRangePicker-dropdownButton {
|
||||
@apply dark:bg-darkgray-100 dark:border-darkgray-300 border-gray-300 bg-white text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:ring-0 focus-within:ring-gray-800 hover:border-gray-400 dark:focus-within:ring-darkgray-900
|
||||
}
|
||||
|
||||
|
||||
.tremor-Card-root{
|
||||
@apply p-5
|
||||
}
|
||||
|
||||
.tremor-TableCell-root{
|
||||
@apply pl-0
|
||||
}
|
||||
|
||||
.recharts-responsive-container{
|
||||
@apply -mx-4
|
||||
}
|
||||
.tremor-Card-root > p {
|
||||
@apply text-base font-semibold text-black mb-2
|
||||
}
|
||||
|
||||
.tremor-Legend-legendItem{
|
||||
@apply ml-2
|
||||
}
|
||||
+6
-4
@@ -15,17 +15,18 @@ const mapEventTypeToOption = (eventType: EventType): Option => ({
|
||||
label: eventType.title,
|
||||
});
|
||||
|
||||
export const EventTypeListInTeam = () => {
|
||||
export const EventTypeList = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter, setSelectedEventTypeId } = useFilterContext();
|
||||
const { selectedTeamId, selectedEventTypeId } = filter;
|
||||
const { selectedTeamId, selectedEventTypeId, selectedUserId } = filter;
|
||||
const { selectedFilter } = filter;
|
||||
const { data, isSuccess } = trpc.viewer.insights.eventTypeList.useQuery({
|
||||
teamId: selectedTeamId,
|
||||
teamId: selectedTeamId ?? undefined,
|
||||
userId: selectedUserId ?? undefined,
|
||||
});
|
||||
|
||||
if (!selectedFilter?.includes("event-type")) return null;
|
||||
if (!selectedTeamId) return null;
|
||||
if (!selectedTeamId && !selectedUserId) return null;
|
||||
|
||||
const filterOptions =
|
||||
data?.map(mapEventTypeToOption) ?? ([{ label: "No event types found", value: "" }] as Option[]);
|
||||
@@ -46,6 +47,7 @@ export const EventTypeListInTeam = () => {
|
||||
}
|
||||
}}
|
||||
defaultValue={eventTypeValue}
|
||||
value={eventTypeValue}
|
||||
className="w-52 min-w-[180px]"
|
||||
placeholder={
|
||||
<div className="flex flex-row">
|
||||
@@ -8,10 +8,10 @@ type Option = { value: "event-type" | "user"; label: string };
|
||||
|
||||
export const FilterType = () => {
|
||||
const { t } = useLocale();
|
||||
const { setSelectedFilter, setSelectedUserId, setSelectedEventTypeId, filter } = useFilterContext();
|
||||
const { selectedFilter } = filter;
|
||||
const { setSelectedFilter, filter } = useFilterContext();
|
||||
const { selectedFilter, selectedUserId } = filter;
|
||||
|
||||
const filterOptions: Option[] = [
|
||||
let filterOptions: Option[] = [
|
||||
{
|
||||
label: t("event_type"),
|
||||
value: "event-type",
|
||||
@@ -22,9 +22,14 @@ export const FilterType = () => {
|
||||
},
|
||||
];
|
||||
|
||||
if (selectedUserId) {
|
||||
// remove user option from filterOptions
|
||||
filterOptions = filterOptions.filter((option) => option.value !== "user");
|
||||
}
|
||||
|
||||
const filterValue = selectedFilter
|
||||
? filterOptions.find((option) => option.value === selectedFilter[0])
|
||||
: undefined;
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Select<Option>
|
||||
|
||||
+24
-10
@@ -7,30 +7,42 @@ import { Select } from "@calcom/ui";
|
||||
import { useFilterContext } from "../context/provider";
|
||||
|
||||
type Team = RouterOutputs["viewer"]["insights"]["teamListForUser"][number];
|
||||
type Option = { value: number; label: string };
|
||||
type Option = { value: number; label: string | null; userId?: number };
|
||||
|
||||
const mapTeamToOption = (team: Team): Option => ({
|
||||
value: team.id,
|
||||
label: team.name ?? "",
|
||||
userId: team.userId,
|
||||
});
|
||||
|
||||
export const TeamList = () => {
|
||||
const { filter, setSelectedTeamId, setSelectedTeamName } = useFilterContext();
|
||||
const { selectedTeamId } = filter;
|
||||
export const TeamAndSelfList = () => {
|
||||
const { filter, setSelectedTeamId, setSelectedTeamName, setSelectedUserId } = useFilterContext();
|
||||
const { selectedTeamId, selectedUserId } = filter;
|
||||
const { data, isSuccess } = trpc.viewer.insights.teamListForUser.useQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data?.length > 0) {
|
||||
setSelectedTeamId(data[0].id);
|
||||
setSelectedTeamName(data[0].name);
|
||||
// We have a team?
|
||||
if (data[0].id) {
|
||||
setSelectedTeamId(data[0].id);
|
||||
setSelectedTeamName(data[0].name);
|
||||
} else if (data[0].userId) {
|
||||
// default to user
|
||||
setSelectedUserId(data[0].userId);
|
||||
}
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const UserListOptions = data?.map(mapTeamToOption) || ([{ label: "Empty", value: -1 }] as Option[]);
|
||||
const selectedTeam = data?.find((item) => item.id === selectedTeamId);
|
||||
const selectedTeam = data?.find((item) => {
|
||||
if (!!selectedUserId && !selectedTeamId) {
|
||||
return item.userId === selectedUserId;
|
||||
}
|
||||
return item.id === selectedTeamId;
|
||||
});
|
||||
const teamValue = selectedTeam ? mapTeamToOption(selectedTeam) : null;
|
||||
|
||||
if (!isSuccess || !selectedTeamId || data?.length === 0) return null;
|
||||
if (!isSuccess || data?.length === 0) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -39,10 +51,12 @@ export const TeamList = () => {
|
||||
isMulti={false}
|
||||
value={teamValue}
|
||||
defaultValue={selectedTeamId ? { value: data[0].id, label: data[0].name } : null}
|
||||
className="h-[38px] w-[90vw] capitalize md:min-w-[100px] md:max-w-[100px]"
|
||||
className="h-[38px] w-[90vw] min-w-[160px] max-w-[100px]"
|
||||
options={UserListOptions}
|
||||
onChange={(input) => {
|
||||
if (input) {
|
||||
if (!!input?.userId) {
|
||||
setSelectedUserId(input.userId);
|
||||
} else if (input && input.value) {
|
||||
setSelectedTeamId(input.value);
|
||||
setSelectedTeamName(input.label);
|
||||
}
|
||||
@@ -15,8 +15,8 @@ const mapUserToOption = (user: User): Option => ({
|
||||
|
||||
export const UserListInTeam = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter, setSelectedUserId } = useFilterContext();
|
||||
const { selectedFilter, selectedTeamId, selectedUserId } = filter;
|
||||
const { filter, setSelectedMemberUserId } = useFilterContext();
|
||||
const { selectedFilter, selectedTeamId, selectedMemberUserId } = filter;
|
||||
const { data, isSuccess } = trpc.viewer.insights.userList.useQuery({
|
||||
teamId: selectedTeamId,
|
||||
});
|
||||
@@ -25,20 +25,21 @@ export const UserListInTeam = () => {
|
||||
if (!selectedTeamId) return null;
|
||||
|
||||
const userListOptions = data?.map(mapUserToOption) ?? ([] as { value: number; label: string }[]);
|
||||
const selectedUser = data?.find((item) => item.id === selectedUserId);
|
||||
const userValue = selectedUser ? mapUserToOption(selectedUser) : null;
|
||||
const selectedTeamUser = data?.find((item) => item.id === selectedMemberUserId);
|
||||
const userValue = selectedTeamUser ? mapUserToOption(selectedTeamUser) : null;
|
||||
|
||||
if (!isSuccess || data?.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Select<Option>
|
||||
isSearchable={false}
|
||||
className="mb-0 ml-2 h-[38px] w-40 min-w-[140px] capitalize md:min-w-[150px] md:max-w-[200px]"
|
||||
isSearchable={true}
|
||||
className="mb-0 h-[38px] w-40 min-w-[140px] capitalize md:min-w-[150px] md:max-w-[200px]"
|
||||
defaultValue={userValue}
|
||||
value={userValue}
|
||||
options={userListOptions}
|
||||
onChange={(input) => {
|
||||
if (input) {
|
||||
setSelectedUserId(input.value);
|
||||
setSelectedMemberUserId(input.value);
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { useFilterContext } from "@calcom/features/insights/context/provider";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, Tooltip } from "@calcom/ui";
|
||||
import { FiX } from "@calcom/ui/components/icon";
|
||||
|
||||
import { DateSelect } from "./DateSelect";
|
||||
import { EventTypeListInTeam } from "./EventTypeListInTeam";
|
||||
import { EventTypeList } from "./EventTypeList";
|
||||
import { FilterType } from "./FilterType";
|
||||
import { TeamList } from "./TeamList";
|
||||
import { TeamAndSelfList } from "./TeamAndSelfList";
|
||||
import { UserListInTeam } from "./UsersListInTeam";
|
||||
|
||||
const ClearFilters = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter, setSelectedUserId, setSelectedFilter, setSelectedEventTypeId } = useFilterContext();
|
||||
const { filter, clearFilters } = useFilterContext();
|
||||
const { selectedFilter } = filter;
|
||||
|
||||
if (!selectedFilter || selectedFilter?.length < 1) return null;
|
||||
@@ -23,14 +22,12 @@ const ClearFilters = () => {
|
||||
color="secondary"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
StartIcon={FiX}
|
||||
className="h-[38px]"
|
||||
onClick={() => {
|
||||
setSelectedFilter(null);
|
||||
setSelectedUserId(null);
|
||||
setSelectedEventTypeId(null);
|
||||
}}
|
||||
/>
|
||||
clearFilters();
|
||||
}}>
|
||||
{t("clear_filters")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -38,13 +35,13 @@ const ClearFilters = () => {
|
||||
export const Filters = () => {
|
||||
return (
|
||||
<div className="mt-2 flex flex-col flex-wrap gap-2 md:flex-row lg:flex-nowrap">
|
||||
<TeamList />
|
||||
<TeamAndSelfList />
|
||||
|
||||
<FilterType />
|
||||
|
||||
<UserListInTeam />
|
||||
|
||||
<EventTypeListInTeam />
|
||||
<EventTypeList />
|
||||
|
||||
<DateSelect />
|
||||
|
||||
|
||||
@@ -132,12 +132,15 @@ class EventsInsights {
|
||||
return resultTimeView;
|
||||
};
|
||||
|
||||
static getWeekTimeline(startDate: Dayjs, endDate: Dayjs) {
|
||||
static getWeekTimeline(startDate: Dayjs, endDate: Dayjs): string[] {
|
||||
const now = dayjs();
|
||||
const endOfDay = now.endOf("day");
|
||||
let pivotDate = dayjs(startDate);
|
||||
const dates = [];
|
||||
while (pivotDate.isBefore(endDate)) {
|
||||
pivotDate = pivotDate.add(7, "day");
|
||||
const dates: string[] = [];
|
||||
while (pivotDate.isBefore(endDate) && pivotDate.isBefore(endOfDay)) {
|
||||
const weekEndDate = pivotDate.add(7, "day").isBefore(endOfDay) ? pivotDate.add(7, "day") : endOfDay;
|
||||
dates.push(pivotDate.format("YYYY-MM-DD"));
|
||||
pivotDate = weekEndDate.add(1, "day");
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { TRPCError } from "@trpc/server";
|
||||
import { EventsInsights } from "./events";
|
||||
|
||||
const UserBelongsToTeamInput = z.object({
|
||||
teamId: z.coerce.number().optional(),
|
||||
teamId: z.coerce.number().optional().nullable(),
|
||||
});
|
||||
|
||||
const userBelongsToTeamMiddleware = isAuthed.unstable_pipe(async ({ ctx, next, rawInput }) => {
|
||||
@@ -19,14 +19,22 @@ const userBelongsToTeamMiddleware = isAuthed.unstable_pipe(async ({ ctx, next, r
|
||||
throw new TRPCError({ code: "BAD_REQUEST" });
|
||||
}
|
||||
|
||||
const team = await ctx.prisma.membership.findFirst({
|
||||
where: {
|
||||
userId: ctx.user.id,
|
||||
teamId: parse.data.teamId,
|
||||
},
|
||||
// If teamId is provided, check if user belongs to team
|
||||
// If teamId is not provided, check if user belongs to any team
|
||||
|
||||
const membershipWhereConditional: Prisma.MembershipWhereInput = {
|
||||
userId: ctx.user.id,
|
||||
};
|
||||
|
||||
if (parse.data.teamId) {
|
||||
membershipWhereConditional["teamId"] = parse.data.teamId;
|
||||
}
|
||||
|
||||
const membership = await ctx.prisma.membership.findFirst({
|
||||
where: membershipWhereConditional,
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
if (!membership) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
@@ -74,28 +82,59 @@ export const insightsRouter = router({
|
||||
startDate: z.string(),
|
||||
endDate: z.string(),
|
||||
eventTypeId: z.coerce.number().optional(),
|
||||
memberUserId: z.coerce.number().optional(),
|
||||
userId: z.coerce.number().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { teamId, startDate, endDate, eventTypeId, userId } = input;
|
||||
const { teamId, startDate, endDate, eventTypeId, memberUserId, userId } = input;
|
||||
|
||||
if (!input.teamId) {
|
||||
return emptyResponseEventsByStatus;
|
||||
if (userId && userId !== ctx.user.id) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
const whereConditional: Prisma.BookingWhereInput = {
|
||||
eventType: {
|
||||
teamId: teamId,
|
||||
},
|
||||
};
|
||||
let whereConditional: Prisma.BookingWhereInput = {};
|
||||
|
||||
if (eventTypeId) {
|
||||
whereConditional["eventTypeId"] = eventTypeId;
|
||||
} else if (userId) {
|
||||
}
|
||||
if (memberUserId) {
|
||||
whereConditional["userId"] = memberUserId;
|
||||
}
|
||||
if (userId) {
|
||||
whereConditional["eventType"] = {
|
||||
teamId: null,
|
||||
};
|
||||
whereConditional["userId"] = userId;
|
||||
}
|
||||
|
||||
if (teamId) {
|
||||
const usersFromTeam = await ctx.prisma.membership.findMany({
|
||||
where: {
|
||||
teamId: teamId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
const userIdsFromTeam = usersFromTeam.map((u) => u.userId);
|
||||
whereConditional = {
|
||||
...whereConditional,
|
||||
OR: [
|
||||
{
|
||||
eventType: {
|
||||
teamId: teamId,
|
||||
},
|
||||
},
|
||||
{
|
||||
userId: {
|
||||
in: userIdsFromTeam,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Migrate to use prisma views
|
||||
const baseBookings = await EventsInsights.getBaseBookingForEventStatus({
|
||||
...whereConditional,
|
||||
@@ -103,10 +142,8 @@ export const insightsRouter = router({
|
||||
gte: new Date(startDate),
|
||||
lte: new Date(endDate),
|
||||
},
|
||||
eventType: {
|
||||
teamId: teamId,
|
||||
},
|
||||
});
|
||||
|
||||
const startTimeEndTimeDiff = dayjs(endDate).diff(dayjs(startDate), "day");
|
||||
|
||||
const baseBookingIds = baseBookings.map((b) => b.id);
|
||||
@@ -136,8 +173,7 @@ export const insightsRouter = router({
|
||||
);
|
||||
|
||||
const lastPeriodTotalCancelled = await EventsInsights.getTotalCancelledEvents(lastPeriodBaseBookingIds);
|
||||
|
||||
return {
|
||||
const result = {
|
||||
empty: false,
|
||||
created: {
|
||||
count: baseBookings.length,
|
||||
@@ -163,16 +199,26 @@ export const insightsRouter = router({
|
||||
endDate: lastPeriodEndDate.format("YYYY-MM-DD"),
|
||||
},
|
||||
};
|
||||
if (
|
||||
result.created.count === 0 &&
|
||||
result.completed.count === 0 &&
|
||||
result.rescheduled.count === 0 &&
|
||||
result.cancelled.count === 0
|
||||
) {
|
||||
return emptyResponseEventsByStatus;
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
eventsTimeline: userBelongsToTeamProcedure
|
||||
.input(
|
||||
z.object({
|
||||
teamId: z.coerce.number().optional(),
|
||||
teamId: z.coerce.number().optional().nullable(),
|
||||
startDate: z.string(),
|
||||
endDate: z.string(),
|
||||
eventTypeId: z.coerce.number().optional(),
|
||||
userId: z.coerce.number().optional(),
|
||||
memberUserId: z.coerce.number().optional(),
|
||||
timeView: z.enum(["week", "month", "year"]),
|
||||
userId: z.coerce.number().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
@@ -181,36 +227,75 @@ export const insightsRouter = router({
|
||||
startDate: startDateString,
|
||||
endDate: endDateString,
|
||||
eventTypeId,
|
||||
userId,
|
||||
memberUserId,
|
||||
timeView: inputTimeView,
|
||||
userId: selfUserId,
|
||||
} = input;
|
||||
|
||||
const startDate = dayjs(startDateString);
|
||||
const endDate = dayjs(endDateString);
|
||||
const user = ctx.user;
|
||||
|
||||
if (selfUserId && user?.id !== selfUserId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
if (!teamId && !selfUserId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const timeView = inputTimeView;
|
||||
|
||||
let whereConditional: Prisma.BookingWhereInput = {
|
||||
eventType: {
|
||||
teamId: teamId,
|
||||
},
|
||||
};
|
||||
let whereConditional: Prisma.BookingWhereInput = {};
|
||||
|
||||
if (teamId) {
|
||||
const usersFromTeam = await ctx.prisma.membership.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
const userIdsFromTeams = usersFromTeam.map((u) => u.userId);
|
||||
|
||||
if (userId) {
|
||||
delete whereConditional.eventType;
|
||||
whereConditional = {
|
||||
...whereConditional,
|
||||
userId,
|
||||
OR: [
|
||||
{
|
||||
eventType: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
{
|
||||
userId: {
|
||||
in: userIdsFromTeams,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (eventTypeId && !!whereConditional) {
|
||||
delete whereConditional.eventType;
|
||||
delete whereConditional.userId;
|
||||
|
||||
if (memberUserId) {
|
||||
whereConditional = {
|
||||
...whereConditional,
|
||||
userId: memberUserId,
|
||||
};
|
||||
}
|
||||
|
||||
if (eventTypeId && !!whereConditional) {
|
||||
whereConditional = {
|
||||
eventTypeId: eventTypeId,
|
||||
};
|
||||
}
|
||||
|
||||
if (selfUserId && !!whereConditional) {
|
||||
// In this delete we are deleting the teamId filter
|
||||
whereConditional["userId"] = selfUserId;
|
||||
whereConditional["eventType"] = {
|
||||
teamId: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Get timeline data
|
||||
const timeline = await EventsInsights.getTimeLine(timeView, dayjs(startDate), dayjs(endDate));
|
||||
|
||||
@@ -277,37 +362,73 @@ export const insightsRouter = router({
|
||||
popularEventTypes: userBelongsToTeamProcedure
|
||||
.input(
|
||||
z.object({
|
||||
userId: z.coerce.number().optional(),
|
||||
memberUserId: z.coerce.number().optional(),
|
||||
teamId: z.coerce.number().optional().nullable(),
|
||||
startDate: z.string(),
|
||||
endDate: z.string(),
|
||||
userId: z.coerce.number().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { teamId, startDate, endDate, userId } = input;
|
||||
const { teamId, startDate, endDate, memberUserId, userId } = input;
|
||||
|
||||
const user = ctx.user;
|
||||
|
||||
if (!input.teamId) {
|
||||
if (userId && user?.id !== userId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
if (!teamId && !userId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const eventTypeWhere: Prisma.EventTypeWhereInput = {
|
||||
teamId: teamId,
|
||||
};
|
||||
|
||||
const bookingWhere: Prisma.BookingWhereInput = {
|
||||
eventType: eventTypeWhere,
|
||||
let bookingWhere: Prisma.BookingWhereInput = {
|
||||
createdAt: {
|
||||
gte: dayjs(startDate).startOf("day").toDate(),
|
||||
lte: dayjs(endDate).endOf("day").toDate(),
|
||||
},
|
||||
};
|
||||
if (teamId) {
|
||||
const usersFromTeam = await ctx.prisma.membership.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
const userIdsFromTeams = usersFromTeam.map((u) => u.userId);
|
||||
|
||||
bookingWhere = {
|
||||
...bookingWhere,
|
||||
OR: [
|
||||
{
|
||||
eventType: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
{
|
||||
userId: {
|
||||
in: userIdsFromTeams,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
bookingWhere.userId = userId;
|
||||
// Don't take bookings from any team
|
||||
bookingWhere.eventType = {
|
||||
teamId: null,
|
||||
};
|
||||
}
|
||||
|
||||
const bookingsFromTeam = await ctx.prisma.booking.groupBy({
|
||||
if (memberUserId) {
|
||||
bookingWhere.userId = memberUserId;
|
||||
}
|
||||
|
||||
const bookingsFromSelected = await ctx.prisma.booking.groupBy({
|
||||
by: ["eventTypeId"],
|
||||
where: bookingWhere,
|
||||
_count: {
|
||||
@@ -320,66 +441,158 @@ export const insightsRouter = router({
|
||||
},
|
||||
take: 10,
|
||||
});
|
||||
const eventTypeIds = bookingsFromTeam
|
||||
|
||||
const eventTypeIds = bookingsFromSelected
|
||||
.filter((booking) => typeof booking.eventTypeId === "number")
|
||||
.map((booking) => booking.eventTypeId);
|
||||
const eventTypesFromTeam = await ctx.prisma.eventType.findMany({
|
||||
where: {
|
||||
teamId: teamId,
|
||||
id: {
|
||||
in: eventTypeIds as number[],
|
||||
|
||||
const eventTypeWhereConditional: Prisma.EventTypeWhereInput = {
|
||||
id: {
|
||||
in: eventTypeIds as number[],
|
||||
},
|
||||
};
|
||||
|
||||
const eventTypesFrom = await ctx.prisma.eventType.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
teamId: true,
|
||||
userId: true,
|
||||
slug: true,
|
||||
users: {
|
||||
select: {
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: eventTypeWhereConditional,
|
||||
});
|
||||
|
||||
const eventTypeHashMap = new Map();
|
||||
eventTypesFromTeam.forEach((eventType) => {
|
||||
eventTypeHashMap.set(eventType.id, eventType.title);
|
||||
const eventTypeHashMap: Map<
|
||||
number,
|
||||
Prisma.EventTypeGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
title: true;
|
||||
teamId: true;
|
||||
userId: true;
|
||||
slug: true;
|
||||
users: {
|
||||
select: {
|
||||
username: true;
|
||||
};
|
||||
};
|
||||
team: {
|
||||
select: {
|
||||
slug: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>
|
||||
> = new Map();
|
||||
eventTypesFrom.forEach((eventType) => {
|
||||
eventTypeHashMap.set(eventType.id, eventType);
|
||||
});
|
||||
|
||||
const result = bookingsFromTeam.map((booking) => {
|
||||
const result = bookingsFromSelected.map((booking) => {
|
||||
const eventTypeSelected = eventTypeHashMap.get(booking.eventTypeId ?? 0);
|
||||
if (!eventTypeSelected) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let eventSlug = "";
|
||||
if (eventTypeSelected.userId) {
|
||||
eventSlug = `${eventTypeSelected?.slug}/${eventTypeSelected?.users[0]?.username}`;
|
||||
}
|
||||
if (eventTypeSelected?.team && eventTypeSelected?.team?.slug) {
|
||||
eventSlug = `${eventTypeSelected.team.slug}/${eventTypeSelected.slug}`;
|
||||
}
|
||||
return {
|
||||
eventTypeId: booking.eventTypeId,
|
||||
eventTypeName: eventTypeHashMap.get(booking.eventTypeId),
|
||||
eventTypeName: eventSlug,
|
||||
count: booking._count.id,
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
}),
|
||||
averageEventDuration: userBelongsToTeamProcedure
|
||||
.input(
|
||||
z.object({
|
||||
userId: z.coerce.number().optional(),
|
||||
memberUserId: z.coerce.number().optional(),
|
||||
teamId: z.coerce.number().optional().nullable(),
|
||||
startDate: z.string(),
|
||||
endDate: z.string(),
|
||||
userId: z.coerce.number().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { teamId, startDate: startDateString, endDate: endDateString, userId } = input;
|
||||
const { teamId, startDate: startDateString, endDate: endDateString, memberUserId, userId } = input;
|
||||
|
||||
if (!teamId) {
|
||||
if (userId && ctx.user?.id !== userId) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
if (!teamId && !userId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const user = ctx.user;
|
||||
const startDate = dayjs(startDateString);
|
||||
const endDate = dayjs(endDateString);
|
||||
|
||||
const whereConditional: Prisma.BookingWhereInput = {
|
||||
eventType: {
|
||||
teamId: teamId,
|
||||
},
|
||||
let whereConditional: Prisma.BookingWhereInput = {
|
||||
createdAt: {
|
||||
gte: dayjs(startDate).startOf("day").toDate(),
|
||||
lte: dayjs(endDate).endOf("day").toDate(),
|
||||
},
|
||||
};
|
||||
|
||||
if (userId) {
|
||||
delete whereConditional.eventType;
|
||||
whereConditional["userId"] = userId;
|
||||
}
|
||||
|
||||
if (teamId) {
|
||||
const usersFromTeam = await ctx.prisma.membership.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
const userIdsFromTeams = usersFromTeam.map((u) => u.userId);
|
||||
|
||||
whereConditional = {
|
||||
...whereConditional,
|
||||
OR: [
|
||||
{
|
||||
eventType: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
{
|
||||
userId: {
|
||||
in: userIdsFromTeams,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (memberUserId) {
|
||||
whereConditional = {
|
||||
userId: memberUserId,
|
||||
eventType: {
|
||||
teamId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const timeView = EventsInsights.getTimeView("week", startDate, endDate);
|
||||
const timeLine = await EventsInsights.getTimeLine("week", startDate, endDate);
|
||||
|
||||
@@ -448,15 +661,42 @@ export const insightsRouter = router({
|
||||
eventTypeWhere["id"] = eventTypeId;
|
||||
}
|
||||
|
||||
const bookingWhere: Prisma.BookingWhereInput = {
|
||||
eventType: eventTypeWhere,
|
||||
createdAt: {
|
||||
gte: dayjs(startDate).startOf("day").toDate(),
|
||||
lte: dayjs(endDate).endOf("day").toDate(),
|
||||
},
|
||||
};
|
||||
|
||||
if (teamId) {
|
||||
const usersFromTeam = await ctx.prisma.membership.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
const userIdsFromTeams = usersFromTeam.map((u) => u.userId);
|
||||
delete bookingWhere.eventType;
|
||||
bookingWhere["OR"] = [
|
||||
{
|
||||
eventType: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
{
|
||||
userId: {
|
||||
in: userIdsFromTeams,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const bookingsFromTeam = await ctx.prisma.booking.groupBy({
|
||||
by: ["userId"],
|
||||
where: {
|
||||
eventType: eventTypeWhere,
|
||||
createdAt: {
|
||||
gte: dayjs(startDate).startOf("day").toDate(),
|
||||
lte: dayjs(endDate).endOf("day").toDate(),
|
||||
},
|
||||
},
|
||||
where: bookingWhere,
|
||||
_count: {
|
||||
id: true,
|
||||
},
|
||||
@@ -467,9 +707,13 @@ export const insightsRouter = router({
|
||||
},
|
||||
take: 10,
|
||||
});
|
||||
|
||||
const userIds = bookingsFromTeam
|
||||
.filter((booking) => typeof booking.userId === "number")
|
||||
.map((booking) => booking.userId);
|
||||
if (userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const usersFromTeam = await ctx.prisma.user.findMany({
|
||||
where: {
|
||||
id: {
|
||||
@@ -487,10 +731,11 @@ export const insightsRouter = router({
|
||||
return {
|
||||
userId: booking.userId,
|
||||
user: userHashMap.get(booking.userId),
|
||||
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),
|
||||
emailMd5: crypto.createHash("md5").update(user?.email).digest("hex"),
|
||||
count: booking._count.id,
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
}),
|
||||
membersWithLeastBookings: userBelongsToTeamProcedure
|
||||
@@ -507,7 +752,7 @@ export const insightsRouter = router({
|
||||
if (!teamId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const user = ctx.user;
|
||||
const eventTypeWhere: Prisma.EventTypeWhereInput = {
|
||||
teamId: teamId,
|
||||
};
|
||||
@@ -515,15 +760,42 @@ export const insightsRouter = router({
|
||||
eventTypeWhere["id"] = eventTypeId;
|
||||
}
|
||||
|
||||
const bookingWhere: Prisma.BookingWhereInput = {
|
||||
eventType: eventTypeWhere,
|
||||
createdAt: {
|
||||
gte: dayjs(startDate).startOf("day").toDate(),
|
||||
lte: dayjs(endDate).endOf("day").toDate(),
|
||||
},
|
||||
};
|
||||
|
||||
if (teamId) {
|
||||
const usersFromTeam = await ctx.prisma.membership.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
const userIdsFromTeams = usersFromTeam.map((u) => u.userId);
|
||||
delete bookingWhere.eventType;
|
||||
bookingWhere["OR"] = [
|
||||
{
|
||||
eventType: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
{
|
||||
userId: {
|
||||
in: userIdsFromTeams,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const bookingsFromTeam = await ctx.prisma.booking.groupBy({
|
||||
by: ["userId"],
|
||||
where: {
|
||||
eventType: eventTypeWhere,
|
||||
createdAt: {
|
||||
gte: dayjs(startDate).startOf("day").toDate(),
|
||||
lte: dayjs(endDate).endOf("day").toDate(),
|
||||
},
|
||||
},
|
||||
where: bookingWhere,
|
||||
_count: {
|
||||
id: true,
|
||||
},
|
||||
@@ -535,53 +807,31 @@ export const insightsRouter = router({
|
||||
take: 10,
|
||||
});
|
||||
|
||||
// Users are obtained from bookings so if a user has 0 they won't be in the list
|
||||
const userIds = bookingsFromTeam
|
||||
.filter((booking) => typeof booking.userId === "number")
|
||||
.map((booking) => booking.userId);
|
||||
|
||||
const usersWithNoBookings = await ctx.prisma.user.findMany({
|
||||
select: UserSelect,
|
||||
if (userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const usersFromTeam = await ctx.prisma.user.findMany({
|
||||
where: {
|
||||
id: {
|
||||
notIn: userIds as number[],
|
||||
},
|
||||
teams: {
|
||||
some: {
|
||||
teamId: teamId,
|
||||
},
|
||||
in: userIds as number[],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let usersFromTeam: Prisma.UserGetPayload<{
|
||||
select: typeof UserSelect;
|
||||
}>[] = [];
|
||||
if (usersWithNoBookings.length < 10) {
|
||||
usersFromTeam = await ctx.prisma.user.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: userIds as number[],
|
||||
},
|
||||
},
|
||||
select: UserSelect,
|
||||
take: 10 - usersWithNoBookings.length,
|
||||
});
|
||||
}
|
||||
|
||||
const userHashMap = new Map();
|
||||
[...usersWithNoBookings, ...usersFromTeam].forEach((user) => {
|
||||
usersFromTeam.forEach((user) => {
|
||||
userHashMap.set(user.id, user);
|
||||
});
|
||||
|
||||
const result = bookingsFromTeam.map((booking) => {
|
||||
const user = userHashMap.get(booking.userId);
|
||||
return {
|
||||
userId: booking.userId,
|
||||
user,
|
||||
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),
|
||||
user: userHashMap.get(booking.userId),
|
||||
emailMd5: crypto.createHash("md5").update(user?.email).digest("hex"),
|
||||
count: booking._count.id,
|
||||
Username: user.name || "No Username found",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -590,6 +840,18 @@ export const insightsRouter = router({
|
||||
teamListForUser: authedProcedure.query(async ({ ctx }) => {
|
||||
const user = ctx.user;
|
||||
|
||||
// Fetch user data
|
||||
const userData = await ctx.prisma.user.findUnique({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Look if user it's admin in multiple teams
|
||||
const belongsToTeams = await ctx.prisma.membership.findMany({
|
||||
where: {
|
||||
@@ -617,8 +879,25 @@ export const insightsRouter = router({
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = belongsToTeams.map((membership) => membership.team);
|
||||
|
||||
const result: {
|
||||
id: number;
|
||||
slug: string | null;
|
||||
name: string | null;
|
||||
logo: string | null;
|
||||
userId?: number;
|
||||
}[] = belongsToTeams.map((membership) => {
|
||||
return { ...membership.team };
|
||||
});
|
||||
if (userData && userData.id) {
|
||||
result.push({
|
||||
id: 0,
|
||||
slug: "",
|
||||
userId: userData.id,
|
||||
name: userData.name,
|
||||
logo: userData.avatar,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
userList: userBelongsToTeamProcedure
|
||||
@@ -665,58 +944,84 @@ export const insightsRouter = router({
|
||||
eventTypeList: userBelongsToTeamProcedure
|
||||
.input(
|
||||
z.object({
|
||||
teamId: z.coerce.number().nullable(),
|
||||
teamId: z.coerce.number().optional().nullable(),
|
||||
userId: z.coerce.number().optional().nullable(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const user = ctx.user;
|
||||
const { prisma, user } = ctx;
|
||||
const { teamId, userId } = input;
|
||||
|
||||
if (!input.teamId) {
|
||||
if (!teamId && !userId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const membership = await ctx.prisma.membership.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
teamId: input.teamId,
|
||||
},
|
||||
});
|
||||
|
||||
if (membership && membership.role === "MEMBER") {
|
||||
const eventTypes = await ctx.prisma.eventType.findMany({
|
||||
where: {
|
||||
teamId: input.teamId,
|
||||
// user its listed as direct user or as part of a group
|
||||
OR: [
|
||||
{
|
||||
userId: user.id,
|
||||
},
|
||||
{
|
||||
users: {
|
||||
some: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return eventTypes;
|
||||
const membershipWhereConditional: Prisma.MembershipWhereInput = {};
|
||||
if (teamId) {
|
||||
membershipWhereConditional["teamId"] = teamId;
|
||||
membershipWhereConditional["userId"] = user.id;
|
||||
}
|
||||
if (userId) {
|
||||
membershipWhereConditional["userId"] = userId;
|
||||
}
|
||||
|
||||
const eventTypes = await ctx.prisma.eventType.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
teamId: true,
|
||||
title: true,
|
||||
},
|
||||
where: {
|
||||
teamId: input.teamId,
|
||||
},
|
||||
// I'm not using unique here since when userId comes from input we should look for every
|
||||
// event type that user owns
|
||||
const membership = await prisma.membership.findFirst({
|
||||
where: membershipWhereConditional,
|
||||
});
|
||||
|
||||
return eventTypes;
|
||||
if (!membership) {
|
||||
throw new Error("User is not part of a team");
|
||||
}
|
||||
|
||||
const eventTypeWhereConditional: Prisma.EventTypeWhereInput = {};
|
||||
if (teamId) {
|
||||
eventTypeWhereConditional["teamId"] = teamId;
|
||||
}
|
||||
if (userId) {
|
||||
eventTypeWhereConditional["userId"] = userId;
|
||||
}
|
||||
let eventTypeResult: Prisma.EventTypeGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
slug: true;
|
||||
teamId: true;
|
||||
title: true;
|
||||
};
|
||||
}>[] = [];
|
||||
|
||||
switch (membership?.role) {
|
||||
case "MEMBER":
|
||||
eventTypeWhereConditional["OR"] = {
|
||||
userId: user.id,
|
||||
users: { some: { id: user.id } },
|
||||
// @TODO this is not working as expected
|
||||
// hosts: { some: { id: user.id } },
|
||||
};
|
||||
eventTypeResult = await prisma.eventType.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
teamId: true,
|
||||
title: true,
|
||||
},
|
||||
where: eventTypeWhereConditional,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
eventTypeResult = await prisma.eventType.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
teamId: true,
|
||||
title: true,
|
||||
},
|
||||
where: eventTypeWhereConditional,
|
||||
});
|
||||
break;
|
||||
}
|
||||
return eventTypeResult;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -46,6 +46,7 @@ function getZapierPayload(data: CalendarEvent & EventTypeInfo & { status?: strin
|
||||
description: data.description,
|
||||
customInputs: data.customInputs,
|
||||
responses: data.responses,
|
||||
userFieldsResponses: data.userFieldsResponses,
|
||||
startTime: data.startTime,
|
||||
endTime: data.endTime,
|
||||
location: location,
|
||||
|
||||
@@ -14,7 +14,6 @@ type BookingSelect = {
|
||||
};
|
||||
};
|
||||
location: true;
|
||||
smsReminderNumber: true;
|
||||
};
|
||||
|
||||
// Backward Compatibility for booking created before we had managed booking questions
|
||||
@@ -104,4 +103,5 @@ export const getBookingWithResponses = <
|
||||
responses: bookingResponsesDbSchema.parse(booking.responses || getResponsesFromOldBooking(booking)),
|
||||
} as Omit<T, "responses"> & { responses: z.infer<typeof bookingResponsesDbSchema> };
|
||||
};
|
||||
|
||||
export default getBooking;
|
||||
@@ -2,221 +2,300 @@
|
||||
{
|
||||
"/*": "This file is auto-generated and updated by `yarn app-store create/edit`. Don't edit it manually",
|
||||
"dirName": "routing-forms",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "routing-forms",
|
||||
"type": "routing-forms_other"
|
||||
},
|
||||
{
|
||||
"dirName": "whereby",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "whereby",
|
||||
"type": "whereby_video"
|
||||
},
|
||||
{
|
||||
"dirName": "around",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "around",
|
||||
"type": "around_video"
|
||||
},
|
||||
{
|
||||
"dirName": "riverside",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "riverside",
|
||||
"type": "riverside_video"
|
||||
},
|
||||
{
|
||||
"dirName": "typeform",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "typeform",
|
||||
"type": "typeform_other"
|
||||
},
|
||||
{
|
||||
"dirName": "ping",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "ping",
|
||||
"type": "ping_video"
|
||||
},
|
||||
{
|
||||
"dirName": "campfire",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "campfire",
|
||||
"type": "campfire_video"
|
||||
},
|
||||
{
|
||||
"dirName": "rainbow",
|
||||
"categories": ["web3"],
|
||||
"categories": [
|
||||
"web3"
|
||||
],
|
||||
"slug": "rainbow",
|
||||
"type": "rainbow_web3"
|
||||
},
|
||||
{
|
||||
"dirName": "raycast",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "raycast",
|
||||
"type": "raycast_other"
|
||||
},
|
||||
{
|
||||
"dirName": "n8n",
|
||||
"categories": ["automation"],
|
||||
"categories": [
|
||||
"automation"
|
||||
],
|
||||
"slug": "n8n",
|
||||
"type": "n8n_automation"
|
||||
},
|
||||
{
|
||||
"dirName": "exchangecalendar",
|
||||
"categories": ["calendar"],
|
||||
"categories": [
|
||||
"calendar"
|
||||
],
|
||||
"slug": "exchange",
|
||||
"type": "exchange_calendar"
|
||||
},
|
||||
{
|
||||
"dirName": "qr_code",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "qr_code",
|
||||
"type": "qr_code_other"
|
||||
},
|
||||
{
|
||||
"dirName": "weather_in_your_calendar",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "weather_in_your_calendar",
|
||||
"type": "weather_in_your_calendar_other"
|
||||
},
|
||||
{
|
||||
"dirName": "fathom",
|
||||
"categories": ["analytics"],
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"slug": "fathom",
|
||||
"type": "fathom_analytics"
|
||||
},
|
||||
{
|
||||
"dirName": "plausible",
|
||||
"categories": ["analytics"],
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"slug": "plausible",
|
||||
"type": "plausible_analytics"
|
||||
},
|
||||
{
|
||||
"dirName": "wordpress",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "wordpress",
|
||||
"type": "wordpress_other"
|
||||
},
|
||||
{
|
||||
"dirName": "ga4",
|
||||
"categories": ["analytics"],
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"slug": "ga4",
|
||||
"type": "ga4_analytics"
|
||||
},
|
||||
{
|
||||
"dirName": "pipedream",
|
||||
"categories": ["automation"],
|
||||
"categories": [
|
||||
"automation"
|
||||
],
|
||||
"slug": "pipedream",
|
||||
"type": "pipedream_automation"
|
||||
},
|
||||
{
|
||||
"dirName": "sirius_video",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "sirius_video",
|
||||
"type": "sirius_video_video"
|
||||
},
|
||||
{
|
||||
"dirName": "sendgrid",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "sendgrid",
|
||||
"type": "sendgrid_other_calendar"
|
||||
},
|
||||
{
|
||||
"dirName": "closecom",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "closecom",
|
||||
"type": "closecom_other_calendar"
|
||||
},
|
||||
{
|
||||
"dirName": "whatsapp",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "whatsapp",
|
||||
"type": "whatsapp_video"
|
||||
},
|
||||
{
|
||||
"dirName": "telegram",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "telegram",
|
||||
"type": "telegram_video"
|
||||
},
|
||||
{
|
||||
"dirName": "signal",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "signal",
|
||||
"type": "signal_video"
|
||||
},
|
||||
{
|
||||
"dirName": "vimcal",
|
||||
"categories": ["calendar"],
|
||||
"categories": [
|
||||
"calendar"
|
||||
],
|
||||
"slug": "vimcal",
|
||||
"type": "vimcal_other"
|
||||
},
|
||||
{
|
||||
"dirName": "amie",
|
||||
"categories": ["calendar"],
|
||||
"categories": [
|
||||
"calendar"
|
||||
],
|
||||
"slug": "amie",
|
||||
"type": "amie_other"
|
||||
},
|
||||
{
|
||||
"dirName": "booking-pages-tag",
|
||||
"categories": ["analytics"],
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"slug": "booking-pages-tag",
|
||||
"type": "booking-pages-tag_other",
|
||||
"isTemplate": true
|
||||
},
|
||||
{
|
||||
"dirName": "event-type-app-card",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "event-type-app-card",
|
||||
"type": "event-type-app-card_other",
|
||||
"isTemplate": true
|
||||
},
|
||||
{
|
||||
"dirName": "event-type-location-video-static",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "event-type-location-video-static",
|
||||
"type": "event-type-location-video-static_other",
|
||||
"isTemplate": true
|
||||
},
|
||||
{
|
||||
"dirName": "general-app-settings",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "general-app-settings",
|
||||
"type": "general-app-settings_other",
|
||||
"isTemplate": true
|
||||
},
|
||||
{
|
||||
"dirName": "link-as-an-app",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "link-as-an-app",
|
||||
"type": "link-as-an-app_other",
|
||||
"isTemplate": true
|
||||
},
|
||||
{
|
||||
"dirName": "basic",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "basic",
|
||||
"type": "basic_other",
|
||||
"isTemplate": true
|
||||
},
|
||||
{
|
||||
"dirName": "facetime",
|
||||
"categories": ["video"],
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"slug": "facetime",
|
||||
"type": "facetime_video",
|
||||
"isTemplate": false
|
||||
},
|
||||
{
|
||||
"dirName": "zohocrm",
|
||||
"categories": ["other"],
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"slug": "zohocrm",
|
||||
"type": "zohocrm_other_calendar",
|
||||
"isTemplate": false
|
||||
},
|
||||
{
|
||||
"dirName": "cron",
|
||||
"categories": ["calendar"],
|
||||
"categories": [
|
||||
"calendar"
|
||||
],
|
||||
"slug": "cron",
|
||||
"type": "cron_other",
|
||||
"isTemplate": false
|
||||
},
|
||||
{
|
||||
"dirName": "google-tag-manager",
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"slug": "google-tag-manager",
|
||||
"type": "google-tag-manager_analytics",
|
||||
"isTemplate": false
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -232,6 +232,7 @@ export default async function main() {
|
||||
});
|
||||
}
|
||||
await createApp("jitsi", "jitsivideo", ["video"], "jitsi_video");
|
||||
await createApp("sylaps", "sylapsvideo", ["video"], "sylaps_video");
|
||||
// Other apps
|
||||
if (process.env.HUBSPOT_CLIENT_ID && process.env.HUBSPOT_CLIENT_SECRET) {
|
||||
await createApp("hubspot", "hubspot", ["other"], "hubspot_other_calendar", {
|
||||
|
||||
@@ -5,6 +5,65 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
|
||||
|
||||
const shuffle = (
|
||||
booking: any,
|
||||
year: number,
|
||||
eventTypesToPick: Prisma.EventTypeGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
userId: true;
|
||||
title: true;
|
||||
length: true;
|
||||
description: true;
|
||||
};
|
||||
}>[],
|
||||
usersIdsToPick?: number[]
|
||||
) => {
|
||||
const startTime = dayjs(booking.startTime)
|
||||
.add(Math.floor(Math.random() * 365), "day")
|
||||
.add(Math.floor(Math.random() * 24), "hour")
|
||||
.add(Math.floor(Math.random() * 60), "minute")
|
||||
.set("y", year);
|
||||
const randomEvent = eventTypesToPick[Math.floor(Math.random() * eventTypesToPick.length)];
|
||||
|
||||
const endTime = dayjs(startTime).add(Math.floor(Math.random() * randomEvent.length), "minute");
|
||||
|
||||
booking.title = randomEvent.title;
|
||||
booking.description = randomEvent.description;
|
||||
booking.startTime = startTime.toISOString();
|
||||
booking.endTime = endTime.toISOString();
|
||||
booking.createdAt = startTime.subtract(1, "day").toISOString();
|
||||
|
||||
// Pick a random status
|
||||
const randomStatusIndex = Math.floor(Math.random() * Object.keys(BookingStatus).length);
|
||||
const statusKey = Object.keys(BookingStatus)[randomStatusIndex];
|
||||
|
||||
booking.status = BookingStatus[statusKey];
|
||||
|
||||
booking.rescheduled = Math.random() > 0.5 && Math.random() > 0.5 && Math.random() > 0.5;
|
||||
|
||||
if (booking.rescheduled) {
|
||||
booking.status = "CANCELLED";
|
||||
}
|
||||
const randomEventTypeId = randomEvent.id;
|
||||
|
||||
booking.eventTypeId = randomEventTypeId;
|
||||
booking.uid = uuidv4();
|
||||
|
||||
if (usersIdsToPick && usersIdsToPick.length > 0) {
|
||||
booking.userId = Math.random() > 0.5 ? usersIdsToPick[0] : usersIdsToPick[1];
|
||||
} else {
|
||||
booking.userId = randomEvent.userId;
|
||||
}
|
||||
|
||||
if (booking.userId === undefined || booking.userId === null) {
|
||||
console.log({ randomEvent, usersIdsToPick });
|
||||
console.log("This should not happen");
|
||||
}
|
||||
|
||||
return booking;
|
||||
};
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
async function main() {
|
||||
// First find if not then create everything
|
||||
@@ -18,25 +77,30 @@ async function main() {
|
||||
insightsAdmin = await prisma.user.create({
|
||||
data: {
|
||||
email: "insights@example.com",
|
||||
password: await hashPassword("insights"),
|
||||
password: await hashPassword("insightsinsightsinsights...!"),
|
||||
name: "Insights Admin",
|
||||
role: "ADMIN",
|
||||
username: "insights",
|
||||
username: "insights-admin",
|
||||
completedOnboarding: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!insightsAdmin) {
|
||||
// Throw error
|
||||
throw new Error("Insights Admin not created");
|
||||
}
|
||||
}
|
||||
|
||||
let insightsUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: "insightuser@example.com",
|
||||
email: "insightsuser@example.com",
|
||||
},
|
||||
});
|
||||
|
||||
if (!insightsUser) {
|
||||
insightsUser = await prisma.user.create({
|
||||
data: {
|
||||
email: "insightuser@example.com",
|
||||
email: "insightsuser@example.com",
|
||||
password: await hashPassword("insightsuser"),
|
||||
name: "Insights User",
|
||||
role: "USER",
|
||||
@@ -44,6 +108,10 @@ async function main() {
|
||||
completedOnboarding: true,
|
||||
},
|
||||
});
|
||||
if (!insightsUser) {
|
||||
// Throw error
|
||||
throw new Error("Insights Admin not created");
|
||||
}
|
||||
}
|
||||
|
||||
let insightsTeam = await prisma.team.findFirst({
|
||||
@@ -75,37 +143,33 @@ async function main() {
|
||||
},
|
||||
],
|
||||
});
|
||||
await prisma.team.update({
|
||||
where: {
|
||||
id: insightsTeam.id,
|
||||
},
|
||||
data: {
|
||||
members: {
|
||||
connect: {
|
||||
userId_teamId: {
|
||||
userId: insightsAdmin.id,
|
||||
teamId: insightsTeam.id,
|
||||
if (!insightsTeam) {
|
||||
insightsTeam = await prisma.team.create({
|
||||
data: {
|
||||
name: "Insights",
|
||||
slug: "insights-team",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (insightsAdmin && insightsTeam) {
|
||||
await prisma.team.update({
|
||||
where: {
|
||||
id: insightsTeam.id,
|
||||
},
|
||||
data: {
|
||||
members: {
|
||||
connect: {
|
||||
userId_teamId: {
|
||||
userId: insightsAdmin.id,
|
||||
teamId: insightsTeam.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
await prisma.team.update({
|
||||
where: {
|
||||
id: insightsTeam.id,
|
||||
},
|
||||
data: {
|
||||
members: {
|
||||
connect: {
|
||||
userId_teamId: {
|
||||
userId: insightsUser.id,
|
||||
teamId: insightsTeam.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let teamEvents = await prisma.eventType.findMany({
|
||||
where: {
|
||||
@@ -113,6 +177,112 @@ async function main() {
|
||||
},
|
||||
});
|
||||
|
||||
const userSingleEventsAdmin = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: insightsUser.id,
|
||||
},
|
||||
});
|
||||
|
||||
const userSingleEvents = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: insightsUser.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (userSingleEventsAdmin.length === 0 && userSingleEvents.length === 0) {
|
||||
await prisma.eventType.create({
|
||||
data: {
|
||||
title: "Single Event Admin",
|
||||
slug: "single-event-admin",
|
||||
description: "Single Event Admin",
|
||||
length: 60,
|
||||
userId: insightsAdmin.id,
|
||||
users: {
|
||||
connect: {
|
||||
id: insightsAdmin.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await prisma.eventType.create({
|
||||
data: {
|
||||
title: "Single Event",
|
||||
slug: "single-event",
|
||||
description: "Single Event",
|
||||
length: 60,
|
||||
userId: insightsUser.id,
|
||||
users: {
|
||||
connect: {
|
||||
id: insightsUser.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const singleEventTypesAdmin = await prisma.eventType.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
title: true,
|
||||
length: true,
|
||||
description: true,
|
||||
},
|
||||
where: {
|
||||
userId: insightsAdmin.id,
|
||||
},
|
||||
});
|
||||
|
||||
const singleEventTypes = await prisma.eventType.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
length: true,
|
||||
title: true,
|
||||
description: true,
|
||||
},
|
||||
where: {
|
||||
userId: insightsUser.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!singleEventTypesAdmin || !singleEventTypes) {
|
||||
throw new Error("Could not create single event types");
|
||||
}
|
||||
|
||||
const singleEvents = [singleEventTypesAdmin, singleEventTypes];
|
||||
|
||||
// create bookings random for single event type
|
||||
const baseBookingSingle: Prisma.BookingCreateManyInput = {
|
||||
uid: "demoUIDSingle",
|
||||
title: "Single Event",
|
||||
description: "Single Event",
|
||||
startTime: dayjs().toISOString(),
|
||||
endTime: dayjs().toISOString(),
|
||||
// This gets overriden in shuffle
|
||||
userId: insightsUser.id,
|
||||
};
|
||||
|
||||
// Validate if they are null of undefined throw error
|
||||
if (!insightsAdmin || !insightsUser || !insightsAdmin.id || !insightsUser.id) {
|
||||
throw new Error("Could not find user");
|
||||
}
|
||||
|
||||
await prisma.booking.createMany({
|
||||
data: [
|
||||
...new Array(100)
|
||||
.fill(0)
|
||||
.map(() => shuffle({ ...baseBookingSingle }, dayjs().get("y") - 1, singleEvents)),
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.booking.createMany({
|
||||
data: [
|
||||
...new Array(100)
|
||||
.fill(0)
|
||||
.map(() => shuffle({ ...baseBookingSingle }, dayjs().get("y") - 0, singleEvents)),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (teamEvents.length === 0) {
|
||||
await prisma.eventType.createMany({
|
||||
data: [
|
||||
@@ -142,7 +312,6 @@ async function main() {
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
teamEvents = await prisma.eventType.findMany({
|
||||
where: {
|
||||
teamId: insightsTeam.id,
|
||||
@@ -152,61 +321,52 @@ async function main() {
|
||||
|
||||
const baseBooking: Prisma.BookingCreateManyInput = {
|
||||
uid: "demoUID",
|
||||
title: "Team Meeting",
|
||||
description: "Team Meeting",
|
||||
title: "Team Meeting should be changed in shuffle",
|
||||
description: "Team Meeting Should be changed in shuffle",
|
||||
startTime: dayjs().toISOString(),
|
||||
endTime: dayjs().toISOString(),
|
||||
userId: insightsUser.id,
|
||||
eventTypeId: teamEvents[0].id,
|
||||
};
|
||||
|
||||
const shuffle = (booking: typeof baseBooking, year) => {
|
||||
const startTime = dayjs(booking.startTime)
|
||||
.add(Math.floor(Math.random() * 365), "day")
|
||||
.add(Math.floor(Math.random() * 24), "hour")
|
||||
.add(Math.floor(Math.random() * 60), "minute")
|
||||
.set("y", year);
|
||||
const randomEvent = teamEvents[Math.floor(Math.random() * teamEvents.length)];
|
||||
const endTime = dayjs(startTime).add(Math.floor(Math.random() * randomEvent.length), "minute");
|
||||
|
||||
booking.startTime = startTime.toISOString();
|
||||
booking.endTime = endTime.toISOString();
|
||||
booking.createdAt = startTime.subtract(1, "day").toISOString();
|
||||
|
||||
// Pick a random status
|
||||
const randomStatusIndex = Math.floor(Math.random() * Object.keys(BookingStatus).length);
|
||||
const statusKey = Object.keys(BookingStatus)[randomStatusIndex];
|
||||
|
||||
booking.status = BookingStatus[statusKey];
|
||||
|
||||
booking.rescheduled = Math.random() > 0.5 && Math.random() > 0.5 && Math.random() > 0.5;
|
||||
|
||||
if (booking.rescheduled) {
|
||||
booking.status = "CANCELLED";
|
||||
}
|
||||
const randomEventTypeId = teamEvents[Math.floor(Math.random() * teamEvents.length)].id;
|
||||
|
||||
booking.eventTypeId = randomEventTypeId;
|
||||
booking.uid = uuidv4();
|
||||
|
||||
if (insightsUser && insightsAdmin) {
|
||||
booking.userId = Math.random() > 0.5 ? insightsUser.id : insightsAdmin.id;
|
||||
}
|
||||
|
||||
return booking;
|
||||
};
|
||||
|
||||
// Create past bookings
|
||||
// Create past bookings -2y, -1y, -0y
|
||||
await prisma.booking.createMany({
|
||||
data: [...new Array(100).fill(0).map(() => shuffle({ ...baseBooking }, dayjs().get("y") - 2))],
|
||||
data: [
|
||||
...new Array(100)
|
||||
.fill(0)
|
||||
.map(() =>
|
||||
shuffle({ ...baseBooking }, dayjs().get("y") - 2, teamEvents, [
|
||||
insightsAdmin?.id ?? 0,
|
||||
insightsUser?.id ?? 0,
|
||||
])
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.booking.createMany({
|
||||
data: [...new Array(100).fill(0).map(() => shuffle({ ...baseBooking }, dayjs().get("y") - 1))],
|
||||
data: [
|
||||
...new Array(100)
|
||||
.fill(0)
|
||||
.map(() =>
|
||||
shuffle({ ...baseBooking }, dayjs().get("y") - 1, teamEvents, [
|
||||
insightsAdmin?.id ?? 0,
|
||||
insightsUser?.id ?? 0,
|
||||
])
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.booking.createMany({
|
||||
data: [...new Array(100).fill(0).map(() => shuffle({ ...baseBooking }, dayjs().get("y")))],
|
||||
data: [
|
||||
...new Array(100)
|
||||
.fill(0)
|
||||
.map(() =>
|
||||
shuffle({ ...baseBooking }, dayjs().get("y"), teamEvents, [
|
||||
insightsAdmin?.id ?? 0,
|
||||
insightsUser?.id ?? 0,
|
||||
])
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { DestinationCalendar, Prisma } from "@prisma/client";
|
||||
import { AppCategories, BookingStatus, IdentityProvider } from "@prisma/client";
|
||||
import { cityMapping } from "city-timezones";
|
||||
import { reverse } from "lodash";
|
||||
import type { NextApiResponse } from "next";
|
||||
import { authenticator } from "otplib";
|
||||
@@ -23,6 +22,7 @@ import dayjs from "@calcom/dayjs";
|
||||
import { sendCancelledEmails, sendFeedbackEmail } from "@calcom/emails";
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { samlTenantProduct } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { featureFlagRouter } from "@calcom/features/flags/server/router";
|
||||
import { insightsRouter } from "@calcom/features/insights/server/trpc-router";
|
||||
@@ -159,7 +159,35 @@ const publicViewerRouter = router({
|
||||
}),
|
||||
// REVIEW: This router is part of both the public and private viewer router?
|
||||
slots: slotsRouter,
|
||||
cityTimezones: publicProcedure.query(() => cityMapping),
|
||||
cityTimezones: publicProcedure.query(async () => {
|
||||
/**
|
||||
* Lazy loads third party dependency to avoid loading 1.5Mb for ALL tRPC procedures.
|
||||
* Thanks @roae for the tip 🙏
|
||||
**/
|
||||
const allCities = await import("city-timezones").then((mod) => mod.cityMapping);
|
||||
/**
|
||||
* Filter out all cities that have the same "city" key and only use the one with the highest population.
|
||||
* This way we return a new array of cities without running the risk of having more than one city
|
||||
* with the same name on the dropdown and prevent users from mistaking the time zone of the desired city.
|
||||
*/
|
||||
const topPopulatedCities: { [key: string]: { city: string; timezone: string; pop: number } } = {};
|
||||
allCities.forEach((city) => {
|
||||
const cityPopulationCount = city.pop;
|
||||
if (
|
||||
topPopulatedCities[city.city]?.pop === undefined ||
|
||||
cityPopulationCount > topPopulatedCities[city.city].pop
|
||||
) {
|
||||
topPopulatedCities[city.city] = { city: city.city, timezone: city.timezone, pop: city.pop };
|
||||
}
|
||||
});
|
||||
const uniqueCities = Object.values(topPopulatedCities);
|
||||
/** Add specific overries in here */
|
||||
uniqueCities.forEach((city) => {
|
||||
if (city.city === "London") city.timezone = "Europe/London";
|
||||
if (city.city === "Londonderry") city.city = "London";
|
||||
});
|
||||
return uniqueCities;
|
||||
}),
|
||||
});
|
||||
|
||||
// routes only available to authenticated users
|
||||
@@ -1008,6 +1036,7 @@ const loggedInViewerRouter = router({
|
||||
...bookingMinimalSelect,
|
||||
recurringEventId: true,
|
||||
userId: true,
|
||||
responses: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -1033,6 +1062,7 @@ const loggedInViewerRouter = router({
|
||||
select: {
|
||||
recurringEvent: true,
|
||||
title: true,
|
||||
bookingFields: true,
|
||||
seatsPerTimeSlot: true,
|
||||
seatsShowAttendees: true,
|
||||
},
|
||||
@@ -1093,12 +1123,15 @@ const loggedInViewerRouter = router({
|
||||
|
||||
const attendeesList = await Promise.all(attendeesListPromises);
|
||||
const tOrganizer = await getTranslation(booking?.user?.locale ?? "en", "common");
|
||||
|
||||
await sendCancelledEmails({
|
||||
type: booking?.eventType?.title as string,
|
||||
title: booking.title,
|
||||
description: booking.description,
|
||||
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||
...getCalEventResponses({
|
||||
bookingFields: booking.eventType?.bookingFields ?? null,
|
||||
booking,
|
||||
}),
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
organizer: {
|
||||
|
||||
@@ -15,8 +15,6 @@ import dayjs from "@calcom/dayjs";
|
||||
import { deleteScheduledEmailReminder } from "@calcom/ee/workflows/lib/reminders/emailReminderManager";
|
||||
import { deleteScheduledSMSReminder } from "@calcom/ee/workflows/lib/reminders/smsReminderManager";
|
||||
import { sendDeclinedEmails, sendLocationChangeEmails, sendRequestRescheduleEmail } from "@calcom/emails";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { bookingResponsesDbSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
@@ -418,6 +416,7 @@ export const bookingsRouter = router({
|
||||
smsReminderNumber: true,
|
||||
scheduledJobs: true,
|
||||
workflowReminders: true,
|
||||
responses: true,
|
||||
},
|
||||
where: {
|
||||
uid: bookingId,
|
||||
@@ -577,6 +576,10 @@ export const bookingsRouter = router({
|
||||
type: event && event.title ? event.title : bookingToReschedule.title,
|
||||
description: bookingToReschedule?.description || "",
|
||||
customInputs: isPrismaObjOrUndefined(bookingToReschedule.customInputs),
|
||||
...getCalEventResponses({
|
||||
booking: bookingToReschedule,
|
||||
bookingFields: bookingToReschedule.eventType?.bookingFields ?? null,
|
||||
}),
|
||||
startTime: bookingToReschedule?.startTime ? dayjs(bookingToReschedule.startTime).format() : "",
|
||||
endTime: bookingToReschedule?.endTime ? dayjs(bookingToReschedule.endTime).format() : "",
|
||||
organizer: userAsPeopleType,
|
||||
@@ -722,7 +725,7 @@ export const bookingsRouter = router({
|
||||
|
||||
const tOrganizer = await getTranslation(user.locale ?? "en", "common");
|
||||
|
||||
const bookingRaw = await prisma.booking.findUniqueOrThrow({
|
||||
const booking = await prisma.booking.findUniqueOrThrow({
|
||||
where: {
|
||||
id: bookingId,
|
||||
},
|
||||
@@ -776,21 +779,6 @@ export const bookingsRouter = router({
|
||||
},
|
||||
});
|
||||
|
||||
const bookingFields = bookingRaw.eventType
|
||||
? getBookingFieldsWithSystemFields(bookingRaw.eventType)
|
||||
: null;
|
||||
|
||||
const booking = {
|
||||
...bookingRaw,
|
||||
responses: bookingResponsesDbSchema.parse(bookingRaw.responses),
|
||||
eventType: bookingRaw.eventType
|
||||
? {
|
||||
...bookingRaw.eventType,
|
||||
bookingFields,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
const authorized = async () => {
|
||||
// if the organizer
|
||||
if (booking.userId === user.id) {
|
||||
@@ -848,18 +836,15 @@ export const bookingsRouter = router({
|
||||
|
||||
const attendeesList = await Promise.all(attendeesListPromises);
|
||||
|
||||
// TODO: Remove the usage of `bookingFields` in computing responses. We can do that by storing `label` with the response. Also, this would allow us to correctly show the label for a field even after the Event Type has been deleted.
|
||||
const { calEventUserFieldsResponses, calEventResponses } = getCalEventResponses({
|
||||
bookingFields: booking.eventType?.bookingFields ?? null,
|
||||
responses: booking.responses,
|
||||
});
|
||||
|
||||
const evt: CalendarEvent = {
|
||||
type: booking.eventType?.title || booking.title,
|
||||
title: booking.title,
|
||||
description: booking.description,
|
||||
responses: calEventResponses,
|
||||
userFieldsResponses: calEventUserFieldsResponses,
|
||||
// TODO: Remove the usage of `bookingFields` in computing responses. We can do that by storing `label` with the response. Also, this would allow us to correctly show the label for a field even after the Event Type has been deleted.
|
||||
...getCalEventResponses({
|
||||
bookingFields: booking.eventType?.bookingFields ?? null,
|
||||
booking,
|
||||
}),
|
||||
customInputs: isPrismaObjOrUndefined(booking.customInputs),
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
|
||||
@@ -122,25 +122,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@auth/core@npm:^0.1.4":
|
||||
version: 0.1.4
|
||||
resolution: "@auth/core@npm:0.1.4"
|
||||
dependencies:
|
||||
"@panva/hkdf": 1.0.2
|
||||
cookie: 0.5.0
|
||||
jose: 4.11.1
|
||||
oauth4webapi: 2.0.5
|
||||
preact: 10.11.3
|
||||
preact-render-to-string: 5.2.3
|
||||
peerDependencies:
|
||||
nodemailer: 6.8.0
|
||||
peerDependenciesMeta:
|
||||
nodemailer:
|
||||
optional: true
|
||||
checksum: 64854404ea1883e0deb5535b34bed95cd43fc85094aeaf4f15a79e14045020eb944f844defe857edfc8528a0a024be89cbb2a3069dedef0e9217a74ca6c3eb79
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-crypto/ie11-detection@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@aws-crypto/ie11-detection@npm:3.0.0"
|
||||
@@ -3893,34 +3874,27 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@calcom/api@workspace:apps/api"
|
||||
dependencies:
|
||||
"@calcom/app-store": "*"
|
||||
"@calcom/core": "*"
|
||||
"@calcom/dayjs": "*"
|
||||
"@calcom/emails": "*"
|
||||
"@calcom/embed-core": "workspace:*"
|
||||
"@calcom/embed-snippet": "workspace:*"
|
||||
"@calcom/features": "*"
|
||||
"@calcom/lib": "*"
|
||||
"@calcom/prisma": "*"
|
||||
"@calcom/trpc": "*"
|
||||
"@calcom/tsconfig": "*"
|
||||
"@calcom/types": "*"
|
||||
"@sentry/nextjs": ^7.20.0
|
||||
"@sentry/nextjs": ^6.19.7
|
||||
babel-jest: ^28.1.0
|
||||
bcryptjs: ^2.4.3
|
||||
jest: ^28.1.0
|
||||
memory-cache: ^0.2.0
|
||||
modify-response-middleware: ^1.1.0
|
||||
next: ^13.2.1
|
||||
next: ^12.2.5
|
||||
next-api-middleware: ^1.0.1
|
||||
next-axiom: ^0.16.0
|
||||
next-axiom: ^0.10.0
|
||||
next-swagger-doc: ^0.3.4
|
||||
next-transpile-modules: ^9.0.0
|
||||
next-validations: ^0.2.0
|
||||
node-mocks-http: ^1.11.0
|
||||
typescript: ^4.9.4
|
||||
typescript: ^4.7.4
|
||||
tzdata: ^1.0.30
|
||||
uuid: ^8.3.2
|
||||
zod: ^3.20.2
|
||||
zod: ^3.18.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -3982,39 +3956,6 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/auth@workspace:apps/auth":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@calcom/auth@workspace:apps/auth"
|
||||
dependencies:
|
||||
"@auth/core": ^0.1.4
|
||||
"@calcom/app-store": "*"
|
||||
"@calcom/app-store-cli": "*"
|
||||
"@calcom/config": "*"
|
||||
"@calcom/core": "*"
|
||||
"@calcom/dayjs": "*"
|
||||
"@calcom/embed-core": "workspace:*"
|
||||
"@calcom/embed-react": "workspace:*"
|
||||
"@calcom/embed-snippet": "workspace:*"
|
||||
"@calcom/features": "*"
|
||||
"@calcom/lib": "*"
|
||||
"@calcom/prisma": "*"
|
||||
"@calcom/trpc": "*"
|
||||
"@calcom/tsconfig": "*"
|
||||
"@calcom/types": "*"
|
||||
"@calcom/ui": "*"
|
||||
"@types/node": 16.9.1
|
||||
"@types/react": 18.0.26
|
||||
"@types/react-dom": 18.0.9
|
||||
eslint: ^8.34.0
|
||||
eslint-config-next: ^13.2.1
|
||||
next: ^13.2.1
|
||||
next-auth: ^4.20.1
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
typescript: ^4.9.4
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/caldavcalendar@workspace:packages/app-store/caldavcalendar":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@calcom/caldavcalendar@workspace:packages/app-store/caldavcalendar"
|
||||
@@ -4699,6 +4640,15 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/sylapsvideo@workspace:packages/app-store/sylapsvideo":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@calcom/sylapsvideo@workspace:packages/app-store/sylapsvideo"
|
||||
dependencies:
|
||||
"@calcom/lib": "*"
|
||||
"@calcom/types": "*"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/tandemvideo@workspace:packages/app-store/tandemvideo":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@calcom/tandemvideo@workspace:packages/app-store/tandemvideo"
|
||||
@@ -7919,6 +7869,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/env@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/env@npm:12.3.4"
|
||||
checksum: daa3fc11efd1344c503eab41311a0e503ba7fd08607eeb3dc571036a6211eb37959cc4ed48b71dcc411cc214e7623ffd02411080aad3e09dc6a1192d5b256e60
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/env@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/env@npm:13.2.3"
|
||||
@@ -7942,6 +7899,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-android-arm-eabi@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-android-arm-eabi@npm:12.3.4"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-android-arm-eabi@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-android-arm-eabi@npm:13.2.3"
|
||||
@@ -7956,6 +7920,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-android-arm64@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-android-arm64@npm:12.3.4"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-android-arm64@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-android-arm64@npm:13.2.3"
|
||||
@@ -7970,6 +7941,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-arm64@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-darwin-arm64@npm:12.3.4"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-arm64@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-darwin-arm64@npm:13.2.3"
|
||||
@@ -7984,6 +7962,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-darwin-x64@npm:12.3.4"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-darwin-x64@npm:13.2.3"
|
||||
@@ -7998,6 +7983,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-freebsd-x64@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-freebsd-x64@npm:12.3.4"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-freebsd-x64@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-freebsd-x64@npm:13.2.3"
|
||||
@@ -8012,6 +8004,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm-gnueabihf@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-linux-arm-gnueabihf@npm:12.3.4"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm-gnueabihf@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-linux-arm-gnueabihf@npm:13.2.3"
|
||||
@@ -8026,6 +8025,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:12.3.4"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:13.2.3"
|
||||
@@ -8040,6 +8046,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:12.3.4"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:13.2.3"
|
||||
@@ -8054,6 +8067,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:12.3.4"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:13.2.3"
|
||||
@@ -8068,6 +8088,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-linux-x64-musl@npm:12.3.4"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-linux-x64-musl@npm:13.2.3"
|
||||
@@ -8082,6 +8109,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:12.3.4"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:13.2.3"
|
||||
@@ -8096,6 +8130,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-ia32-msvc@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-win32-ia32-msvc@npm:12.3.4"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-ia32-msvc@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-win32-ia32-msvc@npm:13.2.3"
|
||||
@@ -8110,6 +8151,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:12.3.4":
|
||||
version: 12.3.4
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:12.3.4"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:13.2.3"
|
||||
@@ -8269,13 +8317,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@panva/hkdf@npm:1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@panva/hkdf@npm:1.0.2"
|
||||
checksum: 75183b4d5ea816ef516dcea70985c610683579a9e2ac540c2d59b9a3ed27eedaff830a43a1c43c1683556a457c92ac66e09109ee995ab173090e4042c4c4bb03
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@panva/hkdf@npm:^1.0.2":
|
||||
version: 1.0.4
|
||||
resolution: "@panva/hkdf@npm:1.0.4"
|
||||
@@ -8391,14 +8432,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/debug@npm:4.11.0":
|
||||
version: 4.11.0
|
||||
resolution: "@prisma/debug@npm:4.11.0"
|
||||
"@prisma/debug@npm:4.12.0":
|
||||
version: 4.12.0
|
||||
resolution: "@prisma/debug@npm:4.12.0"
|
||||
dependencies:
|
||||
"@types/debug": 4.1.7
|
||||
debug: 4.3.4
|
||||
strip-ansi: 6.0.1
|
||||
checksum: 170adb225d6e8e0e0df26d441d550607c7fecb97969b47ca19264d65636564c774ba60b9cefab196bb86d358cdd06a289e2ad8d3f70201cb3becb79547343bf5
|
||||
checksum: 0ae182634f4a25231ff946768e1da1f694b5141d21c92cb2a142580de30d67ebd1595f8575a4c63356740e415d5d64829449a4710d8421e7147016ea0acb2643
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8417,14 +8458,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@prisma/generator-helper@npm:^4.0.0":
|
||||
version: 4.11.0
|
||||
resolution: "@prisma/generator-helper@npm:4.11.0"
|
||||
version: 4.12.0
|
||||
resolution: "@prisma/generator-helper@npm:4.12.0"
|
||||
dependencies:
|
||||
"@prisma/debug": 4.11.0
|
||||
"@prisma/debug": 4.12.0
|
||||
"@types/cross-spawn": 6.0.2
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.3
|
||||
checksum: 4d8cb919893cc22a743e625c7d077e3cbd67e4541500025d253dfd6df2452d294d05bbd0a0599007c562796badba92d57191ecfb980e3c29c906ba0038d37faf
|
||||
checksum: b844fcf736c922ce39e6aa1081b7c133b921ceb5d272f51296cf7afc3128ca6085ef1594ce63045ef47a462c56e5e37b9a1094a36d5dd484f77017d6f227641f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9850,6 +9891,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/browser@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/browser@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/core": 6.19.7
|
||||
"@sentry/types": 6.19.7
|
||||
"@sentry/utils": 6.19.7
|
||||
tslib: ^1.9.3
|
||||
checksum: 071d00c76c2d0384580474c634c58c6196bbd1a3cf510da1309bd1565c57df7422fca8ceb717db189fa557f2c711a21664ee1ab935dfd9869faf416d388e6f78
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/browser@npm:7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/browser@npm:7.20.0"
|
||||
@@ -9862,6 +9915,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/cli@npm:^1.73.0":
|
||||
version: 1.75.0
|
||||
resolution: "@sentry/cli@npm:1.75.0"
|
||||
dependencies:
|
||||
https-proxy-agent: ^5.0.0
|
||||
mkdirp: ^0.5.5
|
||||
node-fetch: ^2.6.7
|
||||
progress: ^2.0.3
|
||||
proxy-from-env: ^1.1.0
|
||||
which: ^2.0.2
|
||||
bin:
|
||||
sentry-cli: bin/sentry-cli
|
||||
checksum: d5b130bb45bc96d0f738801343455e6a113d1cc9e16166f0d6bfa022809b557159f010677d2dc05adcdfa7f8ad23c401182b6daef720fa2a0bad82fa6e3b2148
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/cli@npm:^1.74.6":
|
||||
version: 1.74.6
|
||||
resolution: "@sentry/cli@npm:1.74.6"
|
||||
@@ -9879,6 +9948,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/core@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/core@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/hub": 6.19.7
|
||||
"@sentry/minimal": 6.19.7
|
||||
"@sentry/types": 6.19.7
|
||||
"@sentry/utils": 6.19.7
|
||||
tslib: ^1.9.3
|
||||
checksum: d212e8ef07114549de4a93b81f8bfa217ca1550ca7a5eeaa611e5629faef78ff72663ce561ffa2cff48f3dc556745ef65177044f9965cdd3cbccf617cf3bf675
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/core@npm:7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/core@npm:7.20.0"
|
||||
@@ -9890,6 +9972,29 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/hub@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/hub@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/types": 6.19.7
|
||||
"@sentry/utils": 6.19.7
|
||||
tslib: ^1.9.3
|
||||
checksum: 10bb1c5cba1b0f1e27a3dd0a186c22f94aeaf11c4662890ab07b2774f46f46af78d61e3ba71d76edc750a7b45af86edd032f35efecdb4efa2eaf551080ccdcb1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/integrations@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/integrations@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/types": 6.19.7
|
||||
"@sentry/utils": 6.19.7
|
||||
localforage: ^1.8.1
|
||||
tslib: ^1.9.3
|
||||
checksum: 6ea22ef8d518e6ade96ad00c07f5346c8316264ae6ec353c299e03d1d3cf4b226f639bde07dd5d1da28feaa031338e75df25fc60a083a24d847f4cfde4dbc8e9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/integrations@npm:7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/integrations@npm:7.20.0"
|
||||
@@ -9902,6 +10007,41 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/minimal@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/minimal@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/hub": 6.19.7
|
||||
"@sentry/types": 6.19.7
|
||||
tslib: ^1.9.3
|
||||
checksum: 9153ac426ee056fc34c5be898f83d74ec08f559d69f544c5944ec05e584b62ed356b92d1a9b08993a7022ad42b5661c3d72881221adc19bee5fc1af3ad3864a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/nextjs@npm:^6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/nextjs@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/core": 6.19.7
|
||||
"@sentry/hub": 6.19.7
|
||||
"@sentry/integrations": 6.19.7
|
||||
"@sentry/node": 6.19.7
|
||||
"@sentry/react": 6.19.7
|
||||
"@sentry/tracing": 6.19.7
|
||||
"@sentry/utils": 6.19.7
|
||||
"@sentry/webpack-plugin": 1.18.8
|
||||
tslib: ^1.9.3
|
||||
peerDependencies:
|
||||
next: ^10.0.8 || ^11.0 || ^12.0
|
||||
react: 15.x || 16.x || 17.x || 18.x
|
||||
webpack: ">= 4.0.0"
|
||||
peerDependenciesMeta:
|
||||
webpack:
|
||||
optional: true
|
||||
checksum: f45228d9dd94457db5febca1e7f79b0689000cecc0beb8155724ea69657f871fc0116a3c884be0167196a730856aaf862db7869ef14e16183091ae0607d3a78a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/nextjs@npm:^7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/nextjs@npm:7.20.0"
|
||||
@@ -9930,6 +10070,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/node@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/node@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/core": 6.19.7
|
||||
"@sentry/hub": 6.19.7
|
||||
"@sentry/types": 6.19.7
|
||||
"@sentry/utils": 6.19.7
|
||||
cookie: ^0.4.1
|
||||
https-proxy-agent: ^5.0.0
|
||||
lru_map: ^0.3.3
|
||||
tslib: ^1.9.3
|
||||
checksum: 2293b0d1d1f9fac3a451eb94f820bc27721c8edddd1f373064666ddd6272f0a4c70dbe58c6c4b3d3ccaf4578aab8f466d71ee69f6f6ff93521bbb02dfe829ce5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/node@npm:7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/node@npm:7.20.0"
|
||||
@@ -9945,6 +10101,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/react@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/react@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/browser": 6.19.7
|
||||
"@sentry/minimal": 6.19.7
|
||||
"@sentry/types": 6.19.7
|
||||
"@sentry/utils": 6.19.7
|
||||
hoist-non-react-statics: ^3.3.2
|
||||
tslib: ^1.9.3
|
||||
peerDependencies:
|
||||
react: 15.x || 16.x || 17.x || 18.x
|
||||
checksum: f49b74c617e3d5094109417a4ab957d4304ec5c3c6d7208c93fe484f263c70c9656259471ae0367b75a303466d7715285ae9ac4876927d6bc285dab4d8e1591d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/react@npm:7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/react@npm:7.20.0"
|
||||
@@ -9960,6 +10132,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/tracing@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/tracing@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/hub": 6.19.7
|
||||
"@sentry/minimal": 6.19.7
|
||||
"@sentry/types": 6.19.7
|
||||
"@sentry/utils": 6.19.7
|
||||
tslib: ^1.9.3
|
||||
checksum: 26e7bf435f0478356d95fab50fe4acd2cf449bfdfd457996111aeedd082f6c76f0592abd0c5f5dcfe2b582d3fd7988ffef07787e7ecc7f5d228278731ce6264e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/tracing@npm:7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/tracing@npm:7.20.0"
|
||||
@@ -9972,6 +10157,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/types@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/types@npm:6.19.7"
|
||||
checksum: f46ef74a33376ad6ea9b128115515c58eb9369d89293c60aa67abca26b5d5d519aa4d0a736db56ae0d75ffd816643d62187018298523cbc2e6c2fb3a6b2a9035
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/types@npm:7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/types@npm:7.20.0"
|
||||
@@ -9979,6 +10171,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/utils@npm:6.19.7":
|
||||
version: 6.19.7
|
||||
resolution: "@sentry/utils@npm:6.19.7"
|
||||
dependencies:
|
||||
"@sentry/types": 6.19.7
|
||||
tslib: ^1.9.3
|
||||
checksum: a000223b9c646c64e3565e79cace1eeb75114342b768367c4dddd646476c215eb1bddfb70c63f05e2352d3bce2d7d415344e4757a001605d0e01ac74da5dd306
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/utils@npm:7.20.0":
|
||||
version: 7.20.0
|
||||
resolution: "@sentry/utils@npm:7.20.0"
|
||||
@@ -9989,6 +10191,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/webpack-plugin@npm:1.18.8":
|
||||
version: 1.18.8
|
||||
resolution: "@sentry/webpack-plugin@npm:1.18.8"
|
||||
dependencies:
|
||||
"@sentry/cli": ^1.73.0
|
||||
checksum: e159ee1e4b486a6d602bcf04e08ce4f167c9148aa4b612f6871f81954fba0cf607297a77bcda22f6f882266ac715ce8cb7be55858eaebc9fdbb85737ae92eb04
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/webpack-plugin@npm:1.20.0":
|
||||
version: 1.20.0
|
||||
resolution: "@sentry/webpack-plugin@npm:1.20.0"
|
||||
@@ -11814,6 +12025,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:0.4.11":
|
||||
version: 0.4.11
|
||||
resolution: "@swc/helpers@npm:0.4.11"
|
||||
dependencies:
|
||||
tslib: ^2.4.0
|
||||
checksum: 736857d524b41a8a4db81094e9b027f554004e0fa3e86325d85bdb38f7e6459ce022db079edb6c61ba0f46fe8583b3e663e95f7acbd13e51b8da6c34e45bba2e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:0.4.14":
|
||||
version: 0.4.14
|
||||
resolution: "@swc/helpers@npm:0.4.14"
|
||||
@@ -22715,9 +22935,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"github-buttons@npm:^2.22.0":
|
||||
version: 2.24.0
|
||||
resolution: "github-buttons@npm:2.24.0"
|
||||
checksum: 3bf27f9078180e36cfc87e214e788898da603d606c871deedc40b7edef4abf3f659a6cff65bd6990c07f59f473468de92174a04a91c42ca95b868d5da1c4d287
|
||||
version: 2.26.0
|
||||
resolution: "github-buttons@npm:2.26.0"
|
||||
checksum: e06e8dd23195201250ba4455e3044f75306fbe76400a3978014a5d7490a24e5e360420f50912e3e00ee5a6fa2ad76afe24ec62f9144d529059bf3c1226498f2a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -26006,13 +26226,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jose@npm:4.11.1":
|
||||
version: 4.11.1
|
||||
resolution: "jose@npm:4.11.1"
|
||||
checksum: cd15cba258d0fd20f6168631ce2e94fda8442df80e43c1033c523915cecdf390a1cc8efe0eab0c2d65935ca973d791c668aea80724d2aa9c2879d4e70f3081d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jose@npm:4.12.0":
|
||||
version: 4.12.0
|
||||
resolution: "jose@npm:4.12.0"
|
||||
@@ -29294,6 +29507,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-axiom@npm:^0.10.0":
|
||||
version: 0.10.0
|
||||
resolution: "next-axiom@npm:0.10.0"
|
||||
dependencies:
|
||||
whatwg-fetch: ^3.6.2
|
||||
peerDependencies:
|
||||
next: ^12.1.4
|
||||
checksum: 57aea4edbdab1a4da59eb16644e850a082a23657128f1f953a2b3601b09fa4558090d42b616f8a3e6022c0deef0d95937321f373caa9ca591178dd8445528921
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-axiom@npm:^0.16.0":
|
||||
version: 0.16.0
|
||||
resolution: "next-axiom@npm:0.16.0"
|
||||
@@ -29401,6 +29625,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-transpile-modules@npm:^9.0.0":
|
||||
version: 9.1.0
|
||||
resolution: "next-transpile-modules@npm:9.1.0"
|
||||
dependencies:
|
||||
enhanced-resolve: ^5.10.0
|
||||
escalade: ^3.1.1
|
||||
checksum: 8cc46196db3c2d2063fb29fe5b4d03c21065ab08130085b24d61e4ed512d99c12083d28179771cf02f70f8bf5970db0781a228aacf6cc61662dbdcabaddfc472
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-validations@npm:^0.2.0":
|
||||
version: 0.2.1
|
||||
resolution: "next-validations@npm:0.2.1"
|
||||
@@ -29481,6 +29715,75 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:^12.2.5":
|
||||
version: 12.3.4
|
||||
resolution: "next@npm:12.3.4"
|
||||
dependencies:
|
||||
"@next/env": 12.3.4
|
||||
"@next/swc-android-arm-eabi": 12.3.4
|
||||
"@next/swc-android-arm64": 12.3.4
|
||||
"@next/swc-darwin-arm64": 12.3.4
|
||||
"@next/swc-darwin-x64": 12.3.4
|
||||
"@next/swc-freebsd-x64": 12.3.4
|
||||
"@next/swc-linux-arm-gnueabihf": 12.3.4
|
||||
"@next/swc-linux-arm64-gnu": 12.3.4
|
||||
"@next/swc-linux-arm64-musl": 12.3.4
|
||||
"@next/swc-linux-x64-gnu": 12.3.4
|
||||
"@next/swc-linux-x64-musl": 12.3.4
|
||||
"@next/swc-win32-arm64-msvc": 12.3.4
|
||||
"@next/swc-win32-ia32-msvc": 12.3.4
|
||||
"@next/swc-win32-x64-msvc": 12.3.4
|
||||
"@swc/helpers": 0.4.11
|
||||
caniuse-lite: ^1.0.30001406
|
||||
postcss: 8.4.14
|
||||
styled-jsx: 5.0.7
|
||||
use-sync-external-store: 1.2.0
|
||||
peerDependencies:
|
||||
fibers: ">= 3.1.0"
|
||||
node-sass: ^6.0.0 || ^7.0.0
|
||||
react: ^17.0.2 || ^18.0.0-0
|
||||
react-dom: ^17.0.2 || ^18.0.0-0
|
||||
sass: ^1.3.0
|
||||
dependenciesMeta:
|
||||
"@next/swc-android-arm-eabi":
|
||||
optional: true
|
||||
"@next/swc-android-arm64":
|
||||
optional: true
|
||||
"@next/swc-darwin-arm64":
|
||||
optional: true
|
||||
"@next/swc-darwin-x64":
|
||||
optional: true
|
||||
"@next/swc-freebsd-x64":
|
||||
optional: true
|
||||
"@next/swc-linux-arm-gnueabihf":
|
||||
optional: true
|
||||
"@next/swc-linux-arm64-gnu":
|
||||
optional: true
|
||||
"@next/swc-linux-arm64-musl":
|
||||
optional: true
|
||||
"@next/swc-linux-x64-gnu":
|
||||
optional: true
|
||||
"@next/swc-linux-x64-musl":
|
||||
optional: true
|
||||
"@next/swc-win32-arm64-msvc":
|
||||
optional: true
|
||||
"@next/swc-win32-ia32-msvc":
|
||||
optional: true
|
||||
"@next/swc-win32-x64-msvc":
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
fibers:
|
||||
optional: true
|
||||
node-sass:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
bin:
|
||||
next: dist/bin/next
|
||||
checksum: d96fc4f5bcd5a630d74111519f4820dcbd75dddf16c6d00d2167bd3cb8d74965d46d83c8e5ec301bf999013c7d96f1bfff9424f0221317d68b594c4d01f5825e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:^13.2.1":
|
||||
version: 13.2.3
|
||||
resolution: "next@npm:13.2.3"
|
||||
@@ -29990,13 +30293,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"oauth4webapi@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "oauth4webapi@npm:2.0.5"
|
||||
checksum: 32d0cb7b1cca42d51dfb88075ca2d69fe33172a807e8ea50e317d17cab3bc80588ab8ebcb7eb4600c371a70af4674595b4b341daf6f3a655f1efa1ab715bb6c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"oauth@npm:^0.9.15":
|
||||
version: 0.9.15
|
||||
resolution: "oauth@npm:0.9.15"
|
||||
@@ -31303,23 +31599,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright-core@npm:1.32.1":
|
||||
version: 1.32.1
|
||||
resolution: "playwright-core@npm:1.32.1"
|
||||
"playwright-core@npm:1.32.2":
|
||||
version: 1.32.2
|
||||
resolution: "playwright-core@npm:1.32.2"
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 796185e605df3c970885845c1fbf2de53db5d089d37946850483797c91b81ab9dc87c3440695e96a8c92613324ef3deaea698afaf2c379548a23f8ce074931de
|
||||
checksum: ff000cbf280e5d558fe70fd3edf14910a2e86ec68b04e28327176268345be7b3f88a5d22d78e8dae677dd633dce6cd493237df199773b55312f2ae1ab85d711f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright@npm:^1.31.2":
|
||||
version: 1.32.1
|
||||
resolution: "playwright@npm:1.32.1"
|
||||
version: 1.32.2
|
||||
resolution: "playwright@npm:1.32.2"
|
||||
dependencies:
|
||||
playwright-core: 1.32.1
|
||||
playwright-core: 1.32.2
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: bb2cef8812abf60cf07bbf6edb4ac480ccbf0b129e001fd4f2d499c8888918bc87dcc0f346b5954e6b67cd67528c8263593b091f3c83609a91c8a276196c8951
|
||||
checksum: 36967299a5c4c02830bcb7fb94b96d6c6a7a2d7e749feb924509e89b1fdc34f8ee2218cfaf75e5f32572663568b468ad28fdc170bced41869d5ed69f9a7ff384
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -31652,17 +31948,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"preact-render-to-string@npm:5.2.3":
|
||||
version: 5.2.3
|
||||
resolution: "preact-render-to-string@npm:5.2.3"
|
||||
dependencies:
|
||||
pretty-format: ^3.8.0
|
||||
peerDependencies:
|
||||
preact: ">=10"
|
||||
checksum: 6e46288d8956adde35b9fe3a21aecd9dea29751b40f0f155dea62f3896f27cb8614d457b32f48d33909d2da81135afcca6c55077520feacd7d15164d1371fb44
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"preact-render-to-string@npm:^5.1.19":
|
||||
version: 5.2.6
|
||||
resolution: "preact-render-to-string@npm:5.2.6"
|
||||
@@ -31674,13 +31959,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"preact@npm:10.11.3, preact@npm:^10.6.3":
|
||||
version: 10.11.3
|
||||
resolution: "preact@npm:10.11.3"
|
||||
checksum: 9387115aa0581e8226309e6456e9856f17dfc0e3d3e63f774de80f3d462a882ba7c60914c05942cb51d51e23e120dcfe904b8d392d46f29ad15802941fe7a367
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"preact@npm:10.4.1":
|
||||
version: 10.4.1
|
||||
resolution: "preact@npm:10.4.1"
|
||||
@@ -31695,6 +31973,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"preact@npm:^10.6.3":
|
||||
version: 10.11.3
|
||||
resolution: "preact@npm:10.11.3"
|
||||
checksum: 9387115aa0581e8226309e6456e9856f17dfc0e3d3e63f774de80f3d462a882ba7c60914c05942cb51d51e23e120dcfe904b8d392d46f29ad15802941fe7a367
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prelude-ls@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "prelude-ls@npm:1.2.1"
|
||||
@@ -36273,6 +36558,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"styled-jsx@npm:5.0.7":
|
||||
version: 5.0.7
|
||||
resolution: "styled-jsx@npm:5.0.7"
|
||||
peerDependencies:
|
||||
react: ">= 16.8.0 || 17.x.x || ^18.0.0-0"
|
||||
peerDependenciesMeta:
|
||||
"@babel/core":
|
||||
optional: true
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
checksum: 61959993915f4b1662a682dbbefb3512de9399cf6901969bcadd26ba5441d2b5ca5c1021b233bbd573da2541b41efb45d56c6f618dbc8d88a381ebc62461fefe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"styled-jsx@npm:5.1.1":
|
||||
version: 5.1.1
|
||||
resolution: "styled-jsx@npm:5.1.1"
|
||||
@@ -37956,6 +38255,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^4.7.4":
|
||||
version: 4.9.5
|
||||
resolution: "typescript@npm:4.9.5"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^4.9.4":
|
||||
version: 4.9.4
|
||||
resolution: "typescript@npm:4.9.4"
|
||||
@@ -37966,6 +38275,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@^4.7.4#~builtin<compat/typescript>":
|
||||
version: 4.9.5
|
||||
resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin<compat/typescript>::version=4.9.5&hash=23ec76"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: ab417a2f398380c90a6cf5a5f74badd17866adf57f1165617d6a551f059c3ba0a3e4da0d147b3ac5681db9ac76a303c5876394b13b3de75fdd5b1eaa06181c9d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@^4.9.4#~builtin<compat/typescript>":
|
||||
version: 4.9.4
|
||||
resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin<compat/typescript>::version=4.9.4&hash=23ec76"
|
||||
@@ -37977,16 +38296,16 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"tzdata@npm:^1.0.30":
|
||||
version: 1.0.37
|
||||
resolution: "tzdata@npm:1.0.37"
|
||||
checksum: 5263d22e257639f292e314c15942d147e82211b370cbeccd5e16025b0fdb06371fdb989f247c386db60b580b2fe12b39601a3c21d35e09a037d60a92323cb3cd
|
||||
version: 1.0.38
|
||||
resolution: "tzdata@npm:1.0.38"
|
||||
checksum: 866abff1316bd1311057f5de9da702c78a27f3bba213aa6986e8e4d22d0d25b554dda2330e6d853e2c34566270bb327babe467ad9a5f069ad466f09571872800
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ua-parser-js@npm:^1.0.33":
|
||||
version: 1.0.34
|
||||
resolution: "ua-parser-js@npm:1.0.34"
|
||||
checksum: 78924548870c67b93b4bc6bd3d9736badfca8cc02ea01510fb0ed38e296a8c0713a6bec6226ed2383d5d521bab651a6fcaa75d421f04cafebca0f93250480fb3
|
||||
version: 1.0.35
|
||||
resolution: "ua-parser-js@npm:1.0.35"
|
||||
checksum: 02370d38a0c8b586f2503d1c3bbba5cbc0b97d407282f9023201a99e4c03eae4357a2800fdf50cf80d73ec25c0b0cc5bfbaa03975b0add4043d6e4c86712c9c1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -40555,7 +40874,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.17.3":
|
||||
"zod@npm:^3.17.3, zod@npm:^3.18.0":
|
||||
version: 3.21.4
|
||||
resolution: "zod@npm:3.21.4"
|
||||
checksum: f185ba87342ff16f7a06686767c2b2a7af41110c7edf7c1974095d8db7a73792696bcb4a00853de0d2edeb34a5b2ea6a55871bc864227dace682a0a28de33e1f
|
||||
|
||||
Reference in New Issue
Block a user