perf: fetch data server side for /settings/webhooks (#20583)

* update client components

* update server components

* update middleware

* fix

* fix

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
Benny Joo
2025-04-07 18:21:13 +00:00
committed by GitHub
co-authored by Keith Williams
parent e9b5aa6f22
commit 134086f229
12 changed files with 144 additions and 98 deletions
@@ -0,0 +1,7 @@
"use server";
import { revalidatePath } from "next/cache";
export async function revalidateWebhooksList() {
revalidatePath("/settings/developer/webhooks");
}
@@ -0,0 +1,14 @@
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { SkeletonLoader } from "@calcom/features/webhooks/pages/webhooks-view";
export default function Loading() {
return (
<SettingsHeader
title="Webhooks"
description="Receive meeting data in real-time when something happens in Cal.com"
borderInShellHeader={true}
CTA={null}>
<SkeletonLoader />
</SettingsHeader>
);
}
@@ -0,0 +1,44 @@
import { createRouterCaller } from "app/_trpc/context";
import { _generateMetadata, getTranslate } from "app/_utils";
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { CreateNewWebhookButton } from "@calcom/features/webhooks/components";
import WebhooksView from "@calcom/features/webhooks/pages/webhooks-view";
import { APP_NAME } from "@calcom/lib/constants";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { webhookRouter } from "@calcom/trpc/server/routers/viewer/webhook/_router";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("webhooks"),
(t) => t("add_webhook_description", { appName: APP_NAME })
);
const WebhooksViewServerWrapper = async () => {
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session?.user?.id) {
redirect("/auth/login");
}
const t = await getTranslate();
const isAdmin = session.user.role === UserPermissionRole.ADMIN;
const caller = await createRouterCaller(webhookRouter);
const data = await caller.getByViewer();
return (
<SettingsHeader
title={t("webhooks")}
description={t("add_webhook_description", { appName: APP_NAME })}
CTA={data && data.webhookGroups.length > 0 ? <CreateNewWebhookButton isAdmin={isAdmin} /> : null}
borderInShellHeader={(data && data.profiles.length === 1) || !data?.webhookGroups?.length}>
<WebhooksView data={data} isAdmin={isAdmin} />
</SettingsHeader>
);
};
export default WebhooksViewServerWrapper;
@@ -1,16 +0,0 @@
import { _generateMetadata } from "app/_utils";
import WebhooksView from "@calcom/features/webhooks/pages/webhooks-view";
import { APP_NAME } from "@calcom/lib/constants";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("webhooks"),
(t) => t("add_webhook_description", { appName: APP_NAME })
);
const WebhooksViewServerWrapper = async () => {
return <WebhooksView />;
};
export default WebhooksViewServerWrapper;
+4 -1
View File
@@ -18,7 +18,10 @@ const safeGet = async <T = any>(key: string): Promise<T | undefined> => {
export const POST_METHODS_ALLOWED_API_ROUTES = ["/api/"]; // trailing slash in "/api/" is actually important to block edge cases like `/api.php`
// Some app routes are allowed because "revalidatePath()" is used to revalidate the cache for them
export const POST_METHODS_ALLOWED_APP_ROUTES = ["/settings/my-account/general"];
export const POST_METHODS_ALLOWED_APP_ROUTES = [
"/settings/my-account/general",
"/settings/developer/webhooks",
];
export function checkPostMethod(req: NextRequest) {
const pathname = req.nextUrl.pathname;
@@ -1,3 +1,5 @@
"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
@@ -1,3 +1,5 @@
"use client";
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
@@ -7,10 +9,10 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TimeUnit } from "@calcom/prisma/enums";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import type { RouterOutputs } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components/button";
import { Select } from "@calcom/ui/components/form";
import { TextArea } from "@calcom/ui/components/form";
import { ToggleGroup } from "@calcom/ui/components/form";
import { Button } from "@calcom/ui/components/button";
import { Form } from "@calcom/ui/components/form";
import { Label } from "@calcom/ui/components/form";
import { TextField } from "@calcom/ui/components/form";
@@ -1,3 +1,5 @@
"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
@@ -15,6 +17,7 @@ import {
import { Switch } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/components/toast";
import { Tooltip } from "@calcom/ui/components/tooltip";
import { revalidateWebhooksList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions";
type WebhookProps = {
id: string;
@@ -45,6 +48,7 @@ export default function WebhookListItem(props: {
await utils.viewer.webhook.getByViewer.invalidate();
await utils.viewer.webhook.list.invalidate();
await utils.viewer.eventTypes.get.invalidate();
revalidateWebhooksList();
},
});
const toggleWebhook = trpc.viewer.webhook.edit.useMutation({
@@ -54,6 +58,7 @@ export default function WebhookListItem(props: {
await utils.viewer.webhook.getByViewer.invalidate();
await utils.viewer.webhook.list.invalidate();
await utils.viewer.eventTypes.get.invalidate();
revalidateWebhooksList();
},
});
@@ -1,12 +1,14 @@
"use client";
import { useWatch } from "react-hook-form";
import { ZodError } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { ZTestTriggerInputSchema } from "@calcom/trpc/server/routers/viewer/webhook/testTrigger.schema";
import { showToast } from "@calcom/ui/components/toast";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { showToast } from "@calcom/ui/components/toast";
export default function WebhookTestDisclosure() {
const [subscriberUrl, webhookSecret]: [string, string] = useWatch({ name: ["subscriberUrl", "secret"] });
@@ -7,6 +7,7 @@ import type { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import { SkeletonContainer } from "@calcom/ui/components/skeleton";
import { showToast } from "@calcom/ui/components/toast";
import { revalidateWebhooksList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions";
import type { WebhookFormSubmitData } from "../components/WebhookForm";
import WebhookForm from "../components/WebhookForm";
@@ -45,7 +46,8 @@ export function EditWebhookView({ webhook }: { webhook?: WebhookProps }) {
await utils.viewer.webhook.list.invalidate();
await utils.viewer.webhook.get.invalidate({ webhookId: webhook?.id });
showToast(t("webhook_updated_successfully"), "success");
router.back();
revalidateWebhooksList();
router.push("/settings/developer/webhooks");
},
onError(error) {
showToast(`${error.message}`, "error");
@@ -8,6 +8,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { SkeletonText, SkeletonContainer } from "@calcom/ui/components/skeleton";
import { showToast } from "@calcom/ui/components/toast";
import { revalidateWebhooksList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions";
import type { WebhookFormSubmitData } from "../components/WebhookForm";
import WebhookForm from "../components/WebhookForm";
@@ -50,7 +51,8 @@ export const NewWebhookView = () => {
async onSuccess() {
showToast(t("webhook_created_successfully"), "success");
await utils.viewer.webhook.list.invalidate();
router.back();
revalidateWebhooksList();
router.push("/settings/developer/webhooks");
},
onError(error) {
showToast(`${error.message}`, "error");
@@ -1,23 +1,20 @@
"use client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import type { RouterOutputs } from "@calcom/trpc/react";
import type { WebhooksByViewer } from "@calcom/trpc/server/routers/viewer/webhook/getByViewer.handler";
import { Avatar } from "@calcom/ui/components/avatar";
import classNames from "@calcom/ui/classNames";
import { Avatar } from "@calcom/ui/components/avatar";
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
import { SkeletonText, SkeletonContainer } from "@calcom/ui/components/skeleton";
import { WebhookListItem, CreateNewWebhookButton } from "../components";
const SkeletonLoader = () => {
export const SkeletonLoader = () => {
return (
<SkeletonContainer>
<div className="divide-subtle border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-4">
@@ -28,29 +25,16 @@ const SkeletonLoader = () => {
);
};
const WebhooksView = () => {
const { t } = useLocale();
const session = useSession();
const isAdmin = session.data?.user.role === UserPermissionRole.ADMIN;
const { data, isPending } = trpc.viewer.webhook.getByViewer.useQuery(undefined, {
enabled: session.status === "authenticated",
});
if (isPending || !data) {
return <SkeletonLoader />;
}
type Props = {
data: RouterOutputs["viewer"]["webhook"]["getByViewer"];
isAdmin: boolean;
};
const WebhooksView = ({ data, isAdmin }: Props) => {
return (
<SettingsHeader
title={t("webhooks")}
description={t("add_webhook_description", { appName: APP_NAME })}
CTA={data && data.webhookGroups.length > 0 ? <CreateNewWebhookButton isAdmin={isAdmin} /> : null}
borderInShellHeader={(data && data.profiles.length === 1) || !data?.webhookGroups?.length}>
<div>
<WebhooksList webhooksByViewer={data} isAdmin={isAdmin} />
</div>
</SettingsHeader>
<div>
<WebhooksList webhooksByViewer={data} isAdmin={isAdmin} />
</div>
);
};
@@ -70,59 +54,54 @@ const WebhooksList = ({
return (
<>
{webhookGroups && (
<>
{!!webhookGroups.length && (
<div className={classNames("mt-0", hasTeams && "mt-6")}>
{webhookGroups.map((group) => (
<div key={group.teamId}>
{hasTeams && (
<div className="items-centers flex">
<Avatar
alt={group.profile.image || ""}
imageSrc={group.profile.image || `${bookerUrl}/${group.profile.name}/avatar.png`}
size="md"
className="inline-flex justify-center"
/>
<div className="text-emphasis ml-2 flex flex-grow items-center font-bold">
{group.profile.name || ""}
</div>
</div>
)}
<div className="flex flex-col" key={group.profile.slug}>
<div
className={classNames(
"border-subtle rounded-lg rounded-t-none border border-t-0",
hasTeams && "mb-8 mt-3 rounded-t-lg border-t"
)}>
{group.webhooks.map((webhook, index) => (
<WebhookListItem
key={webhook.id}
webhook={webhook}
readOnly={group.metadata?.readOnly ?? false}
lastItem={group.webhooks.length === index + 1}
onEditWebhook={() =>
router.push(`${WEBAPP_URL}/settings/developer/webhooks/${webhook.id}`)
}
/>
))}
</div>
{!!webhookGroups.length ? (
<div className={classNames("mt-0", hasTeams && "mt-6")}>
{webhookGroups.map((group) => (
<div key={group.teamId}>
{hasTeams && (
<div className="items-centers flex">
<Avatar
alt={group.profile.image || ""}
imageSrc={group.profile.image || `${bookerUrl}/${group.profile.name}/avatar.png`}
size="md"
className="inline-flex justify-center"
/>
<div className="text-emphasis ml-2 flex flex-grow items-center font-bold">
{group.profile.name || ""}
</div>
</div>
))}
)}
<div className="flex flex-col" key={group.profile.slug}>
<div
className={classNames(
"border-subtle rounded-lg rounded-t-none border border-t-0",
hasTeams && "mb-8 mt-3 rounded-t-lg border-t"
)}>
{group.webhooks.map((webhook, index) => (
<WebhookListItem
key={webhook.id}
webhook={webhook}
readOnly={group.metadata?.readOnly ?? false}
lastItem={group.webhooks.length === index + 1}
onEditWebhook={() =>
router.push(`${WEBAPP_URL}/settings/developer/webhooks/${webhook.id}`)
}
/>
))}
</div>
</div>
</div>
)}
{!webhookGroups.length && (
<EmptyScreen
Icon="link"
headline={t("create_your_first_webhook")}
description={t("create_your_first_webhook_description", { appName: APP_NAME })}
className="rounded-b-lg rounded-t-none border-t-0"
buttonRaw={<CreateNewWebhookButton isAdmin={isAdmin} />}
border={true}
/>
)}
</>
))}
</div>
) : (
<EmptyScreen
Icon="link"
headline={t("create_your_first_webhook")}
description={t("create_your_first_webhook_description", { appName: APP_NAME })}
className="rounded-b-lg rounded-t-none border-t-0"
buttonRaw={<CreateNewWebhookButton isAdmin={isAdmin} />}
border={true}
/>
)}
</>
);