add invite link to Zapier setup page (#2696)
* add invite link and toaster to zapier setup page * create env variable for invite link and save in database * fetch invite link form getStaticProps * add getStaticPath method * clean code * Moves app setup and index page * Moves Loader to ui * Trying new way to handle dynamic app store pages * Cleanup * Update tailwind.config.js * zapier invite link fixes * Tests fixes Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
parent
784a91709c
commit
6483182ef6
|
@ -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=""
|
||||
# *********************************************************************************************************
|
||||
|
|
|
@ -1,7 +1 @@
|
|||
export default function Loader() {
|
||||
return (
|
||||
<div className="loader border-brand dark:border-darkmodebrand">
|
||||
<span className="loader-inner bg-brand dark:bg-darkmodebrand"></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export { default } from "@calcom/ui/Loader";
|
||||
|
|
|
@ -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}`)
|
||||
);
|
||||
|
|
|
@ -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<typeof getStaticProps>) {
|
||||
const router = useRouter();
|
||||
const slug = router.query.slug as string;
|
||||
const { status } = useSession();
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-200">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === "unauthenticated") {
|
||||
router.replace({
|
||||
pathname: "/auth/login",
|
||||
query: {
|
||||
callbackUrl: `/apps/${slug}/setup`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return <AppSetupPage slug={slug} {...props} />;
|
||||
}
|
||||
|
||||
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 };
|
|
@ -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 (
|
||||
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-200">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === "unauthenticated") {
|
||||
router.replace({
|
||||
pathname: "/auth/login",
|
||||
query: {
|
||||
callbackUrl: `/apps/setup/${appName}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (appName === _zapierMetadata.name.toLowerCase() && status === "authenticated") {
|
||||
return <ZapierSetup trpc={trpc}></ZapierSetup>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
|
@ -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 Link</1></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",
|
||||
|
|
|
@ -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}",
|
||||
],
|
||||
};
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export function DynamicComponent<T extends Record<string, any>>(props: { componentMap: T; slug: string }) {
|
||||
const { componentMap, slug, ...rest } = props;
|
||||
|
||||
if (!componentMap[slug]) return null;
|
||||
|
||||
const Component = componentMap[slug];
|
||||
|
||||
return <Component {...rest} />;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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 <DynamicComponent<typeof AppSetupMap> componentMap={AppSetupMap} {...props} />;
|
||||
};
|
||||
|
||||
export default AppSetupPage;
|
|
@ -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
|
||||
|
||||
|
|
|
@ -2,5 +2,4 @@ Workflow automation for everyone. Use the Cal.com Zapier app to trigger your wor
|
|||
|
||||
<br />
|
||||
**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: <a href="/apps/setup/zapier">Zapier App Setup</a>
|
||||
|
||||
on how to use the installed app: <a href="/apps/zapier/setup">Zapier App Setup</a>
|
||||
|
|
|
@ -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" });
|
||||
}
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export { default as InstallAppButton } from "./InstallAppButton";
|
||||
export { default as ZapierSetup } from "./zapierSetup";
|
||||
export { default as Icon } from "./icon";
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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) {
|
|||
</>
|
||||
)}
|
||||
|
||||
<ol className="mt-5 mb-5 mr-5 list-decimal">
|
||||
<ol className="mt-5 mb-5 ml-5 mr-5 list-decimal">
|
||||
<Trans i18nKey="zapier_setup_instructions">
|
||||
<li>
|
||||
Go to:
|
||||
<a href={props.inviteLink} className="text-orange-600 underline">
|
||||
Zapier Invite Link
|
||||
</a>
|
||||
</li>
|
||||
<li>Log into your Zapier account and create a new Zap.</li>
|
||||
<li>Select Cal.com as your Trigger app. Also choose a Trigger event.</li>
|
||||
<li>Choose your account and then enter your Unique API Key.</li>
|
||||
|
@ -116,6 +126,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Toaster position="bottom-right" />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export default function Loader() {
|
||||
return (
|
||||
<div className="loader border-brand dark:border-darkmodebrand">
|
||||
<span className="loader-inner bg-brand dark:bg-darkmodebrand"></span>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue
Block a user