From 46f2142e5fc5ac20aab33bec06bb18f2850869ef Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Fri, 2 Sep 2022 16:39:44 -0400 Subject: [PATCH] V2 for app setup pages (#4086) * add v2 for app setup pages * create v2 app setup page * Build fixes Co-authored-by: CarinaWolli Co-authored-by: zomars --- .../components/booking/BookingListItem.tsx | 2 +- .../web/components/booking/SkeletonLoader.tsx | 2 +- apps/web/pages/v2/apps/[slug]/setup.tsx | 41 +++++ .../_pages/v2/setup/_getStaticProps.tsx | 20 +++ packages/app-store/_pages/v2/setup/index.tsx | 18 ++ .../applecalendar/pages/v2/setup/index.tsx | 101 ++++++++++++ .../caldavcalendar/pages/v2/setup/index.tsx | 98 +++++++++++ .../pages/v2/setup/index.tsx | 155 ++++++++++++++++++ .../pages/v2/setup/index.tsx | 95 +++++++++++ .../pages/v2/setup/index.tsx | 95 +++++++++++ .../zapier/pages/v2/setup/_getStaticProps.tsx | 20 +++ .../app-store/zapier/pages/v2/setup/index.tsx | 128 +++++++++++++++ 12 files changed, 773 insertions(+), 2 deletions(-) create mode 100644 apps/web/pages/v2/apps/[slug]/setup.tsx create mode 100644 packages/app-store/_pages/v2/setup/_getStaticProps.tsx create mode 100644 packages/app-store/_pages/v2/setup/index.tsx create mode 100644 packages/app-store/applecalendar/pages/v2/setup/index.tsx create mode 100644 packages/app-store/caldavcalendar/pages/v2/setup/index.tsx create mode 100644 packages/app-store/closecomothercalendar/pages/v2/setup/index.tsx create mode 100644 packages/app-store/exchange2013calendar/pages/v2/setup/index.tsx create mode 100644 packages/app-store/exchange2016calendar/pages/v2/setup/index.tsx create mode 100644 packages/app-store/zapier/pages/v2/setup/_getStaticProps.tsx create mode 100644 packages/app-store/zapier/pages/v2/setup/index.tsx 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 */} + Apple Calendar +
    +
    +

    {t("connect_apple_server")}

    + +
    + {t("apple_server_generate_password")}{" "} + + https://appleid.apple.com/account/manage + + . {t("credentials_stored_encrypted")} +
    +
    +
    { + setErrorMessage(""); + const res = await fetch("/api/integrations/applecalendar/add", { + method: "POST", + body: JSON.stringify(values), + headers: { + "Content-Type": "application/json", + }, + }); + const json = await res.json(); + if (!res.ok) { + setErrorMessage(json?.message || t("something_went_wrong")); + } else { + router.push(json.url); + } + }}> +
    + + +
    + + {errorMessage && } +
    + + +
    + +
    +
    +
    +
    + +
    + ); +} 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 */} + CalDav Calendar +
    +
    +

    {t("connect_caldav_server")}

    +
    {t("credentials_stored_encrypted")}
    +
    +
    { + setErrorMessage(""); + const res = await fetch("/api/integrations/caldavcalendar/add", { + method: "POST", + body: JSON.stringify(values), + headers: { + "Content-Type": "application/json", + }, + }); + const json = await res.json(); + if (!res.ok) { + setErrorMessage(json?.message || t("something_went_wrong")); + } else { + router.push(json.url); + } + }}> +
    + + + +
    + + {errorMessage && } +
    + + +
    + +
    +
    +
    +
    + +
    + ); +} 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 */} + Apple Calendar +
    +
    +

    {t("provide_api_key")}

    + +
    + {t("generate_api_key_description")}{" "} + + Close.com + + . {t("it_stored_encrypted")} +
    +
    +
    { + const res = await fetch("/api/integrations/closecomothercalendar/add", { + method: "POST", + body: JSON.stringify(values), + headers: { + "Content-Type": "application/json", + }, + }); + const json = await res.json(); + + if (res.ok) { + router.push(json.url); + } else { + showToast(json.message, "error"); + } + }}> +
    + ( + { + onChange(e.target.value); + form.setValue("api_key", e.target.value); + await form.trigger("api_key"); + }} + /> + )} + /> +
    +
    + + + +
    +
    +
    +
    +
    +
    + +
    + ); +} 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 ( +
    +
    +
    + Exchange 2013 Calendar +
    +
    +

    {t("add_exchange2013")}

    +
    {t("credentials_stored_encrypted")}
    +
    +
    { + setErrorMessage(""); + const res = await fetch("/api/integrations/exchange2013calendar/add", { + method: "POST", + body: JSON.stringify(values), + headers: { + "Content-Type": "application/json", + }, + }); + const json = await res.json(); + if (!res.ok) { + setErrorMessage(json?.message || t("something_went_wrong")); + } else { + router.push(json.url); + } + }}> +
    + + + +
    + + {errorMessage && } +
    + + +
    + +
    +
    +
    + +
    + ); +} 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 ( +
    +
    +
    + Exchange 2016 Calendar +
    +
    +

    {t("add_exchange2016")}

    +
    {t("credentials_stored_encrypted")}
    +
    +
    { + setErrorMessage(""); + const res = await fetch("/api/integrations/exchange2016calendar/add", { + method: "POST", + body: JSON.stringify(values), + headers: { + "Content-Type": "application/json", + }, + }); + const json = await res.json(); + if (!res.ok) { + setErrorMessage(json?.message || t("something_went_wrong")); + } else { + router.push(json.url); + } + }}> +
    + + + +
    + + {errorMessage && } +
    + + +
    + +
    +
    +
    + +
    + ); +} 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 ? ( +
    +
    +
    + Zapier Logo +
    +
    +
    {t("setting_up_zapier")}
    + {!newApiKey ? ( + <> +
    {t("generate_api_key")}:
    + + + ) : ( + <> +
    {t("your_unique_api_key")}
    +
    +
    {newApiKey}
    + + + +
    +
    + {t("copy_safe_api_key")} +
    + + )} + +
      + {isCalDev && ( +
    1. + {t("go_to")} + + {t("zapier_invite_link")} + +
    2. + )} + +
    3. Log into your Zapier account and create a new Zap.
    4. +
    5. Select Cal.com as your Trigger app. Also choose a Trigger event.
    6. +
    7. Choose your account and then enter your Unique API Key.
    8. +
    9. Test your Trigger.
    10. +
    11. You're set!
    12. +
      +
    + + + +
    +
    +
    + ) : ( +
    +
    {t("install_zapier_app")}
    +
    + + + +
    +
    + )} + +
    + ); +}