Merge branch 'main' of ssh://github.com/calcom/cal.com into feature/reschedule-bookings
This commit is contained in:
commit
542b7b9259
|
@ -0,0 +1,54 @@
|
|||
import { InformationCircleIcon } from "@heroicons/react/outline";
|
||||
import { Trans } from "next-i18next";
|
||||
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
export function UpgradeToProDialog({
|
||||
modalOpen,
|
||||
setModalOpen,
|
||||
children,
|
||||
}: {
|
||||
modalOpen: boolean;
|
||||
setModalOpen: (open: boolean) => void;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<Dialog open={modalOpen}>
|
||||
<DialogContent>
|
||||
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100">
|
||||
<InformationCircleIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mb-4 sm:flex sm:items-start">
|
||||
<div className="mt-3 sm:mt-0 sm:text-left">
|
||||
<h3 className="font-cal text-lg font-bold leading-6 text-gray-900" id="modal-title">
|
||||
{t("only_available_on_pro_plan")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3">
|
||||
<p>{children}</p>
|
||||
<p>
|
||||
<Trans i18nKey="plan_upgrade_instructions">
|
||||
You can
|
||||
<a href="/api/upgrade" className="underline">
|
||||
upgrade here
|
||||
</a>
|
||||
.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<DialogClose asChild>
|
||||
<Button className="btn-wide table-cell text-center" onClick={() => setModalOpen(false)}>
|
||||
{t("dismiss")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -52,7 +52,7 @@ type BookingFormValues = {
|
|||
};
|
||||
};
|
||||
|
||||
const BookingPage = ({ eventType, booking, profile }: BookingPageProps) => {
|
||||
const BookingPage = ({ eventType, booking, profile, locationLabels }: BookingPageProps) => {
|
||||
const { t, i18n } = useLocale();
|
||||
const router = useRouter();
|
||||
const { contracts } = useContracts();
|
||||
|
@ -130,21 +130,6 @@ const BookingPage = ({ eventType, booking, profile }: BookingPageProps) => {
|
|||
const telemetry = useTelemetry();
|
||||
|
||||
const locationInfo = (type: LocationType) => locations.find((location) => location.type === type);
|
||||
|
||||
// TODO: Move to translations
|
||||
// Also TODO: Get these dynamically from App Store
|
||||
const locationLabels = {
|
||||
[LocationType.InPerson]: t("in_person_meeting"),
|
||||
[LocationType.Phone]: t("phone_call"),
|
||||
[LocationType.Link]: t("link_meeting"),
|
||||
[LocationType.GoogleMeet]: "Google Meet",
|
||||
[LocationType.Zoom]: "Zoom Video",
|
||||
[LocationType.Jitsi]: "Jitsi Meet",
|
||||
[LocationType.Daily]: "Cal Video",
|
||||
[LocationType.Huddle01]: "Huddle01 Video",
|
||||
[LocationType.Tandem]: "Tandem Video",
|
||||
[LocationType.Teams]: "MS Teams",
|
||||
};
|
||||
const loggedInIsOwner = eventType?.users[0]?.name === session?.user?.name;
|
||||
const defaultValues = () => {
|
||||
if (!rescheduleUid) {
|
||||
|
|
|
@ -23,7 +23,7 @@ Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, s
|
|||
and/or sell the Software.
|
||||
|
||||
This EE License applies only to the part of this Software that is not distributed under
|
||||
the AGPLv3 license. Any part of this Software distributed under the MIT license or which
|
||||
the AGPLv3 license. Any part of this Software distributed under the AGPLv3 license or which
|
||||
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
|
||||
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
|
||||
in part, is copyrighted under the AGPLv3 license. The full text of this EE License shall
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { Team, User } from ".prisma/client";
|
||||
|
||||
export function isSuccessRedirectAvailable(
|
||||
eventType: {
|
||||
users: {
|
||||
plan: User["plan"];
|
||||
}[];
|
||||
} & {
|
||||
team: Partial<Team> | null;
|
||||
}
|
||||
) {
|
||||
// As Team Event is available in PRO plan only, just check if it's a team event.
|
||||
return eventType.users[0]?.plan !== "FREE" || eventType.team;
|
||||
}
|
|
@ -1 +1 @@
|
|||
export * from "@calcom/lib/location";
|
||||
export * from "@calcom/core/location";
|
||||
|
|
|
@ -5,12 +5,15 @@ import utc from "dayjs/plugin/utc";
|
|||
import { GetServerSidePropsContext } from "next";
|
||||
import { JSONObject } from "superjson/dist/types";
|
||||
|
||||
import { getLocationLabels } from "@calcom/app-store/utils";
|
||||
|
||||
import { asStringOrThrow } from "@lib/asStringOrNull";
|
||||
import prisma from "@lib/prisma";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import BookingPage from "@components/booking/pages/BookingPage";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
@ -133,8 +136,11 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
booking = await getBooking();
|
||||
}
|
||||
|
||||
const t = await getTranslation(context.locale ?? "en", "common");
|
||||
|
||||
return {
|
||||
props: {
|
||||
locationLabels: getLocationLabels(t),
|
||||
profile: {
|
||||
slug: user.username,
|
||||
name: user.name,
|
||||
|
|
|
@ -106,7 +106,7 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
|
|||
/* If the app doesn't have a README we fallback to the packagfe description */
|
||||
source = fs.readFileSync(postFilePath).toString();
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
console.log(`No README.mdx provided for: ${appDirname}`);
|
||||
source = singleApp.description;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import utc from "dayjs/plugin/utc";
|
|||
import { GetServerSidePropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Controller, Noop, useForm } from "react-hook-form";
|
||||
import { Controller, Noop, useForm, UseFormReturn } from "react-hook-form";
|
||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
import Select, { Props as SelectProps } from "react-select";
|
||||
import { JSONObject } from "superjson/dist/types";
|
||||
|
@ -42,6 +42,7 @@ import { QueryCell } from "@lib/QueryCell";
|
|||
import { asStringOrThrow, asStringOrUndefined } from "@lib/asStringOrNull";
|
||||
import { getSession } from "@lib/auth";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
import { isSuccessRedirectAvailable } from "@lib/isSuccessRedirectAvailable";
|
||||
import { LocationType } from "@lib/location";
|
||||
import prisma from "@lib/prisma";
|
||||
import { slugify } from "@lib/slugify";
|
||||
|
@ -52,8 +53,10 @@ import { ClientSuspense } from "@components/ClientSuspense";
|
|||
import DestinationCalendarSelector from "@components/DestinationCalendarSelector";
|
||||
import Loader from "@components/Loader";
|
||||
import Shell from "@components/Shell";
|
||||
import { UpgradeToProDialog } from "@components/UpgradeToProDialog";
|
||||
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
|
||||
import CustomInputTypeForm from "@components/pages/eventtypes/CustomInputTypeForm";
|
||||
import Badge from "@components/ui/Badge";
|
||||
import InfoBadge from "@components/ui/InfoBadge";
|
||||
import CheckboxField from "@components/ui/form/CheckboxField";
|
||||
import CheckedSelect from "@components/ui/form/CheckedSelect";
|
||||
|
@ -86,6 +89,53 @@ type OptionTypeBase = {
|
|||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const SuccessRedirectEdit = <T extends UseFormReturn<any, any>>({
|
||||
eventType,
|
||||
formMethods,
|
||||
}: {
|
||||
eventType: inferSSRProps<typeof getServerSideProps>["eventType"];
|
||||
formMethods: T;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const proUpgradeRequired = !isSuccessRedirectAvailable(eventType);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<hr className="border-neutral-200" />
|
||||
<div className="block sm:flex">
|
||||
<div className="min-w-48 sm:mb-0">
|
||||
<label
|
||||
htmlFor="successRedirectUrl"
|
||||
className="flex h-full items-center text-sm font-medium text-neutral-700">
|
||||
{t("redirect_success_booking")}
|
||||
<span className="ml-1">{proUpgradeRequired && <Badge variant="default">PRO</Badge>}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<input
|
||||
id="successRedirectUrl"
|
||||
onClick={(e) => {
|
||||
if (proUpgradeRequired) {
|
||||
e.preventDefault();
|
||||
setModalOpen(true);
|
||||
}
|
||||
}}
|
||||
readOnly={proUpgradeRequired}
|
||||
type="url"
|
||||
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
|
||||
placeholder={t("external_redirect_url")}
|
||||
defaultValue={eventType.successRedirectUrl || ""}
|
||||
{...formMethods.register("successRedirectUrl")}
|
||||
/>
|
||||
</div>
|
||||
<UpgradeToProDialog modalOpen={modalOpen} setModalOpen={setModalOpen}>
|
||||
{t("redirect_url_upgrade_description")}
|
||||
</UpgradeToProDialog>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type AvailabilityOption = {
|
||||
label: string;
|
||||
value: number;
|
||||
|
@ -166,13 +216,21 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
);
|
||||
},
|
||||
onError: (err) => {
|
||||
let message = "";
|
||||
if (err instanceof HttpError) {
|
||||
const message = `${err.statusCode}: ${err.message}`;
|
||||
showToast(message, "error");
|
||||
}
|
||||
|
||||
if (err.data?.code === "UNAUTHORIZED") {
|
||||
const message = `${err.data.code}: You are not able to update this event`;
|
||||
message = `${err.data.code}: You are not able to update this event`;
|
||||
}
|
||||
|
||||
if (err.data?.code === "PARSE_ERROR") {
|
||||
message = `${err.data.code}: ${err.message}`;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
showToast(message, "error");
|
||||
}
|
||||
},
|
||||
|
@ -432,6 +490,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
integration: string;
|
||||
externalId: string;
|
||||
};
|
||||
successRedirectUrl: string;
|
||||
}>({
|
||||
defaultValues: {
|
||||
locations: eventType.locations || [],
|
||||
|
@ -1508,7 +1567,9 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SuccessRedirectEdit<typeof formMethods>
|
||||
formMethods={formMethods}
|
||||
eventType={eventType}></SuccessRedirectEdit>
|
||||
{hasPaymentIntegration && (
|
||||
<>
|
||||
<hr className="border-neutral-200" />
|
||||
|
@ -1831,6 +1892,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
id: true,
|
||||
avatar: true,
|
||||
email: true,
|
||||
plan: true,
|
||||
locale: true,
|
||||
});
|
||||
|
||||
|
@ -1890,6 +1952,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
beforeEventBuffer: true,
|
||||
afterEventBuffer: true,
|
||||
slotInterval: true,
|
||||
successRedirectUrl: true,
|
||||
team: {
|
||||
select: {
|
||||
slug: true,
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { InformationCircleIcon } from "@heroicons/react/outline";
|
||||
import { TrashIcon } from "@heroicons/react/solid";
|
||||
import crypto from "crypto";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import { signOut } from "next-auth/react";
|
||||
import { Trans } from "next-i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { ComponentProps, FormEvent, RefObject, useEffect, useMemo, useRef, useState } from "react";
|
||||
import Select from "react-select";
|
||||
|
@ -12,7 +10,7 @@ import TimezoneSelect, { ITimezone } from "react-timezone-select";
|
|||
import showToast from "@calcom/lib/notification";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
|
||||
import { Dialog, DialogTrigger } from "@calcom/ui/Dialog";
|
||||
import { TextField } from "@calcom/ui/form/fields";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
@ -33,11 +31,13 @@ import Avatar from "@components/ui/Avatar";
|
|||
import Badge from "@components/ui/Badge";
|
||||
import ColorPicker from "@components/ui/colorpicker";
|
||||
|
||||
import { UpgradeToProDialog } from "../../components/UpgradeToProDialog";
|
||||
|
||||
type Props = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
function HideBrandingInput(props: { hideBrandingRef: RefObject<HTMLInputElement>; user: Props["user"] }) {
|
||||
const { t } = useLocale();
|
||||
const [modelOpen, setModalOpen] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -61,39 +61,9 @@ function HideBrandingInput(props: { hideBrandingRef: RefObject<HTMLInputElement>
|
|||
setModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<Dialog open={modelOpen}>
|
||||
<DialogContent>
|
||||
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100">
|
||||
<InformationCircleIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mb-4 sm:flex sm:items-start">
|
||||
<div className="mt-3 sm:mt-0 sm:text-left">
|
||||
<h3 className="font-cal text-lg leading-6 text-gray-900" id="modal-title">
|
||||
{t("only_available_on_pro_plan")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3">
|
||||
<p>{t("remove_cal_branding_description")}</p>
|
||||
<p>
|
||||
<Trans i18nKey="plan_upgrade_instructions">
|
||||
You can
|
||||
<a href="/api/upgrade" className="underline">
|
||||
upgrade here
|
||||
</a>
|
||||
.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<DialogClose asChild>
|
||||
<Button className="btn-wide table-cell text-center" onClick={() => setModalOpen(false)}>
|
||||
{t("dismiss")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<UpgradeToProDialog modalOpen={modalOpen} setModalOpen={setModalOpen}>
|
||||
{t("remove_cal_branding_description")}
|
||||
</UpgradeToProDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@ import { createEvent } from "ics";
|
|||
import { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
|
||||
import { sdkActionManager } from "@calcom/embed-core";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { EventType, Team, User } from "@calcom/prisma/client";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { EmailInput } from "@calcom/ui/form/fields";
|
||||
|
||||
|
@ -19,6 +20,7 @@ import { asStringOrThrow, asStringOrNull } from "@lib/asStringOrNull";
|
|||
import { getEventName } from "@lib/event";
|
||||
import useTheme from "@lib/hooks/useTheme";
|
||||
import { isBrandingHidden } from "@lib/isBrandingHidden";
|
||||
import { isSuccessRedirectAvailable } from "@lib/isSuccessRedirectAvailable";
|
||||
import prisma from "@lib/prisma";
|
||||
import { isBrowserLocale24h } from "@lib/timeFormat";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
@ -32,6 +34,111 @@ dayjs.extend(utc);
|
|||
dayjs.extend(toArray);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
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 parsedSuccessUrl = new URL(document.URL);
|
||||
const parsedExternalUrl = new URL(url);
|
||||
|
||||
/* @ts-ignore */ //https://stackoverflow.com/questions/49218765/typescript-and-iterator-type-iterableiteratort-is-not-an-array-type
|
||||
for (let [name, value] of parsedExternalUrl.searchParams.entries()) {
|
||||
parsedSuccessUrl.searchParams.set(name, value);
|
||||
}
|
||||
|
||||
const urlWithSuccessParams =
|
||||
parsedExternalUrl.origin +
|
||||
parsedExternalUrl.pathname +
|
||||
"?" +
|
||||
parsedSuccessUrl.searchParams.toString() +
|
||||
parsedExternalUrl.hash;
|
||||
|
||||
const { t } = useLocale();
|
||||
const timerRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
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, urlWithSuccessParams]);
|
||||
|
||||
if (!isToastVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* z-index just higher than Success Message Box */}
|
||||
<div className="fixed inset-x-0 top-4 z-[60] pb-2 sm:pb-5">
|
||||
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
|
||||
<div className="rounded-sm bg-red-600 bg-green-500 p-2 shadow-lg sm:p-3">
|
||||
<div className="flex flex-wrap items-center justify-between">
|
||||
<div className="flex w-0 flex-1 items-center">
|
||||
<p className="ml-3 truncate font-medium text-white">
|
||||
<span className="md:hidden">Redirecting to {url} ...</span>
|
||||
<span className="hidden md:inline">
|
||||
You are being redirected to {url} in {timeRemaining}{" "}
|
||||
{timeRemaining === 1 ? "second" : "seconds"}.
|
||||
</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={() => {
|
||||
redirectToExternalUrl(urlWithSuccessParams);
|
||||
}}
|
||||
className="flex items-center justify-center rounded-sm border border-transparent bg-white px-4 py-2 text-sm font-medium text-indigo-600 shadow-sm hover:bg-indigo-50">
|
||||
{t("Continue")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="order-2 flex-shrink-0 sm:order-3 sm:ml-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsToastVisible(false);
|
||||
window.clearInterval(timerRef.current as number);
|
||||
}}
|
||||
className="-mr-1 flex rounded-md p-2 hover:bg-indigo-500 focus:outline-none focus:ring-2 focus:ring-white">
|
||||
<svg
|
||||
className="h-6 w-6 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Success(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
|
@ -114,6 +221,9 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
|||
/>
|
||||
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
|
||||
<main className="mx-auto max-w-3xl py-24">
|
||||
{isSuccessRedirectAvailable(eventType) && eventType.successRedirectUrl ? (
|
||||
<RedirectionToast url={eventType.successRedirectUrl}></RedirectionToast>
|
||||
) : null}
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div className="fixed inset-0 my-4 transition-opacity sm:my-0" aria-hidden="true">
|
||||
|
@ -329,6 +439,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
eventName: true,
|
||||
requiresConfirmation: true,
|
||||
userId: true,
|
||||
successRedirectUrl: true,
|
||||
users: {
|
||||
select: {
|
||||
name: true,
|
||||
|
|
|
@ -2,12 +2,16 @@ import { Prisma } from "@prisma/client";
|
|||
import { GetServerSidePropsContext } from "next";
|
||||
import { JSONObject } from "superjson/dist/types";
|
||||
|
||||
import { getLocationLabels } from "@calcom/app-store/utils";
|
||||
|
||||
import { asStringOrThrow } from "@lib/asStringOrNull";
|
||||
import prisma from "@lib/prisma";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import BookingPage from "@components/booking/pages/BookingPage";
|
||||
|
||||
import { getTranslation } from "@server/lib/i18n";
|
||||
|
||||
export type TeamBookingPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function TeamBookingPage(props: TeamBookingPageProps) {
|
||||
|
@ -94,8 +98,11 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
booking = await getBooking();
|
||||
}
|
||||
|
||||
const t = await getTranslation(context.locale ?? "en", "common");
|
||||
|
||||
return {
|
||||
props: {
|
||||
locationLabels: getLocationLabels(t),
|
||||
profile: {
|
||||
...eventTypeObject.team,
|
||||
slug: "team/" + eventTypeObject.slug,
|
||||
|
|
|
@ -151,7 +151,7 @@ test.describe("pro user", () => {
|
|||
await bookFirstEvent(page);
|
||||
|
||||
await page.goto("/bookings/upcoming");
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
await page.locator('[data-testid="cancel"]').first().click();
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
return url.pathname.startsWith("/cancel");
|
||||
|
|
|
@ -713,6 +713,9 @@
|
|||
"time_format": "Time format",
|
||||
"12_hour": "12 hour",
|
||||
"24_hour": "24 hour",
|
||||
"redirect_success_booking": "Redirect on booking ",
|
||||
"external_redirect_url": "External Redirect URL - Starts with https://",
|
||||
"redirect_url_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.",
|
||||
"duplicate": "Duplicate",
|
||||
"you_can_manage_your_schedules": "You can manage your schedules on the Availability page.",
|
||||
"request_reschedule_booking": "Request to reschedule your booking",
|
||||
|
|
|
@ -131,6 +131,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
price: true,
|
||||
currency: true,
|
||||
position: true,
|
||||
successRedirectUrl: true,
|
||||
users: {
|
||||
select: {
|
||||
id: true,
|
||||
|
|
|
@ -18,6 +18,22 @@ function isPeriodType(keyInput: string): keyInput is PeriodType {
|
|||
return Object.keys(PeriodType).includes(keyInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that it is a valid HTTP URL
|
||||
* It automatically avoids
|
||||
* - XSS attempts through javascript:alert('hi')
|
||||
* - mailto: links
|
||||
*/
|
||||
function assertValidUrl(url: string | null | undefined) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
throw new TRPCError({ code: "PARSE_ERROR", message: "Invalid URL" });
|
||||
}
|
||||
}
|
||||
|
||||
function handlePeriodType(periodType: string | undefined): PeriodType | undefined {
|
||||
if (typeof periodType !== "string") return undefined;
|
||||
const passedPeriodType = periodType.toUpperCase();
|
||||
|
@ -97,7 +113,6 @@ export const eventTypesRouter = createProtectedRouter()
|
|||
input: createEventTypeInput,
|
||||
async resolve({ ctx, input }) {
|
||||
const { schedulingType, teamId, ...rest } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const data: Prisma.EventTypeCreateInput = {
|
||||
|
@ -181,9 +196,9 @@ export const eventTypesRouter = createProtectedRouter()
|
|||
async resolve({ ctx, input }) {
|
||||
const { schedule, periodType, locations, destinationCalendar, customInputs, users, id, ...rest } =
|
||||
input;
|
||||
assertValidUrl(input.successRedirectUrl);
|
||||
const data: Prisma.EventTypeUpdateInput = rest;
|
||||
data.locations = locations ?? undefined;
|
||||
|
||||
if (periodType) {
|
||||
data.periodType = handlePeriodType(periodType);
|
||||
}
|
||||
|
@ -211,7 +226,7 @@ export const eventTypesRouter = createProtectedRouter()
|
|||
if (users) {
|
||||
data.users = {
|
||||
set: [],
|
||||
connect: users.map((userId) => ({ id: userId })),
|
||||
connect: users.map((userId: number) => ({ id: userId })),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ export const metadata = {
|
|||
// If using static next public folder, can then be referenced from the base URL (/).
|
||||
imageSrc: "/api/app-store/_example/icon.svg",
|
||||
logo: "/api/app-store/_example/icon.svg",
|
||||
label: "Example App",
|
||||
publisher: "Cal.com",
|
||||
rating: 5,
|
||||
reviews: 69,
|
||||
|
|
|
@ -11,7 +11,6 @@ export const metadata = {
|
|||
imageSrc: "/api/app-store/applecalendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
label: "Apple Calendar",
|
||||
logo: "/api/app-store/applecalendar/icon.svg",
|
||||
publisher: "Cal.com",
|
||||
rating: 5,
|
||||
|
|
|
@ -11,7 +11,6 @@ export const metadata = {
|
|||
imageSrc: "/api/app-store/caldavcalendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
label: "CalDav Calendar",
|
||||
logo: "/api/app-store/caldavcalendar/icon.svg",
|
||||
publisher: "Cal.com",
|
||||
rating: 5,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import { LocationType } from "../locations";
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
|
@ -17,12 +18,12 @@ export const metadata = {
|
|||
rating: 4.3, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
reviews: 69, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
category: "video",
|
||||
label: "Cal Video",
|
||||
slug: "dailyvideo",
|
||||
title: "Cal Video",
|
||||
isGlobal: true,
|
||||
email: "help@cal.com",
|
||||
locationType: "integrations:daily",
|
||||
locationType: LocationType.Daily,
|
||||
locationLabel: "Cal Video",
|
||||
key: { apikey: process.env.DAILY_API_KEY },
|
||||
} as App;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { validJson } from "@calcom/lib/jsonUtils";
|
||||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import { LocationType } from "../locations";
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
|
@ -12,7 +13,6 @@ export const metadata = {
|
|||
imageSrc: "/api/app-store/googlecalendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
label: "Google Calendar",
|
||||
logo: "/api/app-store/googlecalendar/icon.svg",
|
||||
publisher: "Cal.com",
|
||||
rating: 5,
|
||||
|
@ -22,6 +22,8 @@ export const metadata = {
|
|||
url: "https://cal.com/",
|
||||
verified: true,
|
||||
email: "help@cal.com",
|
||||
locationType: LocationType.GoogleMeet,
|
||||
locationLabel: "Google Meet",
|
||||
} as App;
|
||||
|
||||
export * as api from "./api";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { validJson } from "@calcom/lib/jsonUtils";
|
||||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import { LocationType } from "../locations";
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
|
@ -13,7 +14,6 @@ export const metadata = {
|
|||
title: "Google Meet",
|
||||
imageSrc: "https://cdn.iconscout.com/icon/free/png-256/google-meet-2923654-2416657.png",
|
||||
variant: "conferencing",
|
||||
label: "Google Meet",
|
||||
logo: "https://cdn.iconscout.com/icon/free/png-256/google-meet-2923654-2416657.png",
|
||||
publisher: "Cal.com",
|
||||
rating: 5,
|
||||
|
@ -22,7 +22,8 @@ export const metadata = {
|
|||
url: "https://cal.com/",
|
||||
verified: true,
|
||||
email: "help@cal.com",
|
||||
locationType: "integrations:google:meet",
|
||||
locationType: LocationType.GoogleMeet,
|
||||
locationLabel: "Google Meet",
|
||||
} as App;
|
||||
|
||||
// export * as api from "./api";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { randomString } from "@calcom/lib/random";
|
||||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import { LocationType } from "../locations";
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
|
@ -17,13 +18,13 @@ export const metadata = {
|
|||
rating: 0, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
reviews: 0, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
category: "web3",
|
||||
label: "Huddle01 Video",
|
||||
slug: "huddle01_video",
|
||||
title: "Huddle01",
|
||||
trending: true,
|
||||
isGlobal: true,
|
||||
email: "support@huddle01.com",
|
||||
locationType: "integrations:huddle01",
|
||||
locationType: LocationType.Huddle01,
|
||||
locationLabel: "Huddle01 Video",
|
||||
key: { apikey: randomString(12) },
|
||||
} as App;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import { LocationType } from "../locations";
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
|
@ -10,19 +11,19 @@ export const metadata = {
|
|||
imageSrc: "/api/app-store/jitsivideo/icon.svg",
|
||||
variant: "conferencing",
|
||||
logo: "/api/app-store/jitsivideo/icon.svg",
|
||||
locationType: "integrations:jitsi",
|
||||
publisher: "Cal.com",
|
||||
url: "https://jitsi.org/",
|
||||
verified: true,
|
||||
rating: 0, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
reviews: 0, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
category: "video",
|
||||
label: "Jitsi Video",
|
||||
slug: "jitsi_video",
|
||||
title: "Jitsi Meet",
|
||||
trending: true,
|
||||
isGlobal: true,
|
||||
email: "help@cal.com",
|
||||
locationType: LocationType.Jitsi,
|
||||
locationLabel: "Jitsi Video",
|
||||
} as App;
|
||||
|
||||
export * as lib from "./lib";
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
/** TODO: These should all come from each individual App Store package, and merge them here. */
|
||||
export enum LocationType {
|
||||
export enum DefaultLocationType {
|
||||
InPerson = "inPerson",
|
||||
Phone = "phone",
|
||||
Link = "link",
|
||||
}
|
||||
|
||||
/** If your App has a location option, add it here */
|
||||
export enum AppStoreLocationType {
|
||||
GoogleMeet = "integrations:google:meet",
|
||||
Zoom = "integrations:zoom",
|
||||
Daily = "integrations:daily",
|
||||
|
@ -11,3 +14,6 @@ export enum LocationType {
|
|||
Tandem = "integrations:tandem",
|
||||
Teams = "integrations:office365_video",
|
||||
}
|
||||
|
||||
export const LocationType = { ...DefaultLocationType, ...AppStoreLocationType };
|
||||
export type LocationType = DefaultLocationType | AppStoreLocationType;
|
|
@ -11,7 +11,6 @@ export const metadata = {
|
|||
imageSrc: "/api/app-store/office365calendar/icon.svg",
|
||||
variant: "calendar",
|
||||
category: "calendar",
|
||||
label: "Example App",
|
||||
logo: "/api/app-store/office365calendar/icon.svg",
|
||||
publisher: "Cal.com",
|
||||
rating: 5,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import { LocationType } from "../locations";
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
|
@ -16,12 +17,12 @@ export const metadata = {
|
|||
rating: 4.3, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
reviews: 69, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
category: "video",
|
||||
label: "MS Teams",
|
||||
slug: "msteams",
|
||||
title: "MS Teams",
|
||||
trending: true,
|
||||
email: "help@cal.com",
|
||||
locationType: "integrations:office365_video",
|
||||
locationType: LocationType.Teams,
|
||||
locationLabel: "MS Teams",
|
||||
} as App;
|
||||
|
||||
export * as api from "./api";
|
||||
|
|
|
@ -17,7 +17,6 @@ export const metadata = {
|
|||
trending: true,
|
||||
reviews: 69,
|
||||
imageSrc: "/api/app-store/stripepayment/icon.svg",
|
||||
label: "Stripe",
|
||||
publisher: "Cal.com",
|
||||
title: "Stripe",
|
||||
type: "stripe_payment",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import { LocationType } from "../locations";
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
|
@ -10,7 +11,6 @@ export const metadata = {
|
|||
title: "Tandem Video",
|
||||
imageSrc: "/api/app-store/tandemvideo/icon.svg",
|
||||
variant: "conferencing",
|
||||
label: "",
|
||||
slug: "tandem",
|
||||
category: "video",
|
||||
logo: "/api/app-store/tandemvideo/icon.svg",
|
||||
|
@ -22,7 +22,8 @@ export const metadata = {
|
|||
reviews: 0,
|
||||
isGlobal: false,
|
||||
email: "help@cal.com",
|
||||
locationType: "integrations:tandem",
|
||||
locationType: LocationType.Tandem,
|
||||
locationLabel: "Tandem Video",
|
||||
} as App;
|
||||
|
||||
export * as api from "./api";
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
import { TFunction } from "next-i18next";
|
||||
|
||||
import { LocationType } from "@calcom/lib/location";
|
||||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import appStore from ".";
|
||||
import { LocationType } from "./locations";
|
||||
|
||||
const ALL_APPS_MAP = Object.keys(appStore).reduce((store, key) => {
|
||||
store[key] = appStore[key as keyof typeof appStore].metadata;
|
||||
|
@ -31,14 +31,13 @@ function translateLocations(locations: OptionTypeBase[], t: TFunction) {
|
|||
label: t(l.label),
|
||||
}));
|
||||
}
|
||||
const defaultLocations: OptionTypeBase[] = [
|
||||
{ value: LocationType.InPerson, label: "in_person_meeting" },
|
||||
{ value: LocationType.Link, label: "link_meeting" },
|
||||
{ value: LocationType.Phone, label: "phone_call" },
|
||||
];
|
||||
|
||||
export function getLocationOptions(integrations: AppMeta, t: TFunction) {
|
||||
const defaultLocations: OptionTypeBase[] = [
|
||||
{ value: LocationType.InPerson, label: "in_person_meeting" },
|
||||
{ value: LocationType.Link, label: "link_meeting" },
|
||||
{ value: LocationType.Phone, label: "phone_call" },
|
||||
];
|
||||
|
||||
integrations.forEach((app) => {
|
||||
if (app.locationOption) {
|
||||
defaultLocations.push(app.locationOption);
|
||||
|
@ -70,8 +69,8 @@ function getApps(userCredentials: CredentialData[]) {
|
|||
/** Check if app has location option AND add it if user has credentials for it */
|
||||
if (credentials.length > 0 && appMeta?.locationType) {
|
||||
locationOption = {
|
||||
value: appMeta.locationType as LocationType,
|
||||
label: appMeta.label,
|
||||
value: appMeta.locationType,
|
||||
label: appMeta.locationLabel || "No label set",
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
@ -112,6 +111,20 @@ export function getLocationTypes(): string[] {
|
|||
}, [] as string[]);
|
||||
}
|
||||
|
||||
export function getLocationLabels(t: TFunction) {
|
||||
const defaultLocationLabels = defaultLocations.reduce((locations, location) => {
|
||||
locations[location.value] = t(location.label);
|
||||
return locations;
|
||||
}, {} as Record<LocationType, string>);
|
||||
|
||||
return ALL_APPS.reduce((locations, app) => {
|
||||
if (typeof app.locationType === "string") {
|
||||
locations[app.locationType] = t(app.locationLabel || "No label set");
|
||||
}
|
||||
return locations;
|
||||
}, defaultLocationLabels);
|
||||
}
|
||||
|
||||
export function getAppName(name: string) {
|
||||
return ALL_APPS_MAP[name as keyof typeof ALL_APPS_MAP]?.name || "No App Name";
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import { LocationType } from "../locations";
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
|
@ -16,12 +17,12 @@ export const metadata = {
|
|||
rating: 4.3, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
reviews: 69, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
category: "video",
|
||||
label: "Zoom Video",
|
||||
slug: "zoom",
|
||||
title: "Zoom Video",
|
||||
trending: true,
|
||||
email: "help@cal.com",
|
||||
locationType: "integrations:zoom",
|
||||
locationType: LocationType.Zoom,
|
||||
locationLabel: "Zoom Video",
|
||||
} as App;
|
||||
|
||||
export * as api from "./api";
|
||||
|
|
|
@ -4,7 +4,6 @@ import merge from "lodash/merge";
|
|||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import { LocationType } from "@calcom/lib/location";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { AdditionInformation, CalendarEvent } from "@calcom/types/Calendar";
|
||||
import type {
|
||||
|
@ -16,6 +15,7 @@ import type {
|
|||
import type { VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
import { createEvent, updateEvent } from "./CalendarManager";
|
||||
import { LocationType } from "./location";
|
||||
import { createMeeting, updateMeeting } from "./videoClient";
|
||||
|
||||
export type Event = AdditionInformation & VideoCallData;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from "@calcom/app-store/locations";
|
|
@ -248,12 +248,16 @@ export default abstract class BaseCalendarService implements Calendar {
|
|||
const vcalendar = new ICAL.Component(jcalData);
|
||||
const vevent = vcalendar.getFirstSubcomponent("vevent");
|
||||
const event = new ICAL.Event(vevent);
|
||||
const timezoneComp = vcalendar.getFirstSubcomponent("vtimezone");
|
||||
const tzid: string = timezoneComp?.getFirstPropertyValue("tzid") ?? "UTC";
|
||||
const vtimezone = vcalendar.getFirstSubcomponent("vtimezone");
|
||||
if (vtimezone) {
|
||||
const zone = new ICAL.Timezone(vtimezone);
|
||||
event.startDate = event.startDate.convertToZone(zone);
|
||||
event.endDate = event.endDate.convertToZone(zone);
|
||||
}
|
||||
|
||||
return {
|
||||
start: dayjs.tz(event.startDate.toJSDate(), tzid).toISOString(),
|
||||
end: dayjs.tz(event.endDate.toJSDate(), tzid).toISOString(),
|
||||
start: dayjs(event.startDate.toJSDate()).toISOString(),
|
||||
end: dayjs(event.endDate.toJSDate()).toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "EventType" ADD COLUMN "successRedirectUrl" TEXT;
|
|
@ -68,6 +68,7 @@ model EventType {
|
|||
currency String @default("usd")
|
||||
slotInterval Int?
|
||||
metadata Json?
|
||||
successRedirectUrl String?
|
||||
|
||||
@@unique([userId, slug])
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { LocationType } from "@calcom/lib/location";
|
||||
import { LocationType } from "@calcom/core/location";
|
||||
import { slugify } from "@calcom/lib/slugify";
|
||||
|
||||
export const eventTypeLocations = z.array(
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
import type { Prisma } from "@prisma/client";
|
||||
|
||||
import type { LocationType } from "@calcom/app-store/locations";
|
||||
|
||||
/**
|
||||
* This is the definition for an app store's app metadata.
|
||||
|
@ -22,7 +24,6 @@ export interface App {
|
|||
imageSrc: string;
|
||||
/** TODO determine if we should use this instead of category */
|
||||
variant: "calendar" | "payment" | "conferencing";
|
||||
label: string;
|
||||
/** The slug for the app store public page inside `/apps/[slug] */
|
||||
slug: string;
|
||||
/** The category to which this app belongs, currently we have `calendar`, `payment` or `video` */
|
||||
|
@ -51,7 +52,9 @@ export interface App {
|
|||
/** A contact email, mainly to ask for support */
|
||||
email: string;
|
||||
/** Add this value as a posible location option in event types */
|
||||
locationType?: string;
|
||||
locationType?: LocationType;
|
||||
/** If the app adds a location, how should it be displayed? */
|
||||
locationLabel?: string;
|
||||
/** Needed API Keys (usually for global apps) */
|
||||
key?: Prisma.JsonValue;
|
||||
/** Needed API Keys (usually for global apps) */
|
||||
|
|
13
turbo.json
13
turbo.json
|
@ -73,7 +73,16 @@
|
|||
"dependsOn": ["@calcom/prisma#db-deploy"]
|
||||
},
|
||||
"@calcom/website#build": {
|
||||
"dependsOn": ["$WEBSITE_BASE_URL"],
|
||||
"dependsOn": [
|
||||
"$NEXT_PUBLIC_STRIPE_FREE_PLAN_PRICE",
|
||||
"$NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE",
|
||||
"$NEXT_PUBLIC_STRIPE_PRO_PLAN_PRICE",
|
||||
"$NEXT_PUBLIC_STRIPE_PRO_PLAN_PRODUCT",
|
||||
"$NEXT_PUBLIC_STRIPE_PUBLIC_KEY",
|
||||
"$NEXT_PUBLIC_WEBAPP_URL",
|
||||
"$NEXT_PUBLIC_WEBSITE_URL",
|
||||
"$STRIPE_WEBHOOK_SECRET"
|
||||
],
|
||||
"outputs": [".next/**"]
|
||||
},
|
||||
"build": {
|
||||
|
@ -83,7 +92,7 @@
|
|||
"db-deploy": {},
|
||||
"db-seed": {},
|
||||
"deploy": {
|
||||
"dependsOn": []
|
||||
"dependsOn": ["@calcom/web#build"]
|
||||
},
|
||||
"clean": {
|
||||
"cache": false
|
||||
|
|
Loading…
Reference in New Issue
Block a user