From 825f55c0a1a46dc1a5e891733f7e89adbff6ff6e Mon Sep 17 00:00:00 2001 From: Keith Williams Date: Sat, 20 Dec 2025 17:13:49 -0300 Subject: [PATCH] fix: extract shared types to non-React modules to fix circular dependencies (#26083) - Move InvalidAppCredentialBannerProps to packages/features/users/types/invalidAppCredentials.ts - Add WorkflowListType to packages/features/ee/workflows/lib/types.ts - Update server file imports to use new type locations - Update React component imports to re-export from new locations This fixes circular dependencies where server files were importing from React component modules that import from @calcom/trpc, creating: server -> component -> @calcom/trpc -> react -> server (circular) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../workflows/components/WorkflowListPage.tsx | 36 +++---------------- packages/features/ee/workflows/lib/types.ts | 33 ++++++++++++++++- .../InvalidAppCredentialsBanner.tsx | 7 ++-- .../users/types/invalidAppCredentials.ts | 4 +++ .../me/checkForInvalidAppCredentials.ts | 2 +- .../routers/viewer/workflows/list.handler.ts | 10 +++--- 6 files changed, 49 insertions(+), 43 deletions(-) create mode 100644 packages/features/users/types/invalidAppCredentials.ts diff --git a/packages/features/ee/workflows/components/WorkflowListPage.tsx b/packages/features/ee/workflows/components/WorkflowListPage.tsx index b430bda0b7..bef97e6180 100644 --- a/packages/features/ee/workflows/components/WorkflowListPage.tsx +++ b/packages/features/ee/workflows/components/WorkflowListPage.tsx @@ -3,10 +3,8 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useState } from "react"; -import type { WorkflowPermissions } from "@calcom/features/workflows/repositories/WorkflowPermissionsRepository"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import type { Membership, Workflow } from "@calcom/prisma/client"; import { trpc } from "@calcom/trpc/react"; import classNames from "@calcom/ui/classNames"; import { ArrowButton } from "@calcom/ui/components/arrow-button"; @@ -25,38 +23,12 @@ import { Icon } from "@calcom/ui/components/icon"; import { Tooltip } from "@calcom/ui/components/tooltip"; import { getActionIcon } from "../lib/getActionIcon"; -import type { WorkflowStep } from "../lib/types"; +import { type WorkflowListType } from "../lib/types"; import { DeleteDialog } from "./DeleteDialog"; -export type WorkflowType = Workflow & { - team: { - id: number; - name: string; - members: Membership[]; - slug: string | null; - logo?: string | null; - } | null; - steps: WorkflowStep[]; - activeOnTeams?: { - team: { - id: number; - name?: string | null; - }; - }[]; - activeOn?: { - eventType: { - id: number; - title: string; - parentId: number | null; - _count: { - children: number; - }; - }; - }[]; - readOnly?: boolean; // Keep for backward compatibility - permissions?: WorkflowPermissions; - isOrg?: boolean; -}; +/** @deprecated Use WorkflowListType from ../lib/types instead */ +export type WorkflowType = WorkflowListType; + interface Props { workflows: WorkflowType[] | undefined; } diff --git a/packages/features/ee/workflows/lib/types.ts b/packages/features/ee/workflows/lib/types.ts index 7e52945151..eac7297072 100644 --- a/packages/features/ee/workflows/lib/types.ts +++ b/packages/features/ee/workflows/lib/types.ts @@ -1,6 +1,7 @@ import type { FORM_SUBMITTED_WEBHOOK_RESPONSES } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils"; +import type { WorkflowPermissions } from "@calcom/features/workflows/repositories/WorkflowPermissionsRepository"; import type { TimeFormat } from "@calcom/lib/timeFormat"; -import type { Prisma } from "@calcom/prisma/client"; +import type { Membership, Prisma, Workflow as PrismaWorkflow } from "@calcom/prisma/client"; import type { TimeUnit, WorkflowTemplates, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import { WorkflowActions } from "@calcom/prisma/enums"; import type { CalEventResponses, RecurringEvent } from "@calcom/types/Calendar"; @@ -93,3 +94,33 @@ export type ScheduleEmailReminderAction = Extract< WorkflowActions, "EMAIL_HOST" | "EMAIL_ATTENDEE" | "EMAIL_ADDRESS" >; + +export type WorkflowListType = PrismaWorkflow & { + team: { + id: number; + name: string; + members: Membership[]; + slug: string | null; + logo?: string | null; + } | null; + steps: WorkflowStep[]; + activeOnTeams?: { + team: { + id: number; + name?: string | null; + }; + }[]; + activeOn?: { + eventType: { + id: number; + title: string; + parentId: number | null; + _count: { + children: number; + }; + }; + }[]; + readOnly?: boolean; + permissions?: WorkflowPermissions; + isOrg?: boolean; +}; diff --git a/packages/features/users/components/InvalidAppCredentialsBanner.tsx b/packages/features/users/components/InvalidAppCredentialsBanner.tsx index 8c5ed0f651..4ce2daaca6 100644 --- a/packages/features/users/components/InvalidAppCredentialsBanner.tsx +++ b/packages/features/users/components/InvalidAppCredentialsBanner.tsx @@ -4,6 +4,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { type RouterOutputs } from "@calcom/trpc"; import { TopBanner } from "@calcom/ui/components/top-banner"; +import { type InvalidAppCredentialBannerProps } from "../types/invalidAppCredentials"; + export type InvalidAppCredentialBannersProps = { data: RouterOutputs["viewer"]["me"]["getUserTopBanners"]["invalidAppCredentialBanners"]; }; @@ -22,10 +24,7 @@ export function InvalidAppCredentialBanners({ data }: InvalidAppCredentialBanner ); } -export type InvalidAppCredentialBannerProps = { - name: string; - slug: string; -}; +export type { InvalidAppCredentialBannerProps }; export function InvalidAppCredentialBanner({ name, slug }: InvalidAppCredentialBannerProps) { const { t } = useLocale(); diff --git a/packages/features/users/types/invalidAppCredentials.ts b/packages/features/users/types/invalidAppCredentials.ts new file mode 100644 index 0000000000..b7e4d20c66 --- /dev/null +++ b/packages/features/users/types/invalidAppCredentials.ts @@ -0,0 +1,4 @@ +export type InvalidAppCredentialBannerProps = { + name: string; + slug: string; +}; diff --git a/packages/trpc/server/routers/viewer/me/checkForInvalidAppCredentials.ts b/packages/trpc/server/routers/viewer/me/checkForInvalidAppCredentials.ts index f723b6e735..11d805f8c0 100644 --- a/packages/trpc/server/routers/viewer/me/checkForInvalidAppCredentials.ts +++ b/packages/trpc/server/routers/viewer/me/checkForInvalidAppCredentials.ts @@ -1,5 +1,5 @@ import { getAppFromSlug } from "@calcom/app-store/utils"; -import { type InvalidAppCredentialBannerProps } from "@calcom/features/users/components/InvalidAppCredentialsBanner"; +import { type InvalidAppCredentialBannerProps } from "@calcom/features/users/types/invalidAppCredentials"; import { prisma } from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/enums"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; diff --git a/packages/trpc/server/routers/viewer/workflows/list.handler.ts b/packages/trpc/server/routers/viewer/workflows/list.handler.ts index 7de529df84..7377ba88c7 100644 --- a/packages/trpc/server/routers/viewer/workflows/list.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/list.handler.ts @@ -1,5 +1,5 @@ import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository"; -import type { WorkflowType } from "@calcom/features/ee/workflows/components/WorkflowListPage"; +import type { WorkflowListType } from "@calcom/features/ee/workflows/lib/types"; import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository"; // import dayjs from "@calcom/dayjs"; // import { getErrorFromUnknown } from "@calcom/lib/errors"; @@ -19,7 +19,7 @@ type ListOptions = { }; export const listHandler = async ({ ctx, input }: ListOptions) => { - const workflows: WorkflowType[] = []; + const workflows: WorkflowListType[] = []; const teamRepository = new TeamRepository(ctx.prisma); const org = await teamRepository.findOrganization({ @@ -42,7 +42,7 @@ export const listHandler = async ({ ctx, input }: ListOptions) => { } if (input && input.teamId) { - const teamWorkflows: WorkflowType[] = await WorkflowRepository.findTeamWorkflows({ + const teamWorkflows: WorkflowListType[] = await WorkflowRepository.findTeamWorkflows({ teamId: input.teamId, userId: ctx.user.id, excludeFormTriggers: input.includeOnlyEventTypeWorkflows, @@ -66,7 +66,7 @@ export const listHandler = async ({ ctx, input }: ListOptions) => { } if (input && input.userId) { - const userWorkflows: WorkflowType[] = await WorkflowRepository.findUserWorkflows({ + const userWorkflows: WorkflowListType[] = await WorkflowRepository.findUserWorkflows({ userId: ctx.user.id, excludeFormTriggers: input.includeOnlyEventTypeWorkflows, }); @@ -87,7 +87,7 @@ export const listHandler = async ({ ctx, input }: ListOptions) => { excludeFormTriggers: input.includeOnlyEventTypeWorkflows, }); - const workflowsWithReadOnly: WorkflowType[] = allWorkflows.map((workflow) => { + const workflowsWithReadOnly: WorkflowListType[] = allWorkflows.map((workflow) => { const readOnly = !!workflow.team?.members?.find( (member) => member.userId === ctx.user.id && member.role === MembershipRole.MEMBER );