diff --git a/apps/web/lib/webhooks/constants.ts b/apps/web/lib/webhooks/constants.ts index ed50eb9f9d..4b18544510 100644 --- a/apps/web/lib/webhooks/constants.ts +++ b/apps/web/lib/webhooks/constants.ts @@ -9,10 +9,10 @@ export const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP = { WebhookTriggerEvents.BOOKING_RESCHEDULED, WebhookTriggerEvents.MEETING_ENDED, ] as const, - routing_forms: [WebhookTriggerEvents.FORM_SUBMITTED] as ["FORM_SUBMITTED"], + "routing-forms": [WebhookTriggerEvents.FORM_SUBMITTED] as ["FORM_SUBMITTED"], }; export const WEBHOOK_TRIGGER_EVENTS = [ ...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP.core, - ...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP.routing_forms, + ...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP["routing-forms"], ] as const; diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index a375716d40..f5c7e99586 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -24,7 +24,8 @@ const V2_WHITELIST = [ // For pages // - which has V1 versions being modified as V2 -const V2_BLACKLIST = ["/apps/routing_forms/", "/apps/typeform/"]; +// - Add routing_forms to keep old links working +const V2_BLACKLIST = ["/apps/routing_forms/", "/apps/routing-forms/", "/apps/typeform/"]; const middleware: NextMiddleware = async (req) => { const url = req.nextUrl; @@ -43,6 +44,13 @@ const middleware: NextMiddleware = async (req) => { return NextResponse.redirect(req.nextUrl); } } + + // Don't 404 old routing_forms links + if (url.pathname.startsWith("/apps/routing_forms")) { + url.pathname = url.pathname.replace("/apps/routing_forms", "/apps/routing-forms"); + return NextResponse.rewrite(url); + } + /** Display available V2 pages */ if ( !V2_BLACKLIST.some((p) => url.pathname.startsWith(p)) && @@ -52,6 +60,7 @@ const middleware: NextMiddleware = async (req) => { url.pathname = `/v2${url.pathname}`; return NextResponse.rewrite(url); } + return NextResponse.next(); }; diff --git a/apps/web/next.config.js b/apps/web/next.config.js index a9086c8526..f168334982 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -135,11 +135,11 @@ const nextConfig = { }, { source: "/forms/:formId", - destination: "/apps/routing_forms/routing-link/:formId", + destination: "/apps/routing-forms/routing-link/:formId", }, { source: "/router", - destination: "/apps/routing_forms/router", + destination: "/apps/routing-forms/router", }, /* TODO: have these files being served from another deployment or CDN { source: "/embed/embed.js", diff --git a/apps/web/pages/apps/[slug]/[...pages].tsx b/apps/web/pages/apps/[slug]/[...pages].tsx index 1a08dad139..f528391e25 100644 --- a/apps/web/pages/apps/[slug]/[...pages].tsx +++ b/apps/web/pages/apps/[slug]/[...pages].tsx @@ -1,7 +1,7 @@ import { GetServerSidePropsContext } from "next"; import { useRouter } from "next/router"; -import RoutingFormsRoutingConfig from "@calcom/app-store/ee/routing_forms/pages/app-routing.config"; +import RoutingFormsRoutingConfig from "@calcom/app-store/ee/routing-forms/pages/app-routing.config"; import TypeformRoutingConfig from "@calcom/app-store/typeform/pages/app-routing.config"; import prisma from "@calcom/prisma"; import { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps"; @@ -28,7 +28,7 @@ type NotFound = { // TODO: It is a candidate for apps.*.generated.* const AppsRouting = { - routing_forms: RoutingFormsRoutingConfig, + "routing-forms": RoutingFormsRoutingConfig, typeform: TypeformRoutingConfig, }; diff --git a/apps/web/pages/settings/developer.tsx b/apps/web/pages/settings/developer.tsx index b2b0de5b95..b297c0a150 100644 --- a/apps/web/pages/settings/developer.tsx +++ b/apps/web/pages/settings/developer.tsx @@ -10,7 +10,7 @@ export default function Settings() { const { data: routingForms } = trpc.useQuery([ "viewer.appById", { - appId: "routing_forms", + appId: "routing-forms", }, ]); @@ -19,7 +19,7 @@ export default function Settings() { {routingForms && ( diff --git a/apps/web/playwright/integrations.e2e.ts b/apps/web/playwright/integrations.e2e.ts index 2acaa9c630..f5383ef834 100644 --- a/apps/web/playwright/integrations.e2e.ts +++ b/apps/web/playwright/integrations.e2e.ts @@ -6,7 +6,7 @@ import { v4 as uuidv4 } from "uuid"; import { prisma } from "@calcom/prisma"; import { test } from "./lib/fixtures"; -import { createHttpServer, selectFirstAvailableTimeSlotNextMonth, todo, waitFor } from "./lib/testUtils"; +import { todo } from "./lib/testUtils"; declare let global: { E2E_EMAILS?: ({ text: string } | Record)[]; @@ -182,7 +182,7 @@ test.fixme("Integrations", () => { }, token: { url: "https://zoom.us/oauth/token", - verify({ requestHeaders, code }) { + verify({ requestHeaders }) { const authorization = requestHeaders.get("authorization").replace("Basic ", ""); const clientPair = Buffer.from(authorization, "base64").toString(); const [clientId, clientSecret] = clientPair.split(":"); @@ -361,75 +361,4 @@ test.fixme("Integrations", () => { todo("Can add CalDav Calendar"); todo("Can add Apple Calendar"); - - test("add webhook & test that creating an event triggers a webhook call", async ({ - page, - users, - }, testInfo) => { - const webhookReceiver = createHttpServer(); - const user = await users.create(); - const [eventType] = user.eventTypes; - await user.login(); - await page.goto(`/settings/developer`); - - // --- add webhook - await page.click('[data-testid="new_webhook"]'); - - await expect(page.locator(`[data-testid='WebhookDialogForm']`)).toBeVisible(); - - await page.fill('[name="subscriberUrl"]', webhookReceiver.url); - - await page.fill('[name="secret"]', "secret"); - - await page.click("[type=submit]"); - - // dialog is closed - await expect(page.locator(`[data-testid='WebhookDialogForm']`)).not.toBeVisible(); - // page contains the url - expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined(); - - // --- Book the first available day next month in the pro user's "30min"-event - await page.goto(`/${user.username}/${eventType.slug}`); - await selectFirstAvailableTimeSlotNextMonth(page); - - // --- fill form - await page.fill('[name="name"]', "Test Testson"); - await page.fill('[name="email"]', "test@example.com"); - await page.press('[name="email"]', "Enter"); - - // --- check that webhook was called - await waitFor(() => { - expect(webhookReceiver.requestList.length).toBe(1); - }); - - const [request] = webhookReceiver.requestList; - const body = request.body as any; - - // remove dynamic properties that differs depending on where you run the tests - const dynamic = "[redacted/dynamic]"; - body.createdAt = dynamic; - body.payload.startTime = dynamic; - body.payload.endTime = dynamic; - body.payload.location = dynamic; - for (const attendee of body.payload.attendees) { - attendee.timeZone = dynamic; - attendee.language = dynamic; - } - body.payload.organizer.email = dynamic; - body.payload.organizer.timeZone = dynamic; - body.payload.organizer.language = dynamic; - body.payload.uid = dynamic; - body.payload.bookingId = dynamic; - body.payload.additionalInformation = dynamic; - body.payload.requiresConfirmation = dynamic; - body.payload.eventTypeId = dynamic; - - // if we change the shape of our webhooks, we can simply update this by clicking `u` - // console.log("BODY", body); - // Text files shouldn't have platform specific suffixes - testInfo.snapshotSuffix = ""; - expect(JSON.stringify(body)).toMatchSnapshot(`webhookResponse.txt`); - - webhookReceiver.close(); - }); }); diff --git a/apps/web/playwright/webhook.e2e.ts b/apps/web/playwright/webhook.e2e.ts new file mode 100644 index 0000000000..5c452c8d41 --- /dev/null +++ b/apps/web/playwright/webhook.e2e.ts @@ -0,0 +1,76 @@ +import { expect } from "@playwright/test"; + +import { test } from "./lib/fixtures"; +import { createHttpServer, selectFirstAvailableTimeSlotNextMonth, waitFor } from "./lib/testUtils"; + +test("add webhook & test that creating an event triggers a webhook call", async ({ + page, + users, +}, testInfo) => { + const webhookReceiver = createHttpServer(); + const user = await users.create(); + const [eventType] = user.eventTypes; + await user.login(); + await page.goto(`/settings/developer/webhooks`); + + // --- add webhook + await page.click('[data-testid="new_webhook"]'); + + await page.fill('[name="subscriberUrl"]', webhookReceiver.url); + + await page.fill('[name="secret"]', "secret"); + + await Promise.all([ + page.click("[type=submit]"), + page.waitForNavigation({ + url: (url) => url.pathname.endsWith("/settings/developer/webhooks"), + }), + ]); + + // page contains the url + expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined(); + + // --- Book the first available day next month in the pro user's "30min"-event + await page.goto(`/${user.username}/${eventType.slug}`); + await selectFirstAvailableTimeSlotNextMonth(page); + + // --- fill form + await page.fill('[name="name"]', "Test Testson"); + await page.fill('[name="email"]', "test@example.com"); + await page.press('[name="email"]', "Enter"); + + // --- check that webhook was called + await waitFor(() => { + expect(webhookReceiver.requestList.length).toBe(1); + }); + + const [request] = webhookReceiver.requestList; + const body = request.body as any; + + // remove dynamic properties that differs depending on where you run the tests + const dynamic = "[redacted/dynamic]"; + body.createdAt = dynamic; + body.payload.startTime = dynamic; + body.payload.endTime = dynamic; + body.payload.location = dynamic; + for (const attendee of body.payload.attendees) { + attendee.timeZone = dynamic; + attendee.language = dynamic; + } + body.payload.organizer.email = dynamic; + body.payload.organizer.timeZone = dynamic; + body.payload.organizer.language = dynamic; + body.payload.uid = dynamic; + body.payload.bookingId = dynamic; + body.payload.additionalInformation = dynamic; + body.payload.requiresConfirmation = dynamic; + body.payload.eventTypeId = dynamic; + + // if we change the shape of our webhooks, we can simply update this by clicking `u` + // console.log("BODY", body); + // Text files shouldn't have platform specific suffixes + testInfo.snapshotSuffix = ""; + expect(JSON.stringify(body)).toMatchSnapshot(`webhookResponse.txt`); + + webhookReceiver.close(); +}); diff --git a/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt b/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt new file mode 100644 index 0000000000..97234902d2 --- /dev/null +++ b/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt @@ -0,0 +1 @@ +{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","uid":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}} \ No newline at end of file diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index f0ddea5552..56b611a2ac 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -12,7 +12,7 @@ }, "include": [ /* Find a way to not require this - App files don't belong here. */ - "../../packages/app-store/routing_forms/env.d.ts", + "../../packages/app-store/routing-forms/env.d.ts", "next-env.d.ts", "../../packages/types/*.d.ts", "../../packages/types/next-auth.d.ts", diff --git a/packages/app-store-cli/src/app-store.ts b/packages/app-store-cli/src/app-store.ts index 3f83756cd9..bfb7b72b68 100644 --- a/packages/app-store-cli/src/app-store.ts +++ b/packages/app-store-cli/src/app-store.ts @@ -13,6 +13,10 @@ if (process.argv[2] === "--watch") { const formatOutput = (source: string) => prettier.format(source, prettierConfig); +const getVariableName = function (appName) { + return appName.replace("-", "_"); +}; + const APP_STORE_PATH = path.join(__dirname, "..", "..", "app-store"); type App = { name: string; @@ -112,7 +116,7 @@ function generateFiles() { ...getObjectExporter("apiHandlers", { fileToBeImported: "api/index.ts", // Import path must have / even for windows and not \ - entryBuilder: (app) => ` ${app.name}: import("./${app.path.replace(/\\/g, "/")}/api"),`, + entryBuilder: (app) => ` "${app.name}": import("./${app.path.replace(/\\/g, "/")}/api"),`, }) ); @@ -121,8 +125,11 @@ function generateFiles() { fileToBeImported: "_metadata.ts", // Import path must have / even for windows and not \ importBuilder: (app) => - `import { metadata as ${app.name}_meta } from "./${app.path.replace(/\\/g, "/")}/_metadata";`, - entryBuilder: (app) => ` ${app.name}:${app.name}_meta,`, + `import { metadata as ${getVariableName(app.name)}_meta } from "./${app.path.replace( + /\\/g, + "/" + )}/_metadata";`, + entryBuilder: (app) => ` "${app.name}":${getVariableName(app.name)}_meta,`, }) ); diff --git a/packages/app-store/apps.browser.generated.tsx b/packages/app-store/apps.browser.generated.tsx index f52c18d67e..0fb77efa4b 100644 --- a/packages/app-store/apps.browser.generated.tsx +++ b/packages/app-store/apps.browser.generated.tsx @@ -10,7 +10,7 @@ import { metadata as caldavcalendar_meta } from "./caldavcalendar/_metadata"; import { metadata as campfire_meta } from "./campfire/_metadata"; import { metadata as closecomothercalendar_meta } from "./closecomothercalendar/_metadata"; import { metadata as dailyvideo_meta } from "./dailyvideo/_metadata"; -import { metadata as routing_forms_meta } from "./ee/routing_forms/_metadata"; +import { metadata as routing_forms_meta } from "./ee/routing-forms/_metadata"; import { metadata as exchange2013calendar_meta } from "./exchange2013calendar/_metadata"; import { metadata as exchange2016calendar_meta } from "./exchange2016calendar/_metadata"; import { metadata as exchangecalendar_meta } from "./exchangecalendar/_metadata"; @@ -45,7 +45,7 @@ export const appStoreMetadata = { campfire: campfire_meta, closecomothercalendar: closecomothercalendar_meta, dailyvideo: dailyvideo_meta, - routing_forms: routing_forms_meta, + "routing-forms": routing_forms_meta, exchange2013calendar: exchange2013calendar_meta, exchange2016calendar: exchange2016calendar_meta, exchangecalendar: exchangecalendar_meta, diff --git a/packages/app-store/apps.server.generated.ts b/packages/app-store/apps.server.generated.ts index 3e0cf5c318..a17b345c50 100644 --- a/packages/app-store/apps.server.generated.ts +++ b/packages/app-store/apps.server.generated.ts @@ -8,7 +8,7 @@ export const apiHandlers = { caldavcalendar: import("./caldavcalendar/api"), campfire: import("./campfire/api"), closecomothercalendar: import("./closecomothercalendar/api"), - routing_forms: import("./ee/routing_forms/api"), + "routing-forms": import("./ee/routing-forms/api"), exchange2013calendar: import("./exchange2013calendar/api"), exchange2016calendar: import("./exchange2016calendar/api"), exchangecalendar: import("./exchangecalendar/api"), diff --git a/packages/app-store/ee/routing_forms/_metadata.ts b/packages/app-store/ee/routing-forms/_metadata.ts similarity index 100% rename from packages/app-store/ee/routing_forms/_metadata.ts rename to packages/app-store/ee/routing-forms/_metadata.ts diff --git a/packages/app-store/ee/routing_forms/api/add.ts b/packages/app-store/ee/routing-forms/api/add.ts similarity index 94% rename from packages/app-store/ee/routing_forms/api/add.ts rename to packages/app-store/ee/routing-forms/api/add.ts index 38f2ff88dc..28bf7305dc 100644 --- a/packages/app-store/ee/routing_forms/api/add.ts +++ b/packages/app-store/ee/routing-forms/api/add.ts @@ -20,7 +20,7 @@ const handler: AppDeclarativeHandler = { }, }); }, - redirectUrl: "/apps/routing_forms/forms", + redirectUrl: "/apps/routing-forms/forms", }; export default handler; diff --git a/packages/app-store/ee/routing_forms/api/index.ts b/packages/app-store/ee/routing-forms/api/index.ts similarity index 100% rename from packages/app-store/ee/routing_forms/api/index.ts rename to packages/app-store/ee/routing-forms/api/index.ts diff --git a/packages/app-store/ee/routing_forms/api/responses/[formId].ts b/packages/app-store/ee/routing-forms/api/responses/[formId].ts similarity index 100% rename from packages/app-store/ee/routing_forms/api/responses/[formId].ts rename to packages/app-store/ee/routing-forms/api/responses/[formId].ts diff --git a/packages/app-store/ee/routing_forms/components/FormActions.tsx b/packages/app-store/ee/routing-forms/components/FormActions.tsx similarity index 98% rename from packages/app-store/ee/routing_forms/components/FormActions.tsx rename to packages/app-store/ee/routing-forms/components/FormActions.tsx index 4edba470f6..1927047c9e 100644 --- a/packages/app-store/ee/routing_forms/components/FormActions.tsx +++ b/packages/app-store/ee/routing-forms/components/FormActions.tsx @@ -86,7 +86,7 @@ function NewFormDialog({ appUrl }: { appUrl: string }) { {t("add_new_form")}
-

{t("forms_description")}

+

{t("form_description")}

_delete.onAction({ routingForm }), diff --git a/packages/app-store/ee/routing_forms/components/RoutingNavBar.tsx b/packages/app-store/ee/routing-forms/components/RoutingNavBar.tsx similarity index 100% rename from packages/app-store/ee/routing_forms/components/RoutingNavBar.tsx rename to packages/app-store/ee/routing-forms/components/RoutingNavBar.tsx diff --git a/packages/app-store/ee/routing_forms/components/SingleForm.tsx b/packages/app-store/ee/routing-forms/components/SingleForm.tsx similarity index 98% rename from packages/app-store/ee/routing_forms/components/SingleForm.tsx rename to packages/app-store/ee/routing-forms/components/SingleForm.tsx index 8f0e805197..602ee49b86 100644 --- a/packages/app-store/ee/routing_forms/components/SingleForm.tsx +++ b/packages/app-store/ee/routing-forms/components/SingleForm.tsx @@ -7,7 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Icon } from "@calcom/ui"; import { Form } from "@calcom/ui/form/fields"; -import { showToast } from "@calcom/ui/v2"; +import { showToast, DropdownMenuSeparator } from "@calcom/ui/v2"; import { ButtonGroup, TextAreaField, TextField, Tooltip, Button, VerticalDivider } from "@calcom/ui/v2"; import Meta from "@calcom/ui/v2/core/Meta"; import { ShellMain } from "@calcom/ui/v2/core/Shell"; @@ -82,6 +82,7 @@ const Actions = ({ StartIcon={Icon.FiCode} tooltip={t("embed")} /> + { + animationRef.current && autoAnimate(animationRef.current); + }, [animationRef]); + const addField = () => { appendHookFormField({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -214,7 +221,7 @@ const FormEdit = ({ return hookFormFields.length ? (
-
+
{hookFormFields.map((field, key) => { return ( deserializeRoute(route, config)); }); + const animationRef = useRef(null); + + useEffect(() => { + animationRef.current && autoAnimate(animationRef.current); + }, [animationRef]); const mainRoutes = routes.filter((route) => !route.isFallback); let fallbackRoute = routes.find((route) => route.isFallback); @@ -370,7 +376,7 @@ const Routes = ({ hookForm.setValue("routes", routesToSave); return (
-
+
{mainRoutes.map((route, key) => { return ( {!customPageMessage ? ( <> - {form.name} | Cal.com Forms + {`${form.name} | Cal.com Forms`}
diff --git a/packages/app-store/ee/routing_forms/playwright/tests/basic.e2e.ts b/packages/app-store/ee/routing-forms/playwright/tests/basic.e2e.ts similarity index 92% rename from packages/app-store/ee/routing_forms/playwright/tests/basic.e2e.ts rename to packages/app-store/ee/routing-forms/playwright/tests/basic.e2e.ts index 19feb7c9e2..8190cbbcd5 100644 --- a/packages/app-store/ee/routing_forms/playwright/tests/basic.e2e.ts +++ b/packages/app-store/ee/routing-forms/playwright/tests/basic.e2e.ts @@ -74,13 +74,13 @@ test.describe("Routing Forms", () => { }); test.describe("Zero State Routing Forms", () => { test.beforeEach(async ({ page, users }) => { - const user = await users.create({ username: "routing_forms" }); + const user = await users.create({ username: "routing-forms" }); await user.login(); // Install app - await page.goto(`/apps/routing_forms`); + await page.goto(`/apps/routing-forms`); await page.click('[data-testid="install-app-button"]'); await page.waitForNavigation({ - url: (url) => url.pathname === `/apps/routing_forms/forms`, + url: (url) => url.pathname === `/apps/routing-forms/forms`, }); }); @@ -89,9 +89,9 @@ test.describe("Routing Forms", () => { const formId = await addForm(page); - await page.click('[href="/apps/routing_forms/forms"]'); + await page.click('[href="/apps/routing-forms/forms"]'); // TODO: Workaround for bug in https://github.com/calcom/cal.com/issues/3410 - await page.click('[href="/apps/routing_forms/forms"]'); + await page.click('[href="/apps/routing-forms/forms"]'); await page.waitForSelector('[data-testid="routing-forms-list"]'); // Ensure that it's visible in forms list @@ -100,7 +100,7 @@ test.describe("Routing Forms", () => { await gotoRoutingLink(page, formId); await page.isVisible("text=Test Form Name"); - await page.goto(`apps/routing_forms/route-builder/${formId}`); + await page.goto(`apps/routing-forms/route-builder/${formId}`); await page.click('[data-testid="toggle-form"] [value="on"]'); await gotoRoutingLink(page, formId); await page.isVisible("text=ERROR 404"); @@ -129,7 +129,7 @@ test.describe("Routing Forms", () => { types[field.typeIndex] ); - await page.click('[href*="/apps/routing_forms/route-builder/"]'); + await page.click('[href*="/apps/routing-forms/route-builder/"]'); await page.click('[data-testid="add-route"]'); await page.click('[data-testid="add-rule"]'); await verifySelectOptions( @@ -151,13 +151,13 @@ test.describe("Routing Forms", () => { users: Fixtures["users"]; page: Page; }) { - const user = await users.create({ username: "routing_forms" }, { seedRoutingForms: true }); + const user = await users.create({ username: "routing-forms" }, { seedRoutingForms: true }); await user.login(); // Install app - await page.goto(`/apps/routing_forms`); + await page.goto(`/apps/routing-forms`); await page.click('[data-testid="install-app-button"]'); await page.waitForNavigation({ - url: (url) => url.pathname === `/apps/routing_forms/forms`, + url: (url) => url.pathname === `/apps/routing-forms/forms`, }); return user; }; @@ -197,7 +197,7 @@ test.describe("Routing Forms", () => { // Log back in to view form responses. await user.login(); - await page.goto(`/apps/routing_forms/route-builder/${routingForm.id}`); + await page.goto(`/apps/routing-forms/route-builder/${routingForm.id}`); const [download] = await Promise.all([ // Start waiting for the download page.waitForEvent("download"), diff --git a/packages/app-store/ee/routing_forms/static/icon.svg b/packages/app-store/ee/routing-forms/static/icon.svg similarity index 100% rename from packages/app-store/ee/routing_forms/static/icon.svg rename to packages/app-store/ee/routing-forms/static/icon.svg diff --git a/packages/app-store/ee/routing_forms/trpc-router.ts b/packages/app-store/ee/routing-forms/trpc-router.ts similarity index 100% rename from packages/app-store/ee/routing_forms/trpc-router.ts rename to packages/app-store/ee/routing-forms/trpc-router.ts diff --git a/packages/app-store/ee/routing_forms/types/types.d.ts b/packages/app-store/ee/routing-forms/types/types.d.ts similarity index 100% rename from packages/app-store/ee/routing_forms/types/types.d.ts rename to packages/app-store/ee/routing-forms/types/types.d.ts diff --git a/packages/app-store/ee/routing_forms/zod.ts b/packages/app-store/ee/routing-forms/zod.ts similarity index 100% rename from packages/app-store/ee/routing_forms/zod.ts rename to packages/app-store/ee/routing-forms/zod.ts diff --git a/packages/app-store/typeform/pages/how-to-use/[...appPages].tsx b/packages/app-store/typeform/pages/how-to-use/[...appPages].tsx index 59a02e89f5..b38706ebd0 100644 --- a/packages/app-store/typeform/pages/how-to-use/[...appPages].tsx +++ b/packages/app-store/typeform/pages/how-to-use/[...appPages].tsx @@ -17,7 +17,7 @@ export default function HowToUse() {
  1. Make sure that you have{" "} - + Routing Forms {" "} app installed diff --git a/packages/features/webhooks/components/WebhookForm.tsx b/packages/features/webhooks/components/WebhookForm.tsx index df07ac159b..c9ab8476d5 100644 --- a/packages/features/webhooks/components/WebhookForm.tsx +++ b/packages/features/webhooks/components/WebhookForm.tsx @@ -1,5 +1,5 @@ import { WebhookTriggerEvents } from "@prisma/client"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { classNames } from "@calcom/lib"; @@ -30,7 +30,7 @@ export type WebhookFormSubmitData = WebhookFormData & { newSecret: string; }; -type WebhookTriggerEventOptions = { value: WebhookTriggerEvents; label: string }[]; +type WebhookTriggerEventOptions = readonly { value: WebhookTriggerEvents; label: string }[]; const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record = { core: [ @@ -39,8 +39,8 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record { const { apps = [] } = props; const { t } = useLocale(); - const triggerOptions = useMemo( - () => - apps.reduce((acc, app) => { - if (!WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2[app]) return acc; - acc.push(...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2[app]); - return acc; - }, WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2["core"]), - [apps] - ); + const triggerOptions = [...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2["core"]]; + if (apps) { + for (const app of apps) { + triggerOptions.push(...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2[app]); + } + } const translatedTriggerOptions = triggerOptions.map((option) => ({ ...option, label: t(option.label) })); const formMethods = useForm({ defaultValues: { - subscriberUrl: props?.webhook?.subscriberUrl || "", - active: props?.webhook?.active || false, - eventTriggers: props?.webhook?.eventTriggers || [], + subscriberUrl: props.webhook?.subscriberUrl || "", + active: props.webhook ? props.webhook.active : true, + eventTriggers: !props.webhook + ? translatedTriggerOptions.map((option) => option.value) + : props.webhook.eventTriggers, secret: props?.webhook?.secret || "", payloadTemplate: props?.webhook?.payloadTemplate || undefined, }, @@ -128,20 +127,24 @@ const WebhookForm = (props: { ( -
    - - { + onChange(event.map((selection) => selection.value)); + }} + /> +
    + ); + }} /> [number]; @@ -21,8 +21,7 @@ export default function WebhookListItem(props: { const deleteWebhook = trpc.useMutation("viewer.webhook.delete", { async onSuccess() { await utils.invalidateQueries(["viewer.webhook.list"]); - // TODO: Better success message - showToast(t("deleted"), "success"); + showToast(t("webhook_removed_successfully"), "success"); }, }); const toggleWebhook = trpc.useMutation("viewer.webhook.edit", { @@ -38,14 +37,13 @@ export default function WebhookListItem(props: {

    {webhook.subscriberUrl}

    - ( -

    {t(`${trigger.toLowerCase()}`)}

    - ))}> -
    - - {t("triggers_when")} - + +
    + {webhook.eventTriggers.map((trigger) => ( + + {t(`${trigger.toLowerCase()}`)} + + ))}
    diff --git a/packages/features/webhooks/pages/webhook-edit-view.tsx b/packages/features/webhooks/pages/webhook-edit-view.tsx index 75857c8bf2..31ba73d75a 100644 --- a/packages/features/webhooks/pages/webhook-edit-view.tsx +++ b/packages/features/webhooks/pages/webhook-edit-view.tsx @@ -16,76 +16,90 @@ const EditWebhook = () => { const { t } = useLocale(); const utils = trpc.useContext(); const router = useRouter(); - const { id: webhookId } = querySchema.parse(router.query); - const { data: installedApps, isLoading } = trpc.useQuery( - ["viewer.integrations", { variant: "other", onlyInstalled: true }], - { + function Component({ webhookId }: { webhookId: string }) { + const { data: installedApps, isLoading } = trpc.useQuery( + ["viewer.integrations", { variant: "other", onlyInstalled: true }], + { + suspense: true, + enabled: router.isReady, + } + ); + const { data: webhook } = trpc.useQuery(["viewer.webhook.get", { webhookId }], { suspense: true, enabled: router.isReady, - } - ); - const { data: webhook } = trpc.useQuery(["viewer.webhook.get", { webhookId }], { - suspense: true, - enabled: router.isReady, - }); - const { data: webhooks } = trpc.useQuery(["viewer.webhook.list"], { - suspense: true, - enabled: router.isReady, - }); - const editWebhookMutation = trpc.useMutation("viewer.webhook.edit", { - async onSuccess() { - await utils.invalidateQueries(["viewer.webhook.list"]); - }, - onError(error) { - showToast(`${error.message}`, "error"); - }, - }); + }); + const { data: webhooks } = trpc.useQuery(["viewer.webhook.list"], { + suspense: true, + enabled: router.isReady, + }); + const editWebhookMutation = trpc.useMutation("viewer.webhook.edit", { + async onSuccess() { + await utils.invalidateQueries(["viewer.webhook.list"]); + showToast(t("webhook_updated_successfully"), "success"); + router.back(); + }, + onError(error) { + showToast(`${error.message}`, "error"); + }, + }); - const subscriberUrlReserved = (subscriberUrl: string, id: string): boolean => { - return !!webhooks?.find((webhook) => webhook.subscriberUrl === subscriberUrl && webhook.id !== id); - }; + const subscriberUrlReserved = (subscriberUrl: string, id: string): boolean => { + return !!webhooks?.find((webhook) => webhook.subscriberUrl === subscriberUrl && webhook.id !== id); + }; - if (isLoading || !webhook) return ; + if (isLoading || !webhook) return ; - return ( - <> - - { - if (subscriberUrlReserved(values.subscriberUrl, webhook.id)) { - showToast(t("webhook_subscriber_url_reserved"), "error"); - return; - } + return ( + <> + + { + if (subscriberUrlReserved(values.subscriberUrl, webhook.id)) { + showToast(t("webhook_subscriber_url_reserved"), "error"); + return; + } - if (values.changeSecret) { - values.secret = values.newSecret.length ? values.newSecret : null; - } + if (values.changeSecret) { + values.secret = values.newSecret.length ? values.newSecret : null; + } - if (!values.payloadTemplate) { - values.payloadTemplate = null; - } + if (!values.payloadTemplate) { + values.payloadTemplate = null; + } - editWebhookMutation.mutate({ - id: webhook.id, - subscriberUrl: values.subscriberUrl, - eventTriggers: values.eventTriggers, - active: values.active, - payloadTemplate: values.payloadTemplate, - secret: values.secret, - }); - showToast(t("webhook_updated_successfully"), "success"); - router.back(); - }} - apps={installedApps?.items.map((app) => app.slug)} - /> - - ); + editWebhookMutation.mutate({ + id: webhook.id, + subscriberUrl: values.subscriberUrl, + eventTriggers: values.eventTriggers, + active: values.active, + payloadTemplate: values.payloadTemplate, + secret: values.secret, + }); + }} + apps={installedApps?.items.map((app) => app.slug)} + /> + + ); + } + + if (!router.isReady) return null; + + const parsed = querySchema.safeParse(router.query); + + if (!parsed.success) { + throw new Error("Invalid query"); + } + + // tRPC useQuery needs webhookId to be available and it becomes available only after router.isReady is true + // It causes this requirement of a new component. + // I think we should do SSR for this page + return ; }; EditWebhook.getLayout = getLayout; diff --git a/packages/features/webhooks/pages/webhook-new-view.tsx b/packages/features/webhooks/pages/webhook-new-view.tsx index c18e0567d4..41f035897d 100644 --- a/packages/features/webhooks/pages/webhook-new-view.tsx +++ b/packages/features/webhooks/pages/webhook-new-view.tsx @@ -27,7 +27,9 @@ const NewWebhookView = () => { const createWebhookMutation = trpc.useMutation("viewer.webhook.create", { async onSuccess() { + showToast(t("webhook_created_successfully"), "success"); await utils.invalidateQueries(["viewer.webhook.list"]); + router.back(); }, onError(error) { showToast(`${error.message}`, "error"); @@ -55,8 +57,6 @@ const NewWebhookView = () => { payloadTemplate: values.payloadTemplate, secret: values.secret, }); - showToast(t("webhook_created_successfully"), "success"); - router.back(); }; if (isLoading) return ; diff --git a/packages/features/webhooks/pages/webhooks-view.tsx b/packages/features/webhooks/pages/webhooks-view.tsx index af51aef371..fdde2f3edb 100644 --- a/packages/features/webhooks/pages/webhooks-view.tsx +++ b/packages/features/webhooks/pages/webhooks-view.tsx @@ -5,7 +5,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Icon } from "@calcom/ui"; -import { ButtonGroup, EmptyScreen, SkeletonText } from "@calcom/ui/v2"; +import { EmptyScreen, SkeletonText } from "@calcom/ui/v2"; import { Button } from "@calcom/ui/v2/core/Button"; import Meta from "@calcom/ui/v2/core/Meta"; import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout"; @@ -31,7 +31,11 @@ const WebhooksView = () => { const NewWebhookButton = () => { const { t, isLocaleReady } = useLocale(); return ( - ); diff --git a/packages/lib/webhooks/constants.ts b/packages/lib/webhooks/constants.ts index ed50eb9f9d..d09b991d48 100644 --- a/packages/lib/webhooks/constants.ts +++ b/packages/lib/webhooks/constants.ts @@ -9,10 +9,10 @@ export const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP = { WebhookTriggerEvents.BOOKING_RESCHEDULED, WebhookTriggerEvents.MEETING_ENDED, ] as const, - routing_forms: [WebhookTriggerEvents.FORM_SUBMITTED] as ["FORM_SUBMITTED"], + "routing-forms": [WebhookTriggerEvents.FORM_SUBMITTED] as const, }; export const WEBHOOK_TRIGGER_EVENTS = [ ...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP.core, - ...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP.routing_forms, + ...WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP["routing-forms"], ] as const; diff --git a/packages/prisma/migrations/20220917042621_rename_routing_forms_slug/migration.sql b/packages/prisma/migrations/20220917042621_rename_routing_forms_slug/migration.sql new file mode 100644 index 0000000000..ba99ff9ce8 --- /dev/null +++ b/packages/prisma/migrations/20220917042621_rename_routing_forms_slug/migration.sql @@ -0,0 +1,2 @@ +UPDATE "App" SET "slug" = 'routing-forms' WHERE "slug" = 'routing_forms'; +UPDATE "App" SET "dirName" = 'routing-forms' WHERE "slug" = 'routing_forms'; \ No newline at end of file diff --git a/packages/prisma/seed-app-store.config.json b/packages/prisma/seed-app-store.config.json index 504a33dd97..f24948a5f8 100644 --- a/packages/prisma/seed-app-store.config.json +++ b/packages/prisma/seed-app-store.config.json @@ -1,10 +1,10 @@ [ { "/*": "This file is auto-generated and updated by `yarn app-store create/edit`. Don't edit it manually", - "dirName": "routing_forms", + "dirName": "routing-forms", "categories": ["other"], - "slug": "routing_forms", - "type": "routing_forms_other" + "slug": "routing-forms", + "type": "routing-forms_other" }, { "dirName": "whereby", diff --git a/packages/trpc/server/routers/viewer.tsx b/packages/trpc/server/routers/viewer.tsx index 272593ff90..5d2782bc25 100644 --- a/packages/trpc/server/routers/viewer.tsx +++ b/packages/trpc/server/routers/viewer.tsx @@ -3,7 +3,7 @@ import _ from "lodash"; import { authenticator } from "otplib"; import { z } from "zod"; -import app_RoutingForms from "@calcom/app-store/ee/routing_forms/trpc-router"; +import app_RoutingForms from "@calcom/app-store/ee/routing-forms/trpc-router"; import ethRouter from "@calcom/app-store/rainbow/trpc/router"; import { deleteStripeCustomer } from "@calcom/app-store/stripepayment/lib/customer"; import { getCustomerAndCheckoutSession } from "@calcom/app-store/stripepayment/lib/getCustomerAndCheckoutSession"; diff --git a/packages/ui/v2/core/Shell.tsx b/packages/ui/v2/core/Shell.tsx index 812e7f8608..8504fe7859 100644 --- a/packages/ui/v2/core/Shell.tsx +++ b/packages/ui/v2/core/Shell.tsx @@ -427,7 +427,7 @@ const navigation: NavigationItemType[] = [ const path = router.asPath.split("?")[0]; // During Server rendering path is /v2/apps but on client it becomes /apps(weird..) return ( - (path.startsWith(item.href) || path.startsWith("/v2" + item.href)) && !path.includes("routing_forms/") + (path.startsWith(item.href) || path.startsWith("/v2" + item.href)) && !path.includes("routing-forms/") ); }, child: [ @@ -439,7 +439,7 @@ const navigation: NavigationItemType[] = [ // During Server rendering path is /v2/apps but on client it becomes /apps(weird..) return ( (path.startsWith(item.href) || path.startsWith("/v2" + item.href)) && - !path.includes("routing_forms/") && + !path.includes("routing-forms/") && !path.includes("/installed") ); }, @@ -461,10 +461,10 @@ const navigation: NavigationItemType[] = [ }, { name: "Routing Forms", - href: "/apps/routing_forms/forms", + href: "/apps/routing-forms/forms", icon: Icon.FiFileText, isCurrent: ({ router }) => { - return router.asPath.startsWith("/apps/routing_forms/"); + return router.asPath.startsWith("/apps/routing-forms/"); }, }, { @@ -511,7 +511,7 @@ const Navigation = () => { function useShouldDisplayNavigationItem(item: NavigationItemType) { const { status } = useSession(); - const { data: routingForms } = trpc.useQuery(["viewer.appById", { appId: "routing_forms" }], { + const { data: routingForms } = trpc.useQuery(["viewer.appById", { appId: "routing-forms" }], { enabled: status === "authenticated" && requiredCredentialNavigationItems.includes(item.name), }); return !requiredCredentialNavigationItems.includes(item.name) || !!routingForms; diff --git a/packages/ui/v2/core/form/fields.tsx b/packages/ui/v2/core/form/fields.tsx index 47b84ad7e6..99cef80673 100644 --- a/packages/ui/v2/core/form/fields.tsx +++ b/packages/ui/v2/core/form/fields.tsx @@ -187,7 +187,11 @@ const InputField = forwardRef(function InputF ...passThrough } = props; - const translatedPlaceholder = isLocaleReady ? placeholder : ""; + const translatedPlaceholder = isLocaleReady + ? !placeholder?.endsWith("_placeholder") + ? placeholder + : "" + : ""; return (