diff --git a/.env.appStore.example b/.env.appStore.example
index 9f6825c3..667b5bf8 100644
--- a/.env.appStore.example
+++ b/.env.appStore.example
@@ -81,4 +81,9 @@ VITAL_WEBHOOK_SECRET=
VITAL_DEVELOPMENT_MODE="sandbox"
# "us" | "eu"
VITAL_REGION="us"
+
+# - ZAPIER
+# Used for the Zapier integration
+# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/zapier/README.md
+ZAPIER_INVITE_LINK=""
# *********************************************************************************************************
diff --git a/apps/web/components/Loader.tsx b/apps/web/components/Loader.tsx
index 29ac50d7..1eb4ef81 100644
--- a/apps/web/components/Loader.tsx
+++ b/apps/web/components/Loader.tsx
@@ -1,7 +1 @@
-export default function Loader() {
- return (
-
-
-
- );
-}
+export { default } from "@calcom/ui/Loader";
diff --git a/apps/web/ee/lib/stripe/server.ts b/apps/web/ee/lib/stripe/server.ts
index 1302fdea..625b985f 100644
--- a/apps/web/ee/lib/stripe/server.ts
+++ b/apps/web/ee/lib/stripe/server.ts
@@ -2,6 +2,7 @@ import { PaymentType, Prisma } from "@prisma/client";
import Stripe from "stripe";
import { v4 as uuidv4 } from "uuid";
+import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import prisma from "@calcom/prisma";
import { createPaymentLink } from "@calcom/stripe/client";
@@ -16,8 +17,8 @@ export type PaymentInfo = {
id?: string | null;
};
-const paymentFeePercentage = process.env.PAYMENT_FEE_PERCENTAGE!;
-const paymentFeeFixed = process.env.PAYMENT_FEE_FIXED!;
+let paymentFeePercentage: number | undefined;
+let paymentFeeFixed: number | undefined;
export async function handlePayment(
evt: CalendarEvent,
@@ -33,6 +34,10 @@ export async function handlePayment(
uid: string;
}
) {
+ const appKeys = await getAppKeysFromSlug("stripe");
+ if (typeof appKeys.payment_fee_fixed === "number") paymentFeePercentage = appKeys.payment_fee_fixed;
+ if (typeof appKeys.payment_fee_percentage === "number") paymentFeeFixed = appKeys.payment_fee_percentage;
+
const paymentFee = Math.round(
selectedEventType.price * parseFloat(`${paymentFeePercentage}`) + parseInt(`${paymentFeeFixed}`)
);
diff --git a/apps/web/pages/apps/[slug].tsx b/apps/web/pages/apps/[slug]/index.tsx
similarity index 100%
rename from apps/web/pages/apps/[slug].tsx
rename to apps/web/pages/apps/[slug]/index.tsx
diff --git a/apps/web/pages/apps/[slug]/setup.tsx b/apps/web/pages/apps/[slug]/setup.tsx
new file mode 100644
index 00000000..f621802e
--- /dev/null
+++ b/apps/web/pages/apps/[slug]/setup.tsx
@@ -0,0 +1,45 @@
+import { InferGetStaticPropsType } from "next";
+import { useSession } from "next-auth/react";
+import { useRouter } from "next/router";
+
+import { AppSetupPage } from "@calcom/app-store/_pages/setup";
+import { AppSetupPageMap, getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
+import prisma from "@calcom/prisma";
+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 = async () => {
+ const appStore = await prisma.app.findMany({ select: { slug: true } });
+ const paths = appStore.filter((a) => a.slug in AppSetupPageMap).map((app) => app.slug);
+
+ return {
+ paths: paths.map((slug) => ({ params: { slug } })),
+ fallback: false,
+ };
+};
+
+export { getStaticProps };
diff --git a/apps/web/pages/apps/setup/[appName].tsx b/apps/web/pages/apps/setup/[appName].tsx
deleted file mode 100644
index b8bdc7c6..00000000
--- a/apps/web/pages/apps/setup/[appName].tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { useSession } from "next-auth/react";
-import { useRouter } from "next/router";
-
-import _zapierMetadata from "@calcom/app-store/zapier/_metadata";
-import { ZapierSetup } from "@calcom/app-store/zapier/components";
-
-import { trpc } from "@lib/trpc";
-
-import Loader from "@components/Loader";
-
-export default function SetupInformation() {
- const router = useRouter();
- const appName = router.query.appName;
- const { status } = useSession();
-
- if (status === "loading") {
- return (
-
-
-
- );
- }
-
- if (status === "unauthenticated") {
- router.replace({
- pathname: "/auth/login",
- query: {
- callbackUrl: `/apps/setup/${appName}`,
- },
- });
- }
-
- if (appName === _zapierMetadata.name.toLowerCase() && status === "authenticated") {
- return ;
- }
-
- return null;
-}
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index f9ab7153..db1a3ded 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -817,7 +817,7 @@
"generate_api_key": "Generate Api Key",
"your_unique_api_key": "Your unique API key",
"copy_safe_api_key": "Copy this API key and save it somewhere safe. If you lose this key you have to generate a new one.",
- "zapier_setup_instructions": "<0>Log into your Zapier account and create a new Zap.0><1>Select Cal.com as your Trigger app. Also choose a Trigger event.1><2>Choose your account and then enter your Unique API Key.2><3>Test your Trigger.3><4>You're set!4>",
+ "zapier_setup_instructions": "<0>Go to: <1>Zapier Invite Link1>0><1>Log into your Zapier account and create a new Zap.1><2>Select Cal.com as your Trigger app. Also choose a Trigger event.2><3>Choose your account and then enter your Unique API Key.3><4>Test your Trigger.4><5>You're set!5>",
"install_zapier_app": "Please first install the Zapier App in the app store.",
"go_to_app_store": "Go to App Store",
"calendar_error": "Something went wrong, try reconnecting your calendar with all necessary permissions",
diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js
index 3a93df35..e1f9b8e7 100644
--- a/apps/web/tailwind.config.js
+++ b/apps/web/tailwind.config.js
@@ -4,6 +4,6 @@ module.exports = {
content: [
...base.content,
"../../packages/ui/**/*.{js,ts,jsx,tsx}",
- "../../packages/app-store/**/components/*.{js,ts,jsx,tsx}",
+ "../../packages/app-store/**/{components,pages}/**/*.{js,ts,jsx,tsx}",
],
};
diff --git a/packages/app-store/_components/DynamicComponent.tsx b/packages/app-store/_components/DynamicComponent.tsx
new file mode 100644
index 00000000..25ba7288
--- /dev/null
+++ b/packages/app-store/_components/DynamicComponent.tsx
@@ -0,0 +1,9 @@
+export function DynamicComponent>(props: { componentMap: T; slug: string }) {
+ const { componentMap, slug, ...rest } = props;
+
+ if (!componentMap[slug]) return null;
+
+ const Component = componentMap[slug];
+
+ return ;
+}
diff --git a/packages/app-store/_pages/setup/_getStaticProps.tsx b/packages/app-store/_pages/setup/_getStaticProps.tsx
new file mode 100644
index 00000000..0e8b6311
--- /dev/null
+++ b/packages/app-store/_pages/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/setup/index.tsx b/packages/app-store/_pages/setup/index.tsx
new file mode 100644
index 00000000..b4e14e81
--- /dev/null
+++ b/packages/app-store/_pages/setup/index.tsx
@@ -0,0 +1,13 @@
+import dynamic from "next/dynamic";
+
+import { DynamicComponent } from "../../_components/DynamicComponent";
+
+export const AppSetupMap = {
+ zapier: dynamic(() => import("../../zapier/pages/setup")),
+};
+
+export const AppSetupPage = (props: { slug: string }) => {
+ return componentMap={AppSetupMap} {...props} />;
+};
+
+export default AppSetupPage;
diff --git a/packages/app-store/zapier/README.md b/packages/app-store/zapier/README.md
index f00a3752..80abd116 100644
--- a/packages/app-store/zapier/README.md
+++ b/packages/app-store/zapier/README.md
@@ -56,9 +56,9 @@ Booking created, Booking rescheduled, Booking cancelled
Create the other two triggers (booking rescheduled, booking cancelled) exactly like this one, just use the appropriate naming (e.g. booking_rescheduled instead of booking_created)
-### Testing integration
+### Set ZAPIER_INVITE_LINK
-Use the sharing link under Manage → Sharing to create your first Cal.com trigger in Zapier
+The invite link can be found under under Manage → Sharing.
## Localhost
diff --git a/packages/app-store/zapier/README.mdx b/packages/app-store/zapier/README.mdx
index 52d0240a..ca51a99e 100644
--- a/packages/app-store/zapier/README.mdx
+++ b/packages/app-store/zapier/README.mdx
@@ -2,5 +2,4 @@ Workflow automation for everyone. Use the Cal.com Zapier app to trigger your wor
**After Installation:** You lost your generated API key? Here you can generate a new key and find all information
-on how to use the installed app: Zapier App Setup
-
+on how to use the installed app: Zapier App Setup
diff --git a/packages/app-store/zapier/api/add.ts b/packages/app-store/zapier/api/add.ts
index 0c01b593..9f1cf72b 100644
--- a/packages/app-store/zapier/api/add.ts
+++ b/packages/app-store/zapier/api/add.ts
@@ -35,5 +35,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(500);
}
- return res.status(200).json({ url: "/apps/setup/zapier" });
+ return res.status(200).json({ url: "/apps/zapier/setup" });
}
diff --git a/packages/app-store/zapier/components/index.ts b/packages/app-store/zapier/components/index.ts
index 5f2c7965..e191e97e 100644
--- a/packages/app-store/zapier/components/index.ts
+++ b/packages/app-store/zapier/components/index.ts
@@ -1,3 +1,2 @@
export { default as InstallAppButton } from "./InstallAppButton";
-export { default as ZapierSetup } from "./zapierSetup";
export { default as Icon } from "./icon";
diff --git a/packages/app-store/zapier/pages/setup/_getStaticProps.tsx b/packages/app-store/zapier/pages/setup/_getStaticProps.tsx
new file mode 100644
index 00000000..b73a0786
--- /dev/null
+++ b/packages/app-store/zapier/pages/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/components/zapierSetup.tsx b/packages/app-store/zapier/pages/setup/index.tsx
similarity index 85%
rename from packages/app-store/zapier/components/zapierSetup.tsx
rename to packages/app-store/zapier/pages/setup/index.tsx
index 362135c8..5c28b267 100644
--- a/packages/app-store/zapier/components/zapierSetup.tsx
+++ b/packages/app-store/zapier/pages/setup/index.tsx
@@ -2,28 +2,31 @@ 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 showToast from "@calcom/lib/notification";
-import { Button } from "@calcom/ui";
-import { Tooltip } from "@calcom/ui/Tooltip";
-import Loader from "@calcom/web/components/Loader";
+import { Button, Loader, Tooltip } from "@calcom/ui";
-import Icon from "./icon";
+/** TODO: Maybe extract this into a package to prevent circular dependencies */
+import { trpc } from "@calcom/web/lib/trpc";
-interface IZapierSetupProps {
- trpc: any;
+import Icon from "../../components/icon";
+
+export interface IZapierSetupProps {
+ inviteLink: string;
}
const ZAPIER = "zapier";
export default function ZapierSetup(props: IZapierSetupProps) {
- const { trpc } = props;
const [newApiKey, setNewApiKey] = useState("");
const { t } = useLocale();
const utils = trpc.useContext();
const integrations = trpc.useQuery(["viewer.integrations"]);
+ // @ts-ignore
const oldApiKey = trpc.useQuery(["viewer.apiKeys.findKeyOfType", { appId: ZAPIER }]);
+
const deleteApiKey = trpc.useMutation("viewer.apiKeys.delete");
const zapierCredentials: { credentialIds: number[] } | undefined = integrations.data?.other?.items.find(
(item: { type: string }) => item.type === "zapier_other"
@@ -33,6 +36,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
async function createApiKey() {
const event = { note: "Zapier", expiresAt: null, appId: ZAPIER };
+ // @ts-ignore
const apiKey = await utils.client.mutation("viewer.apiKeys.create", event);
if (oldApiKey.data) {
deleteApiKey.mutate({
@@ -91,8 +95,14 @@ export default function ZapierSetup(props: IZapierSetupProps) {
>
)}
-
+
+ -
+ Go to:
+
+ 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.
@@ -116,6 +126,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
)}
+
);
}
diff --git a/packages/prisma/seed-app-store.ts b/packages/prisma/seed-app-store.ts
index 905eb009..b990c333 100644
--- a/packages/prisma/seed-app-store.ts
+++ b/packages/prisma/seed-app-store.ts
@@ -95,7 +95,12 @@ async function main() {
webhook_secret: process.env.VITAL_WEBHOOK_SECRET,
});
}
- await createApp("zapier", "zapier", ["other"], "zapier_other");
+
+ if (process.env.ZAPIER_INVITE_LINK) {
+ await createApp("zapier", "zapier", ["other"], "zapier_other", {
+ invite_link: process.env.ZAPIER_INVITE_LINK,
+ });
+ }
// Web3 apps
await createApp("huddle01", "huddle01video", ["web3", "video"], "huddle01_video");
await createApp("metamask", "metamask", ["web3"], "metamask_web3");
diff --git a/packages/ui/Loader.tsx b/packages/ui/Loader.tsx
new file mode 100644
index 00000000..29ac50d7
--- /dev/null
+++ b/packages/ui/Loader.tsx
@@ -0,0 +1,7 @@
+export default function Loader() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx
index c1523e78..f466516e 100644
--- a/packages/ui/index.tsx
+++ b/packages/ui/index.tsx
@@ -1,6 +1,7 @@
export { default as Button } from "./Button";
export { default as EmptyScreen } from "./EmptyScreen";
export { default as Select } from "./form/Select";
+export { default as Loader } from "./Loader";
export * from "./skeleton";
export { default as Switch } from "./Switch";
export { default as Tooltip } from "./Tooltip";