diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000000..027cfc2798 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,43 @@ +tasks: + - init: | + yarn && + cp .env.example .env && + next_auth_secret=$(openssl rand -base64 32) && + calendso_encryption_key=$(openssl rand -base64 24) && + sed -i -e "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$next_auth_secret|" \ + -e "s|^CALENDSO_ENCRYPTION_KEY=.*|CALENDSO_ENCRYPTION_KEY=$calendso_encryption_key|" .env + command: yarn dx + +ports: + - port: 3000 + visibility: public + onOpen: open-preview + - port: 5420 + visibility: private + onOpen: ignore + - port: 1025 + visibility: private + onOpen: ignore + - port: 8025 + visibility: private + onOpen: ignore + +github: + prebuilds: + master: true + pullRequests: true + pullRequestsFromForks: true + addCheck: true + addComment: true + addBadge: true + +vscode: + extensions: + - DavidAnson.vscode-markdownlint + - yzhang.markdown-all-in-one + - esbenp.prettier-vscode + - dbaeumer.vscode-eslint + - bradlc.vscode-tailwindcss + - ban.spellright + - stripe.vscode-stripe + - Prisma.prisma \ No newline at end of file diff --git a/README.md b/README.md index f20957d3dd..d7fc9c6244 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,15 @@ yarn dx ```sh echo 'NEXT_PUBLIC_DEBUG=1' >> .env ``` +#### Gitpod Setup + +1. Click the button below to open this project in Gitpod. + +2. This will open a fully configured workspace in your browser with all the necessary dependencies already installed. + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/calcom/cal.com) + + #### Manual setup diff --git a/apps/web/components/eventtype/EventSetupTab.tsx b/apps/web/components/eventtype/EventSetupTab.tsx index 222a8312d5..82ba7366da 100644 --- a/apps/web/components/eventtype/EventSetupTab.tsx +++ b/apps/web/components/eventtype/EventSetupTab.tsx @@ -390,7 +390,12 @@ export const EventSetupTab = ( addOnLeading={ <> {CAL_URL?.replace(/^(https?:|)\/\//, "")}/ - {team ? "team/" + team.slug : eventType.users[0].username}/ + {!isManagedEventType + ? team + ? "team/" + team.slug + : eventType.users[0].username + : t("username_placeholder")} + / } {...formMethods.register("slug", { diff --git a/apps/web/components/eventtype/EventTypeSingleLayout.tsx b/apps/web/components/eventtype/EventTypeSingleLayout.tsx index 66b0ff7667..72a1778f4f 100644 --- a/apps/web/components/eventtype/EventTypeSingleLayout.tsx +++ b/apps/web/components/eventtype/EventTypeSingleLayout.tsx @@ -170,7 +170,7 @@ function EventTypeSingleLayout({ // Define tab navigation here const EventTypeTabs = useMemo(() => { - let navigation = getNavigation({ + const navigation = getNavigation({ t, eventType, enabledAppsNumber, @@ -210,7 +210,7 @@ function EventTypeSingleLayout({ } if (isManagedEventType || isChildrenManagedEventType) { // Removing apps and workflows for manageg event types by admins v1 - navigation = navigation.slice(0, -2); + navigation.splice(-2, 1); } else { navigation.push({ name: "webhooks", diff --git a/apps/web/components/team/screens/Team.tsx b/apps/web/components/team/screens/Team.tsx index 888593f559..5cbf3fe8ef 100644 --- a/apps/web/components/team/screens/Team.tsx +++ b/apps/web/components/team/screens/Team.tsx @@ -31,7 +31,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n {!isBioEmpty ? ( <>
diff --git a/apps/web/lib/app-providers.tsx b/apps/web/lib/app-providers.tsx index f965b6a636..b8c0fa29dd 100644 --- a/apps/web/lib/app-providers.tsx +++ b/apps/web/lib/app-providers.tsx @@ -26,7 +26,10 @@ const I18nextAdapter = appWithTranslation & { children ); // Workaround for https://github.com/vercel/next.js/issues/8592 -export type AppProps = Omit>, "Component"> & { +export type AppProps = Omit< + NextAppProps>, + "Component" +> & { Component: NextAppProps["Component"] & { requiresLicense?: boolean; isThemeSupported?: boolean; @@ -72,58 +75,21 @@ const enum ThemeSupport { Booking = "userConfigured", } -const CalcomThemeProvider = ( - props: PropsWithChildren< - WithNonceProps & { - isBookingPage?: boolean | ((arg: { router: NextRouter }) => boolean); - isThemeSupported?: boolean; - } - > -) => { - // We now support the inverse of how we handled it in the past. Setting this to false will disable theme. - // undefined or true means we use system theme +type CalcomThemeProps = PropsWithChildren< + Pick & + Pick +>; +const CalcomThemeProvider = (props: CalcomThemeProps) => { const router = useRouter(); - const isBookingPage = (() => { - if (typeof props.isBookingPage === "function") { - return props.isBookingPage({ router: router }); - } - return props.isBookingPage; - })(); - - const themeSupport = isBookingPage - ? ThemeSupport.Booking - : // if isThemeSupported is explicitly false, we don't use theme there - props.isThemeSupported === false - ? ThemeSupport.None - : ThemeSupport.App; - - const forcedTheme = themeSupport === ThemeSupport.None ? "light" : undefined; // Use namespace of embed to ensure same namespaced embed are displayed with same theme. This allows different embeds on the same website to be themed differently // One such example is our Embeds Demo and Testing page at http://localhost:3100 // Having `getEmbedNamespace` defined on window before react initializes the app, ensures that embedNamespace is available on the first mount and can be used as part of storageKey const embedNamespace = typeof window !== "undefined" ? window.getEmbedNamespace() : null; const isEmbedMode = typeof embedNamespace === "string"; - const storageKey = isEmbedMode - ? `embed-theme-${embedNamespace}` - : themeSupport === ThemeSupport.App - ? "app-theme" - : themeSupport === ThemeSupport.Booking - ? "booking-theme" - : undefined; - return ( - + {/* Embed Mode can be detected reliably only on client side here as there can be static generated pages as well which can't determine if it's embed mode at backend */} {/* color-scheme makes background:transparent not work in iframe which is required by embed. */} {typeof window !== "undefined" && !isEmbedMode && ( @@ -140,6 +106,100 @@ const CalcomThemeProvider = ( ); }; +/** + * The most important job for this fn is to generate correct storageKey for theme persistenc. + * `storageKey` is important because that key is listened for changes(using [`storage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event) event) and any pages opened will change it's theme based on that(as part of next-themes implementation). + * Choosing the right storageKey avoids theme flickering caused by another page using different theme + * So, we handle all the cases here namely, + * - Both Booking Pages, /free/30min and /pro/30min but configured with different themes but being operated together. + * - Embeds using different namespace. They can be completely themed different on the same page. + * - Embeds using the same namespace but showing different cal.com links with different themes + * - Embeds using the same namespace and showing same cal.com links with different themes(Different theme is possible for same cal.com link in case of embed because of theme config available in embed) + * - App has different theme then Booking Pages. + * + * All the above cases have one thing in common, which is the origin and thus localStorage is shared and thus `storageKey` is critical to avoid theme flickering. + * + * Some things to note: + * - There is a side effect of so many factors in `storageKey` that many localStorage keys will be created if a user goes through all these scenarios(e.g like booking a lot of different users) + * - Some might recommend disabling localStorage persistence but that doesn't give good UX as then we would default to light theme always for a few seconds before switching to dark theme(if that's the user's preference). + * - We can't disable [`storage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event) event handling as well because changing theme in one tab won't change the theme without refresh in other tabs. That's again a bad UX + * - Theme flickering becomes infinitely ongoing in case of embeds because of the browser's delay in processing `storage` event within iframes. Consider two embeds simulatenously opened with pages A and B. Note the timeline and keep in mind that it happened + * because 'setItem(A)' and 'Receives storageEvent(A)' allowed executing setItem(B) in b/w because of the delay. + * - t1 -> setItem(A) & Fires storageEvent(A) - On Page A) - Current State(A) + * - t2 -> setItem(B) & Fires storageEvent(B) - On Page B) - Current State(B) + * - t3 -> Receives storageEvent(A) & thus setItem(A) & thus fires storageEvent(A) (On Page B) - Current State(A) + * - t4 -> Receives storageEvent(B) & thus setItem(B) & thus fires storageEvent(B) (On Page A) - Current State(B) + * - ... and so on ... + */ +function getThemeProviderProps({ + props, + isEmbedMode, + embedNamespace, + router, +}: { + props: Omit; + isEmbedMode: boolean; + embedNamespace: string | null; + router: NextRouter; +}) { + const isBookingPage = (() => { + if (typeof props.isBookingPage === "function") { + return props.isBookingPage({ router: router }); + } + return props.isBookingPage; + })(); + + const themeSupport = isBookingPage + ? ThemeSupport.Booking + : // if isThemeSupported is explicitly false, we don't use theme there + props.isThemeSupported === false + ? ThemeSupport.None + : ThemeSupport.App; + + const isBookingPageThemSupportRequired = themeSupport === ThemeSupport.Booking; + const themeBasis = props.themeBasis; + + if ((isBookingPageThemSupportRequired || isEmbedMode) && !themeBasis) { + console.warn( + "`themeBasis` is required for booking page theme support. Not providing it will cause theme flicker." + ); + } + + const appearanceIdSuffix = themeBasis ? ":" + themeBasis : ""; + const forcedTheme = themeSupport === ThemeSupport.None ? "light" : undefined; + let embedExplicitlySetThemeSuffix = ""; + + if (typeof window !== "undefined") { + const embedTheme = window.getEmbedTheme(); + if (embedTheme) { + embedExplicitlySetThemeSuffix = ":" + embedTheme; + } + } + + const storageKey = isEmbedMode + ? // Same Namespace, Same Organizer but different themes would still work seamless and not cause theme flicker + // Even though it's recommended to use different namespaces when you want to theme differently on the same page but if the embeds are on different pages, the problem can still arise + `embed-theme-${embedNamespace}${appearanceIdSuffix}${embedExplicitlySetThemeSuffix}` + : themeSupport === ThemeSupport.App + ? "app-theme" + : isBookingPageThemSupportRequired + ? `booking-theme${appearanceIdSuffix}` + : undefined; + + return { + storageKey, + forcedTheme, + themeSupport, + nonce: props.nonce, + enableColorScheme: false, + enableSystem: themeSupport !== ThemeSupport.None, + // next-themes doesn't listen to changes on storageKey. So we need to force a re-render when storageKey changes + // This is how login to dashboard soft navigation changes theme from light to dark + key: storageKey, + attribute: "class", + }; +} + function FeatureFlagsProvider({ children }: { children: React.ReactNode }) { const flags = useFlags(); return {children}; @@ -157,6 +217,7 @@ const AppProviders = (props: AppPropsWithChildren) => { {/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */} diff --git a/apps/web/package.json b/apps/web/package.json index b7d741c5b5..da2db5a857 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@calcom/web", - "version": "2.8.13", + "version": "2.9.0", "private": true, "scripts": { "analyze": "ANALYZE=true next build", @@ -115,12 +115,11 @@ "react-select": "^5.7.0", "react-timezone-select": "^1.4.0", "react-use-intercom": "1.5.1", - "remark": "^14.0.2", + "remove-markdown": "^0.5.0", "rrule": "^2.7.1", "sanitize-html": "^2.10.0", "schema-dts": "^1.1.0", "short-uuid": "^4.2.0", - "strip-markdown": "^5.0.0", "stripe": "^9.16.0", "superjson": "1.9.1", "tailwindcss-radix": "^2.6.0", @@ -153,6 +152,7 @@ "@types/qrcode": "^1.4.3", "@types/react": "18.0.26", "@types/react-phone-number-input": "^3.0.14", + "@types/remove-markdown": "^0.3.1", "@types/sanitize-html": "^2.9.0", "@types/stripe": "^8.0.417", "@types/uuid": "8.3.1", diff --git a/apps/web/pages/[user].tsx b/apps/web/pages/[user].tsx index 2ec25163fd..caa96f6c62 100644 --- a/apps/web/pages/[user].tsx +++ b/apps/web/pages/[user].tsx @@ -23,6 +23,7 @@ import defaultEvents, { import { useLocale } from "@calcom/lib/hooks/useLocale"; import useTheme from "@calcom/lib/hooks/useTheme"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; +import { stripMarkdown } from "@calcom/lib/stripMarkdown"; import prisma from "@calcom/prisma"; import { baseEventTypeSelect } from "@calcom/prisma/selects"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; @@ -37,7 +38,16 @@ import PageWrapper from "@components/PageWrapper"; import { ssrInit } from "@server/lib/ssr"; export default function User(props: inferSSRProps & EmbedProps) { - const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props; + const { + users, + profile, + eventTypes, + isDynamicGroup, + dynamicNames, + dynamicUsernames, + isSingleUser, + markdownStrippedBio, + } = props; const [user] = users; //To be used when we only have a single user, not dynamic group useTheme(user.theme); const { t } = useLocale(); @@ -107,11 +117,9 @@ export default function User(props: inferSSRProps & E <> ({ username, name: dynamicNames[index] })) @@ -136,7 +144,7 @@ export default function User(props: inferSSRProps & E {!isBioEmpty && ( <>
@@ -342,11 +350,14 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const safeBio = markdownToSafeHTML(user.bio) || ""; + const markdownStrippedBio = stripMarkdown(user?.bio || ""); return { props: { users, safeBio, profile, + // Dynamic group has no theme preference right now. It uses system theme. + themeBasis: isDynamicGroup ? null : user.username, user: { emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), }, @@ -361,6 +372,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => dynamicNames, dynamicUsernames, isSingleUser, + markdownStrippedBio, }, }; }; diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx index 7468e89061..882ac3b440 100644 --- a/apps/web/pages/[user]/[type].tsx +++ b/apps/web/pages/[user]/[type].tsx @@ -177,6 +177,8 @@ async function getUserPageProps(context: GetStaticPropsContext) { slug: `${user.username}/${eventType.slug}`, image: `${WEBAPP_URL}/${user.username}/avatar.png`, }, + // Dynamic group has no theme preference right now. It uses system theme. + themeBasis: user.username, away: user?.away, isDynamic: false, trpcState: ssg.dehydrate(), @@ -307,6 +309,8 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) { props: { eventType: eventTypeObject, profile, + // Dynamic group has no theme preference right now. It uses system theme. + themeBasis: null, isDynamic: true, away: false, trpcState: ssg.dehydrate(), diff --git a/apps/web/pages/[user]/book.tsx b/apps/web/pages/[user]/book.tsx index 741f35882d..a7b1f8091a 100644 --- a/apps/web/pages/[user]/book.tsx +++ b/apps/web/pages/[user]/book.tsx @@ -293,6 +293,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { props: { away: user.away, profile, + // Dynamic group has no theme preference right now. It uses system theme. + themeBasis: isDynamicGroupBooking ? null : user.username, eventType: eventTypeObject, booking, currentSlotBooking: currentSlotBooking, diff --git a/apps/web/pages/_document.tsx b/apps/web/pages/_document.tsx index 600eeb009d..dcc53b486c 100644 --- a/apps/web/pages/_document.tsx +++ b/apps/web/pages/_document.tsx @@ -46,7 +46,7 @@ class MyDocument extends Document { - + diff --git a/apps/web/pages/api/social/og/image.tsx b/apps/web/pages/api/social/og/image.tsx index 39bb80e722..b3078fcb8b 100644 --- a/apps/web/pages/api/social/og/image.tsx +++ b/apps/web/pages/api/social/og/image.tsx @@ -4,7 +4,6 @@ import type { SatoriOptions } from "satori"; import { z } from "zod"; import { Meeting, App, Generic } from "@calcom/lib/OgImages"; -import { md } from "@calcom/lib/markdownIt"; const calFont = fetch(new URL("../../../../public/fonts/cal.ttf", import.meta.url)).then((res) => res.arrayBuffer() @@ -75,12 +74,10 @@ export default async function handler(req: NextApiRequest) { imageType, }); - const title_ = md.render(title).replace(/(<([^>]+)>)/gi, ""); - const img = new ImageResponse( ( ({ name, username: usernames[index] }))} /> diff --git a/apps/web/pages/auth/setup/index.tsx b/apps/web/pages/auth/setup/index.tsx index 80214751d2..f9c3074a36 100644 --- a/apps/web/pages/auth/setup/index.tsx +++ b/apps/web/pages/auth/setup/index.tsx @@ -157,10 +157,13 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => }; } - let deploymentKey = await getDeploymentKey(prisma); + const deploymentKey = await prisma.deployment.findUnique({ + where: { id: 1 }, + select: { licenseKey: true }, + }); // Check existant CALCOM_LICENSE_KEY env var and acccount for it - if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey) { + if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey?.licenseKey) { await prisma.deployment.upsert({ where: { id: 1 }, update: { @@ -172,10 +175,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => agreedLicenseAt: new Date(), }, }); - deploymentKey = await getDeploymentKey(prisma); } - const isFreeLicense = deploymentKey === ""; + const isFreeLicense = (await getDeploymentKey(prisma)) === ""; return { props: { diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx index 786b759e81..229babba28 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/pages/booking/[uid].tsx @@ -1092,6 +1092,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { return { props: { + themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username, hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding, profile, eventType, diff --git a/apps/web/pages/d/[link]/[slug].tsx b/apps/web/pages/d/[link]/[slug].tsx index 3017918cdb..c990de4531 100644 --- a/apps/web/pages/d/[link]/[slug].tsx +++ b/apps/web/pages/d/[link]/[slug].tsx @@ -160,6 +160,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => return { props: { away: user.away, + themeBasis: user.username, isDynamicGroup: false, profile, date, diff --git a/apps/web/pages/d/[link]/book.tsx b/apps/web/pages/d/[link]/book.tsx index eb01b2627d..d0cf51559d 100644 --- a/apps/web/pages/d/[link]/book.tsx +++ b/apps/web/pages/d/[link]/book.tsx @@ -122,6 +122,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { return { props: { profile, + themeBasis: user.username, eventType: eventTypeObject, booking: null, currentSlotBooking: null, diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index d5ef9e2ca5..d292055181 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -490,18 +490,20 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL {t("duplicate")} - - - {t("embed")} - - )} + {!isManagedEventType && ( + + + {t("embed")} + + + )} {/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */} {(group.metadata?.readOnly === false || group.metadata.readOnly === null) && !isChildrenManagedEventType && ( diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index f38b1b11c4..80f03d25b5 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect } from "react"; -import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; +import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription"; import { CAL_URL } from "@calcom/lib/constants"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; @@ -12,6 +12,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import useTheme from "@calcom/lib/hooks/useTheme"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { getTeamWithMembers } from "@calcom/lib/server/queries/teams"; +import { stripMarkdown } from "@calcom/lib/stripMarkdown"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import prisma from "@calcom/prisma"; import { Avatar, AvatarGroup, Button, EmptyScreen, HeadSeo } from "@calcom/ui"; @@ -26,7 +27,7 @@ import Team from "@components/team/screens/Team"; import { ssrInit } from "@server/lib/ssr"; export type TeamPageProps = inferSSRProps; -function TeamPage({ team, isUnpublished }: TeamPageProps) { +function TeamPage({ team, isUnpublished, markdownStrippedBio }: TeamPageProps) { useTheme(team.theme); const showMembers = useToggleQuery("members"); const { t } = useLocale(); @@ -67,6 +68,11 @@ function TeamPage({ team, isUnpublished }: TeamPageProps) {
{ + sdkActionManager?.fire("eventTypeSelected", { + eventType: type, + }); + }} data-testid="event-type-link" className="flex justify-between">
@@ -100,7 +106,7 @@ function TeamPage({ team, isUnpublished }: TeamPageProps) { title={teamName} description={teamName} meeting={{ - title: team?.bio || "", + title: markdownStrippedBio, profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) }, }} /> @@ -111,7 +117,7 @@ function TeamPage({ team, isUnpublished }: TeamPageProps) { {!isBioEmpty && ( <>
@@ -196,10 +202,14 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => return { ...member, safeBio: markdownToSafeHTML(member.bio || "") }; }); + const markdownStrippedBio = stripMarkdown(team?.bio || ""); + return { props: { team: { ...team, safeBio, members }, + themeBasis: team.slug, trpcState: ssr.dehydrate(), + markdownStrippedBio, }, } as const; }; diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index 1eeb235b24..d06e7776b7 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -196,6 +196,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => brandColor: team.brandColor, darkBrandColor: team.darkBrandColor, }, + themeBasis: team.slug, date: dateParam, eventType: eventTypeObject, workingHours, diff --git a/apps/web/pages/team/[slug]/book.tsx b/apps/web/pages/team/[slug]/book.tsx index 6f85ead949..53ed06d0ca 100644 --- a/apps/web/pages/team/[slug]/book.tsx +++ b/apps/web/pages/team/[slug]/book.tsx @@ -153,6 +153,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { image: eventTypeObject.team?.logo || null, eventName: null, }, + themeBasis: eventTypeObject.team?.slug, eventType: eventTypeObject, recurringEventCount, booking, diff --git a/apps/web/public/static/locales/ar/common.json b/apps/web/public/static/locales/ar/common.json index 963961bd37..4c0a2d2605 100644 --- a/apps/web/public/static/locales/ar/common.json +++ b/apps/web/public/static/locales/ar/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "سيكون الأعضاء قادرين على رؤية التطبيقات النشطة، ولكن من دون القدرة على تعديل أي إعدادات للتطبيق", "locked_webhooks_description": "سيكون الأعضاء قادرين على رؤية قوالب الويب النشطة، ولكن من دون القدرة على تعديل أي إعدادات للقوالب", "locked_workflows_description": "سيكون الأعضاء قادرين على رؤية مسارات العمل النشطة، ولكن من دون القدرة على تعديل أي إعدادات لمسار العمل", - "locked_by_admin": "مقفل من قبل المشرف", "app_not_connected": "أنت غير متصل بحساب {{appName}}.", "connect_now": "اتصل الآن", "managed_event_dialog_confirm_button_one": "استبدال وإشعار العضو {{count}}", diff --git a/apps/web/public/static/locales/cs/common.json b/apps/web/public/static/locales/cs/common.json index 1ffe4544f5..d61b5515cf 100644 --- a/apps/web/public/static/locales/cs/common.json +++ b/apps/web/public/static/locales/cs/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Členové budou vidět aktivní aplikace, ale nastavení aplikací upravovat moci nebudou", "locked_webhooks_description": "Členové budou vidět aktivní webhooky, ale nastavení webhooků upravovat moci nebudou", "locked_workflows_description": "Členové budou vidět aktivní pracovní postupy, ale nastavení pracovních postupů upravovat moci nebudou", - "locked_by_admin": "Uzamčeno správcem", "app_not_connected": "Nemáte připojený účet {{appName}}.", "connect_now": "Připojte se", "managed_event_dialog_confirm_button_one": "Nahradit a upozornit {{count}} člena", diff --git a/apps/web/public/static/locales/de/common.json b/apps/web/public/static/locales/de/common.json index 53cfea5d8e..3e6525a7c2 100644 --- a/apps/web/public/static/locales/de/common.json +++ b/apps/web/public/static/locales/de/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Mitglieder können die aktiven Apps sehen, können aber keine App-Einstellungen anpassen", "locked_webhooks_description": "Mitglieder können die aktiven Webhooks sehen, können aber keine Webhook-Einstellungen anpassen", "locked_workflows_description": "Mitglieder können die aktiven Workflows sehen, können aber keine Workflow-Einstellungen anpassen", - "locked_by_admin": "Von Admin gesperrt", "app_not_connected": "Sie haben kein {{appName}}-Konto verbunden.", "connect_now": "Jetzt verbinden", "managed_event_dialog_confirm_button_one": "{{count}} Mitglied ersetzen & benachrichtigen", diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index e3bd64ba40..529d56c259 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1195,6 +1195,7 @@ "create_workflow": "Create a workflow", "do_this": "Do this", "turn_off": "Turn off", + "turn_on": "Turn on", "settings_updated_successfully": "Settings updated successfully", "error_updating_settings": "Error updating settings", "personal_cal_url": "My personal {{appName}} URL", @@ -1740,7 +1741,7 @@ "locked_apps_description": "Members will be able to see the active apps but will not be able to edit any app settings", "locked_webhooks_description": "Members will be able to see the active webhooks but will not be able to edit any webhook settings", "locked_workflows_description": "Members will be able to see the active workflows but will not be able to edit any workflow settings", - "locked_by_admin": "Locked by admin", + "locked_by_admin": "Locked by team admin", "app_not_connected": "You have not connected a {{appName}} account.", "connect_now": "Connect now", "managed_event_dialog_confirm_button_one": "Replace & notify {{count}} member", diff --git a/apps/web/public/static/locales/es/common.json b/apps/web/public/static/locales/es/common.json index e47ab44879..fd98748da5 100644 --- a/apps/web/public/static/locales/es/common.json +++ b/apps/web/public/static/locales/es/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Los miembros podrán ver las aplicaciones activas pero no podrán editar ninguna configuración de la aplicación", "locked_webhooks_description": "Los miembros podrán ver los webhooks activos, pero no podrán editar ninguna configuración de webhooks", "locked_workflows_description": "Los miembros podrán ver los flujos de trabajo activos, pero no podrán editar ninguna configuración de flujo de trabajo", - "locked_by_admin": "Bloqueado por el administrador", "app_not_connected": "No ha conectado una cuenta de {{appName}}.", "connect_now": "Conectar ahora", "managed_event_dialog_confirm_button_one": "Reemplazar y notificar a {{count}} miembro", diff --git a/apps/web/public/static/locales/fr/common.json b/apps/web/public/static/locales/fr/common.json index b3bd2ba62d..e058ebaf12 100644 --- a/apps/web/public/static/locales/fr/common.json +++ b/apps/web/public/static/locales/fr/common.json @@ -1195,6 +1195,7 @@ "create_workflow": "Créer un workflow", "do_this": "Effectuer ceci", "turn_off": "Désactiver", + "turn_on": "Activer", "settings_updated_successfully": "Paramètres mis à jour avec succès", "error_updating_settings": "Erreur lors de la mise à jour des paramètres", "personal_cal_url": "Mon lien {{appName}} personnel", @@ -1740,7 +1741,7 @@ "locked_apps_description": "Les membres pourront voir les applications actives, mais ne pourront pas modifier leurs paramètres.", "locked_webhooks_description": "Les membres pourront voir les webhooks actifs, mais ne pourront pas modifier leurs paramètres.", "locked_workflows_description": "Les membres pourront voir les workflows actifs, mais ne pourront pas modifier leurs paramètres.", - "locked_by_admin": "Verrouillé par l'administrateur", + "locked_by_admin": "Verrouillé par l'administrateur de l'équipe", "app_not_connected": "Vous n'avez pas connecté de compte {{appName}}.", "connect_now": "Connecter maintenant", "managed_event_dialog_confirm_button_one": "Remplacer et notifier {{count}} membre", diff --git a/apps/web/public/static/locales/it/common.json b/apps/web/public/static/locales/it/common.json index 2320b55c54..2afc031a68 100644 --- a/apps/web/public/static/locales/it/common.json +++ b/apps/web/public/static/locales/it/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "I membri saranno in grado di vedere le app attive, ma non saranno in grado di modificare le impostazioni delle app", "locked_webhooks_description": "I membri saranno in grado di vedere i webhook attivi, ma non saranno in grado di modificare le impostazioni dei webhook", "locked_workflows_description": "I membri saranno in grado di vedere i flussi di lavoro attivi, ma non saranno in grado di modificare le impostazioni dei flussi di lavoro", - "locked_by_admin": "Bloccato dall'amministratore", "app_not_connected": "Non è stato connesso un account di {{appName}}.", "connect_now": "Connetti ora", "managed_event_dialog_confirm_button_one": "Sostituisci e invia notifica a {{count}} membro", diff --git a/apps/web/public/static/locales/ja/common.json b/apps/web/public/static/locales/ja/common.json index 634a63d7eb..c33a7ef8e7 100644 --- a/apps/web/public/static/locales/ja/common.json +++ b/apps/web/public/static/locales/ja/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "メンバーは有効なアプリを確認できますが、アプリの設定を編集することはできません", "locked_webhooks_description": "メンバーは有効なウェブフックを確認できますが、ウェブフックの設定を編集することはできません", "locked_workflows_description": "メンバーは有効なワークフローを確認できますが、ワークフローの設定を編集することはできません", - "locked_by_admin": "管理者によりロックされています", "app_not_connected": "{{appName}} のアカウントに接続していません。", "connect_now": "今すぐ接続", "managed_event_dialog_confirm_button_one": "{{count}} 人のメンバーを置き換えて通知する", diff --git a/apps/web/public/static/locales/ko/common.json b/apps/web/public/static/locales/ko/common.json index e3acc7adb1..61ec386b46 100644 --- a/apps/web/public/static/locales/ko/common.json +++ b/apps/web/public/static/locales/ko/common.json @@ -1737,7 +1737,6 @@ "locked_apps_description": "회원은 활성 앱을 볼 수 있지만 앱 설정을 편집할 수는 없습니다", "locked_webhooks_description": "회원은 활성 웹훅을 볼 수 있지만 웹훅 설정을 편집할 수는 없습니다", "locked_workflows_description": "회원은 활성 워크플로를 볼 수 있지만 워크플로 설정을 편집할 수는 없습니다", - "locked_by_admin": "관리자에 의해 잠금 됨", "app_not_connected": "{{appName}} 계정을 연결하지 않으셨습니다.", "connect_now": "지금 연결", "managed_event_dialog_confirm_button_one": "회원 {{count}}명 바꾸기 및 알림", diff --git a/apps/web/public/static/locales/nl/common.json b/apps/web/public/static/locales/nl/common.json index f94c2bd5a2..8d73f1ad84 100644 --- a/apps/web/public/static/locales/nl/common.json +++ b/apps/web/public/static/locales/nl/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Leden kunnen de actieve apps zien, maar kunnen geen appinstellingen bewerken", "locked_webhooks_description": "Leden kunnen de actieve webhooks zien, maar kunnen geen webhookinstellingen bewerken", "locked_workflows_description": "Leden kunnen de actieve werkstromen zien, maar kunnen geen werkstroominstellingen bewerken", - "locked_by_admin": "Vergrendeld door beheerder", "app_not_connected": "U heeft geen {{appName}}-account gekoppeld.", "connect_now": "Nu koppelen", "managed_event_dialog_confirm_button_one": "{{count}} lid vervangen en informeren", diff --git a/apps/web/public/static/locales/pl/common.json b/apps/web/public/static/locales/pl/common.json index 4a6ebb6f1c..fc86885e8e 100644 --- a/apps/web/public/static/locales/pl/common.json +++ b/apps/web/public/static/locales/pl/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Członkowie będą mogli zobaczyć aktywne aplikacje, ale nie będą mogli zmieniać ich ustawień.", "locked_webhooks_description": "Członkowie będą mogli zobaczyć aktywne webhooki, ale nie będą mogli zmieniać ich ustawień.", "locked_workflows_description": "Członkowie będą mogli zobaczyć aktywne przepływy pracy, ale nie będą mogli zmieniać ich ustawień.", - "locked_by_admin": "Zablokowane przez administratora", "app_not_connected": "Nie połączono konta aplikacji {{appName}}.", "connect_now": "Połącz teraz", "managed_event_dialog_confirm_button_one": "Zastąp i poinformuj {{count}} członka", diff --git a/apps/web/public/static/locales/pt-BR/common.json b/apps/web/public/static/locales/pt-BR/common.json index c082e22892..7befbd7f5c 100644 --- a/apps/web/public/static/locales/pt-BR/common.json +++ b/apps/web/public/static/locales/pt-BR/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Os membros poderão ver os aplicativos ativos, mas não poderão editar as configurações", "locked_webhooks_description": "Os membros poderão ver os webhooks ativos, mas não poderão editar as configurações", "locked_workflows_description": "Os membros poderão ver os fluxos de trabalho ativos, mas não poderão editar as configurações", - "locked_by_admin": "Bloqueado pelo administrador", "app_not_connected": "Você não conectou uma conta do {{appName}}.", "connect_now": "Conectar agora", "managed_event_dialog_confirm_button_one": "Substituir e notificar {{count}} membro", diff --git a/apps/web/public/static/locales/pt/common.json b/apps/web/public/static/locales/pt/common.json index a09ad159db..b6ae5d2220 100644 --- a/apps/web/public/static/locales/pt/common.json +++ b/apps/web/public/static/locales/pt/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Os membros poderão ver as aplicações ativas, mas não poderão editar quaisquer configurações das aplicações", "locked_webhooks_description": "Os membros poderão ver os webhooks ativos, mas não poderão editar quaisquer configurações dos webhooks", "locked_workflows_description": "Os membros poderão ver os fluxos de trabalho ativos, mas não poderão editar quaisquer configurações dos fluxos de trabalho", - "locked_by_admin": "Bloqueado pelo administrador", "app_not_connected": "Não associou uma conta {{appName}}.", "connect_now": "Associar agora", "managed_event_dialog_confirm_button_one": "Substituir e notificar {{count}} membro", diff --git a/apps/web/public/static/locales/ro/common.json b/apps/web/public/static/locales/ro/common.json index bd35210afc..00c718ee4f 100644 --- a/apps/web/public/static/locales/ro/common.json +++ b/apps/web/public/static/locales/ro/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Membrii vor putea vedea aplicațiile active, însă nu le vor putea modifica deloc setările", "locked_webhooks_description": "Membrii vor putea vedea webhook-urile active, însă nu le vor putea modifica deloc setările", "locked_workflows_description": "Membrii vor putea vedea fluxurile de lucru active, însă nu le vor putea modifica deloc setările", - "locked_by_admin": "Blocat de administrator", "app_not_connected": "Nu ați conectat un cont {{appName}}.", "connect_now": "Conectați-l acum", "managed_event_dialog_confirm_button_one": "Înlocuiți și anunțați {{count}} membru", diff --git a/apps/web/public/static/locales/ru/common.json b/apps/web/public/static/locales/ru/common.json index 68376a4264..8affc056cd 100644 --- a/apps/web/public/static/locales/ru/common.json +++ b/apps/web/public/static/locales/ru/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Участники смогут видеть активные приложения, но не смогут редактировать их настройки", "locked_webhooks_description": "Участники смогут видеть активные вебхуки, но не смогут редактировать их настройки", "locked_workflows_description": "Участники смогут видеть активные рабочие процессы, но не смогут редактировать их настройки", - "locked_by_admin": "Заблокировано администратором", "app_not_connected": "Вы не подключили аккаунт {{appName}}.", "connect_now": "Подключить сейчас", "managed_event_dialog_confirm_button_one": "Заменить и уведомить {{count}} участника", diff --git a/apps/web/public/static/locales/sr/common.json b/apps/web/public/static/locales/sr/common.json index 716b625429..60596c4c11 100644 --- a/apps/web/public/static/locales/sr/common.json +++ b/apps/web/public/static/locales/sr/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Članovi će moći da vide aktivne aplikacije, ali neće moći da uređuju bilo kakva podešavanja aplikacije", "locked_webhooks_description": "Članovi će moći da vide aktivne webhook-ove, ali neće moći da uređuju bilo kakva podešavanja webhook-ova", "locked_workflows_description": "Članovi će moći da vide aktivne radne tokove, ali neće moći da uređuju bilo kakva podešavanja radnih tokova", - "locked_by_admin": "Zaključao administrator", "app_not_connected": "Niste povezali {{appName}} nalog.", "connect_now": "Povežite odmah", "managed_event_dialog_confirm_button_one": "Zameni i obavesti {{count}} člana", diff --git a/apps/web/public/static/locales/sv/common.json b/apps/web/public/static/locales/sv/common.json index 516b8cd887..9fb50e2812 100644 --- a/apps/web/public/static/locales/sv/common.json +++ b/apps/web/public/static/locales/sv/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Medlemmarna kommer att kunna se de aktiva apparna, men de kommer inte att kunna ändra appinställningarna", "locked_webhooks_description": "Medlemmarna kommer att kunna se aktiva webhooks men inte kunna redigera inställningar för webhooks", "locked_workflows_description": "Medlemmarna kommer att kunna se de aktiva arbetsflödena, men de kommer inte att kunna ändra några arbetsflödesinställningar", - "locked_by_admin": "Låst av administratör", "app_not_connected": "Du har inte anslutit ett {{appName}}-konto.", "connect_now": "Anslut nu", "managed_event_dialog_confirm_button_one": "Ersätt och meddela {{count}} medlem", diff --git a/apps/web/public/static/locales/tr/common.json b/apps/web/public/static/locales/tr/common.json index 525a10c8fe..3a5d622e62 100644 --- a/apps/web/public/static/locales/tr/common.json +++ b/apps/web/public/static/locales/tr/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Üyeler aktif uygulamaları görebilir, ancak hiçbir uygulama ayarını düzenleyemez", "locked_webhooks_description": "Üyeler aktif web kancalarını görebilir, ancak hiçbir web kancası ayarını düzenleyemez", "locked_workflows_description": "Üyeler aktif iş akışlarını görebilir, ancak hiçbir iş akışı ayarını düzenleyemez", - "locked_by_admin": "Yönetici tarafından kilitlendi", "app_not_connected": "Bir {{appName}} hesabı bağlamadınız.", "connect_now": "Hemen bağlan", "managed_event_dialog_confirm_button_one": "{{count}} üyeyi değiştirin ve bilgilendirin", diff --git a/apps/web/public/static/locales/uk/common.json b/apps/web/public/static/locales/uk/common.json index dea6b56fb4..4521b177bc 100644 --- a/apps/web/public/static/locales/uk/common.json +++ b/apps/web/public/static/locales/uk/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Учасники зможуть бачити активні додатки, але не зможуть редагувати налаштування додатка", "locked_webhooks_description": "Учасники бачитимуть активні вебгуки, але не зможуть змінювати їх налаштування", "locked_workflows_description": "Учасники бачитимуть активні робочі процеси, але не зможуть змінювати їх налаштування", - "locked_by_admin": "Заблоковано адміністратором", "app_not_connected": "Ви не під’єднали обліковий запис {{appName}}.", "connect_now": "Під’єднати", "managed_event_dialog_confirm_button_one": "Замінити й повідомити {{count}} учасника", diff --git a/apps/web/public/static/locales/vi/common.json b/apps/web/public/static/locales/vi/common.json index 9490052fd0..c33b42d771 100644 --- a/apps/web/public/static/locales/vi/common.json +++ b/apps/web/public/static/locales/vi/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "Các thành viên có thể thấy được những ứng dụng đang hoạt động nhưng không thể sửa bất kỳ thiết lập nào của ứng dụng", "locked_webhooks_description": "Các thành viên có thể thấy được những webhook đang hoạt động nhưng không thể sửa bất kỳ thiết lập nào của webhook", "locked_workflows_description": "Các thành viên có thể thấy được những tiến độ công việc đang hoạt động nhưng không thể sửa bất kỳ thiết lập nào của tiến độ công việc", - "locked_by_admin": "Bị khoá bởi quản trị viên", "app_not_connected": "Bạn không có tài khoản {{appName}} đã được kết nối.", "connect_now": "Kết nối ngay", "managed_event_dialog_confirm_button_one": "Thay thế & thông báo cho {{count}} thành viên", diff --git a/apps/web/public/static/locales/zh-CN/common.json b/apps/web/public/static/locales/zh-CN/common.json index a4acd53bce..beb1c929aa 100644 --- a/apps/web/public/static/locales/zh-CN/common.json +++ b/apps/web/public/static/locales/zh-CN/common.json @@ -1191,6 +1191,7 @@ "create_workflow": "创建工作流程", "do_this": "执行此操作", "turn_off": "关闭", + "turn_on": "打开", "settings_updated_successfully": "设置已成功更新", "error_updating_settings": "更新设置时出错", "personal_cal_url": "我的个人 {{appName}} URL", @@ -1726,7 +1727,7 @@ "locked_apps_description": "成员将能够查看活动的应用,但不能编辑任何应用设置", "locked_webhooks_description": "成员将能够查看活动的 Webhook,但不能编辑任何 Webhook 设置", "locked_workflows_description": "成员将能够查看活动的工作流程,但不能编辑任何工作流程设置", - "locked_by_admin": "被管理员锁定", + "locked_by_admin": "被团队管理员锁定", "app_not_connected": "您尚未连接 {{appName}} 账户。", "connect_now": "立即连接", "managed_event_dialog_confirm_button_one": "替换并通知 {{count}} 个成员", diff --git a/apps/web/public/static/locales/zh-TW/common.json b/apps/web/public/static/locales/zh-TW/common.json index 30642a56bd..c1e4469c9d 100644 --- a/apps/web/public/static/locales/zh-TW/common.json +++ b/apps/web/public/static/locales/zh-TW/common.json @@ -1724,7 +1724,6 @@ "locked_apps_description": "成員將能查看啟用的應用程式,但無法編輯任何應用程式設定", "locked_webhooks_description": "成員將能查看啟用的 Webhook,但無法編輯任何 Webhook 設定", "locked_workflows_description": "成員將能查看啟用的工作流程,但無法編輯任何工作流程設定", - "locked_by_admin": "已由管理員鎖定", "app_not_connected": "您尚未連結 {{appName}} 帳號。", "connect_now": "立即連結", "managed_event_dialog_confirm_button_one": "取代並通知 {{count}} 位成員", diff --git a/apps/web/test/lib/handleChildrenEventTypes.test.ts b/apps/web/test/lib/handleChildrenEventTypes.test.ts index e88e89cc9c..d02d168100 100644 --- a/apps/web/test/lib/handleChildrenEventTypes.test.ts +++ b/apps/web/test/lib/handleChildrenEventTypes.test.ts @@ -3,7 +3,7 @@ import type { Prisma } from "@prisma/client"; import updateChildrenEventTypes from "@calcom/features/ee/managed-event-types/lib/handleChildrenEventTypes"; import { buildEventType } from "@calcom/lib/test/builder"; -import type { CompleteEventType } from "@calcom/prisma/zod"; +import type { CompleteEventType, CompleteWorkflowsOnEventTypes } from "@calcom/prisma/zod"; import { prismaMock } from "../../../../tests/config/singleton"; @@ -291,4 +291,85 @@ describe("handleChildrenEventTypes", () => { expect(result.deletedExistentEventTypes).toEqual([123]); }); }); + + describe("Workflows", () => { + it("Links workflows to new and existing assigned members", async () => { + const { schedulingType, id, teamId, locations, timeZone, parentId, userId, ...evType } = + mockFindFirstEventType({ + metadata: { managedEventConfig: {} }, + locations: [], + workflows: [ + { + workflowId: 11, + } as CompleteWorkflowsOnEventTypes, + ], + }); + prismaMock.$transaction.mockResolvedValue([{ id: 2 }]); + await updateChildrenEventTypes({ + eventTypeId: 1, + oldEventType: { children: [{ userId: 4 }], team: { name: "" } }, + children: [ + { hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: [] } }, + { hidden: false, owner: { id: 5, name: "", email: "", eventTypeSlugs: [] } }, + ], + updatedEventType: { schedulingType: "MANAGED", slug: "something" }, + currentUserId: 1, + hashedLink: undefined, + connectedLink: null, + prisma: prismaMock, + }); + expect(prismaMock.eventType.create).toHaveBeenCalledWith({ + data: { + ...evType, + bookingLimits: undefined, + durationLimits: undefined, + recurringEvent: undefined, + hashedLink: undefined, + locations: [], + parentId: 1, + userId: 5, + users: { + connect: [ + { + id: 5, + }, + ], + }, + workflows: { + create: [{ workflowId: 11 }], + }, + }, + }); + expect(prismaMock.eventType.update).toHaveBeenCalledWith({ + data: { + ...evType, + bookingLimits: undefined, + durationLimits: undefined, + recurringEvent: undefined, + hashedLink: undefined, + workflows: undefined, + scheduleId: undefined, + }, + where: { + userId_parentId: { + userId: 4, + parentId: 1, + }, + }, + }); + expect(prismaMock.workflowsOnEventTypes.upsert).toHaveBeenCalledWith({ + create: { + eventTypeId: 2, + workflowId: 11, + }, + update: {}, + where: { + workflowId_eventTypeId: { + eventTypeId: 2, + workflowId: 11, + }, + }, + }); + }); + }); }); diff --git a/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts b/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts index a7076b832f..e0a988f5e2 100644 --- a/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts +++ b/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts @@ -93,7 +93,7 @@ const DailyVideoApiAdapter = (): VideoApiAdapter => { const body = await translateEvent(event); const dailyEvent = await postToDailyAPI(endpoint, body).then(dailyReturnTypeSchema.parse); const meetingToken = await postToDailyAPI("/meeting-tokens", { - properties: { room_name: dailyEvent.name, is_owner: true }, + properties: { room_name: dailyEvent.name, exp: dailyEvent.config.exp, is_owner: true }, }).then(meetingTokenSchema.parse); return Promise.resolve({ diff --git a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx index 5661a2e203..2cd80e0bb9 100644 --- a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx @@ -192,6 +192,7 @@ export const getServerSideProps = async function getServerSideProps( include: { user: { select: { + username: true, theme: true, brandColor: true, darkBrandColor: true, @@ -209,6 +210,7 @@ export const getServerSideProps = async function getServerSideProps( return { props: { isEmbed, + themeBasis: form.user.username, profile: { theme: form.user.theme, brandColor: form.user.brandColor, diff --git a/packages/embeds/embed-core/index.html b/packages/embeds/embed-core/index.html index cd7e1396ff..67559d77a5 100644 --- a/packages/embeds/embed-core/index.html +++ b/packages/embeds/embed-core/index.html @@ -9,7 +9,7 @@ const randomInt = Math.floor(Math.random() * 16777216); // Convert the integer to a hex string with 6 digits and add leading zeros if necessary - const hexString = randomInt.toString(16).padStart(6, '0'); + const hexString = randomInt.toString(16).padStart(6, "0"); // Return the hex string with a '#' prefix return `#${hexString}`; @@ -161,7 +161,7 @@

[Dark Theme][Guests(janedoe@example.com and test@example.com)]

- + You would see last Booking page action in my place
@@ -187,7 +187,9 @@ - + @@ -234,6 +236,13 @@

Hide EventType Details Test

+
+

You would be able to test out conflicting themes for the same namespace here.

+
+
+ Note that one of the embeds would stay in loading state as they are using the same namespace and it is not supported to have more than 1 embeds using same namespace +
+
diff --git a/packages/embeds/embed-core/playground.ts b/packages/embeds/embed-core/playground.ts index 5b030f7ef9..4295396420 100644 --- a/packages/embeds/embed-core/playground.ts +++ b/packages/embeds/embed-core/playground.ts @@ -245,6 +245,28 @@ if (only === "all" || only === "hideEventTypeDetails") { ); } +if (only === "conflicting-theme") { + Cal("init", "conflictingTheme", { + debug: true, + origin: "http://localhost:3000", + }); + + Cal.ns.conflictingTheme("inline", { + elementOrSelector: "#cal-booking-place-conflicting-theme .dark", + calLink: "pro/30min", + config: { + theme: "dark", + }, + }); + Cal.ns.conflictingTheme("inline", { + elementOrSelector: "#cal-booking-place-conflicting-theme .light", + calLink: "pro/30min", + config: { + theme: "light", + }, + }); +} + Cal("init", "popupDarkTheme", { debug: true, origin: "http://localhost:3000", @@ -268,10 +290,12 @@ Cal("init", "popupAutoTheme", { debug: true, origin: "http://localhost:3000", }); + Cal("init", "popupTeamLinkLightTheme", { debug: true, origin: "http://localhost:3000", }); + Cal("init", "popupTeamLinkDarkTheme", { debug: true, origin: "http://localhost:3000", diff --git a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx index 82142f71d4..8c5f00a71d 100644 --- a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx +++ b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx @@ -70,11 +70,15 @@ export const BookEventForm = ({ onCancel }: BookEventFormProps) => { const timeslot = useBookerStore((state) => state.selectedTimeslot); const recurringEventCount = useBookerStore((state) => state.recurringEventCount); const username = useBookerStore((state) => state.username); + const formValues = useBookerStore((state) => state.formValues); + const setFormValues = useBookerStore((state) => state.setFormValues); const isRescheduling = !!rescheduleUid && !!rescheduleBooking; const event = useEvent(); const eventType = event.data; const defaultValues = useMemo(() => { + if (Object.keys(formValues).length) return formValues; + if (!eventType?.bookingFields) { return {}; } @@ -128,7 +132,7 @@ export const BookEventForm = ({ onCancel }: BookEventFormProps) => { email: defaultUserValues.email, }; return defaults; - }, [eventType?.bookingFields, isRescheduling, rescheduleBooking, rescheduleUid]); + }, [eventType?.bookingFields, formValues, isRescheduling, rescheduleBooking, rescheduleUid]); const bookingFormSchema = z .object({ @@ -188,7 +192,6 @@ export const BookEventForm = ({ onCancel }: BookEventFormProps) => { ); }, onError: () => { - errorRef && errorRef.current?.scrollIntoView({ behavior: "smooth" }); }, }); @@ -227,6 +230,8 @@ export const BookEventForm = ({ onCancel }: BookEventFormProps) => { ); const bookEvent = (values: BookingFormValues) => { + // Clears form values stored in store, so old values won't stick around. + setFormValues({}); bookingForm.clearErrors(); // It shouldn't be possible that this method is fired without having event data, @@ -281,7 +286,18 @@ export const BookEventForm = ({ onCancel }: BookEventFormProps) => { return (
-
+ { + // Form data is saved in store. This way when user navigates back to + // still change the timeslot, and comes back to the form, all their values + // still exist. This gets cleared when the form is submitted. + const values = bookingForm.getValues(); + setFormValues(values); + }} + form={bookingForm} + handleSubmit={bookEvent} + noValidate> -1)} fields={eventType.bookingFields} @@ -306,7 +322,7 @@ export const BookEventForm = ({ onCancel }: BookEventFormProps) => { />
)} -
+
{!!onCancel && ( - -
- -
+ {!workflow.readOnly && ( +
+ + + +
+ )} + +
+ {workflow.readOnly && props.isChildrenManagedEventType && ( + + )} { activateEventTypeMutation.mutate({ workflowId: workflow.id, eventTypeId: eventType.id }); }} @@ -163,9 +179,14 @@ type Props = { function EventWorkflowsTab(props: Props) { const { workflows, eventType } = props; const { t } = useLocale(); + const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager( + eventType, + t("locked_fields_admin_description"), + t("locked_fields_member_description") + ); const { data, isLoading } = trpc.viewer.workflows.list.useQuery({ teamId: eventType.team?.id, - userId: eventType.userId || undefined, + userId: !isChildrenManagedEventType ? eventType.userId || undefined : undefined, }); const router = useRouter(); const [sortedWorkflows, setSortedWorkflows] = useState>([]); @@ -173,7 +194,11 @@ function EventWorkflowsTab(props: Props) { useEffect(() => { if (data?.workflows) { const activeWorkflows = workflows.map((workflowOnEventType) => { - return workflowOnEventType; + const dataWf = data.workflows.find((wf) => wf.id === workflowOnEventType.id); + return { + ...workflowOnEventType, + readOnly: isChildrenManagedEventType && dataWf?.teamId ? true : dataWf?.readOnly ?? false, + } as WorkflowType; }); const disabledWorkflows = data.workflows.filter( (workflow) => @@ -204,12 +229,6 @@ function EventWorkflowsTab(props: Props) { }, }); - const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager( - eventType, - t("locked_fields_admin_description"), - t("locked_fields_member_description") - ); - return ( {!isLoading ? ( @@ -217,6 +236,7 @@ function EventWorkflowsTab(props: Props) { {isManagedEventType && ( @@ -226,7 +246,12 @@ function EventWorkflowsTab(props: Props) {
{sortedWorkflows.map((workflow) => { return ( - + ); })}
@@ -238,19 +263,13 @@ function EventWorkflowsTab(props: Props) { headline={t("workflows")} description={t("no_workflows_description")} buttonRaw={ - isChildrenManagedEventType && !isManagedEventType ? ( - - ) : ( - - ) + } />
diff --git a/packages/features/ee/workflows/components/TimeTimeUnitInput.tsx b/packages/features/ee/workflows/components/TimeTimeUnitInput.tsx index d5e8e35611..baa797e90a 100644 --- a/packages/features/ee/workflows/components/TimeTimeUnitInput.tsx +++ b/packages/features/ee/workflows/components/TimeTimeUnitInput.tsx @@ -17,6 +17,7 @@ import type { FormValues } from "../pages/workflow"; type Props = { form: UseFormReturn; + disabled: boolean; }; export const TimeTimeUnitInput = (props: Props) => { @@ -33,6 +34,7 @@ export const TimeTimeUnitInput = (props: Props) => { type="number" min="1" label="" + disabled={props.disabled} defaultValue={form.getValues("time") || 24} className="-mt-2 rounded-r-none text-sm focus:ring-0" {...form.register("time", { valueAsNumber: true })} diff --git a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx index 6a8bb886eb..85da5b44ce 100644 --- a/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx +++ b/packages/features/ee/workflows/components/WorkflowDetailsPage.tsx @@ -6,7 +6,7 @@ import { Controller } from "react-hook-form"; import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { WorkflowTemplates, SchedulingType } from "@calcom/prisma/enums"; +import { WorkflowTemplates } from "@calcom/prisma/enums"; import type { WorkflowActions } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui"; @@ -26,6 +26,7 @@ interface Props { setSelectedEventTypes: Dispatch>; teamId?: number; isMixedEventType: boolean; + readOnly: boolean; } export default function WorkflowDetailsPage(props: Props) { @@ -48,15 +49,10 @@ export default function WorkflowDetailsPage(props: Props) { if (teamId && teamId !== group.teamId) return options; return [ ...options, - ...group.eventTypes - .filter( - (evType) => - !evType.metadata?.managedEventConfig && evType.schedulingType !== SchedulingType.MANAGED - ) - .map((eventType) => ({ - value: String(eventType.id), - label: eventType.title, - })), + ...group.eventTypes.map((eventType) => ({ + value: String(eventType.id), + label: `${eventType.title} ${eventType.children.length ? `(+${eventType.children.length})` : ``}`, + })), ]; }, [] as Option[]) || [], [data] @@ -117,7 +113,12 @@ export default function WorkflowDetailsPage(props: Props) {
- +
- + {!props.readOnly && ( + + )}
@@ -154,7 +158,7 @@ export default function WorkflowDetailsPage(props: Props) {
{form.getValues("trigger") && (
- +
)} {form.getValues("steps") && ( @@ -168,23 +172,28 @@ export default function WorkflowDetailsPage(props: Props) { reload={reload} setReload={setReload} teamId={teamId} + readOnly={props.readOnly} /> ); })} )} -
- -
-
- -
+ {!props.readOnly && ( + <> +
+ +
+
+ +
+ + )}
{workflow.activeOn && workflow.activeOn.length > 0 ? ( ( -

{activeOn.eventType.title}

- ))}> + content={workflow.activeOn + .filter((wf) => (workflow.teamId ? wf.eventType.parentId === null : true)) + .map((activeOn, key) => ( +

+ {activeOn.eventType.title} + {activeOn.eventType._count.children > 0 + ? ` (+${activeOn.eventType._count.children})` + : ""} +

+ ))}>
) : ( diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index 4b5e9dce11..7135b6891c 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -52,6 +52,7 @@ type WorkflowStepProps = { reload?: boolean; setReload?: Dispatch>; teamId?: number; + readOnly: boolean; }; export default function WorkflowStepContainer(props: WorkflowStepProps) { @@ -249,6 +250,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { { if (val) { const oldValue = form.getValues(`steps.${step.stepNumber - 1}.action`); @@ -468,8 +475,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { { const isAlreadyVerified = !!verifiedNumbers @@ -483,9 +491,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { />
) : ( - <> -
- { - setVerificationCode(e.target.value); - }} - required - /> - -
- {form.formState.errors.steps && - form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && ( -

- {form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""} -

- )} - + !props.readOnly && ( + <> +
+ { + setVerificationCode(e.target.value); + }} + required + /> + +
+ {form.formState.errors.steps && + form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && ( +

+ {form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""} +

+ )} + + ) )}
)} @@ -551,6 +562,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { @@ -566,6 +578,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { @@ -580,6 +593,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { control={form.control} render={() => ( @@ -611,6 +626,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {