diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx
index 3ed55b9134..3d303dc368 100644
--- a/apps/web/components/booking/BookingListItem.tsx
+++ b/apps/web/components/booking/BookingListItem.tsx
@@ -151,7 +151,7 @@ function BookingListItem(booking: BookingItemProps) {
const RequestSentMessage = () => {
return (
-
+
{t("reschedule_request_sent")}
diff --git a/apps/web/components/booking/SkeletonLoader.tsx b/apps/web/components/booking/SkeletonLoader.tsx
index 1123b7fd78..e5d16750f0 100644
--- a/apps/web/components/booking/SkeletonLoader.tsx
+++ b/apps/web/components/booking/SkeletonLoader.tsx
@@ -16,7 +16,7 @@ export default SkeletonLoader;
function SkeletonItem() {
return (
-
+
diff --git a/apps/web/pages/v2/apps/[slug]/setup.tsx b/apps/web/pages/v2/apps/[slug]/setup.tsx
new file mode 100644
index 0000000000..a412c29b3a
--- /dev/null
+++ b/apps/web/pages/v2/apps/[slug]/setup.tsx
@@ -0,0 +1,41 @@
+import { GetStaticPaths, InferGetStaticPropsType } from "next";
+import { useSession } from "next-auth/react";
+import { useRouter } from "next/router";
+
+import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
+import { AppSetupPage } from "@calcom/app-store/_pages/v2/setup";
+import Loader from "@calcom/ui/Loader";
+
+export default function SetupInformation(props: InferGetStaticPropsType
) {
+ const router = useRouter();
+ const slug = router.query.slug as string;
+ const { status } = useSession();
+
+ if (status === "loading") {
+ return (
+
+
+
+ );
+ }
+
+ if (status === "unauthenticated") {
+ router.replace({
+ pathname: "/auth/login",
+ query: {
+ callbackUrl: `/apps/${slug}/setup`,
+ },
+ });
+ }
+
+ return ;
+}
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ return {
+ paths: [],
+ fallback: "blocking",
+ };
+};
+
+export { getStaticProps };
diff --git a/packages/app-store/_pages/v2/setup/_getStaticProps.tsx b/packages/app-store/_pages/v2/setup/_getStaticProps.tsx
new file mode 100644
index 0000000000..04e0d46ef6
--- /dev/null
+++ b/packages/app-store/_pages/v2/setup/_getStaticProps.tsx
@@ -0,0 +1,20 @@
+import { GetStaticPropsContext } from "next";
+
+export const AppSetupPageMap = {
+ zapier: import("../../../zapier/pages/setup/_getStaticProps"),
+};
+
+export const getStaticProps = async (ctx: GetStaticPropsContext) => {
+ const { slug } = ctx.params || {};
+ if (typeof slug !== "string") return { notFound: true } as const;
+
+ if (!(slug in AppSetupPageMap)) return { props: {} };
+
+ const page = await AppSetupPageMap[slug as keyof typeof AppSetupPageMap];
+
+ if (!page.getStaticProps) return { props: {} };
+
+ const props = await page.getStaticProps(ctx);
+
+ return props;
+};
diff --git a/packages/app-store/_pages/v2/setup/index.tsx b/packages/app-store/_pages/v2/setup/index.tsx
new file mode 100644
index 0000000000..05de0f9478
--- /dev/null
+++ b/packages/app-store/_pages/v2/setup/index.tsx
@@ -0,0 +1,18 @@
+import dynamic from "next/dynamic";
+
+import { DynamicComponent } from "../../../_components/DynamicComponent";
+
+export const AppSetupMap = {
+ "apple-calendar-V2": dynamic(() => import("../../../applecalendar/pages/v2/setup")),
+ "exchange2013-calendar-V2": dynamic(() => import("../../../exchange2013calendar/pages/v2/setup")),
+ "exchange2016-calendar-V2": dynamic(() => import("../../../exchange2016calendar/pages/v2/setup")),
+ "caldav-calendar-V2": dynamic(() => import("../../../caldavcalendar/pages/v2/setup")),
+ "zapier-V2": dynamic(() => import("../../../zapier/pages/v2/setup")),
+ "closecom-V2": dynamic(() => import("../../../closecomothercalendar/pages/v2/setup")),
+};
+
+export const AppSetupPage = (props: { slug: string }) => {
+ return componentMap={AppSetupMap} {...props} />;
+};
+
+export default AppSetupPage;
diff --git a/packages/app-store/applecalendar/pages/v2/setup/index.tsx b/packages/app-store/applecalendar/pages/v2/setup/index.tsx
new file mode 100644
index 0000000000..20a21d526f
--- /dev/null
+++ b/packages/app-store/applecalendar/pages/v2/setup/index.tsx
@@ -0,0 +1,101 @@
+import { useRouter } from "next/router";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { Toaster } from "react-hot-toast";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Alert, Button, Form, TextField } from "@calcom/ui/v2";
+
+export default function AppleCalendarSetup() {
+ const { t } = useLocale();
+ const router = useRouter();
+ const form = useForm({
+ defaultValues: {
+ username: "",
+ password: "",
+ },
+ });
+
+ const [errorMessage, setErrorMessage] = useState("");
+
+ return (
+
+
+
+
+ {/* eslint-disable @next/next/no-img-element */}
+

+
+
+
{t("connect_apple_server")}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/app-store/caldavcalendar/pages/v2/setup/index.tsx b/packages/app-store/caldavcalendar/pages/v2/setup/index.tsx
new file mode 100644
index 0000000000..67219de946
--- /dev/null
+++ b/packages/app-store/caldavcalendar/pages/v2/setup/index.tsx
@@ -0,0 +1,98 @@
+import { useRouter } from "next/router";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { Toaster } from "react-hot-toast";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Alert, Button, Form, TextField } from "@calcom/ui/v2";
+
+export default function CalDavCalendarSetup() {
+ const { t } = useLocale();
+ const router = useRouter();
+ const form = useForm({
+ defaultValues: {
+ url: "",
+ username: "",
+ password: "",
+ },
+ });
+
+ const [errorMessage, setErrorMessage] = useState("");
+
+ return (
+
+
+
+
+ {/* eslint-disable @next/next/no-img-element */}
+

+
+
+
{t("connect_caldav_server")}
+
{t("credentials_stored_encrypted")}
+
+
+
+
+
+
+ );
+}
diff --git a/packages/app-store/closecomothercalendar/pages/v2/setup/index.tsx b/packages/app-store/closecomothercalendar/pages/v2/setup/index.tsx
new file mode 100644
index 0000000000..6ce7ac746b
--- /dev/null
+++ b/packages/app-store/closecomothercalendar/pages/v2/setup/index.tsx
@@ -0,0 +1,155 @@
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useRouter } from "next/router";
+import { useState, useEffect } from "react";
+import { Check, X } from "react-feather";
+import { useForm, Controller } from "react-hook-form";
+import { Toaster } from "react-hot-toast";
+import z from "zod";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Button, Form, showToast, TextField } from "@calcom/ui/v2";
+
+const formSchema = z.object({
+ api_key: z.string(),
+});
+
+export default function CloseComSetup() {
+ const { t } = useLocale();
+ const router = useRouter();
+ const [testPassed, setTestPassed] = useState(undefined);
+ const [testLoading, setTestLoading] = useState(false);
+
+ const form = useForm<{
+ api_key: string;
+ }>({
+ resolver: zodResolver(formSchema),
+ });
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ if (testPassed === false) {
+ setTestPassed(undefined);
+ }
+ }, 3000);
+ return () => clearTimeout(timer);
+ }, [testPassed]);
+
+ return (
+
+
+
+
+ {/* eslint-disable @next/next/no-img-element */}
+

+
+
+
{t("provide_api_key")}
+
+
+ {t("generate_api_key_description")}{" "}
+
+ Close.com
+
+ . {t("it_stored_encrypted")}
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/app-store/exchange2013calendar/pages/v2/setup/index.tsx b/packages/app-store/exchange2013calendar/pages/v2/setup/index.tsx
new file mode 100644
index 0000000000..73904caa78
--- /dev/null
+++ b/packages/app-store/exchange2013calendar/pages/v2/setup/index.tsx
@@ -0,0 +1,95 @@
+import { useRouter } from "next/router";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { Toaster } from "react-hot-toast";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Alert, Button, Form, TextField } from "@calcom/ui/v2";
+
+export default function Exchange2013CalendarSetup() {
+ const { t } = useLocale();
+ const router = useRouter();
+ const form = useForm({
+ defaultValues: {
+ username: "",
+ password: "",
+ url: process.env.EXCHANGE_DEFAULT_EWS_URL || "",
+ },
+ });
+
+ const [errorMessage, setErrorMessage] = useState("");
+
+ return (
+
+
+
+

+
+
+
{t("add_exchange2013")}
+
{t("credentials_stored_encrypted")}
+
+
+
+
+
+ );
+}
diff --git a/packages/app-store/exchange2016calendar/pages/v2/setup/index.tsx b/packages/app-store/exchange2016calendar/pages/v2/setup/index.tsx
new file mode 100644
index 0000000000..0179c9ef9d
--- /dev/null
+++ b/packages/app-store/exchange2016calendar/pages/v2/setup/index.tsx
@@ -0,0 +1,95 @@
+import { useRouter } from "next/router";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { Toaster } from "react-hot-toast";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Alert, Button, Form, TextField } from "@calcom/ui/v2";
+
+export default function Exchange2016CalendarSetup() {
+ const { t } = useLocale();
+ const router = useRouter();
+ const form = useForm({
+ defaultValues: {
+ username: "",
+ password: "",
+ url: process.env.EXCHANGE_DEFAULT_EWS_URL || "",
+ },
+ });
+
+ const [errorMessage, setErrorMessage] = useState("");
+
+ return (
+
+
+
+

+
+
+
{t("add_exchange2016")}
+
{t("credentials_stored_encrypted")}
+
+
+
+
+
+ );
+}
diff --git a/packages/app-store/zapier/pages/v2/setup/_getStaticProps.tsx b/packages/app-store/zapier/pages/v2/setup/_getStaticProps.tsx
new file mode 100644
index 0000000000..bef3853ab3
--- /dev/null
+++ b/packages/app-store/zapier/pages/v2/setup/_getStaticProps.tsx
@@ -0,0 +1,20 @@
+import { GetStaticPropsContext } from "next";
+
+import getAppKeysFromSlug from "../../../../_utils/getAppKeysFromSlug";
+
+export interface IZapierSetupProps {
+ inviteLink: string;
+}
+
+export const getStaticProps = async (ctx: GetStaticPropsContext) => {
+ if (typeof ctx.params?.slug !== "string") return { notFound: true } as const;
+ let inviteLink = "";
+ const appKeys = await getAppKeysFromSlug("zapier");
+ if (typeof appKeys.invite_link === "string") inviteLink = appKeys.invite_link;
+
+ return {
+ props: {
+ inviteLink,
+ },
+ };
+};
diff --git a/packages/app-store/zapier/pages/v2/setup/index.tsx b/packages/app-store/zapier/pages/v2/setup/index.tsx
new file mode 100644
index 0000000000..b8e63ec69c
--- /dev/null
+++ b/packages/app-store/zapier/pages/v2/setup/index.tsx
@@ -0,0 +1,128 @@
+import { ClipboardCopyIcon } from "@heroicons/react/solid";
+import { Trans } from "next-i18next";
+import Link from "next/link";
+import { useState } from "react";
+import { Toaster } from "react-hot-toast";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { trpc } from "@calcom/trpc/react";
+import { Button, Loader, showToast, Tooltip } from "@calcom/ui/v2";
+
+export interface IZapierSetupProps {
+ inviteLink: string;
+}
+
+const ZAPIER = "zapier";
+
+export default function ZapierSetup(props: IZapierSetupProps) {
+ const [newApiKey, setNewApiKey] = useState("");
+ const { t } = useLocale();
+ const utils = trpc.useContext();
+ const integrations = trpc.useQuery(["viewer.integrations", { variant: "other" }]);
+ const oldApiKey = trpc.useQuery(["viewer.apiKeys.findKeyOfType", { appId: ZAPIER }]);
+
+ const deleteApiKey = trpc.useMutation("viewer.apiKeys.delete");
+ const zapierCredentials: { credentialIds: number[] } | undefined = integrations.data?.items.find(
+ (item: { type: string }) => item.type === "zapier_other"
+ );
+ const [credentialId] = zapierCredentials?.credentialIds || [false];
+ const showContent = integrations.data && integrations.isSuccess && credentialId;
+ const isCalDev = process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.dev";
+
+ async function createApiKey() {
+ const event = { note: "Zapier", expiresAt: null, appId: ZAPIER };
+ const apiKey = await utils.client.mutation("viewer.apiKeys.create", event);
+ if (oldApiKey.data) {
+ deleteApiKey.mutate({
+ id: oldApiKey.data.id,
+ });
+ }
+ setNewApiKey(apiKey);
+ }
+
+ if (integrations.isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {showContent ? (
+
+
+
+

+
+
+
{t("setting_up_zapier")}
+ {!newApiKey ? (
+ <>
+
{t("generate_api_key")}:
+
+ >
+ ) : (
+ <>
+
{t("your_unique_api_key")}
+
+
{newApiKey}
+
+
+
+
+
+ {t("copy_safe_api_key")}
+
+ >
+ )}
+
+
+ {isCalDev && (
+ -
+ {t("go_to")}
+
+ {t("zapier_invite_link")}
+
+
+ )}
+
+ - Log into your Zapier account and create a new Zap.
+ - Select Cal.com as your Trigger app. Also choose a Trigger event.
+ - Choose your account and then enter your Unique API Key.
+ - Test your Trigger.
+ - You're set!
+
+
+
+
+
+
+
+
+ ) : (
+
+
{t("install_zapier_app")}
+
+
+
+
+
+
+ )}
+
+
+ );
+}