Routing Forms Improvements - Rename routing_forms to routing-forms (#4546)
* Animate fields list and routes list * Rename routing_forms slug to routing-forms * Add comments * Fixtypo * Add dropdown separator for consistency * Fix missing occurences of routing_forms and improve types for webhooks * Fix weird error about title child is an array * Fix webhook issues * Fix webhook tests and issues found during fixing them * Fix lint errors and warnings Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
kodiakhq[bot]
parent
81aba55710
commit
ce41397517
@@ -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;
|
||||
|
||||
+10
-1
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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() {
|
||||
<WebhookListContainer title="Event Webhooks" subtitle={t("receive_cal_meeting_data")} />
|
||||
{routingForms && (
|
||||
<WebhookListContainer
|
||||
appId="routing_forms"
|
||||
appId="routing-forms"
|
||||
title="Routing Webhooks"
|
||||
subtitle="Receive Routing Form responses at a specified URL, in real-time, when a Routing Form is submitted"
|
||||
/>
|
||||
|
||||
@@ -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<string, unknown>)[];
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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]"}}
|
||||
@@ -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",
|
||||
|
||||
@@ -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,`,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ const handler: AppDeclarativeHandler = {
|
||||
},
|
||||
});
|
||||
},
|
||||
redirectUrl: "/apps/routing_forms/forms",
|
||||
redirectUrl: "/apps/routing-forms/forms",
|
||||
};
|
||||
|
||||
export default handler;
|
||||
+2
-2
@@ -86,7 +86,7 @@ function NewFormDialog({ appUrl }: { appUrl: string }) {
|
||||
{t("add_new_form")}
|
||||
</h3>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">{t("forms_description")}</p>
|
||||
<p className="text-sm text-gray-500">{t("form_description")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Form
|
||||
@@ -338,7 +338,7 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
|
||||
href: `${appUrl}/form-edit/${routingForm?.id}`,
|
||||
},
|
||||
download: {
|
||||
href: `/api/integrations/routing_forms/responses/${routingForm?.id}`,
|
||||
href: `/api/integrations/routing-forms/responses/${routingForm?.id}`,
|
||||
},
|
||||
_delete: {
|
||||
onClick: () => _delete.onAction({ routingForm }),
|
||||
+2
-1
@@ -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")}
|
||||
/>
|
||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||
<FormAction
|
||||
routingForm={form}
|
||||
action="_delete"
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { Settings, Widgets, SelectWidgetProps } from "react-awesome-query-builder";
|
||||
// Figure out why ee/routing_forms/env.d.ts doesn't work
|
||||
// Figure out why ee/routing-forms/env.d.ts doesn't work
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
import BasicConfig from "react-awesome-query-builder/lib/config/basic";
|
||||
+5
-5
@@ -2,11 +2,11 @@
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Routing Forms",
|
||||
"title": "Routing Forms",
|
||||
"slug": "routing_forms",
|
||||
"type": "routing_forms_other",
|
||||
"imageSrc": "/api/app-store/ee/routing_forms/icon.svg",
|
||||
"logo": "/api/app-store/ee/routing_forms/icon.svg",
|
||||
"url": "https://cal.com/apps/routing_forms",
|
||||
"slug": "routing-forms",
|
||||
"type": "routing-forms_other",
|
||||
"imageSrc": "/api/app-store/ee/routing-forms/icon.svg",
|
||||
"logo": "/api/app-store/ee/routing-forms/icon.svg",
|
||||
"url": "https://cal.com/apps/routing-forms",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
"publisher": "Cal.com",
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/routing_forms",
|
||||
"name": "@calcom/routing-forms",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"description": "It would allow a booker to connect with the right person or choose the right event, faster. It would work by taking inputs from the booker and using that data to route to the correct booker/event as configured by Cal user ",
|
||||
+9
-2
@@ -1,5 +1,6 @@
|
||||
import autoAnimate from "@formkit/auto-animate";
|
||||
import { App_RoutingForms_Form } from "@prisma/client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Controller, useFieldArray } from "react-hook-form";
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
@@ -196,6 +197,12 @@ const FormEdit = ({
|
||||
name: fieldsNamespace,
|
||||
});
|
||||
|
||||
const animationRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
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 ? (
|
||||
<div className="flex flex-col-reverse lg:flex-row">
|
||||
<div className="w-full ltr:mr-2 rtl:ml-2">
|
||||
<div className="flex w-full flex-col">
|
||||
<div ref={animationRef} className="flex w-full flex-col">
|
||||
{hookFormFields.map((field, key) => {
|
||||
return (
|
||||
<Field
|
||||
+8
-2
@@ -1,5 +1,6 @@
|
||||
import autoAnimate from "@formkit/auto-animate";
|
||||
import { App_RoutingForms_Form } from "@prisma/client";
|
||||
import React, { useState, useCallback } from "react";
|
||||
import React, { useState, useCallback, useRef, useEffect } from "react";
|
||||
import { Query, Config, Builder, Utils as QbUtils } from "react-awesome-query-builder";
|
||||
// types
|
||||
import { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder";
|
||||
@@ -325,6 +326,11 @@ const Routes = ({
|
||||
|
||||
return transformRoutes().map((route) => 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 (
|
||||
<div className="flex flex-col-reverse md:flex-row">
|
||||
<div className="w-full ltr:mr-2 rtl:ml-2">
|
||||
<div ref={animationRef} className="w-full ltr:mr-2 rtl:ml-2">
|
||||
{mainRoutes.map((route, key) => {
|
||||
return (
|
||||
<Route
|
||||
+1
-1
@@ -100,7 +100,7 @@ function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>
|
||||
{!customPageMessage ? (
|
||||
<>
|
||||
<Head>
|
||||
<title>{form.name} | Cal.com Forms</title>
|
||||
<title>{`${form.name} | Cal.com Forms`}</title>
|
||||
</Head>
|
||||
<div className={classNames("mx-auto my-0 max-w-3xl", isEmbed ? "" : "md:my-24")}>
|
||||
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
|
||||
+11
-11
@@ -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"),
|
||||
|
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
@@ -17,7 +17,7 @@ export default function HowToUse() {
|
||||
<ol className="mt-5 mb-5 ml-5 mr-5 list-decimal">
|
||||
<li>
|
||||
Make sure that you have{" "}
|
||||
<Link href="/apps/routing_forms" className="text-base text-gray-500">
|
||||
<Link href="/apps/routing-forms" className="text-base text-gray-500">
|
||||
<a className="text-base text-gray-500">Routing Forms</a>
|
||||
</Link>{" "}
|
||||
app installed
|
||||
|
||||
@@ -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<string, WebhookTriggerEventOptions> = {
|
||||
core: [
|
||||
@@ -39,8 +39,8 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record<string, WebhookTriggerEve
|
||||
{ value: WebhookTriggerEvents.BOOKING_RESCHEDULED, label: "booking_rescheduled" },
|
||||
{ value: WebhookTriggerEvents.MEETING_ENDED, label: "meeting_ended" },
|
||||
],
|
||||
routing_forms: [{ value: WebhookTriggerEvents.FORM_SUBMITTED, label: "form_submitted" }],
|
||||
};
|
||||
"routing-forms": [{ value: WebhookTriggerEvents.FORM_SUBMITTED, label: "form_submitted" }],
|
||||
} as const;
|
||||
|
||||
const WebhookForm = (props: {
|
||||
webhook?: WebhookFormData;
|
||||
@@ -49,23 +49,22 @@ const WebhookForm = (props: {
|
||||
}) => {
|
||||
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: {
|
||||
<Controller
|
||||
name="eventTriggers"
|
||||
control={formMethods.control}
|
||||
render={({ field: { onChange } }) => (
|
||||
<div className="mt-8">
|
||||
<Label className="font-sm mt-8 font-medium text-gray-900">
|
||||
<>{t("event_triggers")}</>
|
||||
</Label>
|
||||
<Select
|
||||
options={translatedTriggerOptions}
|
||||
isMulti
|
||||
onChange={(event) => {
|
||||
onChange(event.map((selection) => selection.value));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const selectValue = translatedTriggerOptions.filter((option) => value.includes(option.value));
|
||||
return (
|
||||
<div className="mt-8">
|
||||
<Label className="font-sm mt-8 font-medium text-gray-900">
|
||||
<>{t("event_triggers")}</>
|
||||
</Label>
|
||||
<Select
|
||||
options={translatedTriggerOptions}
|
||||
isMulti
|
||||
value={selectValue}
|
||||
onChange={(event) => {
|
||||
onChange(event.map((selection) => selection.value));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
name="secret"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import showToast from "@calcom/lib/notification";
|
||||
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import Badge from "@calcom/ui/v2/core/Badge";
|
||||
import Button from "@calcom/ui/v2/core/Button";
|
||||
import Switch from "@calcom/ui/v2/core/Switch";
|
||||
import { Tooltip } from "@calcom/ui/v2/core/Tooltip";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
|
||||
export type TWebhook = inferQueryOutput<"viewer.webhook.list">[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: {
|
||||
<div className={classNames("flex w-full justify-between p-4", props.lastItem ? "" : "border-b")}>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">{webhook.subscriberUrl}</p>
|
||||
<Tooltip
|
||||
content={webhook.eventTriggers.map((trigger) => (
|
||||
<p key={trigger}>{t(`${trigger.toLowerCase()}`)}</p>
|
||||
))}>
|
||||
<div className="mt-2.5 w-max">
|
||||
<Badge variant="gray" bold StartIcon={Icon.FiAlertCircle}>
|
||||
{t("triggers_when")}
|
||||
</Badge>
|
||||
<Tooltip content={t("triggers_when")}>
|
||||
<div className="mt-2.5 w-4/5">
|
||||
{webhook.eventTriggers.map((trigger) => (
|
||||
<Badge key={trigger} className="mr-2" variant="gray" bold StartIcon={Icon.FiAlertCircle}>
|
||||
{t(`${trigger.toLowerCase()}`)}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -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 <SkeletonContainer />;
|
||||
if (isLoading || !webhook) return <SkeletonContainer />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title="Edit Webhook"
|
||||
description="Receive meeting data in real-time when something happens in Cal.com"
|
||||
backButton
|
||||
/>
|
||||
<WebhookForm
|
||||
webhook={webhook}
|
||||
onSubmit={(values: WebhookFormSubmitData) => {
|
||||
if (subscriberUrlReserved(values.subscriberUrl, webhook.id)) {
|
||||
showToast(t("webhook_subscriber_url_reserved"), "error");
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title="Edit Webhook"
|
||||
description="Receive meeting data in real-time when something happens in Cal.com"
|
||||
backButton
|
||||
/>
|
||||
<WebhookForm
|
||||
webhook={webhook}
|
||||
onSubmit={(values: WebhookFormSubmitData) => {
|
||||
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 <Component webhookId={parsed.data.id} />;
|
||||
};
|
||||
|
||||
EditWebhook.getLayout = getLayout;
|
||||
|
||||
@@ -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 <SkeletonContainer />;
|
||||
|
||||
@@ -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 (
|
||||
<Button color="secondary" StartIcon={Icon.FiPlus} href={`${WEBAPP_URL}/settings/developer/webhooks/new`}>
|
||||
<Button
|
||||
color="secondary"
|
||||
data-testid="new_webhook"
|
||||
StartIcon={Icon.FiPlus}
|
||||
href={`${WEBAPP_URL}/settings/developer/webhooks/new`}>
|
||||
{isLocaleReady ? t("new_webhook") : <SkeletonText className="h-4 w-24" />}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
UPDATE "App" SET "slug" = 'routing-forms' WHERE "slug" = 'routing_forms';
|
||||
UPDATE "App" SET "dirName" = 'routing-forms' WHERE "slug" = 'routing_forms';
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -187,7 +187,11 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
|
||||
...passThrough
|
||||
} = props;
|
||||
|
||||
const translatedPlaceholder = isLocaleReady ? placeholder : "";
|
||||
const translatedPlaceholder = isLocaleReady
|
||||
? !placeholder?.endsWith("_placeholder")
|
||||
? placeholder
|
||||
: ""
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div className={classNames(containerClassName)}>
|
||||
|
||||
Reference in New Issue
Block a user