fix: cascade organization hideBranding setting to user and team events (#21703)

- Add utility function for cascading hideBranding logic
- Update all getServerSideProps files to check organization hideBranding as fallback
- Ensure team queries include parent hideBranding data
- Fix hideBranding not working for organization members and teams

Fixes the issue where organization-level branding settings were not
being applied to user and team event pages when the direct entity
didn't have hideBranding enabled.

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2025-06-12 09:14:55 -03:00
committed by GitHub
co-authored by Hariom Balhara
parent 2aaf672b10
commit 2fcb8a750d
10 changed files with 220 additions and 7 deletions
+5
View File
@@ -70,6 +70,11 @@ export const getEventTypesFromDB = async (id: number) => {
slug: true,
name: true,
hideBranding: true,
parent: {
select: {
hideBranding: true,
},
},
},
},
workflows: {
@@ -6,6 +6,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { shouldHideBrandingForTeamEvent, shouldHideBrandingForUserEvent } from "@calcom/lib/hideBranding";
import { EventRepository } from "@calcom/lib/server/repository/event";
import { UserRepository } from "@calcom/lib/server/repository/user";
import slugify from "@calcom/lib/slugify";
@@ -49,6 +50,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
id: true,
slug: true,
hideBranding: true,
parent: {
select: {
hideBranding: true,
},
},
},
},
},
@@ -71,7 +77,10 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
if (hashedLink.eventType.team) {
name = hashedLink.eventType.team.slug || "";
hideBranding = hashedLink.eventType.team.hideBranding;
hideBranding = shouldHideBrandingForTeamEvent({
eventTypeId: hashedLink.eventTypeId,
team: hashedLink.eventType.team,
});
} else {
if (!username) {
return notFound;
@@ -101,7 +110,10 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
return notFound;
}
hideBranding = user.hideBranding;
hideBranding = shouldHideBrandingForUserEvent({
eventTypeId: hashedLink.eventTypeId,
owner: user,
});
}
let booking: GetBookingType | null = null;
@@ -5,6 +5,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { shouldHideBrandingForTeamEvent } from "@calcom/lib/hideBranding";
import { EventRepository } from "@calcom/lib/server/repository/event";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
@@ -28,6 +29,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
select: {
id: true,
hideBranding: true,
parent: {
select: {
hideBranding: true,
},
},
},
});
@@ -75,7 +81,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
user: teamSlug,
teamId: team.id,
slug: meetingSlug,
isBrandingHidden: team?.hideBranding,
isBrandingHidden: shouldHideBrandingForTeamEvent({
eventTypeId: eventData.id,
team,
}),
themeBasis: null,
},
};
@@ -8,6 +8,7 @@ import { getSlugOrRequestedSlug, orgDomainConfig } from "@calcom/features/ee/org
import { getOrganizationSEOSettings } from "@calcom/features/ee/organizations/lib/orgSettings";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { shouldHideBrandingForTeamEvent } from "@calcom/lib/hideBranding";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import type { User } from "@calcom/prisma/client";
@@ -156,7 +157,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
user: teamSlug,
teamId: team.id,
slug: meetingSlug,
isBrandingHidden: team?.hideBranding,
isBrandingHidden: shouldHideBrandingForTeamEvent({
eventTypeId: eventData.id,
team,
}),
isInstantMeeting: eventData && queryIsInstantMeeting ? true : false,
themeBasis: null,
orgBannerUrl: team.parent?.bannerUrl ?? "",
@@ -192,6 +196,7 @@ const getTeamWithEventsData = async (
name: true,
bannerUrl: true,
logoUrl: true,
hideBranding: true,
organizationSettings: {
select: {
allowSEOIndexing: true,
@@ -6,6 +6,7 @@ import { orgDomainConfig } from "@calcom/ee/organizations/lib/orgDomains";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import getBookingInfo from "@calcom/features/bookings/lib/getBookingInfo";
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding";
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
@@ -229,7 +230,12 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
props: {
orgSlug: currentOrgDomain,
themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username,
hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding,
hideBranding: await shouldHideBrandingForEvent({
eventTypeId: eventType.id,
team: eventType.team,
owner: eventType.users[0] ?? null,
orgSlug: currentOrgDomain,
}),
profile,
eventType,
recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId),
@@ -8,6 +8,7 @@ import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/featu
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import type { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import { shouldHideBrandingForUserEvent } from "@calcom/lib/hideBranding";
import { EventRepository } from "@calcom/lib/server/repository/event";
import { UserRepository } from "@calcom/lib/server/repository/user";
import slugify from "@calcom/lib/slugify";
@@ -270,7 +271,10 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
eventData: eventData,
user: username,
slug,
isBrandingHidden: user?.hideBranding,
isBrandingHidden: shouldHideBrandingForUserEvent({
eventTypeId: eventData.id,
owner: user,
}),
isSEOIndexable: allowSEOIndexing,
themeBasis: username,
bookingUid: bookingUid ? `${bookingUid}` : null,
@@ -1,7 +1,9 @@
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { getClientSecretFromPayment } from "@calcom/features/ee/payments/pages/getClientSecretFromPayment";
import { shouldHideBrandingForEvent } from "@calcom/lib/hideBranding";
import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import { paymentDataSelect } from "@calcom/prisma/selects/payment";
@@ -16,6 +18,8 @@ const querySchema = z.object({
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { uid } = querySchema.parse(context.query);
const { currentOrgDomain } = orgDomainConfig(context.req);
const rawPayment = await prisma.payment.findFirst({
where: {
uid,
@@ -51,7 +55,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const profile = {
name: eventType.team?.name || user?.name || null,
theme: (!eventType.team?.name && user?.theme) || null,
hideBranding: eventType.team?.hideBranding || user?.hideBranding || null,
hideBranding: await shouldHideBrandingForEvent({
eventTypeId: eventType.id,
team: eventType.team,
owner: eventType.users[0] ?? null,
orgSlug: currentOrgDomain,
}),
};
if (
+143
View File
@@ -0,0 +1,143 @@
import logger from "./logger";
import { ProfileRepository } from "./server/repository/profile";
const log = logger.getSubLogger({ name: "hideBranding" });
type Team = {
hideBranding: boolean | null;
parent: {
hideBranding: boolean | null;
} | null;
};
type Profile = {
organization: {
hideBranding: boolean | null;
} | null;
};
type UserWithoutProfile = {
id: number;
hideBranding: boolean | null;
};
type UserWithProfile = UserWithoutProfile & {
profile: Profile | null;
};
type OrgSlug = string | null;
/**
* Determines if branding should be hidden by checking entity and organization settings
*/
function resolveHideBranding(options: {
entityHideBranding: boolean | null;
organizationHideBranding: boolean | null;
}): boolean {
// If the organization has branding hidden, we should hide branding for the entity regardless of its own setting
if (options.organizationHideBranding) {
return true;
}
return options.entityHideBranding ?? false;
}
/**
* Determines if branding should be hidden for an event that could be a team event or user event
*/
export function shouldHideBrandingForEventUsingProfile({
eventTypeId,
owner,
team,
}: {
owner: UserWithProfile | null;
team: Team | null;
eventTypeId: number;
}) {
let hideBranding;
if (team) {
hideBranding = resolveHideBranding({
entityHideBranding: team.hideBranding ?? null,
organizationHideBranding: team.parent?.hideBranding ?? null,
});
} else if (owner) {
hideBranding = resolveHideBranding({
entityHideBranding: owner.hideBranding ?? null,
organizationHideBranding: owner.profile?.organization?.hideBranding ?? null,
});
} else {
log.error(`No owner or team found for event: ${eventTypeId}`);
return false;
}
return hideBranding;
}
/**
* A wrapper over shouldHideBrandingForEventUsingProfile that fetches the profile itself
* Use it when you don't have user's profile
*/
export async function shouldHideBrandingForEvent({
eventTypeId,
team,
owner,
orgSlug,
}: {
eventTypeId: number;
team: Team | null;
owner: UserWithoutProfile | null;
orgSlug: OrgSlug;
}) {
let ownerProfile = null;
if (team) {
return shouldHideBrandingForTeamEvent({
team,
eventTypeId,
});
} else if (owner) {
// Needed only for User events, not for Team events
ownerProfile = orgSlug
? await ProfileRepository.findByUserIdAndOrgSlug({
userId: owner.id,
orgSlug,
})
: null;
return shouldHideBrandingForUserEvent({
eventTypeId,
owner: {
...owner,
profile: ownerProfile,
},
});
} else {
log.error(`No owner or team found for event: ${eventTypeId}`);
return false;
}
}
/**
* A convenience wrapper for shouldHideBrandingForEventUsingProfile to use for Team events
*/
export function shouldHideBrandingForTeamEvent({ eventTypeId, team }: { eventTypeId: number; team: Team }) {
return shouldHideBrandingForEventUsingProfile({
owner: null,
team,
eventTypeId,
});
}
/**
* A convenience wrapper for shouldHideBrandingForEventUsingProfile to use for User events
*/
export function shouldHideBrandingForUserEvent({
eventTypeId,
owner,
}: {
eventTypeId: number;
owner: UserWithProfile;
}) {
return shouldHideBrandingForEventUsingProfile({
owner,
team: null,
eventTypeId,
});
}
+14
View File
@@ -50,6 +50,7 @@ const organizationSelect = {
logoUrl: true,
bannerUrl: true,
isPlatform: true,
hideBranding: true,
};
const organizationWithSettingsSelect = {
...organizationSelect,
@@ -480,6 +481,7 @@ export class ProfileRepository {
bannerUrl: true,
isPrivate: true,
isPlatform: true,
hideBranding: true,
organizationSettings: {
select: {
lockEventTypeCreationForUsers: true,
@@ -659,6 +661,18 @@ export class ProfileRepository {
return normalizeProfile(profile);
}
static async findByUserIdAndOrgSlug({ userId, orgSlug }: { userId: number; orgSlug: string }) {
const profile = await prisma.profile.findFirst({
where: { userId, organization: { slug: orgSlug } },
include: {
organization: {
select: organizationSelect,
},
},
});
return profile;
}
/**
* Personal profile should come from Profile table only
*/
+6
View File
@@ -49,6 +49,7 @@ export const paymentDataSelect = Prisma.validator<Prisma.PaymentSelect>()({
metadata: true,
users: {
select: {
id: true,
name: true,
username: true,
hideBranding: true,
@@ -59,6 +60,11 @@ export const paymentDataSelect = Prisma.validator<Prisma.PaymentSelect>()({
select: {
name: true,
hideBranding: true,
parent: {
select: {
hideBranding: true,
},
},
},
},
price: true,