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:
co-authored by
Hariom Balhara
parent
2aaf672b10
commit
2fcb8a750d
@@ -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;
|
||||
|
||||
+10
-1
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user