diff --git a/.eslintrc.js b/.eslintrc.js index 8d24ded0b0..ac65eaf8e4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,10 +23,15 @@ module.exports = { files: ["packages/app-store/**/*.{ts,tsx,js,jsx}"], rules: { "no-restricted-imports": [ - "warn", + "error", { - paths: ["@calcom/trpc"], - patterns: ["@calcom/trpc/*"], + allowTypeImports: false, + patterns: [ + { + group: ["@calcom/trpc/*", "@trpc/*"], + message: "tRPC imports are blocked in packages/app-store. Move UI to apps/web/components/apps or introduce an API boundary.", + }, + ], }, ], }, diff --git a/packages/app-store/routing-forms/pages/form-edit/[...appPages].tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx similarity index 97% rename from packages/app-store/routing-forms/pages/form-edit/[...appPages].tsx rename to apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx index 4fa3055a9d..2939aedd26 100644 --- a/packages/app-store/routing-forms/pages/form-edit/[...appPages].tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/FormEdit.tsx @@ -6,6 +6,8 @@ import { Controller, useFieldArray, useWatch } from "react-hook-form"; import { Toaster } from "sonner"; import { v4 as uuidv4 } from "uuid"; +import { FieldTypes } from "@calcom/app-store/routing-forms/lib/FieldTypes"; +import type { RoutingFormWithResponseCount } from "@calcom/app-store/routing-forms/types/types"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import classNames from "@calcom/ui/classNames"; import { Button } from "@calcom/ui/components/button"; @@ -19,13 +21,11 @@ import { } from "@calcom/ui/components/form"; import { Icon } from "@calcom/ui/components/icon"; import { Tooltip } from "@calcom/ui/components/tooltip"; +import type { getServerSidePropsForSingleFormView as getServerSideProps } from "@calcom/web/lib/apps/routing-forms/[...pages]/getServerSidePropsSingleForm"; import type { inferSSRProps } from "@lib/types/inferSSRProps"; -import SingleForm from "../../components/SingleForm"; -import type { getServerSidePropsForSingleFormView as getServerSideProps } from "../../components/getServerSidePropsSingleForm"; -import { FieldTypes } from "../../lib/FieldTypes"; -import type { RoutingFormWithResponseCount } from "../../types/types"; +import SingleForm from "@components/apps/routing-forms/SingleForm"; type HookForm = UseFormReturn; diff --git a/packages/app-store/routing-forms/pages/incomplete-booking/[...appPages].tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/IncompleteBooking.tsx similarity index 97% rename from packages/app-store/routing-forms/pages/incomplete-booking/[...appPages].tsx rename to apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/IncompleteBooking.tsx index daeaccbe69..8af8bfc8ea 100644 --- a/packages/app-store/routing-forms/pages/incomplete-booking/[...appPages].tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/IncompleteBooking.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from "react"; import type z from "zod"; +import type { RoutingFormWithResponseCount } from "@calcom/app-store/routing-forms/types/types"; import { WhenToWriteToRecord, SalesforceFieldType } from "@calcom/app-store/salesforce/lib/enums"; import type { writeToRecordDataSchema as salesforceWriteToRecordDataSchema } from "@calcom/app-store/salesforce/zod"; import { routingFormIncompleteBookingDataSchema as salesforceRoutingFormIncompleteBookingDataSchema } from "@calcom/app-store/salesforce/zod"; @@ -17,10 +18,9 @@ import { Select } from "@calcom/ui/components/form"; import { Label } from "@calcom/ui/components/form"; import { Icon } from "@calcom/ui/components/icon"; import { showToast } from "@calcom/ui/components/toast"; +import type { getServerSidePropsForSingleFormView as getServerSideProps } from "@calcom/web/lib/apps/routing-forms/[...pages]/getServerSidePropsSingleForm"; -import SingleForm from "../../components/SingleForm"; -import type { getServerSidePropsForSingleFormView as getServerSideProps } from "../../components/getServerSidePropsSingleForm"; -import type { RoutingFormWithResponseCount } from "../../types/types"; +import SingleForm from "@components/apps/routing-forms/SingleForm"; function Page({ form }: { form: RoutingFormWithResponseCount }) { const { t } = useLocale(); diff --git a/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx similarity index 97% rename from packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx rename to apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx index 3afec49d48..cb514c9181 100644 --- a/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RouteBuilder.tsx @@ -10,6 +10,36 @@ import type { UseFormReturn } from "react-hook-form"; import { Toaster } from "sonner"; import type { z } from "zod"; +import { routingFormAppComponents } from "@calcom/app-store/routing-forms/appComponents"; +import DynamicAppComponent from "@calcom/app-store/routing-forms/components/DynamicAppComponent"; +import { EmptyState } from "@calcom/app-store/routing-forms/components/_components/EmptyState"; +import { RoutingSkeleton } from "@calcom/app-store/routing-forms/components/_components/RoutingSkeleton"; +import { + withRaqbSettingsAndWidgets, + ConfigFor, +} from "@calcom/app-store/routing-forms/components/react-awesome-query-builder/config/uiConfig"; +import { RoutingPages } from "@calcom/app-store/routing-forms/lib/RoutingPages"; +import { createFallbackRoute } from "@calcom/app-store/routing-forms/lib/createFallbackRoute"; +import getEventTypeAppMetadata from "@calcom/app-store/routing-forms/lib/getEventTypeAppMetadata"; +import { + getQueryBuilderConfigForFormFields, + getQueryBuilderConfigForAttributes, + type FormFieldsQueryBuilderConfigWithRaqbFields, + type AttributesQueryBuilderConfigWithRaqbFields, + isDynamicOperandField, +} from "@calcom/app-store/routing-forms/lib/getQueryBuilderConfig"; +import isRouter from "@calcom/app-store/routing-forms/lib/isRouter"; +import type { RoutingFormWithResponseCount } from "@calcom/app-store/routing-forms/types/types"; +import type { + GlobalRoute, + LocalRoute, + SerializableRoute, + Attribute, + EditFormRoute, + AttributeRoutingConfig, +} from "@calcom/app-store/routing-forms/types/types"; +import type { zodRoutes } from "@calcom/app-store/routing-forms/zod"; +import { RouteActionType } from "@calcom/app-store/routing-forms/zod"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import { areTheySiblingEntities } from "@calcom/lib/entityPermissionUtils.shared"; import type { EventTypesByViewer } from "@calcom/lib/event-types/getEventTypesByViewer"; @@ -29,39 +59,9 @@ import { SelectField } from "@calcom/ui/components/form"; import { Switch } from "@calcom/ui/components/form"; import type { IconName } from "@calcom/ui/components/icon"; import { Icon } from "@calcom/ui/components/icon"; +import type { getServerSidePropsForSingleFormView as getServerSideProps } from "@calcom/web/lib/apps/routing-forms/[...pages]/getServerSidePropsSingleForm"; -import { routingFormAppComponents } from "../../appComponents"; -import DynamicAppComponent from "../../components/DynamicAppComponent"; -import SingleForm from "../../components/SingleForm"; -import { EmptyState } from "../../components/_components/EmptyState"; -import { RoutingSkeleton } from "../../components/_components/RoutingSkeleton"; -import type { getServerSidePropsForSingleFormView as getServerSideProps } from "../../components/getServerSidePropsSingleForm"; -import { - withRaqbSettingsAndWidgets, - ConfigFor, -} from "../../components/react-awesome-query-builder/config/uiConfig"; -import { RoutingPages } from "../../lib/RoutingPages"; -import { createFallbackRoute } from "../../lib/createFallbackRoute"; -import getEventTypeAppMetadata from "../../lib/getEventTypeAppMetadata"; -import { - getQueryBuilderConfigForFormFields, - getQueryBuilderConfigForAttributes, - type FormFieldsQueryBuilderConfigWithRaqbFields, - type AttributesQueryBuilderConfigWithRaqbFields, - isDynamicOperandField, -} from "../../lib/getQueryBuilderConfig"; -import isRouter from "../../lib/isRouter"; -import type { RoutingFormWithResponseCount } from "../../types/types"; -import type { - GlobalRoute, - LocalRoute, - SerializableRoute, - Attribute, - EditFormRoute, - AttributeRoutingConfig, -} from "../../types/types"; -import type { zodRoutes } from "../../zod"; -import { RouteActionType } from "../../zod"; +import SingleForm from "@components/apps/routing-forms/SingleForm"; type Form = inferSSRProps["form"]; diff --git a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RoutingLink.tsx similarity index 91% rename from packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx rename to apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RoutingLink.tsx index e0d21e9390..c81cf7c2c4 100644 --- a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/RoutingLink.tsx @@ -6,7 +6,15 @@ import { useEffect, useRef, useState } from "react"; import { Toaster } from "sonner"; import { v4 as uuidv4 } from "uuid"; +import FormInputFields from "@calcom/app-store/routing-forms/components/FormInputFields"; +import { getAbsoluteEventTypeRedirectUrlWithEmbedSupport } from "@calcom/app-store/routing-forms/getEventTypeRedirectUrl"; +import getFieldIdentifier from "@calcom/app-store/routing-forms/lib/getFieldIdentifier"; +import { findMatchingRoute } from "@calcom/app-store/routing-forms/lib/processRoute"; +import { substituteVariables } from "@calcom/app-store/routing-forms/lib/substituteVariables"; +import { getFieldResponseForJsonLogic } from "@calcom/app-store/routing-forms/lib/transformResponse"; +import type { NonRouterRoute, FormResponse } from "@calcom/app-store/routing-forms/types/types"; import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; +import { getUrlSearchParamsToForward } from "@calcom/features/routing-forms/lib/getUrlSearchParamsToForward"; import useGetBrandingColours from "@calcom/lib/getBrandColours"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -18,16 +26,7 @@ import classNames from "@calcom/ui/classNames"; import { Button } from "@calcom/ui/components/button"; import { showToast } from "@calcom/ui/components/toast"; import { useCalcomTheme } from "@calcom/ui/styles"; - -import FormInputFields from "../../components/FormInputFields"; -import { getAbsoluteEventTypeRedirectUrlWithEmbedSupport } from "../../getEventTypeRedirectUrl"; -import getFieldIdentifier from "../../lib/getFieldIdentifier"; -import { findMatchingRoute } from "../../lib/processRoute"; -import { substituteVariables } from "../../lib/substituteVariables"; -import { getFieldResponseForJsonLogic } from "../../lib/transformResponse"; -import type { NonRouterRoute, FormResponse } from "../../types/types"; -import type { getServerSideProps } from "./getServerSideProps"; -import { getUrlSearchParamsToForward } from "./getUrlSearchParamsToForward"; +import type { getServerSideProps } from "@calcom/web/lib/apps/routing-forms/[...pages]/getServerSidePropsRoutingLink"; type Props = inferSSRProps; const useBrandColors = ({ diff --git a/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/app-routing.client-config.tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/app-routing.client-config.tsx new file mode 100644 index 0000000000..19ddd97328 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/app-routing.client-config.tsx @@ -0,0 +1,6 @@ +export const routingFormsComponents = { + "form-edit": () => import("./FormEdit").then((mod) => mod.default), + "route-builder": () => import("./RouteBuilder").then((mod) => mod.default), + "routing-link": () => import("./RoutingLink").then((mod) => mod.default), + "incomplete-booking": () => import("./IncompleteBooking").then((mod) => mod.default), +}; diff --git a/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/page.tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/page.tsx index 30a0bee29b..869584510a 100644 --- a/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/page.tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/[...pages]/page.tsx @@ -3,12 +3,12 @@ import type { PageProps as ServerPageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { cookies, headers } from "next/headers"; -import { routingFormsComponents } from "@calcom/app-store/routing-forms/pages/app-routing.client-config"; -import type { routingServerSidePropsConfig } from "@calcom/app-store/routing-forms/pages/app-routing.server-config"; - +import type { routingServerSidePropsConfig } from "@lib/apps/routing-forms/[...pages]/app-routing.server-config"; import { getServerSideProps } from "@lib/apps/routing-forms/[...pages]/getServerSideProps"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import { routingFormsComponents } from "./app-routing.client-config"; + const normalizePages = (pages: string[] | string | undefined) => { const normalizedPages = Array.isArray(pages) ? pages : pages?.split("/") ?? []; return normalizedPages[0]; diff --git a/packages/app-store/routing-forms/pages/forms/[...appPages].tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/forms/[[...pages]]/Forms.tsx similarity index 96% rename from packages/app-store/routing-forms/pages/forms/[...appPages].tsx rename to apps/web/app/(use-page-wrapper)/apps/routing-forms/forms/[[...pages]]/Forms.tsx index 64abaf47f4..3e80f45a10 100644 --- a/packages/app-store/routing-forms/pages/forms/[...appPages].tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/forms/[[...pages]]/Forms.tsx @@ -4,6 +4,8 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useEffect, useState } from "react"; import { useFormContext } from "react-hook-form"; +import { isFallbackRoute } from "@calcom/app-store/routing-forms/lib/isFallbackRoute"; +import type { RoutingFormWithResponseCount } from "@calcom/app-store/routing-forms/types/types"; import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired"; import SkeletonLoaderTeamList from "@calcom/features/ee/teams/components/SkeletonloaderTeamList"; import { CreateButtonWithTeamsList } from "@calcom/features/ee/teams/components/createButton/CreateButtonWithTeamsList"; @@ -25,11 +27,15 @@ import { EmptyScreen } from "@calcom/ui/components/empty-screen"; import { Icon } from "@calcom/ui/components/icon"; import { List, ListLinkItem } from "@calcom/ui/components/list"; import { Tooltip } from "@calcom/ui/components/tooltip"; - -import type { SetNewFormDialogState, NewFormDialogState } from "../../components/FormActions"; -import { FormAction, FormActionsDropdown, FormActionsProvider } from "../../components/FormActions"; -import { isFallbackRoute } from "../../lib/isFallbackRoute"; -import type { RoutingFormWithResponseCount } from "../../types/types"; +import type { + SetNewFormDialogState, + NewFormDialogState, +} from "@calcom/web/components/apps/routing-forms/FormActions"; +import { + FormAction, + FormActionsDropdown, + FormActionsProvider, +} from "@calcom/web/components/apps/routing-forms/FormActions"; function NewFormButton({ setNewFormDialogState }: { setNewFormDialogState: SetNewFormDialogState }) { const { t } = useLocale(); diff --git a/apps/web/app/(use-page-wrapper)/apps/routing-forms/forms/[[...pages]]/page.tsx b/apps/web/app/(use-page-wrapper)/apps/routing-forms/forms/[[...pages]]/page.tsx index de9cda14d1..10c3d36ba2 100644 --- a/apps/web/app/(use-page-wrapper)/apps/routing-forms/forms/[[...pages]]/page.tsx +++ b/apps/web/app/(use-page-wrapper)/apps/routing-forms/forms/[[...pages]]/page.tsx @@ -1,6 +1,6 @@ import { _generateMetadata } from "app/_utils"; -import Forms from "@calcom/app-store/routing-forms/pages/forms/[...appPages]"; +import Forms from "./Forms"; export const generateMetadata = async ({ params }: { params: Promise<{ pages: string[] }> }) => { const pages = (await params).pages; diff --git a/packages/features/ee/payments/components/PaymentPage.tsx b/apps/web/app/(use-page-wrapper)/payment/[uid]/PaymentPage.tsx similarity index 93% rename from packages/features/ee/payments/components/PaymentPage.tsx rename to apps/web/app/(use-page-wrapper)/payment/[uid]/PaymentPage.tsx index 0c396b214f..56b3e4b96e 100644 --- a/packages/features/ee/payments/components/PaymentPage.tsx +++ b/apps/web/app/(use-page-wrapper)/payment/[uid]/PaymentPage.tsx @@ -10,6 +10,7 @@ import dayjs from "@calcom/dayjs"; import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; import { PayIcon } from "@calcom/features/bookings/components/event-meta/PayIcon"; import { Price } from "@calcom/features/bookings/components/event-meta/Price"; +import type { PaymentPageProps } from "@calcom/features/ee/payments/pages/payment"; import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants"; import { getPaymentAppData } from "@calcom/lib/getPaymentAppData"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -18,24 +19,20 @@ import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/t import { CURRENT_TIMEZONE } from "@calcom/lib/timezoneConstants"; import { localStorage } from "@calcom/lib/webstorage"; -import type { PaymentPageProps } from "../pages/payment"; - -const StripePaymentComponent = dynamic(() => import("./Payment"), { +const StripePaymentComponent = dynamic(() => import("@calcom/features/ee/payments/components/Payment"), { ssr: false, }); const PaypalPaymentComponent = dynamic( () => - import("@calcom/app-store/paypal/components/PaypalPaymentComponent").then( - (m) => m.PaypalPaymentComponent - ), + import("@calcom/web/components/apps/paypal/PaypalPaymentComponent").then((m) => m.PaypalPaymentComponent), { ssr: false, } ); const AlbyPaymentComponent = dynamic( - () => import("@calcom/app-store/alby/components/AlbyPaymentComponent").then((m) => m.AlbyPaymentComponent), + () => import("@calcom/web/components/apps/alby/AlbyPaymentComponent").then((m) => m.AlbyPaymentComponent), { ssr: false, } @@ -43,9 +40,7 @@ const AlbyPaymentComponent = dynamic( const HitpayPaymentComponent = dynamic( () => - import("@calcom/app-store/hitpay/components/HitpayPaymentComponent").then( - (m) => m.HitpayPaymentComponent - ), + import("@calcom/web/components/apps/hitpay/HitpayPaymentComponent").then((m) => m.HitpayPaymentComponent), { ssr: false, } @@ -53,7 +48,7 @@ const HitpayPaymentComponent = dynamic( const BtcpayPaymentComponent = dynamic( () => - import("@calcom/app-store/btcpayserver/components/BtcpayPaymentComponent").then( + import("@calcom/web/components/apps/btcpayserver/BtcpayPaymentComponent").then( (m) => m.BtcpayPaymentComponent ), { diff --git a/apps/web/app/(use-page-wrapper)/payment/[uid]/page.tsx b/apps/web/app/(use-page-wrapper)/payment/[uid]/page.tsx index 0e318f2bf1..439e02d647 100644 --- a/apps/web/app/(use-page-wrapper)/payment/[uid]/page.tsx +++ b/apps/web/app/(use-page-wrapper)/payment/[uid]/page.tsx @@ -3,12 +3,13 @@ import type { PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { cookies, headers } from "next/headers"; -import PaymentPage from "@calcom/features/ee/payments/components/PaymentPage"; import { getServerSideProps, type PaymentPageProps } from "@calcom/features/ee/payments/pages/payment"; import { APP_NAME } from "@calcom/lib/constants"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import PaymentPage from "./PaymentPage"; + export const generateMetadata = async ({ params, searchParams }: PageProps) => { const props = await getData( buildLegacyCtx(await headers(), await cookies(), await params, await searchParams) diff --git a/apps/web/components/apps/AppSetupPage.tsx b/apps/web/components/apps/AppSetupPage.tsx new file mode 100644 index 0000000000..a31f52fc3f --- /dev/null +++ b/apps/web/components/apps/AppSetupPage.tsx @@ -0,0 +1,26 @@ +import dynamic from "next/dynamic"; + +import { DynamicComponent } from "@calcom/app-store/_components/DynamicComponent"; + +export const AppSetupMap = { + alby: dynamic(() => import("@calcom/web/components/apps/alby/Setup")), + "apple-calendar": dynamic(() => import("@calcom/web/components/apps/applecalendar/Setup")), + exchange: dynamic(() => import("@calcom/web/components/apps/exchangecalendar/Setup")), + "exchange2013-calendar": dynamic(() => import("@calcom/web/components/apps/exchange2013calendar/Setup")), + "exchange2016-calendar": dynamic(() => import("@calcom/web/components/apps/exchange2016calendar/Setup")), + "caldav-calendar": dynamic(() => import("@calcom/web/components/apps/caldavcalendar/Setup")), + "ics-feed": dynamic(() => import("@calcom/web/components/apps/ics-feedcalendar/Setup")), + zapier: dynamic(() => import("@calcom/web/components/apps/zapier/Setup")), + make: dynamic(() => import("@calcom/web/components/apps/make/Setup")), + sendgrid: dynamic(() => import("@calcom/web/components/apps/sendgrid/Setup")), + stripe: dynamic(() => import("@calcom/web/components/apps/stripepayment/Setup")), + paypal: dynamic(() => import("@calcom/web/components/apps/paypal/Setup")), + hitpay: dynamic(() => import("@calcom/web/components/apps/hitpay/Setup")), + btcpayserver: dynamic(() => import("@calcom/web/components/apps/btcpayserver/Setup")), +}; + +export const AppSetupPage = (props: { slug: string }) => { + return componentMap={AppSetupMap} {...props} />; +}; + +export default AppSetupPage; diff --git a/packages/app-store/alby/components/AlbyPaymentComponent.tsx b/apps/web/components/apps/alby/AlbyPaymentComponent.tsx similarity index 100% rename from packages/app-store/alby/components/AlbyPaymentComponent.tsx rename to apps/web/components/apps/alby/AlbyPaymentComponent.tsx diff --git a/packages/app-store/alby/pages/setup/index.tsx b/apps/web/components/apps/alby/Setup.tsx similarity index 96% rename from packages/app-store/alby/pages/setup/index.tsx rename to apps/web/components/apps/alby/Setup.tsx index 97242b0b77..9f46004694 100644 --- a/packages/app-store/alby/pages/setup/index.tsx +++ b/apps/web/components/apps/alby/Setup.tsx @@ -5,22 +5,15 @@ import { useState, useCallback, useEffect } from "react"; import { Toaster } from "sonner"; import AppNotInstalledMessage from "@calcom/app-store/_components/AppNotInstalledMessage"; +import { albyCredentialKeysSchema } from "@calcom/app-store/alby/lib/albyCredentialKeysSchema"; +import type { IAlbySetupProps } from "@calcom/app-store/alby/pages/setup/_getServerSideProps"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; -import { showToast } from "@calcom/ui/components/toast"; import { Badge } from "@calcom/ui/components/badge"; import { Button } from "@calcom/ui/components/button"; import { Icon } from "@calcom/ui/components/icon"; - -import { albyCredentialKeysSchema } from "../../lib/albyCredentialKeysSchema"; - -export interface IAlbySetupProps { - email: string | null; - lightningAddress: string | null; - clientId: string; - clientSecret: string; -} +import { showToast } from "@calcom/ui/components/toast"; export default function AlbySetup(props: IAlbySetupProps) { const params = useCompatSearchParams(); diff --git a/packages/app-store/applecalendar/pages/setup/index.tsx b/apps/web/components/apps/applecalendar/Setup.tsx similarity index 100% rename from packages/app-store/applecalendar/pages/setup/index.tsx rename to apps/web/components/apps/applecalendar/Setup.tsx index 909216afff..5b56842eaf 100644 --- a/packages/app-store/applecalendar/pages/setup/index.tsx +++ b/apps/web/components/apps/applecalendar/Setup.tsx @@ -5,10 +5,10 @@ import { Toaster } from "sonner"; import { APP_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { PasswordField } from "@calcom/ui/components/form"; -import { TextField } from "@calcom/ui/components/form"; import { Alert } from "@calcom/ui/components/alert"; import { Button } from "@calcom/ui/components/button"; +import { PasswordField } from "@calcom/ui/components/form"; +import { TextField } from "@calcom/ui/components/form"; import { Form } from "@calcom/ui/components/form"; export default function AppleCalendarSetup() { diff --git a/packages/app-store/btcpayserver/components/BtcpayPaymentComponent.tsx b/apps/web/components/apps/btcpayserver/BtcpayPaymentComponent.tsx similarity index 100% rename from packages/app-store/btcpayserver/components/BtcpayPaymentComponent.tsx rename to apps/web/components/apps/btcpayserver/BtcpayPaymentComponent.tsx diff --git a/packages/app-store/btcpayserver/pages/setup/index.tsx b/apps/web/components/apps/btcpayserver/Setup.tsx similarity index 95% rename from packages/app-store/btcpayserver/pages/setup/index.tsx rename to apps/web/components/apps/btcpayserver/Setup.tsx index da867304dd..b084cea686 100644 --- a/packages/app-store/btcpayserver/pages/setup/index.tsx +++ b/apps/web/components/apps/btcpayserver/Setup.tsx @@ -1,354 +1,352 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import { useSession } from "next-auth/react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { useState, useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { Toaster } from "sonner"; -import { z } from "zod"; - -import AppNotInstalledMessage from "@calcom/app-store/_components/AppNotInstalledMessage"; -import { WEBAPP_URL } from "@calcom/lib/constants"; -import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { trpc } from "@calcom/trpc"; -import { Button } from "@calcom/ui/components/button"; -import { Icon } from "@calcom/ui/components/icon"; -import { showToast } from "@calcom/ui/components/toast"; - -import KeyField from "../../components/KeyInput"; -import { btcpayCredentialKeysSchema } from "../../lib/btcpayCredentialKeysSchema"; - -export type IBTCPaySetupProps = z.infer; - -export default function BTCPaySetup(props: IBTCPaySetupProps) { - const params = useCompatSearchParams(); - if (params?.get("callback") === "true") { - return ; - } - return ; -} - -enum BTCPayOAuthError { - Declined = "declined", - Unknown = "unknown", -} - -function BTCPaySetupCallback() { - const [error, setError] = useState(null); - const searchParams = useCompatSearchParams(); - - useEffect(() => { - if (!searchParams) { - return; - } - if (!window.opener) { - setError("Something went wrong. Opener not available. Please contact support"); - return; - } - const code = searchParams?.get("code"); - const error = searchParams?.get("error"); - - if (!code) { - setError(BTCPayOAuthError.Declined); - } - if (error) { - setError(error); - return; - } - - window.opener.postMessage({ - type: "btcpayserver:oauth:success", - payload: { code }, - }); - window.close(); - }, [searchParams]); - - return ( -
- {error &&

Authorization failed: {error}

} - {!error &&

Connecting...

} -
- ); -} - -function BTCPaySetupPage(props: IBTCPaySetupProps) { - const router = useRouter(); - const { t } = useLocale(); - const session = useSession(); - const [loading, setLoading] = useState(false); - const [validating, setValidating] = useState(false); - const [updatable, setUpdatable] = useState(false); - const [keyData, setKeyData] = useState< - | { - storeId: string; - serverUrl: string; - apiKey: string; - webhookSecret: string; - } - | undefined - >(); - const settingsSchema = z.object({ - storeId: z.string().trim(), - serverUrl: z.string().trim(), - apiKey: z.string().trim(), - webhookSecret: z.string().optional(), - }); - const integrations = trpc.viewer.apps.integrations.useQuery({ variant: "payment", appId: "btcpayserver" }); - const [btcPayPaymentAppCredentials] = integrations.data?.items || []; - const [credentialId] = btcPayPaymentAppCredentials?.userCredentialIds || [-1]; - const showContent = !!integrations.data && integrations.isSuccess && !!credentialId; - - const saveKeysMutation = trpc.viewer.apps.updateAppCredentials.useMutation({ - onSuccess: () => { - showToast(t("keys_have_been_saved"), "success"); - router.push("/event-types"); - }, - onError: (error) => { - showToast(error.message, "error"); - }, - }); - const deleteMutation = trpc.viewer.credentials.delete.useMutation({ - onSuccess: () => { - router.push("/apps/btcpayserver"); - }, - onError: () => { - showToast(t("error_removing_app"), "error"); - }, - }); - - const { - register, - handleSubmit, - formState: { errors }, - watch, - reset, - } = useForm>({ - reValidateMode: "onChange", - resolver: zodResolver(settingsSchema), - }); - - useEffect(() => { - const _keyData = { - storeId: props?.storeId || "", - serverUrl: props?.serverUrl || "", - apiKey: props?.apiKey || "", - webhookSecret: props?.webhookSecret || "", - }; - setKeyData(_keyData); - }, [props]); - - useEffect(() => { - const subscription = watch((value) => { - const { serverUrl, storeId, apiKey, webhookSecret } = value; - if ( - serverUrl && - storeId && - apiKey && - (keyData?.serverUrl !== serverUrl || keyData?.storeId !== storeId || keyData?.apiKey !== apiKey) - ) { - setUpdatable(true); - } else { - setUpdatable(false); - } - }); - return () => subscription.unsubscribe(); - }, [watch, keyData]); - - const configureBTCPayWebhook = async (data: z.infer) => { - setValidating(true); - const specificEvents = ["InvoiceSettled", "InvoiceProcessing"]; - const serverUrl = data.serverUrl.endsWith("/") ? data.serverUrl.slice(0, -1) : data.serverUrl; - const endpoint = `${serverUrl}/api/v1/stores/${data.storeId}/webhooks`; - const webhookUrl = `${WEBAPP_URL}/api/integrations/btcpayserver/webhook`; - const requestBody = { - enabled: true, - automaticRedelivery: false, - url: webhookUrl, - authorizedEvents: { - everything: false, - specificEvents: specificEvents, - }, - secret: null, - }; - - try { - const response = await fetch(endpoint, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `token ${data.apiKey}`, - }, - body: JSON.stringify(requestBody), - }); - if (!response.ok) { - const errorBody = await response.text(); - showToast(`Failed to configure webhook: ${errorBody}`, "error"); - return false; - } - const webhookResponse = await response.json(); - saveKeysMutation.mutate({ - credentialId, - key: btcpayCredentialKeysSchema.parse({ - ...data, - webhookSecret: webhookResponse.secret, - }), - }); - return true; - } catch (error) { - if (error instanceof Error) { - showToast(error.message || "Failed to configure BTCPay webhook", "error"); - } else { - showToast("An unknown error occurred while configuring BTCPay webhook", "error"); - } - return false; - } finally { - setValidating(false); - } - }; - - const onSubmit = handleSubmit(async (data) => { - if (loading) return; - setLoading(true); - - try { - const isValid = await configureBTCPayWebhook(data); - if (!isValid) { - setLoading(false); - return; - } - } catch (error: unknown) { - let message = ""; - if (error instanceof Error) { - message = error.message; - } - showToast(message, "error"); - } finally { - setLoading(false); - } - }); - - const onCancel = () => { - deleteMutation.mutate({ id: credentialId }); - }; - - const btcpayIcon = ( - <> - BTCPay Server Icon - - ); - - if (session.status === "loading") return <>; - - if (integrations.isPending) { - return
; - } - - const isNewCredential = !props.serverUrl && !props.storeId && !props.webhookSecret && !props.apiKey; - const webhookUri = `${WEBAPP_URL}/api/integrations/btcpayserver/webhook`; - - return ( - <> -
- {showContent ? ( -
-
-
-
-

BTCPay Server Information

-
-
-
- - {errors.serverUrl && ( -

- {errors.serverUrl?.message} -

- )} -
- -
- - {errors.storeId && ( -

- {errors.storeId?.message} -

- )} -
- -
- - {errors.apiKey && ( -

- {errors.apiKey?.message} -

- )} -
-
-
-
- {isNewCredential ? ( - <> - - - - ) : ( - <> - - - - - )} -
-
-
- ) : ( - - )} - -
- - ); -} +import { zodResolver } from "@hookform/resolvers/zod"; +import { useSession } from "next-auth/react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useState, useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { Toaster } from "sonner"; +import { z } from "zod"; + +import AppNotInstalledMessage from "@calcom/app-store/_components/AppNotInstalledMessage"; +import KeyField from "@calcom/app-store/btcpayserver/components/KeyInput"; +import { btcpayCredentialKeysSchema } from "@calcom/app-store/btcpayserver/lib/btcpayCredentialKeysSchema"; +import type { IBTCPaySetupProps } from "@calcom/app-store/btcpayserver/pages/setup/_getServerSideProps"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc"; +import { Button } from "@calcom/ui/components/button"; +import { Icon } from "@calcom/ui/components/icon"; +import { showToast } from "@calcom/ui/components/toast"; + +export default function BTCPaySetup(props: IBTCPaySetupProps) { + const params = useCompatSearchParams(); + if (params?.get("callback") === "true") { + return ; + } + return ; +} + +enum BTCPayOAuthError { + Declined = "declined", + Unknown = "unknown", +} + +function BTCPaySetupCallback() { + const [error, setError] = useState(null); + const searchParams = useCompatSearchParams(); + + useEffect(() => { + if (!searchParams) { + return; + } + if (!window.opener) { + setError("Something went wrong. Opener not available. Please contact support"); + return; + } + const code = searchParams?.get("code"); + const error = searchParams?.get("error"); + + if (!code) { + setError(BTCPayOAuthError.Declined); + } + if (error) { + setError(error); + return; + } + + window.opener.postMessage({ + type: "btcpayserver:oauth:success", + payload: { code }, + }); + window.close(); + }, [searchParams]); + + return ( +
+ {error &&

Authorization failed: {error}

} + {!error &&

Connecting...

} +
+ ); +} + +function BTCPaySetupPage(props: IBTCPaySetupProps) { + const router = useRouter(); + const { t } = useLocale(); + const session = useSession(); + const [loading, setLoading] = useState(false); + const [validating, setValidating] = useState(false); + const [updatable, setUpdatable] = useState(false); + const [keyData, setKeyData] = useState< + | { + storeId: string; + serverUrl: string; + apiKey: string; + webhookSecret: string; + } + | undefined + >(); + const settingsSchema = z.object({ + storeId: z.string().trim(), + serverUrl: z.string().trim(), + apiKey: z.string().trim(), + webhookSecret: z.string().optional(), + }); + const integrations = trpc.viewer.apps.integrations.useQuery({ variant: "payment", appId: "btcpayserver" }); + const [btcPayPaymentAppCredentials] = integrations.data?.items || []; + const [credentialId] = btcPayPaymentAppCredentials?.userCredentialIds || [-1]; + const showContent = !!integrations.data && integrations.isSuccess && !!credentialId; + + const saveKeysMutation = trpc.viewer.apps.updateAppCredentials.useMutation({ + onSuccess: () => { + showToast(t("keys_have_been_saved"), "success"); + router.push("/event-types"); + }, + onError: (error) => { + showToast(error.message, "error"); + }, + }); + const deleteMutation = trpc.viewer.credentials.delete.useMutation({ + onSuccess: () => { + router.push("/apps/btcpayserver"); + }, + onError: () => { + showToast(t("error_removing_app"), "error"); + }, + }); + + const { + register, + handleSubmit, + formState: { errors }, + watch, + reset, + } = useForm>({ + reValidateMode: "onChange", + resolver: zodResolver(settingsSchema), + }); + + useEffect(() => { + const _keyData = { + storeId: props?.storeId || "", + serverUrl: props?.serverUrl || "", + apiKey: props?.apiKey || "", + webhookSecret: props?.webhookSecret || "", + }; + setKeyData(_keyData); + }, [props]); + + useEffect(() => { + const subscription = watch((value) => { + const { serverUrl, storeId, apiKey, webhookSecret } = value; + if ( + serverUrl && + storeId && + apiKey && + (keyData?.serverUrl !== serverUrl || keyData?.storeId !== storeId || keyData?.apiKey !== apiKey) + ) { + setUpdatable(true); + } else { + setUpdatable(false); + } + }); + return () => subscription.unsubscribe(); + }, [watch, keyData]); + + const configureBTCPayWebhook = async (data: z.infer) => { + setValidating(true); + const specificEvents = ["InvoiceSettled", "InvoiceProcessing"]; + const serverUrl = data.serverUrl.endsWith("/") ? data.serverUrl.slice(0, -1) : data.serverUrl; + const endpoint = `${serverUrl}/api/v1/stores/${data.storeId}/webhooks`; + const webhookUrl = `${WEBAPP_URL}/api/integrations/btcpayserver/webhook`; + const requestBody = { + enabled: true, + automaticRedelivery: false, + url: webhookUrl, + authorizedEvents: { + everything: false, + specificEvents: specificEvents, + }, + secret: null, + }; + + try { + const response = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `token ${data.apiKey}`, + }, + body: JSON.stringify(requestBody), + }); + if (!response.ok) { + const errorBody = await response.text(); + showToast(`Failed to configure webhook: ${errorBody}`, "error"); + return false; + } + const webhookResponse = await response.json(); + saveKeysMutation.mutate({ + credentialId, + key: btcpayCredentialKeysSchema.parse({ + ...data, + webhookSecret: webhookResponse.secret, + }), + }); + return true; + } catch (error) { + if (error instanceof Error) { + showToast(error.message || "Failed to configure BTCPay webhook", "error"); + } else { + showToast("An unknown error occurred while configuring BTCPay webhook", "error"); + } + return false; + } finally { + setValidating(false); + } + }; + + const onSubmit = handleSubmit(async (data) => { + if (loading) return; + setLoading(true); + + try { + const isValid = await configureBTCPayWebhook(data); + if (!isValid) { + setLoading(false); + return; + } + } catch (error: unknown) { + let message = ""; + if (error instanceof Error) { + message = error.message; + } + showToast(message, "error"); + } finally { + setLoading(false); + } + }); + + const onCancel = () => { + deleteMutation.mutate({ id: credentialId }); + }; + + const btcpayIcon = ( + <> + BTCPay Server Icon + + ); + + if (session.status === "loading") return <>; + + if (integrations.isPending) { + return
; + } + + const isNewCredential = !props.serverUrl && !props.storeId && !props.webhookSecret && !props.apiKey; + const webhookUri = `${WEBAPP_URL}/api/integrations/btcpayserver/webhook`; + + return ( + <> +
+ {showContent ? ( +
+
+
+
+

BTCPay Server Information

+
+
+
+ + {errors.serverUrl && ( +

+ {errors.serverUrl?.message} +

+ )} +
+ +
+ + {errors.storeId && ( +

+ {errors.storeId?.message} +

+ )} +
+ +
+ + {errors.apiKey && ( +

+ {errors.apiKey?.message} +

+ )} +
+
+
+
+ {isNewCredential ? ( + <> + + + + ) : ( + <> + + + + + )} +
+
+
+ ) : ( + + )} + +
+ + ); +} diff --git a/packages/app-store/caldavcalendar/pages/setup/index.tsx b/apps/web/components/apps/caldavcalendar/Setup.tsx similarity index 100% rename from packages/app-store/caldavcalendar/pages/setup/index.tsx rename to apps/web/components/apps/caldavcalendar/Setup.tsx diff --git a/packages/app-store/exchange2013calendar/pages/setup/index.tsx b/apps/web/components/apps/exchange2013calendar/Setup.tsx similarity index 100% rename from packages/app-store/exchange2013calendar/pages/setup/index.tsx rename to apps/web/components/apps/exchange2013calendar/Setup.tsx diff --git a/packages/app-store/exchange2016calendar/pages/setup/index.tsx b/apps/web/components/apps/exchange2016calendar/Setup.tsx similarity index 100% rename from packages/app-store/exchange2016calendar/pages/setup/index.tsx rename to apps/web/components/apps/exchange2016calendar/Setup.tsx diff --git a/packages/app-store/exchangecalendar/pages/setup/index.tsx b/apps/web/components/apps/exchangecalendar/Setup.tsx similarity index 98% rename from packages/app-store/exchangecalendar/pages/setup/index.tsx rename to apps/web/components/apps/exchangecalendar/Setup.tsx index e352df5a9c..e9d2cb2076 100644 --- a/packages/app-store/exchangecalendar/pages/setup/index.tsx +++ b/apps/web/components/apps/exchangecalendar/Setup.tsx @@ -5,18 +5,17 @@ import { Controller, useForm } from "react-hook-form"; import { Toaster } from "sonner"; import z from "zod"; +import { ExchangeAuthentication, ExchangeVersion } from "@calcom/app-store/exchangecalendar/enums"; import { emailSchema } from "@calcom/lib/emailSchema"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { EmailField } from "@calcom/ui/components/form"; import { Alert } from "@calcom/ui/components/alert"; import { Button } from "@calcom/ui/components/button"; +import { EmailField } from "@calcom/ui/components/form"; import { Form } from "@calcom/ui/components/form"; import { PasswordField } from "@calcom/ui/components/form"; import { TextField } from "@calcom/ui/components/form"; import { SelectField } from "@calcom/ui/components/form"; -import { ExchangeAuthentication, ExchangeVersion } from "../../enums"; - interface IFormData { url: string; username: string; diff --git a/packages/app-store/hitpay/components/HitpayPaymentComponent.tsx b/apps/web/components/apps/hitpay/HitpayPaymentComponent.tsx similarity index 96% rename from packages/app-store/hitpay/components/HitpayPaymentComponent.tsx rename to apps/web/components/apps/hitpay/HitpayPaymentComponent.tsx index b215819785..ad712f31d5 100644 --- a/packages/app-store/hitpay/components/HitpayPaymentComponent.tsx +++ b/apps/web/components/apps/hitpay/HitpayPaymentComponent.tsx @@ -1,9 +1,11 @@ +"use client"; + import { useRouter } from "next/navigation"; import qs from "qs"; import { useEffect, useRef } from "react"; import { z } from "zod"; -import { useHitPayDropIn } from "./HitPayDropIn"; +import { useHitPayDropIn } from "@calcom/app-store/hitpay/components/HitPayDropIn"; const PaymentHitpayDataSchema = z.object({ id: z.string(), diff --git a/packages/app-store/hitpay/pages/setup/index.tsx b/apps/web/components/apps/hitpay/Setup.tsx similarity index 97% rename from packages/app-store/hitpay/pages/setup/index.tsx rename to apps/web/components/apps/hitpay/Setup.tsx index 35f5a57b2d..7d3a0b2e40 100644 --- a/packages/app-store/hitpay/pages/setup/index.tsx +++ b/apps/web/components/apps/hitpay/Setup.tsx @@ -8,6 +8,9 @@ import { Toaster } from "sonner"; import { z } from "zod"; import AppNotInstalledMessage from "@calcom/app-store/_components/AppNotInstalledMessage"; +import KeyField from "@calcom/app-store/hitpay/components/KeyInput"; +import { hitpayCredentialKeysSchema } from "@calcom/app-store/hitpay/lib/hitpayCredentialKeysSchema"; +import type { IHitPaySetupProps } from "@calcom/app-store/hitpay/pages/setup/_getServerSideProps"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; @@ -16,11 +19,6 @@ import { Switch } from "@calcom/ui/components/form"; import { Icon } from "@calcom/ui/components/icon"; import { showToast } from "@calcom/ui/components/toast"; -import KeyField from "../../components/KeyInput"; -import { hitpayCredentialKeysSchema } from "../../lib/hitpayCredentialKeysSchema"; - -export type IHitPaySetupProps = z.infer; - export default function HitPaySetup(props: IHitPaySetupProps) { const params = useCompatSearchParams(); if (params?.get("callback") === "true") { diff --git a/packages/app-store/ics-feedcalendar/pages/setup/index.tsx b/apps/web/components/apps/ics-feedcalendar/Setup.tsx similarity index 100% rename from packages/app-store/ics-feedcalendar/pages/setup/index.tsx rename to apps/web/components/apps/ics-feedcalendar/Setup.tsx diff --git a/packages/app-store/make/pages/setup/index.tsx b/apps/web/components/apps/make/Setup.tsx similarity index 98% rename from packages/app-store/make/pages/setup/index.tsx rename to apps/web/components/apps/make/Setup.tsx index 7829ac25c0..105339b2d1 100644 --- a/packages/app-store/make/pages/setup/index.tsx +++ b/apps/web/components/apps/make/Setup.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import { Toaster } from "sonner"; import AppNotInstalledMessage from "@calcom/app-store/_components/AppNotInstalledMessage"; +import type { getServerSideProps } from "@calcom/app-store/make/pages/setup/_getServerSideProps"; import ServerTrans from "@calcom/lib/components/ServerTrans"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; @@ -12,8 +13,6 @@ import { Icon } from "@calcom/ui/components/icon"; import { showToast } from "@calcom/ui/components/toast"; import { Tooltip } from "@calcom/ui/components/tooltip"; -import type { getServerSideProps } from "./_getServerSideProps"; - const MAKE = "make"; export default function MakeSetup({ inviteLink }: InferGetServerSidePropsType) { diff --git a/packages/app-store/paypal/components/PaypalPaymentComponent.tsx b/apps/web/components/apps/paypal/PaypalPaymentComponent.tsx similarity index 99% rename from packages/app-store/paypal/components/PaypalPaymentComponent.tsx rename to apps/web/components/apps/paypal/PaypalPaymentComponent.tsx index d377ede98f..843e7fae10 100644 --- a/packages/app-store/paypal/components/PaypalPaymentComponent.tsx +++ b/apps/web/components/apps/paypal/PaypalPaymentComponent.tsx @@ -1,3 +1,5 @@ +"use client"; + import Link from "next/link"; import z from "zod"; diff --git a/packages/app-store/paypal/pages/setup/index.tsx b/apps/web/components/apps/paypal/Setup.tsx similarity index 100% rename from packages/app-store/paypal/pages/setup/index.tsx rename to apps/web/components/apps/paypal/Setup.tsx diff --git a/packages/app-store/routing-forms/components/FormActions.tsx b/apps/web/components/apps/routing-forms/FormActions.tsx similarity index 99% rename from packages/app-store/routing-forms/components/FormActions.tsx rename to apps/web/components/apps/routing-forms/FormActions.tsx index 77f6f3ecf0..1424d79c7c 100644 --- a/packages/app-store/routing-forms/components/FormActions.tsx +++ b/apps/web/components/apps/routing-forms/FormActions.tsx @@ -3,6 +3,7 @@ import { createContext, forwardRef, useContext, useState } from "react"; import { useForm } from "react-hook-form"; import { v4 as uuidv4 } from "uuid"; +import getFieldIdentifier from "@calcom/app-store/routing-forms/lib/getFieldIdentifier"; import { Dialog } from "@calcom/features/components/controlled-dialog"; import { dataTableQueryParamsSerializer } from "@calcom/features/data-table/lib/serializers"; import { ColumnFilterType } from "@calcom/features/data-table/lib/types"; @@ -34,8 +35,6 @@ import { Form } from "@calcom/ui/components/form"; import { Switch } from "@calcom/ui/components/form"; import { showToast } from "@calcom/ui/components/toast"; -import getFieldIdentifier from "../lib/getFieldIdentifier"; - type FormField = { identifier?: string; id: string; diff --git a/packages/app-store/routing-forms/components/_components/FormSettingsSlideover.tsx b/apps/web/components/apps/routing-forms/FormSettingsSlideover.tsx similarity index 97% rename from packages/app-store/routing-forms/components/_components/FormSettingsSlideover.tsx rename to apps/web/components/apps/routing-forms/FormSettingsSlideover.tsx index 8639dee073..4fd453fff9 100644 --- a/packages/app-store/routing-forms/components/_components/FormSettingsSlideover.tsx +++ b/apps/web/components/apps/routing-forms/FormSettingsSlideover.tsx @@ -5,6 +5,8 @@ import { useRef } from "react"; import { Controller } from "react-hook-form"; import type { UseFormReturn } from "react-hook-form"; +import { TeamMemberSelect } from "@calcom/app-store/routing-forms/components/_components/TeamMemberSelect"; +import type { RoutingFormWithResponseCount } from "@calcom/app-store/routing-forms/types/types"; import { IS_CALCOM } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; @@ -13,9 +15,6 @@ import { Button } from "@calcom/ui/components/button"; import { Switch, TextAreaField, TextField } from "@calcom/ui/components/form"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter } from "@calcom/ui/components/sheet"; -import type { RoutingFormWithResponseCount } from "../../types/types"; -import { TeamMemberSelect } from "./TeamMemberSelect"; - type FormSettingsSlideoverProps = { form: RoutingFormWithResponseCount; hookForm: UseFormReturn; diff --git a/packages/app-store/routing-forms/components/_components/Header.tsx b/apps/web/components/apps/routing-forms/Header.tsx similarity index 99% rename from packages/app-store/routing-forms/components/_components/Header.tsx rename to apps/web/components/apps/routing-forms/Header.tsx index fd93ce6800..c32918736c 100644 --- a/packages/app-store/routing-forms/components/_components/Header.tsx +++ b/apps/web/components/apps/routing-forms/Header.tsx @@ -12,7 +12,7 @@ import { ToggleGroup } from "@calcom/ui/components/form"; import { Icon } from "@calcom/ui/components/icon"; import { Tooltip } from "@calcom/ui/components/tooltip"; -import { FormAction, FormActionsDropdown } from "../FormActions"; +import { FormAction, FormActionsDropdown } from "./FormActions"; import { FormSettingsSlideover } from "./FormSettingsSlideover"; // Toggle group doesnt support HREF navigation, so we need to use this hook to handle navigation diff --git a/packages/app-store/routing-forms/components/SingleForm.tsx b/apps/web/components/apps/routing-forms/SingleForm.tsx similarity index 88% rename from packages/app-store/routing-forms/components/SingleForm.tsx rename to apps/web/components/apps/routing-forms/SingleForm.tsx index df52c18e56..20556ba348 100644 --- a/packages/app-store/routing-forms/components/SingleForm.tsx +++ b/apps/web/components/apps/routing-forms/SingleForm.tsx @@ -3,21 +3,39 @@ import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useState } from "react"; import { useFormContext } from "react-hook-form"; +import type { UseFormReturn } from "react-hook-form"; +import { InfoLostWarningDialog } from "@calcom/app-store/routing-forms/components/InfoLostWarningDialog"; +import type { RoutingFormWithResponseCount } from "@calcom/app-store/routing-forms/types/types"; import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; +import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import classNames from "@calcom/ui/classNames"; import { Form } from "@calcom/ui/components/form"; import { showToast } from "@calcom/ui/components/toast"; -import type { SingleFormComponentProps } from "../types/shared"; -import type { RoutingFormWithResponseCount } from "../types/types"; +import type { getServerSidePropsForSingleFormView } from "@lib/apps/routing-forms/[...pages]/getServerSidePropsSingleForm"; + import type { NewFormDialogState } from "./FormActions"; import { FormActionsProvider } from "./FormActions"; -import { InfoLostWarningDialog } from "./InfoLostWarningDialog"; -import { Header } from "./_components/Header"; -import { TestFormRenderer, type UptoDateForm } from "./_components/TestForm"; +import { Header } from "./Header"; +import type { UptoDateForm } from "./TestForm"; +import { TestFormRenderer } from "./TestForm"; + +export type SingleFormComponentProps = { + form: RoutingFormWithResponseCount; + appUrl: string; + Page: React.FC<{ + form: RoutingFormWithResponseCount; + appUrl: string; + hookForm: UseFormReturn; + }>; + enrichedWithUserProfileForm: inferSSRProps< + typeof getServerSidePropsForSingleFormView + >["enrichedWithUserProfileForm"]; + permissions: inferSSRProps["permissions"]; +}; const BREAKPOINTS = { sm: 640, diff --git a/packages/app-store/routing-forms/components/_components/TestForm.tsx b/apps/web/components/apps/routing-forms/TestForm.tsx similarity index 92% rename from packages/app-store/routing-forms/components/_components/TestForm.tsx rename to apps/web/components/apps/routing-forms/TestForm.tsx index dbf424fc91..40da5ac92f 100644 --- a/packages/app-store/routing-forms/components/_components/TestForm.tsx +++ b/apps/web/components/apps/routing-forms/TestForm.tsx @@ -4,29 +4,30 @@ import { AnimatePresence, motion } from "framer-motion"; import type { Dispatch, SetStateAction } from "react"; import { useState, useMemo } from "react"; +import FormInputFields from "@calcom/app-store/routing-forms/components/FormInputFields"; +import { ResultsView as Results } from "@calcom/app-store/routing-forms/components/_components/ResultSection"; +import { TeamMembersMatchResult } from "@calcom/app-store/routing-forms/components/_components/TeamMembersMatchResult"; +import type { MembersMatchResultType } from "@calcom/app-store/routing-forms/components/_components/TeamMembersMatchResult"; +import { findMatchingRoute } from "@calcom/app-store/routing-forms/lib/processRoute"; +import { substituteVariables } from "@calcom/app-store/routing-forms/lib/substituteVariables"; +import type { RoutingForm, FormResponse, NonRouterRoute } from "@calcom/app-store/routing-forms/types/types"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import { WEBSITE_URL } from "@calcom/lib/constants"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; +import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import type { Brand } from "@calcom/types/utils"; import { Button } from "@calcom/ui/components/button"; import { Dialog, DialogContent, DialogHeader, DialogFooter } from "@calcom/ui/components/dialog"; import { showToast } from "@calcom/ui/components/toast"; +import type { getServerSidePropsForSingleFormView } from "@lib/apps/routing-forms/[...pages]/getServerSidePropsSingleForm"; + import { TRPCClientError } from "@trpc/react-query"; -import { findMatchingRoute } from "../../lib/processRoute"; -import { substituteVariables } from "../../lib/substituteVariables"; -import type { SingleFormComponentProps } from "../../types/shared"; -import type { RoutingForm, FormResponse, NonRouterRoute } from "../../types/types"; -import FormInputFields from "../FormInputFields"; -import { ResultsView as Results } from "./ResultSection"; -import type { MembersMatchResultType } from "./TeamMembersMatchResult"; -import { TeamMembersMatchResult } from "./TeamMembersMatchResult"; - export type UptoDateForm = Brand< - NonNullable, + NonNullable["enrichedWithUserProfileForm"]>, "UptoDateForm" >; diff --git a/packages/app-store/routing-forms/__tests__/TestFormDialog.test.tsx b/apps/web/components/apps/routing-forms/TestFormDialog.test.tsx similarity index 97% rename from packages/app-store/routing-forms/__tests__/TestFormDialog.test.tsx rename to apps/web/components/apps/routing-forms/TestFormDialog.test.tsx index 2625d23442..2804257336 100644 --- a/packages/app-store/routing-forms/__tests__/TestFormDialog.test.tsx +++ b/apps/web/components/apps/routing-forms/TestFormDialog.test.tsx @@ -2,8 +2,9 @@ import { render, screen, fireEvent } from "@testing-library/react"; import type { Mock } from "vitest"; import { vi } from "vitest"; -import { TestFormRenderer } from "../components/_components/TestForm"; -import { findMatchingRoute } from "../lib/processRoute"; +import { findMatchingRoute } from "@calcom/app-store/routing-forms/lib/processRoute"; + +import { TestFormRenderer } from "./TestForm"; vi.mock("framer-motion", async () => { return { @@ -14,7 +15,7 @@ vi.mock("framer-motion", async () => { }; }); -vi.mock("../lib/processRoute", () => ({ +vi.mock("@calcom/app-store/routing-forms/lib/processRoute", () => ({ findMatchingRoute: vi.fn(), })); @@ -70,15 +71,18 @@ vi.mock("@calcom/lib/hooks/useApp", () => ({ * Avoids the error due to Formbricks */ -vi.mock("../components/FormActions", () => ({ +vi.mock("./FormActions", () => ({ FormAction: vi.fn(), FormActionsDropdown: vi.fn(), FormActionsProvider: vi.fn(), })); -vi.mock("../../components/react-awesome-query-builder/widgets", () => ({ - default: {}, -})); +vi.mock( + "@calcom/app-store/routing-forms/components/react-awesome-query-builder/widgets", + async (importOriginal) => { + return await importOriginal(); + } +); // Mock the necessary dependencies vi.mock("@calcom/lib/hooks/useLocale", () => ({ diff --git a/packages/app-store/sendgrid/pages/setup/index.tsx b/apps/web/components/apps/sendgrid/Setup.tsx similarity index 100% rename from packages/app-store/sendgrid/pages/setup/index.tsx rename to apps/web/components/apps/sendgrid/Setup.tsx diff --git a/packages/app-store/stripepayment/pages/setup/index.tsx b/apps/web/components/apps/stripepayment/Setup.tsx similarity index 100% rename from packages/app-store/stripepayment/pages/setup/index.tsx rename to apps/web/components/apps/stripepayment/Setup.tsx diff --git a/packages/app-store/wipemycalother/components/confirmDialog.tsx b/apps/web/components/apps/wipemycalother/confirmDialog.tsx similarity index 100% rename from packages/app-store/wipemycalother/components/confirmDialog.tsx rename to apps/web/components/apps/wipemycalother/confirmDialog.tsx diff --git a/packages/app-store/wipemycalother/components/wipeMyCalActionButton.tsx b/apps/web/components/apps/wipemycalother/wipeMyCalActionButton.tsx similarity index 100% rename from packages/app-store/wipemycalother/components/wipeMyCalActionButton.tsx rename to apps/web/components/apps/wipemycalother/wipeMyCalActionButton.tsx diff --git a/packages/app-store/zapier/pages/setup/index.tsx b/apps/web/components/apps/zapier/Setup.tsx similarity index 100% rename from packages/app-store/zapier/pages/setup/index.tsx rename to apps/web/components/apps/zapier/Setup.tsx diff --git a/apps/web/components/dialog/RerouteDialog.tsx b/apps/web/components/dialog/RerouteDialog.tsx index ea01b97825..d4b48835d5 100644 --- a/apps/web/components/dialog/RerouteDialog.tsx +++ b/apps/web/components/dialog/RerouteDialog.tsx @@ -12,12 +12,12 @@ import FormInputFields, { import { getAbsoluteEventTypeRedirectUrl } from "@calcom/app-store/routing-forms/getEventTypeRedirectUrl"; import { findMatchingRoute } from "@calcom/app-store/routing-forms/lib/processRoute"; import { substituteVariables } from "@calcom/app-store/routing-forms/lib/substituteVariables"; -import { getUrlSearchParamsToForwardForReroute } from "@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward"; import type { FormResponse, LocalRoute } from "@calcom/app-store/routing-forms/types/types"; import { RouteActionType } from "@calcom/app-store/routing-forms/zod"; import dayjs from "@calcom/dayjs"; import { createBooking } from "@calcom/features/bookings/lib/create-booking"; import { Dialog } from "@calcom/features/components/controlled-dialog"; +import { getUrlSearchParamsToForwardForReroute } from "@calcom/features/routing-forms/lib/getUrlSearchParamsToForward"; import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { EventType, User, Team, Attendee, Booking as PrismaBooking } from "@calcom/prisma/client"; diff --git a/packages/app-store/routing-forms/pages/app-routing.server-config.tsx b/apps/web/lib/apps/routing-forms/[...pages]/app-routing.server-config.tsx similarity index 84% rename from packages/app-store/routing-forms/pages/app-routing.server-config.tsx rename to apps/web/lib/apps/routing-forms/[...pages]/app-routing.server-config.tsx index 9c94078656..b84d5373ac 100644 --- a/packages/app-store/routing-forms/pages/app-routing.server-config.tsx +++ b/apps/web/lib/apps/routing-forms/[...pages]/app-routing.server-config.tsx @@ -2,8 +2,8 @@ import "server-only"; import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps"; -import { getServerSidePropsForSingleFormView as getServerSidePropsSingleForm } from "../components/getServerSidePropsSingleForm"; -import { getServerSideProps as getServerSidePropsRoutingLink } from "./routing-link/getServerSideProps"; +import { getServerSideProps as getServerSidePropsRoutingLink } from "./getServerSidePropsRoutingLink"; +import { getServerSidePropsForSingleFormView as getServerSidePropsSingleForm } from "./getServerSidePropsSingleForm"; export const routingServerSidePropsConfig: Record = { "form-edit": getServerSidePropsSingleForm, diff --git a/apps/web/lib/apps/routing-forms/[...pages]/getServerSideProps.ts b/apps/web/lib/apps/routing-forms/[...pages]/getServerSideProps.ts index d49e179228..cf46106434 100644 --- a/apps/web/lib/apps/routing-forms/[...pages]/getServerSideProps.ts +++ b/apps/web/lib/apps/routing-forms/[...pages]/getServerSideProps.ts @@ -1,11 +1,12 @@ import type { GetServerSidePropsResult, GetServerSidePropsContext } from "next"; import { z } from "zod"; -import { routingServerSidePropsConfig } from "@calcom/app-store/routing-forms/pages/app-routing.server-config"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import prisma from "@calcom/prisma"; import type { AppGetServerSidePropsContext } from "@calcom/types/AppGetServerSideProps"; +import { routingServerSidePropsConfig } from "./app-routing.server-config"; + const paramsSchema = z.object({ pages: z.array(z.string()), }); diff --git a/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts b/apps/web/lib/apps/routing-forms/[...pages]/getServerSidePropsRoutingLink.ts similarity index 91% rename from packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts rename to apps/web/lib/apps/routing-forms/[...pages]/getServerSidePropsRoutingLink.ts index 6a1b71f3b4..69c36692f2 100644 --- a/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts +++ b/apps/web/lib/apps/routing-forms/[...pages]/getServerSidePropsRoutingLink.ts @@ -1,10 +1,9 @@ +import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData"; +import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm"; import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import { isAuthorizedToViewFormOnOrgDomain } from "@calcom/features/routing-forms/lib/isAuthorizedToViewForm"; import type { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetServerSideProps"; -import { enrichFormWithMigrationData } from "../../enrichFormWithMigrationData"; -import { getSerializableForm } from "../../lib/getSerializableForm"; - export const getServerSideProps = async function getServerSideProps( context: AppGetServerSidePropsContext, prisma: AppPrisma diff --git a/packages/app-store/routing-forms/components/getServerSidePropsSingleForm.ts b/apps/web/lib/apps/routing-forms/[...pages]/getServerSidePropsSingleForm.ts similarity index 95% rename from packages/app-store/routing-forms/components/getServerSidePropsSingleForm.ts rename to apps/web/lib/apps/routing-forms/[...pages]/getServerSidePropsSingleForm.ts index c5c839253a..3203fbb905 100644 --- a/packages/app-store/routing-forms/components/getServerSidePropsSingleForm.ts +++ b/apps/web/lib/apps/routing-forms/[...pages]/getServerSidePropsSingleForm.ts @@ -1,12 +1,11 @@ +import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData"; +import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSerializableForm"; import { Resource } from "@calcom/features/pbac/domain/types/permission-registry"; import { getResourcePermissions } from "@calcom/features/pbac/lib/resource-permissions"; import { MembershipRepository } from "@calcom/lib/server/repository/membership"; import { MembershipRole } from "@calcom/prisma/enums"; import type { AppGetServerSidePropsContext, AppPrisma, AppUser } from "@calcom/types/AppGetServerSideProps"; -import { enrichFormWithMigrationData } from "../enrichFormWithMigrationData"; -import { getSerializableForm } from "../lib/getSerializableForm"; - export const getServerSidePropsForSingleFormView = async function getServerSidePropsForSingleFormView( context: AppGetServerSidePropsContext, prisma: AppPrisma, diff --git a/apps/web/modules/apps/[slug]/setup/setup-view.tsx b/apps/web/modules/apps/[slug]/setup/setup-view.tsx index da07c32b8e..d4ba66e49f 100644 --- a/apps/web/modules/apps/[slug]/setup/setup-view.tsx +++ b/apps/web/modules/apps/[slug]/setup/setup-view.tsx @@ -3,10 +3,10 @@ import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; -import { AppSetupPage } from "@calcom/app-store/_pages/setup"; import type { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import type { inferSSRProps } from "@calcom/types/inferSSRProps"; +import { AppSetupPage } from "@calcom/web/components/apps/AppSetupPage"; export type PageProps = inferSSRProps; diff --git a/apps/web/modules/bookings/views/bookings-listing-view.tsx b/apps/web/modules/bookings/views/bookings-listing-view.tsx index 3743af9daf..33a7b76534 100644 --- a/apps/web/modules/bookings/views/bookings-listing-view.tsx +++ b/apps/web/modules/bookings/views/bookings-listing-view.tsx @@ -4,7 +4,6 @@ import { useReactTable, getCoreRowModel, getSortedRowModel, createColumnHelper } import { useSearchParams, usePathname } from "next/navigation"; import { useMemo, useRef } from "react"; -import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components"; import dayjs from "@calcom/dayjs"; import { useDataTable, @@ -29,6 +28,7 @@ import { EmptyScreen } from "@calcom/ui/components/empty-screen"; import type { HorizontalTabItemProps } from "@calcom/ui/components/navigation"; import { HorizontalTabs } from "@calcom/ui/components/navigation"; import type { VerticalTabItemProps } from "@calcom/ui/components/navigation"; +import { WipeMyCalActionButton } from "@calcom/web/components/apps/wipemycalother/wipeMyCalActionButton"; import BookingListItem from "@components/booking/BookingListItem"; import SkeletonLoader from "@components/booking/SkeletonLoader"; diff --git a/apps/web/modules/insights/insights-virtual-queues-view.tsx b/apps/web/modules/insights/insights-virtual-queues-view.tsx index 0d06d78c1e..c82b0e733d 100644 --- a/apps/web/modules/insights/insights-virtual-queues-view.tsx +++ b/apps/web/modules/insights/insights-virtual-queues-view.tsx @@ -2,13 +2,14 @@ import { useState } from "react"; -import { TestForm } from "@calcom/app-store/routing-forms/components/_components/TestForm"; import type { RoutingForm } from "@calcom/app-store/routing-forms/types/types"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; import { Label } from "@calcom/ui/components/form"; import { Select } from "@calcom/ui/components/form"; +import { TestForm } from "../../components/apps/routing-forms/TestForm"; + export default function InsightsVirtualQueuesPage() { const { t } = useLocale(); const { data: routingForms, isLoading: isRoutingFormsLoading } = diff --git a/packages/app-store/_components/AppCard.tsx b/packages/app-store/_components/AppCard.tsx index 8ce29283e5..09d481fadd 100644 --- a/packages/app-store/_components/AppCard.tsx +++ b/packages/app-store/_components/AppCard.tsx @@ -25,8 +25,10 @@ export default function AppCard({ switchTooltip, hideSettingsIcon = false, hideAppCardOptions = false, + onAppInstallSuccess, }: { app: AppCardApp; + onAppInstallSuccess: () => void; description?: React.ReactNode; switchChecked?: boolean; switchOnClick?: (e: boolean) => void; @@ -112,6 +114,7 @@ export default function AppCard({ appId={app.slug} returnTo={returnTo} teamId={teamId} + onAppInstallSuccess={onAppInstallSuccess} /> )}
diff --git a/packages/app-store/_components/DynamicComponent.tsx b/packages/app-store/_components/DynamicComponent.tsx index e13a5a4292..5b114a8086 100644 --- a/packages/app-store/_components/DynamicComponent.tsx +++ b/packages/app-store/_components/DynamicComponent.tsx @@ -2,6 +2,7 @@ export function DynamicComponent>>(props: { componentMap: T; slug: string; + onAppInstallSuccess?: () => void; wrapperClassName?: string; }) { const { componentMap, slug, wrapperClassName, ...rest } = props; diff --git a/packages/app-store/_components/EventTypeAppCardInterface.tsx b/packages/app-store/_components/EventTypeAppCardInterface.tsx index 80d77a970e..bcf2947f09 100644 --- a/packages/app-store/_components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/_components/EventTypeAppCardInterface.tsx @@ -16,12 +16,13 @@ export const EventTypeAppCard = (props: { eventType: EventTypeForAppCard; getAppData: GetAppData; setAppData: SetAppData; + onAppInstallSuccess?: () => void; // For event type apps, get these props from shouldLockDisableProps LockedIcon?: JSX.Element | false; eventTypeFormMetadata: z.infer; disabled?: boolean; }) => { - const { app, getAppData, setAppData, LockedIcon, disabled } = props; + const { app, getAppData, setAppData, LockedIcon, disabled, onAppInstallSuccess } = props; return ( @@ -29,6 +30,7 @@ export const EventTypeAppCard = (props: { slug={app.slug === "stripe" ? "stripepayment" : app.slug} componentMap={EventTypeAddonMap} {...props} + onAppInstallSuccess={onAppInstallSuccess} /> diff --git a/packages/app-store/_components/OmniInstallAppButton.tsx b/packages/app-store/_components/OmniInstallAppButton.tsx index 7105d42d46..b0c2ea5133 100644 --- a/packages/app-store/_components/OmniInstallAppButton.tsx +++ b/packages/app-store/_components/OmniInstallAppButton.tsx @@ -1,6 +1,5 @@ import useApp from "@calcom/lib/hooks/useApp"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { trpc } from "@calcom/trpc/react"; import classNames from "@calcom/ui/classNames"; import { Button } from "@calcom/ui/components/button"; import { showToast } from "@calcom/ui/components/toast"; @@ -17,24 +16,21 @@ export default function OmniInstallAppButton({ className, returnTo, teamId, + onAppInstallSuccess, }: { appId: string; className: string; + onAppInstallSuccess: () => void; returnTo?: string; teamId?: number; }) { const { t } = useLocale(); const { data: app } = useApp(appId); - const utils = trpc.useUtils(); const mutation = useAddAppMutation(null, { returnTo, onSuccess: (data) => { - utils.viewer.apps.appById.invalidate({ appId }); - utils.viewer.apps.integrations.invalidate({ - extendsFeature: "EventType", - ...(teamId && { teamId }), - }); + onAppInstallSuccess(); if (data?.setupPending) return; showToast(t("app_successfully_installed"), "success"); }, diff --git a/packages/app-store/_components/eventTypeAppCardInterface.test.tsx b/packages/app-store/_components/eventTypeAppCardInterface.test.tsx index 79a001216b..5fbb6f30fd 100644 --- a/packages/app-store/_components/eventTypeAppCardInterface.test.tsx +++ b/packages/app-store/_components/eventTypeAppCardInterface.test.tsx @@ -30,6 +30,7 @@ const mockProps = { setAppData: setAppDataMock, LockedIcon:
MockedIcon
, disabled: false, + onAppInstallSuccess: vi.fn(), // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; diff --git a/packages/app-store/_pages/setup/index.tsx b/packages/app-store/_pages/setup/index.tsx deleted file mode 100644 index 2f57ca3140..0000000000 --- a/packages/app-store/_pages/setup/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import dynamic from "next/dynamic"; - -import { DynamicComponent } from "../../_components/DynamicComponent"; - -export const AppSetupMap = { - alby: dynamic(() => import("../../alby/pages/setup")), - "apple-calendar": dynamic(() => import("../../applecalendar/pages/setup")), - exchange: dynamic(() => import("../../exchangecalendar/pages/setup")), - "exchange2013-calendar": dynamic(() => import("../../exchange2013calendar/pages/setup")), - "exchange2016-calendar": dynamic(() => import("../../exchange2016calendar/pages/setup")), - "caldav-calendar": dynamic(() => import("../../caldavcalendar/pages/setup")), - "ics-feed": dynamic(() => import("../../ics-feedcalendar/pages/setup")), - zapier: dynamic(() => import("../../zapier/pages/setup")), - make: dynamic(() => import("../../make/pages/setup")), - sendgrid: dynamic(() => import("../../sendgrid/pages/setup")), - stripe: dynamic(() => import("../../stripepayment/pages/setup")), - paypal: dynamic(() => import("../../paypal/pages/setup")), - hitpay: dynamic(() => import("../../hitpay/pages/setup")), - btcpayserver: dynamic(() => import("../../btcpayserver/pages/setup")), -}; - -export const AppSetupPage = (props: { slug: string }) => { - return componentMap={AppSetupMap} {...props} />; -}; - -export default AppSetupPage; diff --git a/packages/app-store/alby/components/EventTypeAppCardInterface.tsx b/packages/app-store/alby/components/EventTypeAppCardInterface.tsx index 70b497522e..0b23757c9e 100644 --- a/packages/app-store/alby/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/alby/components/EventTypeAppCardInterface.tsx @@ -15,6 +15,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, eventTypeFormMetadata, + onAppInstallSuccess, }) { const searchParams = useSearchParams(); const { t } = useLocale(); @@ -32,6 +33,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ return ( { const notFound = { notFound: true } as const; diff --git a/packages/app-store/basecamp3/components/EventTypeAppCardInterface.tsx b/packages/app-store/basecamp3/components/EventTypeAppCardInterface.tsx index d359812bea..19972ab072 100644 --- a/packages/app-store/basecamp3/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/basecamp3/components/EventTypeAppCardInterface.tsx @@ -6,12 +6,17 @@ import useIsAppEnabled from "../../_utils/useIsAppEnabled"; import type { appDataSchema } from "../zod"; import EventTypeAppSettingsInterface from "./EventTypeAppSettingsInterface"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + onAppInstallSuccess, +}) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { updateEnabled(e); diff --git a/packages/app-store/btcpayserver/components/EventTypeAppCardInterface.tsx b/packages/app-store/btcpayserver/components/EventTypeAppCardInterface.tsx index 97d9472722..91ef9815d8 100644 --- a/packages/app-store/btcpayserver/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/btcpayserver/components/EventTypeAppCardInterface.tsx @@ -17,6 +17,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ eventType, app, eventTypeFormMetadata, + onAppInstallSuccess, }) { const { t } = useLocale(); const pathname = usePathname(); @@ -27,6 +28,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ return ( ; export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { try { diff --git a/packages/app-store/closecom/components/EventTypeAppCardInterface.tsx b/packages/app-store/closecom/components/EventTypeAppCardInterface.tsx index 53a8297d6f..9482d9158d 100644 --- a/packages/app-store/closecom/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/closecom/components/EventTypeAppCardInterface.tsx @@ -5,13 +5,18 @@ import useIsAppEnabled from "@calcom/app-store/_utils/useIsAppEnabled"; import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import { WEBAPP_URL } from "@calcom/lib/constants"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + onAppInstallSuccess, +}) { const pathname = usePathname(); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( (); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { diff --git a/packages/app-store/ga4/components/EventTypeAppCardInterface.tsx b/packages/app-store/ga4/components/EventTypeAppCardInterface.tsx index 10e0a0f8a6..a48438f4b8 100644 --- a/packages/app-store/ga4/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/ga4/components/EventTypeAppCardInterface.tsx @@ -6,12 +6,17 @@ import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import type { appDataSchema } from "../zod"; import EventTypeAppSettingsInterface from "./EventTypeAppSettingsInterface"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + onAppInstallSuccess, +}) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { diff --git a/packages/app-store/giphy/components/EventTypeAppCardInterface.tsx b/packages/app-store/giphy/components/EventTypeAppCardInterface.tsx index b48ce29e34..301e76699f 100644 --- a/packages/app-store/giphy/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/giphy/components/EventTypeAppCardInterface.tsx @@ -7,7 +7,11 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { appDataSchema } from "../zod"; import EventTypeAppSettingsInterface from "./EventTypeAppSettingsInterface"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + onAppInstallSuccess, +}) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const { enabled: showGifSelection, updateEnabled: setShowGifSelection } = useIsAppEnabled(app); @@ -15,6 +19,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ return ( { diff --git a/packages/app-store/gtm/components/EventTypeAppCardInterface.tsx b/packages/app-store/gtm/components/EventTypeAppCardInterface.tsx index 10e0a0f8a6..e00394daba 100644 --- a/packages/app-store/gtm/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/gtm/components/EventTypeAppCardInterface.tsx @@ -6,12 +6,13 @@ import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import type { appDataSchema } from "../zod"; import EventTypeAppSettingsInterface from "./EventTypeAppSettingsInterface"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, onAppInstallSuccess }) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { diff --git a/packages/app-store/hitpay/components/EventTypeAppCardInterface.tsx b/packages/app-store/hitpay/components/EventTypeAppCardInterface.tsx index 0340a969cd..57e92c488b 100644 --- a/packages/app-store/hitpay/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/hitpay/components/EventTypeAppCardInterface.tsx @@ -16,6 +16,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, eventTypeFormMetadata, + onAppInstallSuccess, }) { const { t } = useLocale(); const pathname = usePathname(); @@ -27,6 +28,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ return ( ; export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { const notFound = { notFound: true } as const; diff --git a/packages/app-store/hubspot/components/EventTypeAppCardInterface.tsx b/packages/app-store/hubspot/components/EventTypeAppCardInterface.tsx index 53a8297d6f..84f8ca2110 100644 --- a/packages/app-store/hubspot/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/hubspot/components/EventTypeAppCardInterface.tsx @@ -5,13 +5,14 @@ import useIsAppEnabled from "@calcom/app-store/_utils/useIsAppEnabled"; import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import { WEBAPP_URL } from "@calcom/lib/constants"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, onAppInstallSuccess }) { const pathname = usePathname(); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( (); const siteId = getAppData("SITE_ID"); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { updateEnabled(e); diff --git a/packages/app-store/matomo/components/EventTypeAppCardInterface.tsx b/packages/app-store/matomo/components/EventTypeAppCardInterface.tsx index ff6803d9b4..0180455d9b 100644 --- a/packages/app-store/matomo/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/matomo/components/EventTypeAppCardInterface.tsx @@ -6,7 +6,7 @@ import { TextField } from "@calcom/ui/components/form"; import type { appDataSchema } from "../zod"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, onAppInstallSuccess }) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const matomoUrl = getAppData("MATOMO_URL"); const siteId = getAppData("SITE_ID"); @@ -14,6 +14,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ return ( { diff --git a/packages/app-store/metapixel/components/EventTypeAppCardInterface.tsx b/packages/app-store/metapixel/components/EventTypeAppCardInterface.tsx index 8bab69cac9..13d334ff3d 100644 --- a/packages/app-store/metapixel/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/metapixel/components/EventTypeAppCardInterface.tsx @@ -6,12 +6,13 @@ import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import type { appDataSchema } from "../zod"; import EventTypeAppSettingsInterface from "./EventTypeAppSettingsInterface"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, onAppInstallSuccess }) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { +type TUpdateAppCredentialsInputSchema = { credentialId: number; key: { [k: string]: unknown } }; + +const handlePaypalValidations = async ({ input }: { input: TUpdateAppCredentialsInputSchema }) => { const validated = schema.safeParse(input); if (!validated.success) throw new Error("Invalid input"); const { key } = validated.data; diff --git a/packages/app-store/pipedrive-crm/components/EventTypeAppCardInterface.tsx b/packages/app-store/pipedrive-crm/components/EventTypeAppCardInterface.tsx index 53a8297d6f..84f8ca2110 100644 --- a/packages/app-store/pipedrive-crm/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/pipedrive-crm/components/EventTypeAppCardInterface.tsx @@ -5,13 +5,14 @@ import useIsAppEnabled from "@calcom/app-store/_utils/useIsAppEnabled"; import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import { WEBAPP_URL } from "@calcom/lib/constants"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, onAppInstallSuccess }) { const pathname = usePathname(); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( (); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { diff --git a/packages/app-store/posthog/components/EventTypeAppCardInterface.tsx b/packages/app-store/posthog/components/EventTypeAppCardInterface.tsx index 442079f27c..3fa13d99cc 100644 --- a/packages/app-store/posthog/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/posthog/components/EventTypeAppCardInterface.tsx @@ -6,7 +6,11 @@ import { TextField } from "@calcom/ui/components/form"; import type { appDataSchema } from "../zod"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + onAppInstallSuccess, +}) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const trackingId = getAppData("TRACKING_ID"); const apiHost = getAppData("API_HOST"); @@ -14,6 +18,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ return ( { diff --git a/packages/app-store/qr_code/components/EventTypeAppCardInterface.tsx b/packages/app-store/qr_code/components/EventTypeAppCardInterface.tsx index 53df00b922..867d11ed06 100644 --- a/packages/app-store/qr_code/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/qr_code/components/EventTypeAppCardInterface.tsx @@ -6,12 +6,13 @@ import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import type { appDataSchema } from "../zod"; import EventTypeAppSettingsInterface from "./EventTypeAppSettingsInterface"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ eventType, app }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ eventType, app, onAppInstallSuccess }) { const { enabled, updateEnabled } = useIsAppEnabled(app); const { disabled, getAppData, setAppData } = useAppContextWithSchema(); return ( { updateEnabled(e); diff --git a/packages/app-store/routing-forms/lib/formSubmissionUtils.ts b/packages/app-store/routing-forms/lib/formSubmissionUtils.ts index 53d29670af..7c8948a4ed 100644 --- a/packages/app-store/routing-forms/lib/formSubmissionUtils.ts +++ b/packages/app-store/routing-forms/lib/formSubmissionUtils.ts @@ -1,20 +1,18 @@ -import { prisma } from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; -import type { App_RoutingForms_Form, User } from "@calcom/prisma/client"; - import dayjs from "@calcom/dayjs"; import type { Tasker } from "@calcom/features/tasker/tasker"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; +import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { withReporting } from "@calcom/lib/sentryWrapper"; +import { prisma } from "@calcom/prisma"; +import type { Prisma } from "@calcom/prisma/client"; +import type { App_RoutingForms_Form, User } from "@calcom/prisma/client"; import { WebhookTriggerEvents } from "@calcom/prisma/enums"; import { RoutingFormSettings } from "@calcom/prisma/zod-utils"; import type { Ensure } from "@calcom/types/utils"; -import { TRPCError } from "@trpc/server"; - import type { FormResponse, SerializableForm, SerializableField, OrderedResponses } from "../types/types"; const moduleLogger = logger.getSubLogger({ prefix: ["routing-forms/lib/formSubmissionUtils"] }); @@ -269,9 +267,7 @@ export const onSubmissionOfFormResponse = async ({ }) => { if (!form.fields) { // There is no point in submitting a form that doesn't have fields defined - throw new TRPCError({ - code: "BAD_REQUEST", - }); + throw new HttpError({ statusCode: 400 }); } const settings = RoutingFormSettings.parse(form.settings); let userWithEmails: string[] = []; diff --git a/packages/app-store/routing-forms/lib/handleResponse.test.ts b/packages/app-store/routing-forms/lib/handleResponse.test.ts index e7b0246263..44ef7ea319 100644 --- a/packages/app-store/routing-forms/lib/handleResponse.test.ts +++ b/packages/app-store/routing-forms/lib/handleResponse.test.ts @@ -2,11 +2,9 @@ import "@calcom/lib/__mocks__/logger"; import { prisma } from "@calcom/prisma/__mocks__/prisma"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { z } from "zod"; import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic"; import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; -import type { ZResponseInputSchema } from "@calcom/trpc/server/routers/viewer/routing-forms/response.schema"; import isRouter from "../lib/isRouter"; import routerGetCrmContactOwnerEmail from "./crmRouting/routerGetCrmContactOwnerEmail"; @@ -104,7 +102,14 @@ const mockForm: TargetRoutingFormForResponse = { updatedById: null, }; -const mockResponse: z.infer["response"] = { +const mockResponse: Record< + string, + { + value: (string | number | string[]) & (string | number | string[] | undefined); + label: string; + identifier?: string | undefined; + } +> = { name: { value: "John Doe", label: "Name" }, email: { value: "john.doe@example.com", label: "Email" }, }; @@ -118,7 +123,7 @@ describe("handleResponse", () => { ); }); - it("should throw a TRPCError for missing required fields", async () => { + it("should throw an Error for missing required fields", async () => { await expect( handleResponse({ response: { email: { value: "test@test.com", label: "Email" } }, // Name is missing @@ -131,7 +136,7 @@ describe("handleResponse", () => { ).rejects.toThrow(/Missing required fields Name/); }); - it("should throw a TRPCError for invalid email", async () => { + it("should throw an Error for invalid email", async () => { await expect( handleResponse({ response: { diff --git a/packages/app-store/routing-forms/lib/handleResponse.ts b/packages/app-store/routing-forms/lib/handleResponse.ts index 5f08256cf5..cd46fe1384 100644 --- a/packages/app-store/routing-forms/lib/handleResponse.ts +++ b/packages/app-store/routing-forms/lib/handleResponse.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { emailSchema } from "@calcom/lib/emailSchema"; +import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -8,9 +9,6 @@ import { withReporting } from "@calcom/lib/sentryWrapper"; import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse"; import { prisma } from "@calcom/prisma"; import { Prisma } from "@calcom/prisma/client"; -import type { ZResponseInputSchema } from "@calcom/trpc/server/routers/viewer/routing-forms/response.schema"; - -import { TRPCError } from "@trpc/server"; import isRouter from "../lib/isRouter"; import routerGetCrmContactOwnerEmail from "./crmRouting/routerGetCrmContactOwnerEmail"; @@ -29,7 +27,14 @@ const _handleResponse = async ({ queueFormResponse, fetchCrm, }: { - response: z.infer["response"]; + response: Record< + string, + { + value: string | number | string[]; + label: string; + identifier?: string; + } + >; identifierKeyedResponse: Record | null; form: TargetRoutingFormForResponse; formFillerId: string; @@ -41,9 +46,7 @@ const _handleResponse = async ({ try { if (!form.fields) { // There is no point in submitting a form that doesn't have fields defined - throw new TRPCError({ - code: "BAD_REQUEST", - }); + throw new HttpError({ statusCode: 400 }); } const formTeamId = form.teamId; @@ -58,8 +61,8 @@ const _handleResponse = async ({ .map((f) => f.label); if (missingFields.length) { - throw new TRPCError({ - code: "BAD_REQUEST", + throw new HttpError({ + statusCode: 400, message: `Missing required fields ${missingFields.join(", ")}`, }); } @@ -83,8 +86,8 @@ const _handleResponse = async ({ .map((f) => ({ label: f.label, type: f.type, value: response[f.id]?.value })); if (invalidFields.length) { - throw new TRPCError({ - code: "BAD_REQUEST", + throw new HttpError({ + statusCode: 400, message: `Invalid value for fields ${invalidFields .map((f) => `'${f.label}' with value '${f.value}' should be valid ${f.type}`) .join(", ")}`, @@ -100,8 +103,8 @@ const _handleResponse = async ({ let timeTaken: Record = {}; if (chosenRoute) { if (isRouter(chosenRoute)) { - throw new TRPCError({ - code: "BAD_REQUEST", + throw new HttpError({ + statusCode: 400, message: "Chosen route is a router", }); } @@ -225,8 +228,9 @@ const _handleResponse = async ({ } catch (e) { if (e instanceof Prisma.PrismaClientKnownRequestError) { if (e.code === "P2002") { - throw new TRPCError({ - code: "CONFLICT", + throw new HttpError({ + statusCode: 409, + message: "Form response already exists", }); } } diff --git a/packages/app-store/routing-forms/pages/app-routing.client-config.tsx b/packages/app-store/routing-forms/pages/app-routing.client-config.tsx deleted file mode 100644 index 24bf4e5412..0000000000 --- a/packages/app-store/routing-forms/pages/app-routing.client-config.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export const routingFormsComponents = { - "form-edit": () => import("./form-edit/[...appPages]").then((mod) => mod.default), - "route-builder": () => import("./route-builder/[...appPages]").then((mod) => mod.default), - "routing-link": () => import("./routing-link/[...appPages]").then((mod) => mod.default), - "incomplete-booking": () => import("./incomplete-booking/[...appPages]").then((mod) => mod.default), -}; diff --git a/packages/app-store/routing-forms/types/shared.ts b/packages/app-store/routing-forms/types/shared.ts deleted file mode 100644 index 7cc707a548..0000000000 --- a/packages/app-store/routing-forms/types/shared.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { UseFormReturn } from "react-hook-form"; - -import type { inferSSRProps } from "@calcom/types/inferSSRProps"; - -import type { getServerSidePropsForSingleFormView } from "../components/getServerSidePropsSingleForm"; -import type { RoutingFormWithResponseCount } from "../types/types"; - -export type SingleFormComponentProps = { - form: RoutingFormWithResponseCount; - appUrl: string; - Page: React.FC<{ - form: RoutingFormWithResponseCount; - appUrl: string; - hookForm: UseFormReturn; - }>; - enrichedWithUserProfileForm: inferSSRProps< - typeof getServerSidePropsForSingleFormView - >["enrichedWithUserProfileForm"]; - permissions: inferSSRProps["permissions"]; -}; diff --git a/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx b/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx index 46dc677fd3..a12c8d5e78 100644 --- a/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx @@ -20,7 +20,7 @@ import { SalesforceRecordEnum } from "../lib/enums"; import type { appDataSchema } from "../zod"; import WriteToObjectSettings, { BookingActionEnum } from "./components/WriteToObjectSettings"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, onAppInstallSuccess }) { const pathname = usePathname(); const { getAppData, setAppData, disabled } = useAppContextWithSchema(); @@ -95,6 +95,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ return ( (); const trackingId = getAppData("trackingId"); const [enabled, setEnabled] = useState(getAppData("enabled")); return ( { if (!e) { diff --git a/packages/app-store/templates/event-type-app-card/components/EventTypeAppCardInterface.tsx b/packages/app-store/templates/event-type-app-card/components/EventTypeAppCardInterface.tsx index afb0c1ecb5..482814104f 100644 --- a/packages/app-store/templates/event-type-app-card/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/templates/event-type-app-card/components/EventTypeAppCardInterface.tsx @@ -6,13 +6,18 @@ import { Icon } from "@calcom/ui/components/icon"; import type { appDataSchema } from "../zod"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ eventType, app }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + eventType, + app, + onAppInstallSuccess, +}) { const { getAppData, setAppData } = useAppContextWithSchema(); const isSunrise = getAppData("isSunrise"); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { if (!e) { diff --git a/packages/app-store/trpc-routers.ts b/packages/app-store/trpc-routers.ts deleted file mode 100644 index f8db632aaf..0000000000 --- a/packages/app-store/trpc-routers.ts +++ /dev/null @@ -1,6 +0,0 @@ -// TODO: Generate this file automatically. This supposed to contain trpc-routes from all the apps automatically imported here and then exported. -// Can't use this file right now as I am not able to figure out how to keep getting tRPC typesafety with merge calls done on already created router -// Till that time import routers from each app directly to core. -// import { Router } from "@trpc/server/dist/declarations/src/router"; - -export {}; diff --git a/packages/app-store/twipla/components/EventTypeAppCardInterface.tsx b/packages/app-store/twipla/components/EventTypeAppCardInterface.tsx index 9844d03577..9115848e4d 100644 --- a/packages/app-store/twipla/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/twipla/components/EventTypeAppCardInterface.tsx @@ -6,13 +6,14 @@ import { TextField } from "@calcom/ui/components/form"; import type { appDataSchema } from "../zod"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType, onAppInstallSuccess }) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const siteId = getAppData("SITE_ID"); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { updateEnabled(e); diff --git a/packages/app-store/types.d.ts b/packages/app-store/types.d.ts index 4615834cd3..57715fba90 100644 --- a/packages/app-store/types.d.ts +++ b/packages/app-store/types.d.ts @@ -65,6 +65,7 @@ export type EventTypeAppCardComponentProps = { URL: string; }; app: EventTypeAppCardApp; + onAppInstallSuccess: () => void; disabled?: boolean; LockedIcon?: JSX.Element | false; eventTypeFormMetadata?: z.infer; diff --git a/packages/app-store/umami/components/EventTypeAppCardInterface.tsx b/packages/app-store/umami/components/EventTypeAppCardInterface.tsx index 1c24133995..167b50f628 100644 --- a/packages/app-store/umami/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/umami/components/EventTypeAppCardInterface.tsx @@ -6,7 +6,11 @@ import { TextField } from "@calcom/ui/components/form"; import type { appDataSchema } from "../zod"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + onAppInstallSuccess, +}) { const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const siteId = getAppData("SITE_ID"); const scriptURL = getAppData("SCRIPT_URL"); @@ -14,6 +18,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ return ( { updateEnabled(e); diff --git a/packages/app-store/wipemycalother/components/index.ts b/packages/app-store/wipemycalother/components/index.ts deleted file mode 100644 index 09a1499635..0000000000 --- a/packages/app-store/wipemycalother/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { WipeMyCalActionButton } from "./wipeMyCalActionButton"; diff --git a/packages/app-store/zoho-bigin/components/EventTypeAppCardInterface.tsx b/packages/app-store/zoho-bigin/components/EventTypeAppCardInterface.tsx index 53a8297d6f..9482d9158d 100644 --- a/packages/app-store/zoho-bigin/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/zoho-bigin/components/EventTypeAppCardInterface.tsx @@ -5,13 +5,18 @@ import useIsAppEnabled from "@calcom/app-store/_utils/useIsAppEnabled"; import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import { WEBAPP_URL } from "@calcom/lib/constants"; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + onAppInstallSuccess, +}) { const pathname = usePathname(); const { enabled, updateEnabled } = useIsAppEnabled(app); return ( { teamId: eventType.team?.id || eventType.parent?.teamId, }); + const utils = trpc.useUtils(); + const formMethods = useFormContext(); const installedApps = eventTypeApps?.items.filter((app) => app.userCredentialIds.length || app.teams.length) || []; @@ -41,6 +43,14 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { const appsDisableProps = shouldLockDisableProps("apps", { simple: true }); const lockedText = appsDisableProps.isLocked ? "locked" : "unlocked"; + const handleAppInstallSuccess = (appId: string) => () => { + utils.viewer.apps.appById.invalidate({ appId }); + utils.viewer.apps.integrations.invalidate({ + extendsFeature: "EventType", + ...(eventType.team?.id && { teamId: eventType.team.id }), + }); + }; + const appsWithTeamCredentials = eventTypeApps?.items.filter((app) => app.teams.length) || []; const cardsForAppsWithTeams = appsWithTeamCredentials.map((app) => { const appCards = []; @@ -58,6 +68,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { app={app} eventType={eventType} eventTypeFormMetadata={eventTypeFormMetadata} + onAppInstallSuccess={handleAppInstallSuccess(app.slug)} /> ); } @@ -82,6 +93,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { eventType={eventType} eventTypeFormMetadata={eventTypeFormMetadata} disabled={shouldLockDisableProps("apps").disabled} + onAppInstallSuccess={handleAppInstallSuccess(app.slug)} /> ); } @@ -147,6 +159,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { app={app} eventType={eventType} eventTypeFormMetadata={eventTypeFormMetadata} + onAppInstallSuccess={handleAppInstallSuccess(app.slug)} /> ); })} @@ -180,6 +193,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { app={app} eventType={eventType} eventTypeFormMetadata={eventTypeFormMetadata} + onAppInstallSuccess={handleAppInstallSuccess(app.slug)} /> ))}
diff --git a/packages/features/routing-forms/lib/getRoutedUrl.test.ts b/packages/features/routing-forms/lib/getRoutedUrl.test.ts index 1ea49720e0..5c6bcb23cc 100644 --- a/packages/features/routing-forms/lib/getRoutedUrl.test.ts +++ b/packages/features/routing-forms/lib/getRoutedUrl.test.ts @@ -10,7 +10,6 @@ import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSeri import { handleResponse } from "@calcom/app-store/routing-forms/lib/handleResponse"; import { findMatchingRoute } from "@calcom/app-store/routing-forms/lib/processRoute"; import { substituteVariables } from "@calcom/app-store/routing-forms/lib/substituteVariables"; -import { getUrlSearchParamsToForward } from "@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward"; import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import { isAuthorizedToViewFormOnOrgDomain } from "@calcom/features/routing-forms/lib/isAuthorizedToViewForm"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; @@ -18,8 +17,10 @@ import { PrismaRoutingFormRepository } from "@calcom/lib/server/repository/Prism import { UserRepository } from "@calcom/lib/server/repository/user"; import { getRoutedUrl } from "./getRoutedUrl"; +import { getUrlSearchParamsToForward } from "./getUrlSearchParamsToForward"; // Mock dependencies +vi.mock("./getUrlSearchParamsToForward"); vi.mock("@calcom/lib/checkRateLimitAndThrowError"); vi.mock("@calcom/app-store/routing-forms/lib/handleResponse"); vi.mock("@calcom/lib/server/repository/PrismaRoutingFormRepository"); @@ -36,7 +37,6 @@ vi.mock("@calcom/app-store/routing-forms/lib/getSerializableForm"); vi.mock("@calcom/app-store/routing-forms/lib/getResponseToStore"); vi.mock("@calcom/app-store/routing-forms/lib/processRoute"); vi.mock("@calcom/app-store/routing-forms/lib/substituteVariables"); -vi.mock("@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward"); vi.mock("@calcom/app-store/routing-forms/getEventTypeRedirectUrl"); vi.mock("@calcom/app-store/routing-forms/enrichFormWithMigrationData", () => ({ enrichFormWithMigrationData: vi.fn((form) => form), diff --git a/packages/features/routing-forms/lib/getRoutedUrl.ts b/packages/features/routing-forms/lib/getRoutedUrl.ts index 1702c697da..a11555697a 100644 --- a/packages/features/routing-forms/lib/getRoutedUrl.ts +++ b/packages/features/routing-forms/lib/getRoutedUrl.ts @@ -13,7 +13,6 @@ import { getServerTimingHeader } from "@calcom/app-store/routing-forms/lib/getSe import { handleResponse } from "@calcom/app-store/routing-forms/lib/handleResponse"; import { findMatchingRoute } from "@calcom/app-store/routing-forms/lib/processRoute"; import { substituteVariables } from "@calcom/app-store/routing-forms/lib/substituteVariables"; -import { getUrlSearchParamsToForward } from "@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward"; import type { FormResponse } from "@calcom/app-store/routing-forms/types/types"; import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import { isAuthorizedToViewFormOnOrgDomain } from "@calcom/features/routing-forms/lib/isAuthorizedToViewForm"; @@ -27,6 +26,8 @@ import prisma from "@calcom/prisma"; import { TRPCError } from "@trpc/server"; +import { getUrlSearchParamsToForward } from "./getUrlSearchParamsToForward"; + const log = logger.getSubLogger({ prefix: ["[routing-forms]", "[router]"] }); const querySchema = z .object({ diff --git a/packages/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward.test.ts b/packages/features/routing-forms/lib/getUrlSearchParamsToForward.test.ts similarity index 100% rename from packages/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward.test.ts rename to packages/features/routing-forms/lib/getUrlSearchParamsToForward.test.ts diff --git a/packages/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward.ts b/packages/features/routing-forms/lib/getUrlSearchParamsToForward.ts similarity index 92% rename from packages/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward.ts rename to packages/features/routing-forms/lib/getUrlSearchParamsToForward.ts index 64428bfae4..2252a1a6d3 100644 --- a/packages/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward.ts +++ b/packages/features/routing-forms/lib/getUrlSearchParamsToForward.ts @@ -1,12 +1,9 @@ -import type { inferSSRProps } from "@calcom/types/inferSSRProps"; - -import { ROUTING_FORM_RESPONSE_ID_QUERY_STRING } from "../../lib/constants"; -import getFieldIdentifier from "../../lib/getFieldIdentifier"; -import type { FormResponse, LocalRoute } from "../../types/types"; -import type { getServerSideProps } from "./getServerSideProps"; +import { ROUTING_FORM_RESPONSE_ID_QUERY_STRING } from "@calcom/app-store/routing-forms/lib/constants"; +import getFieldIdentifier from "@calcom/app-store/routing-forms/lib/getFieldIdentifier"; +import type { FormResponse, LocalRoute } from "@calcom/app-store/routing-forms/types/types"; type FormResponseValueOnly = { [key: string]: { value: FormResponse[keyof FormResponse]["value"] } }; -type Props = inferSSRProps; + type AttributeRoutingConfig = NonNullable; type GetUrlSearchParamsToForwardOptions = { formResponse: Record< @@ -15,10 +12,16 @@ type GetUrlSearchParamsToForwardOptions = { value: number | string | string[]; } >; - fields: Pick< - NonNullable[number], - "id" | "type" | "options" | "identifier" | "label" - >[]; + fields: { + id: string; + type: string; + label: string; + options?: { + id: string | null; + label: string; + }[]; + identifier?: string; + }[]; searchParams: URLSearchParams; formResponseId: number | null; queuedFormResponseId: string | null; diff --git a/packages/trpc/server/routers/viewer/apps/updateAppCredentials.handler.ts b/packages/trpc/server/routers/viewer/apps/updateAppCredentials.handler.ts index e8ae05d591..87de99c2e0 100644 --- a/packages/trpc/server/routers/viewer/apps/updateAppCredentials.handler.ts +++ b/packages/trpc/server/routers/viewer/apps/updateAppCredentials.handler.ts @@ -18,7 +18,6 @@ const validators = { }; export const handleCustomValidations = async ({ - ctx, input, appId, }: UpdateAppCredentialsOptions & { appId: string }) => { @@ -28,7 +27,7 @@ export const handleCustomValidations = async ({ if (!validatorGetter) return key; try { const validator = (await validatorGetter()).default; - return await validator({ input, ctx }); + return await validator({ input }); } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", diff --git a/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts b/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts index efaf1d8f88..966c8e94e0 100644 --- a/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts +++ b/packages/trpc/server/routers/viewer/routing-forms/findTeamMembersMatchingAttributeLogicOfRoute.handler.ts @@ -7,7 +7,7 @@ import type { ServerResponse } from "http"; import type { NextApiResponse } from "next"; import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData"; -import { getUrlSearchParamsToForwardForTestPreview } from "@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward"; +import { getUrlSearchParamsToForwardForTestPreview } from "@calcom/features/routing-forms/lib/getUrlSearchParamsToForward"; import { enrichHostsWithDelegationCredentials } from "@calcom/lib/delegationCredential/server"; import { getLuckyUserService } from "@calcom/lib/di/containers/LuckyUser"; import { entityPrismaWhereClause } from "@calcom/lib/entityPermissionUtils.server";