moved embed and webhooks from settings into /integrations (#978)
This commit is contained in:
parent
c146231b31
commit
656d58b94f
6 changed files with 292 additions and 288 deletions
|
@ -1,4 +1,4 @@
|
|||
import { CodeIcon, CreditCardIcon, KeyIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid";
|
||||
import { CreditCardIcon, KeyIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid";
|
||||
import React from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
@ -19,7 +19,6 @@ export default function SettingsShell({ children }: { children: React.ReactNode
|
|||
href: "/settings/security",
|
||||
icon: KeyIcon,
|
||||
},
|
||||
{ name: t("embed_and_webhooks"), href: "/settings/embed", icon: CodeIcon },
|
||||
{
|
||||
name: t("teams"),
|
||||
href: "/settings/teams",
|
||||
|
|
|
@ -11,7 +11,7 @@ import Button from "@components/ui/Button";
|
|||
|
||||
export default function WebhookListItem(props: {
|
||||
onChange: () => void;
|
||||
key: number;
|
||||
key: string;
|
||||
webhook: Webhook;
|
||||
onEditWebhook: () => void;
|
||||
}) {
|
||||
|
|
285
pages/integrations/embed.tsx
Normal file
285
pages/integrations/embed.tsx
Normal file
|
@ -0,0 +1,285 @@
|
|||
import { useSession } from "next-auth/client";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { trpc } from "@lib/trpc";
|
||||
import { Webhook } from "@lib/webhook";
|
||||
|
||||
import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTrigger } from "@components/Dialog";
|
||||
import { List, ListItem, ListItemText, ListItemTitle } from "@components/List";
|
||||
import Loader from "@components/Loader";
|
||||
import { ShellSubHeading } from "@components/Shell";
|
||||
import Button from "@components/ui/Button";
|
||||
import Switch from "@components/ui/Switch";
|
||||
import EditWebhook from "@components/webhook/EditWebhook";
|
||||
import WebhookList from "@components/webhook/WebhookList";
|
||||
|
||||
export default function Embed() {
|
||||
const user = trpc.useQuery(["viewer.me"]).data;
|
||||
const [, loading] = useSession();
|
||||
const { t } = useLocale();
|
||||
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [bookingCreated, setBookingCreated] = useState(true);
|
||||
const [bookingRescheduled, setBookingRescheduled] = useState(true);
|
||||
const [bookingCancelled, setBookingCancelled] = useState(true);
|
||||
const [editWebhookEnabled, setEditWebhookEnabled] = useState(false);
|
||||
const [webhooks, setWebhooks] = useState([]);
|
||||
const [webhookToEdit, setWebhookToEdit] = useState<Webhook | null>();
|
||||
const [webhookEventTrigger, setWebhookEventTriggers] = useState([
|
||||
"BOOKING_CREATED",
|
||||
"BOOKING_RESCHEDULED",
|
||||
"BOOKING_CANCELLED",
|
||||
]);
|
||||
|
||||
const subUrlRef = useRef<HTMLInputElement>() as React.MutableRefObject<HTMLInputElement>;
|
||||
|
||||
useEffect(() => {
|
||||
const arr = [];
|
||||
bookingCreated && arr.push("BOOKING_CREATED");
|
||||
bookingRescheduled && arr.push("BOOKING_RESCHEDULED");
|
||||
bookingCancelled && arr.push("BOOKING_CANCELLED");
|
||||
setWebhookEventTriggers(arr);
|
||||
}, [bookingCreated, bookingRescheduled, bookingCancelled]);
|
||||
|
||||
useEffect(() => {
|
||||
getWebhooks();
|
||||
}, []);
|
||||
|
||||
const iframeTemplate = `<iframe src="${process.env.NEXT_PUBLIC_BASE_URL}/${user?.username}" frameborder="0" allowfullscreen></iframe>`;
|
||||
const htmlTemplate = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>${t(
|
||||
"schedule_a_meeting"
|
||||
)}</title><style>body {margin: 0;}iframe {height: calc(100vh - 4px);width: calc(100vw - 4px);box-sizing: border-box;}</style></head><body>${iframeTemplate}</body></html>`;
|
||||
const handleErrors = async (resp: Response) => {
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json();
|
||||
throw new Error(err.message);
|
||||
}
|
||||
return resp.json();
|
||||
};
|
||||
|
||||
const getWebhooks = () => {
|
||||
fetch("/api/webhook")
|
||||
.then(handleErrors)
|
||||
.then((data) => {
|
||||
setWebhooks(
|
||||
data.webhooks.map((webhook: Webhook) => {
|
||||
return {
|
||||
...webhook,
|
||||
eventTriggers: webhook.eventTriggers.map((eventTrigger: string) => eventTrigger.toLowerCase()),
|
||||
};
|
||||
})
|
||||
);
|
||||
console.log(data.webhooks);
|
||||
})
|
||||
.catch(console.log);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const createWebhook = () => {
|
||||
setLoading(true);
|
||||
fetch("/api/webhook", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
subscriberUrl: subUrlRef.current.value,
|
||||
eventTriggers: webhookEventTrigger,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(getWebhooks)
|
||||
.catch(console.log);
|
||||
};
|
||||
|
||||
const editWebhook = (webhook: Webhook) => {
|
||||
setEditWebhookEnabled(true);
|
||||
setWebhookToEdit(webhook);
|
||||
};
|
||||
|
||||
const onCloseEdit = () => {
|
||||
getWebhooks();
|
||||
setEditWebhookEnabled(false);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!editWebhookEnabled && (
|
||||
<>
|
||||
<ShellSubHeading className="mt-10" title={t("Webhooks")} subtitle={t("receive_cal_meeting_data")} />
|
||||
<List>
|
||||
<ListItem className={classNames("flex-col")}>
|
||||
<div className={classNames("flex flex-1 space-x-2 w-full p-3 items-center")}>
|
||||
<Image width={40} height={40} src="/integrations/webhooks.svg" alt="Webhooks" />
|
||||
<div className="flex-grow pl-2 truncate">
|
||||
<ListItemTitle component="h3">Webhooks</ListItemTitle>
|
||||
<ListItemText component="p">Automation</ListItemText>
|
||||
</div>
|
||||
<div>
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Button color="secondary">{t("new_webhook")}</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader
|
||||
title={t("create_new_webhook")}
|
||||
subtitle={t("create_new_webhook_to_account")}
|
||||
/>
|
||||
<div className="my-4">
|
||||
<div className="mb-4">
|
||||
<label htmlFor="subUrl" className="block text-sm font-medium text-gray-700">
|
||||
{t("subscriber_url")}
|
||||
</label>
|
||||
<input
|
||||
ref={subUrlRef}
|
||||
type="text"
|
||||
name="subUrl"
|
||||
id="subUrl"
|
||||
placeholder="https://example.com/sub"
|
||||
required
|
||||
className="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-sm shadow-sm focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
|
||||
/>
|
||||
<legend className="block pt-4 mb-2 text-sm font-medium text-gray-700">
|
||||
{" "}
|
||||
{t("event_triggers")}{" "}
|
||||
</legend>
|
||||
<div className="p-2 border border-gray-300 rounded-sm">
|
||||
<div className="flex pb-4">
|
||||
<div className="w-10/12">
|
||||
<h2 className="font-medium text-gray-800">{t("booking_created")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-2/12 text-right">
|
||||
<Switch
|
||||
defaultChecked={true}
|
||||
id="booking-created"
|
||||
value={bookingCreated}
|
||||
onCheckedChange={() => {
|
||||
setBookingCreated(!bookingCreated);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex py-1">
|
||||
<div className="w-10/12">
|
||||
<h2 className="font-medium text-gray-800">{t("booking_rescheduled")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-2/12 text-right">
|
||||
<Switch
|
||||
defaultChecked={true}
|
||||
id="booking-rescheduled"
|
||||
value={bookingRescheduled}
|
||||
onCheckedChange={() => {
|
||||
setBookingRescheduled(!bookingRescheduled);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-4">
|
||||
<div className="w-10/12">
|
||||
<h2 className="font-medium text-gray-800">{t("booking_cancelled")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-2/12 text-right">
|
||||
<Switch
|
||||
defaultChecked={true}
|
||||
id="booking-cancelled"
|
||||
value={bookingCancelled}
|
||||
onCheckedChange={() => {
|
||||
setBookingCancelled(!bookingCancelled);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gap-2 mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
loading={isLoading}
|
||||
onClick={createWebhook}
|
||||
color="primary"
|
||||
className="ml-2">
|
||||
{t("create_webhook")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary">{t("cancel")}</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<div className="divide-y divide-gray-200 lg:col-span-9">
|
||||
<div className="py-6 lg:pb-8">
|
||||
<div>
|
||||
{!!webhooks.length && (
|
||||
<WebhookList
|
||||
webhooks={webhooks}
|
||||
onChange={getWebhooks}
|
||||
onEditWebhook={editWebhook}></WebhookList>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ShellSubHeading className="mt-10" title={t("iframe_embed")} subtitle={t("embed_calcom")} />
|
||||
<div className="py-6 lg:pb-8 lg:col-span-9">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg font-medium leading-6 text-gray-900 font-cal"></h2>
|
||||
<p className="mt-1 text-sm text-gray-500"></p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 space-x-4">
|
||||
<div>
|
||||
<label htmlFor="iframe" className="block text-sm font-medium text-gray-700">
|
||||
{t("standard_iframe")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id="iframe"
|
||||
className="block w-full h-32 border-gray-300 rounded-sm shadow-sm focus:ring-black focus:border-black sm:text-sm"
|
||||
placeholder={t("loading")}
|
||||
defaultValue={iframeTemplate}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="fullscreen" className="block text-sm font-medium text-gray-700">
|
||||
{t("responsive_fullscreen_iframe")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id="fullscreen"
|
||||
className="block w-full h-32 border-gray-300 rounded-sm shadow-sm focus:ring-black focus:border-black sm:text-sm"
|
||||
placeholder={t("loading")}
|
||||
defaultValue={htmlTemplate}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ShellSubHeading className="mt-10" title="Cal.com API" subtitle={t("leverage_our_api")} />
|
||||
<a href="https://developer.cal.com/api" className="btn btn-primary">
|
||||
{t("browse_api_documentation")}
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!!editWebhookEnabled && webhookToEdit && (
|
||||
<EditWebhook webhook={webhookToEdit} onCloseEdit={onCloseEdit} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -18,6 +18,8 @@ import Badge from "@components/ui/Badge";
|
|||
import Button, { ButtonBaseProps } from "@components/ui/Button";
|
||||
import Switch from "@components/ui/Switch";
|
||||
|
||||
import Embed from "./embed";
|
||||
|
||||
function pluralize(opts: { num: number; plural: string; singular: string }) {
|
||||
if (opts.num === 0) {
|
||||
return opts.singular;
|
||||
|
@ -425,6 +427,8 @@ export default function IntegrationsPage() {
|
|||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Embed />
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,285 +0,0 @@
|
|||
import { PlusIcon } from "@heroicons/react/outline";
|
||||
import { useSession } from "next-auth/client";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { trpc } from "@lib/trpc";
|
||||
import { Webhook } from "@lib/webhook";
|
||||
|
||||
import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTrigger } from "@components/Dialog";
|
||||
import Loader from "@components/Loader";
|
||||
import SettingsShell from "@components/SettingsShell";
|
||||
import Shell from "@components/Shell";
|
||||
import Button from "@components/ui/Button";
|
||||
import Switch from "@components/ui/Switch";
|
||||
import EditWebhook from "@components/webhook/EditWebhook";
|
||||
import WebhookList from "@components/webhook/WebhookList";
|
||||
|
||||
export default function Embed() {
|
||||
const user = trpc.useQuery(["viewer.me"]).data;
|
||||
const [, loading] = useSession();
|
||||
const { t } = useLocale();
|
||||
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [bookingCreated, setBookingCreated] = useState(true);
|
||||
const [bookingRescheduled, setBookingRescheduled] = useState(true);
|
||||
const [bookingCancelled, setBookingCancelled] = useState(true);
|
||||
const [editWebhookEnabled, setEditWebhookEnabled] = useState(false);
|
||||
const [webhooks, setWebhooks] = useState([]);
|
||||
const [webhookToEdit, setWebhookToEdit] = useState<Webhook | null>();
|
||||
const [webhookEventTrigger, setWebhookEventTriggers] = useState([
|
||||
"BOOKING_CREATED",
|
||||
"BOOKING_RESCHEDULED",
|
||||
"BOOKING_CANCELLED",
|
||||
]);
|
||||
|
||||
const subUrlRef = useRef<HTMLInputElement>() as React.MutableRefObject<HTMLInputElement>;
|
||||
|
||||
useEffect(() => {
|
||||
const arr = [];
|
||||
bookingCreated && arr.push("BOOKING_CREATED");
|
||||
bookingRescheduled && arr.push("BOOKING_RESCHEDULED");
|
||||
bookingCancelled && arr.push("BOOKING_CANCELLED");
|
||||
setWebhookEventTriggers(arr);
|
||||
}, [bookingCreated, bookingRescheduled, bookingCancelled]);
|
||||
|
||||
useEffect(() => {
|
||||
getWebhooks();
|
||||
}, []);
|
||||
|
||||
const iframeTemplate = `<iframe src="${process.env.NEXT_PUBLIC_BASE_URL}/${user?.username}" frameborder="0" allowfullscreen></iframe>`;
|
||||
const htmlTemplate = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>${t(
|
||||
"schedule_a_meeting"
|
||||
)}</title><style>body {margin: 0;}iframe {height: calc(100vh - 4px);width: calc(100vw - 4px);box-sizing: border-box;}</style></head><body>${iframeTemplate}</body></html>`;
|
||||
const handleErrors = async (resp: Response) => {
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json();
|
||||
throw new Error(err.message);
|
||||
}
|
||||
return resp.json();
|
||||
};
|
||||
|
||||
const getWebhooks = () => {
|
||||
fetch("/api/webhook")
|
||||
.then(handleErrors)
|
||||
.then((data) => {
|
||||
setWebhooks(
|
||||
data.webhooks.map((webhook: Webhook) => {
|
||||
return {
|
||||
...webhook,
|
||||
eventTriggers: webhook.eventTriggers.map((eventTrigger: string) => eventTrigger.toLowerCase()),
|
||||
};
|
||||
})
|
||||
);
|
||||
console.log(data.webhooks);
|
||||
})
|
||||
.catch(console.log);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const createWebhook = () => {
|
||||
setLoading(true);
|
||||
fetch("/api/webhook", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
subscriberUrl: subUrlRef.current.value,
|
||||
eventTriggers: webhookEventTrigger,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(getWebhooks)
|
||||
.catch(console.log);
|
||||
};
|
||||
|
||||
const editWebhook = (webhook: Webhook) => {
|
||||
setEditWebhookEnabled(true);
|
||||
setWebhookToEdit(webhook);
|
||||
};
|
||||
|
||||
const onCloseEdit = () => {
|
||||
getWebhooks();
|
||||
setEditWebhookEnabled(false);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Shell heading={t("embed_and_webhooks")} subtitle={t("integrate_using_embed_or_webhooks")}>
|
||||
<SettingsShell>
|
||||
{!editWebhookEnabled && (
|
||||
<div className="py-6 lg:pb-8 lg:col-span-9">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">{t("iframe_embed")}</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">{t("embed_calcom")}</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 space-x-4">
|
||||
<div>
|
||||
<label htmlFor="iframe" className="block text-sm font-medium text-gray-700">
|
||||
{t("standard_iframe")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id="iframe"
|
||||
className="block w-full h-32 border-gray-300 rounded-sm shadow-sm focus:ring-black focus:border-black sm:text-sm"
|
||||
placeholder={t("loading")}
|
||||
defaultValue={iframeTemplate}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="fullscreen" className="block text-sm font-medium text-gray-700">
|
||||
{t("responsive_fullscreen_iframe")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id="fullscreen"
|
||||
className="block w-full h-32 border-gray-300 rounded-sm shadow-sm focus:ring-black focus:border-black sm:text-sm"
|
||||
placeholder={t("loading")}
|
||||
defaultValue={htmlTemplate}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="mt-8" />
|
||||
<div className="flex justify-between my-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">Webhooks</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">{t("receive_cal_meeting_data")} </p>
|
||||
</div>
|
||||
<div>
|
||||
<Dialog>
|
||||
<DialogTrigger className="px-4 py-2 my-6 text-sm font-medium text-white border border-transparent rounded-sm shadow-sm bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
|
||||
<PlusIcon className="inline w-5 h-5 mr-1" />
|
||||
{t("new_webhook")}
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader
|
||||
title={t("create_new_webhook")}
|
||||
subtitle={t("create_new_webhook_to_account")}
|
||||
/>
|
||||
<div className="my-4">
|
||||
<div className="mb-4">
|
||||
<label htmlFor="subUrl" className="block text-sm font-medium text-gray-700">
|
||||
{t("subscriber_url")}
|
||||
</label>
|
||||
<input
|
||||
ref={subUrlRef}
|
||||
type="text"
|
||||
name="subUrl"
|
||||
id="subUrl"
|
||||
placeholder="https://example.com/sub"
|
||||
required
|
||||
className="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-sm shadow-sm focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
|
||||
/>
|
||||
<legend className="block pt-4 mb-2 text-sm font-medium text-gray-700">
|
||||
{" "}
|
||||
{t("event_triggers")}{" "}
|
||||
</legend>
|
||||
<div className="p-2 border border-gray-300 rounded-sm">
|
||||
<div className="flex pb-4">
|
||||
<div className="w-10/12">
|
||||
<h2 className="font-medium text-gray-800">{t("booking_created")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-2/12 text-right">
|
||||
<Switch
|
||||
defaultChecked={true}
|
||||
id="booking-created"
|
||||
value={bookingCreated}
|
||||
onCheckedChange={() => {
|
||||
setBookingCreated(!bookingCreated);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex py-1">
|
||||
<div className="w-10/12">
|
||||
<h2 className="font-medium text-gray-800">{t("booking_rescheduled")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-2/12 text-right">
|
||||
<Switch
|
||||
defaultChecked={true}
|
||||
id="booking-rescheduled"
|
||||
value={bookingRescheduled}
|
||||
onCheckedChange={() => {
|
||||
setBookingRescheduled(!bookingRescheduled);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex pt-4">
|
||||
<div className="w-10/12">
|
||||
<h2 className="font-medium text-gray-800">{t("booking_cancelled")}</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-2/12 text-right">
|
||||
<Switch
|
||||
defaultChecked={true}
|
||||
id="booking-cancelled"
|
||||
value={bookingCancelled}
|
||||
onCheckedChange={() => {
|
||||
setBookingCancelled(!bookingCancelled);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gap-2 mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
loading={isLoading}
|
||||
onClick={createWebhook}
|
||||
color="primary"
|
||||
className="ml-2">
|
||||
{t("create_webhook")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary">{t("cancel")}</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-gray-200 lg:col-span-9">
|
||||
<div className="py-6 lg:pb-8">
|
||||
<div className="flex flex-col justify-between md:flex-row">
|
||||
<div></div>
|
||||
</div>
|
||||
<div>
|
||||
{!!webhooks.length && (
|
||||
<WebhookList
|
||||
webhooks={webhooks}
|
||||
onChange={getWebhooks}
|
||||
onEditWebhook={editWebhook}></WebhookList>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="mt-8" />
|
||||
<div className="my-6">
|
||||
<h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">Cal.com API</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">{t("leverage_our_api")}</p>
|
||||
</div>
|
||||
<a href="https://developer.cal.com/api" className="btn btn-primary">
|
||||
{t("browse_api_documentation")}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{!!editWebhookEnabled && webhookToEdit && (
|
||||
<EditWebhook webhook={webhookToEdit} onCloseEdit={onCloseEdit} />
|
||||
)}
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
);
|
||||
}
|
1
public/integrations/webhooks.svg
Normal file
1
public/integrations/webhooks.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="2500" height="2334" viewBox="0 0 256 239" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M119.54 100.503c-10.61 17.836-20.775 35.108-31.152 52.25-2.665 4.401-3.984 7.986-1.855 13.58 5.878 15.454-2.414 30.493-17.998 34.575-14.697 3.851-29.016-5.808-31.932-21.543-2.584-13.927 8.224-27.58 23.58-29.757 1.286-.184 2.6-.205 4.762-.367l23.358-39.168C73.612 95.465 64.868 78.39 66.803 57.23c1.368-14.957 7.25-27.883 18-38.477 20.59-20.288 52.002-23.573 76.246-8.001 23.284 14.958 33.948 44.094 24.858 69.031-6.854-1.858-13.756-3.732-21.343-5.79 2.854-13.865.743-26.315-8.608-36.981-6.178-7.042-14.106-10.733-23.12-12.093-18.072-2.73-35.815 8.88-41.08 26.618-5.976 20.13 3.069 36.575 27.784 48.967z" fill="#C73A63"/><path d="M149.841 79.41c7.475 13.187 15.065 26.573 22.587 39.836 38.02-11.763 66.686 9.284 76.97 31.817 12.422 27.219 3.93 59.457-20.465 76.25-25.04 17.238-56.707 14.293-78.892-7.851 5.654-4.733 11.336-9.487 17.407-14.566 21.912 14.192 41.077 13.524 55.305-3.282 12.133-14.337 11.87-35.714-.615-49.75-14.408-16.197-33.707-16.691-57.035-1.143-9.677-17.168-19.522-34.199-28.893-51.491-3.16-5.828-6.648-9.21-13.77-10.443-11.893-2.062-19.571-12.275-20.032-23.717-.453-11.316 6.214-21.545 16.634-25.53 10.322-3.949 22.435-.762 29.378 8.014 5.674 7.17 7.477 15.24 4.491 24.083-.83 2.466-1.905 4.852-3.07 7.774z" fill="#4B4B4B"/><path d="M167.707 187.21h-45.77c-4.387 18.044-13.863 32.612-30.19 41.876-12.693 7.2-26.373 9.641-40.933 7.29-26.808-4.323-48.728-28.456-50.658-55.63-2.184-30.784 18.975-58.147 47.178-64.293 1.947 7.071 3.915 14.21 5.862 21.264-25.876 13.202-34.832 29.836-27.59 50.636 6.375 18.304 24.484 28.337 44.147 24.457 20.08-3.962 30.204-20.65 28.968-47.432 19.036 0 38.088-.197 57.126.097 7.434.117 13.173-.654 18.773-7.208 9.22-10.784 26.191-9.811 36.121.374 10.148 10.409 9.662 27.157-1.077 37.127-10.361 9.62-26.73 9.106-36.424-1.26-1.992-2.136-3.562-4.673-5.533-7.298z" fill="#4A4A4A"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
Loading…
Reference in a new issue