From 134086f229370bad91de181bf1ebec348b32355b Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 7 Apr 2025 14:21:13 -0400 Subject: [PATCH] perf: fetch data server side for /settings/webhooks (#20583) * update client components * update server components * update middleware * fix * fix --------- Co-authored-by: Keith Williams --- .../webhooks/(with-loader)/actions.ts | 7 + .../webhooks/(with-loader)/loading.tsx | 14 ++ .../developer/webhooks/(with-loader)/page.tsx | 44 ++++++ .../developer/webhooks/page.tsx | 16 --- apps/web/middleware.ts | 5 +- .../components/EventTypeWebhookListItem.tsx | 2 + .../webhooks/components/WebhookForm.tsx | 4 +- .../webhooks/components/WebhookListItem.tsx | 5 + .../components/WebhookTestDisclosure.tsx | 4 +- .../webhooks/pages/webhook-edit-view.tsx | 4 +- .../webhooks/pages/webhook-new-view.tsx | 4 +- .../features/webhooks/pages/webhooks-view.tsx | 133 ++++++++---------- 12 files changed, 144 insertions(+), 98 deletions(-) create mode 100644 apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions.ts create mode 100644 apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/loading.tsx create mode 100644 apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/page.tsx delete mode 100644 apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/page.tsx diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions.ts b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions.ts new file mode 100644 index 0000000000..fd9836607c --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions.ts @@ -0,0 +1,7 @@ +"use server"; + +import { revalidatePath } from "next/cache"; + +export async function revalidateWebhooksList() { + revalidatePath("/settings/developer/webhooks"); +} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/loading.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/loading.tsx new file mode 100644 index 0000000000..ebd2717b55 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/loading.tsx @@ -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 ( + + + + ); +} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/page.tsx new file mode 100644 index 0000000000..12ccfde849 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/page.tsx @@ -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 ( + 0 ? : null} + borderInShellHeader={(data && data.profiles.length === 1) || !data?.webhookGroups?.length}> + + + ); +}; + +export default WebhooksViewServerWrapper; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/page.tsx deleted file mode 100644 index b856d73837..0000000000 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/page.tsx +++ /dev/null @@ -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 ; -}; - -export default WebhooksViewServerWrapper; diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 469e7aa001..daafb65bfe 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -18,7 +18,10 @@ const safeGet = async (key: string): Promise => { 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; diff --git a/packages/features/webhooks/components/EventTypeWebhookListItem.tsx b/packages/features/webhooks/components/EventTypeWebhookListItem.tsx index 297767a293..7fdf71b7c7 100644 --- a/packages/features/webhooks/components/EventTypeWebhookListItem.tsx +++ b/packages/features/webhooks/components/EventTypeWebhookListItem.tsx @@ -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"; diff --git a/packages/features/webhooks/components/WebhookForm.tsx b/packages/features/webhooks/components/WebhookForm.tsx index 4bd079f3ba..4bc4a84d23 100644 --- a/packages/features/webhooks/components/WebhookForm.tsx +++ b/packages/features/webhooks/components/WebhookForm.tsx @@ -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"; diff --git a/packages/features/webhooks/components/WebhookListItem.tsx b/packages/features/webhooks/components/WebhookListItem.tsx index 82288b7110..8eeb899b67 100644 --- a/packages/features/webhooks/components/WebhookListItem.tsx +++ b/packages/features/webhooks/components/WebhookListItem.tsx @@ -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(); }, }); diff --git a/packages/features/webhooks/components/WebhookTestDisclosure.tsx b/packages/features/webhooks/components/WebhookTestDisclosure.tsx index fb28422936..e5bf6bd08f 100644 --- a/packages/features/webhooks/components/WebhookTestDisclosure.tsx +++ b/packages/features/webhooks/components/WebhookTestDisclosure.tsx @@ -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"] }); diff --git a/packages/features/webhooks/pages/webhook-edit-view.tsx b/packages/features/webhooks/pages/webhook-edit-view.tsx index 3ec5a70b38..48d87332cb 100644 --- a/packages/features/webhooks/pages/webhook-edit-view.tsx +++ b/packages/features/webhooks/pages/webhook-edit-view.tsx @@ -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"); diff --git a/packages/features/webhooks/pages/webhook-new-view.tsx b/packages/features/webhooks/pages/webhook-new-view.tsx index 2206a1f54c..2a5df6ed46 100644 --- a/packages/features/webhooks/pages/webhook-new-view.tsx +++ b/packages/features/webhooks/pages/webhook-new-view.tsx @@ -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"); diff --git a/packages/features/webhooks/pages/webhooks-view.tsx b/packages/features/webhooks/pages/webhooks-view.tsx index 724e776cdd..c76d5a366e 100644 --- a/packages/features/webhooks/pages/webhooks-view.tsx +++ b/packages/features/webhooks/pages/webhooks-view.tsx @@ -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 (
@@ -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 ; - } +type Props = { + data: RouterOutputs["viewer"]["webhook"]["getByViewer"]; + isAdmin: boolean; +}; +const WebhooksView = ({ data, isAdmin }: Props) => { return ( - 0 ? : null} - borderInShellHeader={(data && data.profiles.length === 1) || !data?.webhookGroups?.length}> -
- -
-
+
+ +
); }; @@ -70,59 +54,54 @@ const WebhooksList = ({ return ( <> - {webhookGroups && ( - <> - {!!webhookGroups.length && ( -
- {webhookGroups.map((group) => ( -
- {hasTeams && ( -
- -
- {group.profile.name || ""} -
-
- )} -
-
- {group.webhooks.map((webhook, index) => ( - - router.push(`${WEBAPP_URL}/settings/developer/webhooks/${webhook.id}`) - } - /> - ))} -
+ {!!webhookGroups.length ? ( +
+ {webhookGroups.map((group) => ( +
+ {hasTeams && ( +
+ +
+ {group.profile.name || ""}
- ))} + )} +
+
+ {group.webhooks.map((webhook, index) => ( + + router.push(`${WEBAPP_URL}/settings/developer/webhooks/${webhook.id}`) + } + /> + ))} +
+
- )} - {!webhookGroups.length && ( - } - border={true} - /> - )} - + ))} +
+ ) : ( + } + border={true} + /> )} );